Bài 6: Khi dự án lớn dần: biết lúc nào nên phức tạp hoá

Bài 5 đã nói nguyên tắc nền: bắt đầu đơn giản, phức tạp hoá dần khi có nhu cầu thật. Bài này trả lời câu hỏi cụ thể hơn: làm sao biết một nhu cầu đã đủ thật để đáng phức tạp hoá, và một khi đã phức tạp hoá rồi thì cần chú ý gì khác đi so với lúc còn đơn giản.

Nói trước một điều: bài này không đi sâu vào công nghệ cụ thể nào. Những thuật ngữ như backend hay server dưới đây chỉ ở mức khái niệm, để minh hoạ nguyên tắc, không phải hướng dẫn kỹ thuật hay gợi ý nên dùng công cụ nào. Trọng tâm của bài vẫn là nguyên tắc ra quyết định, áp dụng được cho bất kỳ công cụ nào bạn dùng.

Dấu hiệu thực tế cho thấy cần nâng cấp

Vài dấu hiệu cụ thể: nhiều người hoặc nhiều thiết bị cần dùng chung một dữ liệu, dữ liệu cần được bảo vệ an toàn hơn mức thông thường, hoặc cần phân quyền khác nhau giữa những người dùng.

Ví dụ là app "Bài tập về nhà" tôi nhắc ở bài 2. Ý tưởng ban đầu chỉ là chuyển giao bài tập, nhưng thực tế cả nhà, bố, mẹ, từng con, đều cần xem và cập nhật thông tin, trên nhiều thiết bị khác nhau, và bố mẹ cần quyền khác con (ví dụ chỉ bố mẹ mới giao được bài mới). Đây đúng là lúc dữ liệu lưu trên một máy không còn đủ nữa. Cần một backend (phần xử lý và lưu dữ liệu phía server, tức máy chủ, người dùng không thấy trực tiếp) để mọi thiết bị đọc cùng một nguồn dữ liệu, và cần phân quyền để mỗi người chỉ làm được đúng phần việc của mình.

Đừng nâng cấp trước khi có nhu cầu thật

Ngược lại, mỗi lần phức tạp hoá là một khoản chi phí bảo trì lâu dài thêm vào, không phải cứ "càng hiện đại càng tốt". Hai ví dụ phản diện tôi đã kể ở các bài trước là minh hoạ tốt cho điều này.

"Heo đất thông minh" (bài 5): xây xong một bộ tính năng khá đầy đủ, nhưng thực ra tôi chưa từng kiểm chứng thật kỹ nhu cầu, để rồi làm xong vẫn chưa đưa vào dùng. "Sổ tay checklist" (bài 2): phình to tới hơn 8.000 dòng code chỉ vì cứ thêm tính năng theo cảm hứng, không theo nhu cầu thật nào rõ ràng, tới mức chính AI cũng khuyên việc refactor lúc đó rủi ro hơn lợi ích.

Cả hai đều có một điểm chung: việc phức tạp hoá diễn ra trước khi có bằng chứng rõ ràng là cần nó. Câu hỏi nên tự đặt ra trước khi nâng cấp bất cứ thứ gì: nhu cầu này đã thật sự xảy ra chưa, hay mới chỉ là dự đoán "chắc sẽ cần".

Phức tạp hoá tới đâu, tự kiểm tra phải tăng tới đó

Càng phức tạp hoá, hậu quả của một sai sót càng lớn, vì vậy mức độ bạn tự kiểm tra cũng phải tăng lên tương ứng, không thể giữ nguyên thói quen kiểm tra như lúc app còn đơn giản.

Với phân quyền, một nguyên tắc cụ thể tôi luôn giữ: quyền hạn không bao giờ nên để người dùng tự gán cho mình, và bất kỳ thay đổi nào ảnh hưởng tới dữ liệu của người khác đều cần thận trọng hơn hẳn một thay đổi giao diện. Ở app "Bài tập về nhà", điều này có nghĩa là con không thể có cách nào tự đổi mình thành tài khoản có quyền của bố mẹ, dù là vô tình hay cố ý.

Nhưng độ phức tạp không chỉ nằm ở backend hay phân quyền. Đôi khi nó nằm ở khối lượng nội dung quá lớn để tự mình kiểm tra hết. Ví dụ là game đố vui "Triệu phú tri thức" tôi làm cho hai con, ở hai trình độ khác nhau (8 và 11 tuổi). Để hai con cùng chơi được mà không đứa thì chán vì dễ, đứa thì nản vì khó, tôi cần một bộ câu hỏi đủ lớn, chia theo hai cấp độ, bao quát nhiều lĩnh vực, phù hợp học sinh tiểu học, không có yếu tố bạo lực hay nhạy cảm, chính xác tuyệt đối với các câu hỏi lịch sử hay địa danh, và với câu hỏi tiếng Anh thì bám theo chuẩn CEFR (khung tham chiếu trình độ tiếng Anh chung của châu Âu, các mức A1, A2, B1, B2...).

Vấn đề là không ai có thể ngồi đọc hết hàng nghìn câu hỏi để kiểm tra từng câu. Kênh kiểm tra thực tế hiệu quả nhất hoá ra lại là chính hai con tôi, những người chơi đầu tiên: khi gặp câu hỏi có nội dung lạ hoặc sai, các con ghi ra giấy và báo lại để tôi kiểm tra. Bài học ở đây: khi khối lượng quá lớn để tự soát hết, hãy tìm một kênh kiểm tra thực tế khác, không nhất thiết phải là bạn.

Bảo mật, backup, và vận hành: những việc dễ bị xem nhẹ

Phần này hơi sâu với người dùng thông thường, nhưng nếu ứng dụng của bạn chứa thông tin quan trọng, cần có cái nhìn đúng ngay từ đầu, không nên chủ quan.

Bảo mật khi dữ liệu lên online. Khi app chỉ lưu dữ liệu trên máy của bạn, rủi ro bảo mật chỉ nằm ở chính thiết bị đó. Nhưng khi có nhu cầu đồng bộ, lưu dữ liệu online, câu chuyện khác hẳn. Nhìn bề ngoài, một ứng dụng được quảng cáo mã hoá "cấp quân đội", đăng nhập bằng FaceID có vẻ khá an toàn, nhưng đó có thể chỉ là lớp vỏ bên ngoài. Người có nghề vẫn có thể dùng DevTools (công cụ có sẵn trong trình duyệt, cho phép xem và can thiệp vào mã đang chạy) để sửa đổi thông tin nếu ứng dụng thiếu những lớp phòng vệ cần thiết ở phía server. Tôi luôn yêu cầu AI kiểm tra các yếu tố bảo mật ngay trong prompt cho những ứng dụng đưa dữ liệu lên online và có nhiều người dùng, nhưng không phải lúc nào AI cũng phát hiện ra ngay, thường cần dùng tới model cao cấp hơn mới đủ khả năng nhìn ra lỗ hổng. Đôi khi vá xong lỗ hổng này, chính việc chỉnh sửa lại vô tình sinh ra lỗ hổng khác. Bảo mật vì vậy không phải việc làm một lần rồi yên tâm mãi, mà phải được coi trọng đúng mức mỗi khi có thay đổi.

Backup dữ liệu. Tôi từng tiếc ngẩn ngơ vì mất dữ liệu khi cập nhật lên phiên bản mới của một ứng dụng. Từ đó, hầu hết ứng dụng có lưu trữ dữ liệu tôi làm đều có tính năng backup: vừa có backup thủ công, vừa có backup tự động hàng ngày với những ứng dụng có đồng bộ dữ liệu online.

Giới hạn lưu trữ miễn phí. Một vấn đề vận hành khác chỉ lộ ra khi ứng dụng đã chạy được một thời gian: với ứng dụng có nhiều dữ liệu, mỗi lần chỉnh sửa rồi đẩy code lên GitHub, triển khai lại qua Vercel, dữ liệu tích luỹ dần có thể chạm ngưỡng lưu trữ miễn phí. Lúc đó tôi phải tìm cách xoá bớt bằng dòng lệnh và thiết lập lại một số cài đặt trên Vercel để giảm lượng lưu trữ không cần thiết. Đây không phải lỗi thiết kế ban đầu, mà là cái giá tự nhiên của việc dữ liệu lớn dần theo thời gian, nên cũng đáng để ý ngay từ khi dự án còn nhỏ.

Công cụ kiểm soát khi dự án đủ phức tạp: một "master prompt" ngoài khung chat

Instruction (bài 3) là bộ luật chơi cố định, áp dụng chung cho mọi lần làm việc trong một project. Nhưng với một dự án đủ phức tạp, có nhiều ràng buộc riêng và liên tục phát sinh ý tưởng mới, tôi thấy cần thêm một thứ khác: một bản phác thảo ý tưởng viết ở ngoài khung chat, có đánh dấu phiên bản mỗi lần cập nhật. Mỗi khi copy nội dung từ bản đó vào khung chat để triển khai, tôi đánh dấu lại bên ngoài là đã dùng tới đâu. Việc này giúp khi có ý tưởng mới, tôi đọc lại được toàn bộ những gì đã chốt trước đó để liên kết cho khớp, tránh trường hợp ý mới mâu thuẫn với hướng đã quyết định từ đầu mà không hay biết.

Ví dụ rõ nhất vẫn là "Triệu phú tri thức". Ứng dụng này có logic thưởng khá nhiều lớp: phân chia tỷ lệ câu hỏi theo độ khó, ở mỗi độ khó lại chia phần thưởng theo tỷ lệ phần trăm trả lời đúng, rồi lại chia tiếp phần thưởng theo khung giờ chơi (ví dụ phần thưởng liên quan vận động thì không xuất hiện vào buổi tối). Từng lớp riêng lẻ không phức tạp, nhưng gộp lại có rất nhiều chi tiết nhỏ. Không có một bản phác thảo để đối chiếu, rất dễ bỏ sót một trường hợp, vô tình lặp lại một quy tắc đã có theo cách khác, hoặc đi lệch dần so với thiết kế ban đầu.

Logic tưởng đơn giản nhưng phức tạp khi triển khai thật

Một ví dụ khác từ "Triệu phú tri thức": tôi phân kết quả chơi thành 5 bậc, bậc 3, 4, 5 có phần thưởng từ khuyến khích tới hiện vật, bậc 1, 2 thì bị phạt. Việc phân chia này lúc đầu tưởng đơn giản, nhưng khi tiến hành mới phát hiện khá nhiều lỗi logic, và phải mất không ít thời gian chỉnh sửa mới giúp ứng dụng hoạt động đúng như mục tiêu ban đầu.

Đây là lời nhắc cho thông điệp cốt lõi của cả bài: càng phức tạp, càng cần hiểu rõ AI đang làm gì, không chỉ dừng ở "chạy được là xong".

Chốt lại

Phức tạp hoá khi nhu cầu thật đòi hỏi, không phải vì nghe có vẻ nên làm vậy. Và mỗi lần phức tạp hoá thêm đòi hỏi một mức cẩn trọng mới tương ứng, từ phân quyền, khối lượng nội dung, cho tới bảo mật, backup, và vận hành lâu dài.

Dữ liệu cá nhân lưu trên máy so với dữ liệu dùng chung lưu trên server Dữ liệu cá nhân lưu trên máy so với dữ liệu dùng chung lưu trên server

Bài tiếp theo tôi sẽ nói về quy trình an toàn khi để AI sửa code, tránh tình trạng vá lỗi này lại sinh lỗi khác.