Top 7 sai lầm thường gặp về lập trình web và mobile

Vì sao chúng tôi chọn đúng bảy lỗi này

Bạn thử mở lại một file code mình viết cách đây ba tháng, đọc vài dòng rồi tự hỏi lúc đó mình đang nghĩ gì. Build vẫn chạy, app vẫn lên đúng giao diện, nhưng cảm giác như đang đọc code của người khác. Đội ngũ chúng tôi gặp cảnh đó rất nhiều lần khi ngồi review code cho sinh viên mới ra trường, và cả với những bạn đã đi làm một, hai năm.

Điều đáng chú ý là gần như không ai trong nhóm đó sai cú pháp. Họ viết vòng lặp ngon, dùng class ổn, gọi API trơn tru. Thứ làm họ khổ nằm ở những thói quen hình thành từ lúc còn học, khi dự án chỉ có một người và chưa ai bắt lỗi. Đó cũng là chất liệu cho bài top 7 sai lầm thường gặp về lập trình web và mobile mà chúng tôi muốn chia sẻ hôm nay.

Bảy lỗi dưới đây được chọn theo ba tiêu chí. Một là tần suất gặp trong review code thực tế, cái nào tuần nào cũng thấy thì xếp trước. Hai là mức độ tốn thời gian khi phải sửa lại, tính bằng giờ chứ không phải bằng phút. Ba là mức độ phổ biến ở cả web lẫn mobile, để bạn đọc xong dùng được cho cả hai hướng.

Danh sách này hợp với dev mới ra trường, dev tự học chuyển ngành, và cả những bạn đã đi làm nhưng chưa từng qua một buổi review nghiêm túc. Nếu bạn thấy mình dính ba, bốn cái trở lên thì cũng đừng ngại. Đội ngũ chúng tôi thời mới vào nghề cũng dính gần hết.

Sai lầm 1: Nhồi hết logic vào trong component

Dấu hiệu dễ thấy nhất là file component dài quá 400 dòng. Trong đó có đủ thứ: vừa gọi API, vừa tính toán, vừa format ngày tháng, vừa render giao diện. Component ở đây hiểu đơn giản là một khối giao diện độc lập, kiểu như một cái thẻ sản phẩm hay một cái form đăng nhập.

Cách viết này nguy hiểm ở chỗ bạn không thể test phần logic mà không phải dựng cả giao diện lên. Một thay đổi nhỏ ở giao diện cũng có thể vô tình làm vỡ logic phía dưới. Chuyện xóa một biến mà không biết nó còn dùng ở chỗ khác là kịch bản quen thuộc.

Chúng tôi từng nhận một dự án bán hàng, file chi tiết sản phẩm dài gần 900 dòng. Khách chỉ yêu cầu đổi vị trí nút mua hàng. Vậy mà sau khi sửa thì phần tính giá khuyến mãi sai luôn. Lý do là giá được tính ngay trong hàm render, phụ thuộc vào thứ tự các dòng giao diện. Đổi thứ tự là đổi kết quả.

Cách tách gọn nhất là đưa phần tính toán ra một hàm riêng, phần gọi dữ liệu ra một hàm riêng, component chỉ còn nhiệm vụ hiển thị. Bạn có thể tham khảo thêm cách tổ chức công việc theo từng giai đoạn trong bài cách tự vạch lộ trình cho sinh viên CNTT để biết khi nào nên đầu tư vào cấu trúc code.

Nhóm dễ dính lỗi này nhất là dev web dùng framework component như React hay Vue, và dev mobile dùng Jetpack Compose hoặc SwiftUI. Các framework này khuyến khích viết giao diện bằng code, nên rất dễ trượt dài sang logic nghiệp vụ.

Nói vậy không có nghĩa là tách càng sớm càng tốt. Với app nhỏ, một màn hình, một người dùng, việc tách sớm có thể thành over-engineering, tức là làm phức tạp hóa vấn đề không cần thiết. Quy tắc thực dụng của chúng tôi: chỉ tách khi thấy một đoạn logic được gọi từ hai nơi trở lên, hoặc khi file vượt qua 300 dòng.

Sai lầm 2: Bỏ qua quản lý trạng thái và vòng đời

Đây là lỗi xuất hiện nhiều nhất trong các buổi debug app đã chạy ổn trên máy dev nhưng crash sau vài phút dùng thật. Trạng thái hiểu đơn giản là dữ liệu đang được ghi nhớ tại một thời điểm, ví dụ ô nhập đang có chữ gì, đang tải hay đã tải xong.

Ở web, biểu hiện thường thấy là các hook như useEffect hay useState chạy loạn. Bạn tải dữ liệu trong useEffect nhưng quên dọn dẹp khi component bị gỡ. Việc dọn dẹp đó gọi là cleanup, hàm chạy khi component biến mất, để hủy những việc đang dang dở. Không có cleanup thì request cũ vẫn trả về sau khi người dùng đã rời trang, gây cảnh báo và rò rỉ bộ nhớ. Rò rỉ bộ nhớ nghĩa là phần bộ nhớ đã dùng không được giải phóng, càng dùng càng nặng.

Ở mobile, lỗi tương tự nhưng khó thấy hơn. Bạn giữ một tham chiếu tới Activity hoặc Context trong một tác vụ chạy nền. Khi người dùng xoay màn hình, Activity cũ bị hủy nhưng tác vụ nền vẫn giữ nó lại, không cho hệ thống thu hồi bộ nhớ. Vài lần xoay màn hình là app bắt đầu giật, rồi văng.

Loại lỗi này khiến app crash sau vài phút sử dụng chứ không crash ngay lúc mở. Vì vậy tester khó tái hiện, dev cũng khó tin. Kinh nghiệm của chúng tôi là bật sẵn công cụ theo dõi bộ nhớ trong lúc phát triển, đừng đợi tới lúc có người phàn nàn mới soi. Cách làm việc này khá giống với quy trình kiểm thử trong các dự án embedded, khi người ta kiểm tra bộ nhớ ngay từ khâu phát triển chứ không để tới cuối. Bạn có thể đọc thêm bài phân biệt unit test và integration test để thấy cách ngành khắt khe hơn tổ chức việc kiểm tra.

Một lưu ý thật lòng: không phải cứ dùng thư viện quản lý trạng thái là xong. Nhiều thư viện có learning curve khá cao, tức là mất thời gian học mới dùng được. Nếu team chỉ có hai, ba người và app chưa phức tạp, dùng công cụ có sẵn của framework thường đủ. Cái cần là hiểu vòng đời, không phải thêm thư viện.

Sai lầm 3: Xử lý bất đồng bộ và lỗi mạng bằng cảm tính

Hầu hết app hiện nay đều gọi API, tức là gửi yêu cầu tới một máy chủ khác để lấy hoặc gửi dữ liệu. Bất đồng bộ nghĩa là app gửi yêu cầu rồi làm việc khác, khi nào có kết quả mới xử lý tiếp. Chính vì không chờ sẵn kết quả nên mọi thứ dễ rối.

Lỗi thường gặp là không đặt timeout, không retry, và không phân biệt lỗi 4xx với 5xx. Nói dễ hiểu: lỗi 4xx là lỗi từ phía người gửi, ví dụ sai token đăng nhập hoặc gọi sai đường dẫn. Retry trong trường hợp này chỉ tốn thời gian vô ích. Lỗi 5xx là lỗi từ phía máy chủ, ví dụ máy chủ đang quá tải. Thử lại sau vài giây có thể thành công. Gộp hai loại này làm một là tự làm khó mình.

Timeout nghĩa là đặt giới hạn thời gian chờ, ví dụ 10 giây không có phản hồi thì bỏ. Không có timeout thì app treo im lặng khi mạng yếu, người dùng chỉ biết ngồi nhìn màn hình xoay. Retry là thử gửi lại, nhưng phải có giới hạn số lần, thường là hai tới ba lần, và nên chờ lâu dần giữa các lần thử.

Về thông báo lỗi, câu “Có lỗi xảy ra” là câu tệ nhất trong mọi app. Người dùng không biết nên thử lại hay bỏ. Dev nhận ảnh chụp màn hình cũng không biết chuyện gì. Chúng tôi thường yêu cầu tối thiểu phải hiện nội dung gợi ý hành động: thử lại, kiểm tra kết nối mạng, hoặc liên hệ hỗ trợ kèm mã lỗi.

Có một điểm cần kiểm chứng trước khi tin: hành vi retry mặc định khác nhau tuỳ HTTP client. Có thư viện tự thử lại, có thư viện không, có thư viện chỉ thử lại với một số loại lỗi nhất định. Bạn phải mở tài liệu của đúng thư viện mình dùng để đọc, đừng đoán theo thói quen. Chúng tôi từng mất nửa ngày chỉ vì giả định sai chuyện này.

Sai lầm 4: Tin vào việc tự test tay mà không viết test

Test tay là tự mở app lên, bấm thử vài trường hợp rồi kết luận là chạy được. Cách này chỉ phủ được happy path, tức là luồng đi đúng như thiết kế: nhập đủ thông tin, mạng ổn, dữ liệu hợp lệ. Bug thật thường nằm ở edge case, những trường hợp biên như ô nhập rỗng, số âm, mạng đứt giữa chừng, hoặc người dùng bấm nút hai lần thật nhanh.

Vì sao chúng tôi xếp lỗi này vào top 7? Vì test tay không lặp lại được. Hôm nay bạn bấm thấy ổn, mai sửa chỗ khác, không ai bấm lại đúng tổ hợp cũ. Bug lọt ra production, và chi phí sửa lúc đó thường cao hơn nhiều lần so với viết test ngay khi code. Con số cụ thể thì tùy dự án, tùy quy trình, nên bạn đừng lấy một con số chung nào áp cho mọi trường hợp.

Ai nên viết test? Dev làm sản phẩm có nhiều người dùng, có nhiều luồng tính tiền, phân quyền, hoặc dữ liệu nhạy cảm. Ai có thể bỏ qua bước này? Prototype demo một lần, dùng để trình bày ý tưởng rồi vứt đi. Chúng tôi vẫn hay nói với các bạn mới: nếu code này bạn còn sửa trong ba tháng tới thì nên có test.

Một cái bẫy tinh vi là chạy theo coverage 100%. Coverage là tỷ lệ phần trăm dòng code được test chạm tới. Nhưng chạm tới không có nghĩa là kiểm tra đúng. Bạn có thể viết test gọi hàm rồi không kiểm tra gì cả, coverage vẫn tăng mà lỗi vẫn nguyên. Chúng tôi khuyên tập trung vào các luồng nghiệp vụ quan trọng: đăng nhập, thanh toán, phân quyền, và các hàm tính toán có công thức rõ ràng.

Sai lầm 5: Copy-paste code mà không hiểu ngữ cảnh

Bạn gặp lỗi, lên Stack Overflow hoặc hỏi một công cụ AI, nhận được một đoạn code, dán vào và nó chạy. Chuyện tới đây chưa có gì sai. Vấn đề bắt đầu khi nó không chạy nữa, hoặc chạy sai trong một tình huống khác, và bạn không biết tại sao.

Snippet là đoạn code ngắn ai đó chia sẻ để minh hoạ giải pháp. Nó thường được viết cho một phiên bản thư viện cụ thể, cho một ngữ cảnh cụ thể, và bỏ qua những thứ mà tác giả cho là hiển nhiên. Bạn dán vào dự án của mình với phiên bản khác thì hành vi có thể khác hoàn toàn.

Mobile là nơi dính lỗi này nặng nhất, với hai mảng điển hình. Một là code xin quyền truy cập camera, vị trí, thông báo. Mỗi phiên bản hệ điều hành có cách xin quyền và trạng thái trả về khác nhau, copy mà không đọc tài liệu là xin hoài không được. Hai là cấu hình build, khi bạn copy từ dự án dùng SDK phiên bản khác, dẫn tới lỗi build rất khó đọc.

Nhóm dễ dính nhất là dev mới và dev đang gấp deadline. Cả hai đều thiếu thời gian đọc tài liệu gốc, và cả hai đều nghĩ đọc sau cũng được. Chúng tôi không bài trừ việc copy. Copy đúng lúc là cách tiết kiệm thời gian hợp lý. Vấn đề là phải biết mình đang copy cái gì và nó có hợp với dự án của mình không.

Mẹo thực tế của chúng tôi: khi lấy một đoạn code ở đâu về, viết ngay một dòng chú thích trên nó, ghi rõ lấy từ đâu, vì sao cần, và còn chỗ nào chưa chắc. Ba tháng sau đọc lại, dòng chú thích đó tiết kiệm cả buổi mò mẫm.

Với những bạn muốn hiểu thêm về công cụ hỗ trợ học tập và làm việc, bài công cụ học tập miễn phí cho dev mới có danh sách khá gọn để bắt đầu.

Sai lầm 6: Bỏ qua hiệu năng cho tới khi bị phàn nàn

Có một khoảng thời gian khá dài giữa lúc app bắt đầu chậm và lúc có người phàn nàn. Trong khoảng đó, bạn vẫn thấy app chạy ổn vì máy bạn mạnh, mạng bạn nhanh. Nhưng người dùng thật thì không có điều kiện đó.

Ở web, hai biểu hiện thường gặp là render danh sách dài không phân trang hoặc không ảo hóa, và bundle phình to. Phân trang là chia danh sách thành nhiều trang nhỏ. Ảo hóa là kỹ thuật chỉ vẽ những mục đang thực sự hiện trên màn hình, còn lại để đó chờ. Bundle là gói tất cả mã nguồn gửi xuống trình duyệt. Gói càng lớn thì tải càng chậm, đặc biệt trên mạng 3G.

Ở mobile, hai lỗi phổ biến là ảnh không resize và chạy tác vụ nặng trên main thread. Ảnh chụp từ điện thoại ngày nay thường vài MB. Nhét thẳng vào danh sách là mỗi mục ăn vài MB bộ nhớ, cuộn vài chục mục là giật. Main thread là luồng chính xử lý giao diện. Chạy tính toán nặng trên đó thì giao diện đứng hình, không bấm được gì.

Khi khảo sát hiệu năng, chúng tôi thường liệt kê vài dấu hiệu quen mặt rồi đối chiếu với công cụ có sẵn. Cuộn danh sách giật sau vài chục mục thường do không ảo hóa. Vào trang lần đầu chậm thường do bundle lớn. Ảnh hiển thị chậm thường vì file gốc chưa nén. Bấm nút mà mất một tích tắc mới phản hồi thường là tác vụ nặng đang chiếm main thread. Bốn dấu hiệu này không cần cài thêm gì, chỉ mở tab Performance trong trình duyệt hoặc profiler trong IDE là thấy.

Con số cụ thể thì phải đo trên máy thật, dùng mạng thật, chứ đừng lấy số chung từ bài viết nào. Một máy cấu hình thấp phổ biến ở Việt Nam và một máy flagship cho ra kết quả khác hẳn. Chúng tôi hay khuyên các bạn mua hoặc mượn một máy tầm trung để test, vì đó mới là trải nghiệm của phần lớn người dùng.

Ai cần quan tâm mục này nhất? App có danh sách dài, nhiều ảnh, hoặc chạy trên máy cấu hình thấp. Nếu app của bạn chỉ có vài màn hình nhập liệu đơn giản thì đừng tối ưu sớm, chỉ tổ mất thời gian.

Sai lầm 7: Coi nhẹ version control, review và tài liệu bàn giao

Ba thứ này nằm chung một mục vì chúng có cùng một gốc: không ai muốn làm, nhưng thiếu thì trả giá. Version control là hệ thống lưu lịch sử thay đổi mã nguồn, phổ biến nhất là Git. Review là bước người khác đọc code trước khi nó được gộp vào nhánh chính.

Lỗi thường gặp khi commit, tức là lưu một mốc thay đổi: gộp cả đống thay đổi vào một lần, message ghi mỗi chữ “fix” hoặc “update”. Nhánh phụ sống quá lâu, vài tuần không gộp, tới lúc gộp thì xung đột chồng chất. Xung đột là khi hai người sửa cùng một đoạn code, hệ thống không biết giữ bản nào, phải có người quyết.

Về review, lý do hay được đưa ra nhất là sợ mất thời gian. Nhưng chi phí thật của việc bỏ review thường xuất hiện muộn hơn: bug lọt tới staging rồi ra production, và lúc đó phải sửa gấp. Chúng tôi từng thấy một lỗi nhỏ trong điều kiện kiểm tra quyền, lọt qua mọi bước vì không ai đọc, tới lúc phát hiện thì đã có vài trăm người dùng bị ảnh hưởng.

Nếu bạn mới dùng Git, bài hướng dẫn Git cơ bản cho dev mới giải thích các lệnh nền tảng theo cách dễ theo. Hiểu được luồng commit, branch, merge là đủ để không tự làm rối mình.

Đây là sai lầm có chi phí sửa thấp nhất nhưng lợi ích dài hạn lớn nhất. Bạn chỉ cần đổi thói quen commit nhỏ hơn, viết message rõ hơn, và nhờ người khác đọc trước khi gộp. Cả ba việc này không đòi kiến thức kỹ thuật mới, chỉ đòi kỷ luật.

Nhưng phải nói thẳng: đừng dựng quy trình quá nặng. Nhiều approver, checklist dài, giấy tờ bàn giao chi tiết thì team nhỏ làm vài tuần là bỏ hết, rồi quay lại trạng thái cũ. Chúng tôi thường bắt đầu bằng ba quy tắc tối giản: mỗi lần commit một việc, mỗi thay đổi có ít nhất một người đọc, và mọi quyết định kỹ thuật quan trọng được ghi lại ở đâu đó dễ tìm.

Vì sao dev Việt hay mắc đúng bảy lỗi này

Nhìn vào danh sách trên, bạn có thể thấy chúng đều là lỗi của người biết code chứ không phải người không biết. Đó là lý do chúng phổ biến đến vậy. Áp lực tiến độ khiến bước kiểm tra bị bỏ qua trước tiên. Làm nhiều dự án song song khiến bạn quen với việc làm sao cho chạy, chứ không quen với việc làm sao cho dễ đọc lại.

Cách tự kiểm tra đơn giản nhất mà chúng tôi hay đưa cho các bạn mới: mở ba file gần nhất mình viết, rồi chấm theo bảy mục trên. File có dài quá 400 dòng không? Có chỗ nào dữ liệu được tải mà không dọn dẹp không? Có hàm gọi API nào không đặt timeout không? Ba file đó thường đủ để bạn biết mình đang ở đâu.

Đừng ôm hết một lượt. Mỗi sprint, tức là mỗi chu kỳ làm việc ngắn vài tuần, chỉ chọn một tới hai sai lầm để cải thiện. Chọn cái nào tốn ít công nhất để bắt đầu, ví dụ đổi thói quen commit. Xong rồi mới sang cái khó hơn như tách logic khỏi UI.

Với các bạn quan tâm tới lập trình automotive, nơi quy trình nghiêm ngặt hơn nhiều, bài những điều cần biết về công nghệ automotive là điểm khởi đầu hợp lý. Đọc để thấy cùng một vấn đề về chất lượng code, mỗi ngành xử lý một kiểu.

Câu hỏi thường gặp

Dev mới nên ưu tiên sửa sai lầm nào trong bảy cái trên trước?

Bắt đầu với việc tách logic khỏi UI. Nó không đòi công cụ mới, không đòi thư viện mới, chỉ đòi bạn chia lại file. Khi logic đã tách ra, việc viết test và xử lý lỗi mạng trở nên dễ hơn hẳn.

Nếu bạn đang làm dự án đã chạy thật, hãy ưu tiên xử lý lỗi mạng trước, vì nó ảnh hưởng trực tiếp tới người dùng.

Lập trình web và mobile thì sai lầm nào giống nhau nhất?

Hai cái giống nhất là xử lý lỗi mạng bằng cảm tính và copy-paste code không hiểu ngữ cảnh. Cả hai nền tảng đều gọi API, đều có thư viện riêng với hành vi mặc định khác nhau, đều dễ dán nhầm code từ nguồn khác phiên bản.

Về quản lý vòng đời thì biểu hiện khác nhau, nhưng gốc lỗi vẫn là một: không dọn dẹp thứ mình đã tạo ra.

Có nhất thiết phải viết test cho mọi dự án, kể cả app nhỏ không?

Không. Với prototype demo một lần rồi bỏ, viết test là thời gian lãng phí. Với app nhỏ nhưng sẽ còn sửa trong vài tháng tới, nên có test cho các luồng nghiệp vụ chính như đăng nhập và thanh toán.

Tiêu chí của chúng tôi rất đơn giản: nếu bạn còn phải sửa code này trong ba tháng tới thì nên viết test.

Làm sao biết app đang rò rỉ bộ nhớ do quản lý vòng đời sai?

Dấu hiệu dễ thấy là bộ nhớ tăng dần theo thời gian dùng, không giảm xuống dù bạn đã đóng các màn hình. App bắt đầu giật sau vài phút, rồi có thể bị hệ thống đóng đột ngột.

Cách kiểm tra là dùng profiler của IDE, xem biểu đồ bộ nhớ khi bạn đi qua lại giữa các màn hình vài lần. Nếu biểu đồ đi lên mà không về mức cũ, đó là dấu hiệu cần soi lại vòng đời.

Copy code từ AI hay Stack Overflow có luôn là sai lầm không?

Không. Copy đúng lúc là cách tiết kiệm thời gian hoàn toàn hợp lý, đặc biệt với những đoạn code mang tính mẫu như cấu hình hay xử lý chuỗi. Vấn đề nằm ở chỗ bạn không biết đoạn code đó làm gì và nó có hợp với dự án của mình không.

Kinh nghiệm của chúng tôi: đọc lướt qua đoạn code trước khi dán, kiểm tra phiên bản thư viện, và ghi lại chú thích về nguồn. Ba việc này chỉ mất vài phút nhưng tiết kiệm rất nhiều lần debug sau đó.

Lời kết

Điểm chung của cả bảy lỗi là đều bắt nguồn từ một bước kiểm tra bị bỏ qua, chứ không phải từ thiếu kiến thức kỹ thuật. Không ai trong chúng tôi thời mới đi làm biết tách logic khỏi UI, biết đặt timeout, hay biết viết commit cho tử tế. Chúng tôi học được vì có người bắt lỗi, và vì phải sửa những thứ do chính mình gây ra.

Nếu bạn là dev mới, thứ tự chúng tôi khuyên sửa là: tách logic khỏi UI trước, vì nó mở đường cho mọi thứ khác dễ hơn. Tiếp theo là xử lý lỗi mạng cho tử tế, vì đó là thứ người dùng cảm nhận được ngay. Cuối cùng là dọn lại thói quen version control, để ba tháng sau bạn còn đọc hiểu được chính mình đã viết gì. Một điều nhớ suốt: cái gì chưa đo được trên máy thật thì đừng tin số liệu chung chung.

Nếu bạn cần thêm tài liệu tham khảo trước khi bắt tay sửa code, bạn có thể tham khảo bài tổng hợp về kiến thức nền cho người mới học lập trình trên MDN. Đọc lướt qua đó rồi quay lại soi bảy lỗi này, bạn sẽ thấy nhiều thứ sáng ra.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *