Think Like a Developer #8: Boundary Thinking - "Nghĩ về cái sẽ sai, không chỉ cái sẽ đúng"
Bạn prompt AI build feature "user upload ảnh để làm avatar". AI làm. Bạn test với cái ảnh JPG 200KB của mình, chạy ngon lành. Ship.
Tuần sau, mọi thứ bắt đầu vỡ.
User A upload file 50MB. Server tốn RAM, request timeout, các user khác bị chậm theo. User B upload file đặt tên là "../../../etc/passwd.jpg" và cách AI handle đường dẫn cho phép ghi đè file ngoài thư mục upload. User C upload file đuôi .jpg nhưng nội dung thật ra là HTML có script. Browser khi render lại chạy script đó. XSS thẳng vào dashboard admin.
Bạn ngồi đọc bug report mà choáng. Một feature đơn giản, làm xong 30 phút, mà gây ra 3 sự cố trong 1 tuần.
Không phải AI dở. AI làm đúng cái bạn yêu cầu. Vấn đề là bạn chỉ yêu cầu happy path.
8 cách user phá feature upload ảnh
Để bạn thấy rõ vấn đề, mình kể tiếp các tình huống thật. Vẫn cùng feature upload avatar:
User D upload ảnh chụp bằng iPhone. Ảnh đó chứa EXIF metadata, trong đó có GPS location chính xác đến mét. Bạn không strip metadata trước khi serve. Giờ tọa độ nhà của user nằm trong file public. Ai cũng download được, đọc được.
User E upload xong thì cancel request giữa chừng. File rác nằm trong storage. Nhân lên 10000 user làm vậy, storage đầy. Bill cloud tăng vọt.
User F upload cùng tên file với user khác. File ghi đè nhau. Avatar của user X bị thay bằng avatar của user F mà cả hai không biết.
User G dùng concurrent upload, viết script gửi 100 request cùng lúc. Hệ thống không có rate limit. Server sập. Tất cả user khác không vào được.
User H upload thành công, bạn lưu URL ảnh vào database. User update avatar mới. Ảnh cũ vẫn nằm trong storage mãi mãi vì không có job cleanup. Một tháng sau bạn nhận ra 80% file trong storage là rác.
8 vấn đề. Không có cái nào nằm trong "happy path đơn giản". Và đa số AI không tự nghĩ ra hết, vì prompt của bạn không yêu cầu.
Đây là Boundary Thinking. Tư duy về biên. Về cái sẽ sai, trước khi nghĩ về cái sẽ đúng.
Tại sao builder mới hay bỏ sót boundary
Vì bạn build với tâm thế của người dùng tử tế. Bạn dùng app như bạn muốn nó được dùng. Upload ảnh thì upload ảnh thật, kích thước hợp lý, tên file bình thường.
Bạn quên mất rằng trên internet có hàng triệu user. Trong đó có người vô tình làm sai. Có người cố tình phá. Có bot tự động dò lỗ hổng. Có người ngu ngơ làm action mà bạn không tưởng tượng được.
Developer làm production lâu năm thì khác. Họ có một cái phản xạ mà builder mới chưa có: trước khi mừng vì code chạy, họ tự hỏi "code này sẽ sập trong điều kiện nào?".
Họ không tin user. Họ không tin network. Họ không tin third-party service. Họ không tin chính code của mình cho đến khi nó được test với các case xấu.
Tư duy này nghe có vẻ paranoid. Nhưng nó là khác biệt giữa app demo chạy ổn 1 lần và app production chạy ổn 24/7 với 10000 user.
5 trục để hỏi câu hỏi boundary
Khi nhìn bất kỳ feature nào, developer kinh nghiệm chia câu hỏi thành 5 trục. Bạn không cần thuộc lòng. Chỉ cần biết là có 5 trục để soi.
Trục 1: Input
Mọi thứ user nhập vào đều là input. Form, file upload, URL parameter, cookie, header. Tất cả.
Câu hỏi: Size lớn nhất là bao nhiêu? Format nào cấm? Tên có thể chứa ký tự độc hại không? Content có khớp với extension không? Có rỗng được không? Có null được không? Có chứa emoji được không? Có chứa SQL/HTML/script không?
Trục 2: Concurrency
Bạn build app cho 1 user dùng 1 lúc. Thực tế nhiều user dùng song song. Một user cũng có thể tự gửi nhiều request cùng lúc.
Câu hỏi: Hai user cùng action lên một thứ thì sao? Một user spam 100 request/giây thì sao? Request bị cancel giữa chừng để lại gì? Race condition có xảy ra không?
Trục 3: Lifecycle
Data có vòng đời. Được tạo, được đọc, được sửa, được xóa. Mỗi giai đoạn có thể có vấn đề.
Câu hỏi: Khi data được tạo, các field nào bắt buộc? Khi update, ai có quyền? Khi xóa, có cascade xóa data liên quan không? Data cũ có cần lưu trữ không hay xóa luôn? Có cần audit log không?
Trục 4: Failure
Mọi thứ đều có thể fail. Database disconnect. Storage full. Third-party API trả 500. Network drop. Server out of memory.
Câu hỏi: Nếu storage full thì user thấy gì? Nếu API thanh toán trả lỗi thì transaction xử lý sao? Nếu user mất mạng giữa chừng upload thì retry như nào? Retry mấy lần thì dừng? Có cần notify admin không?
Trục 5: Security
User có thể giả mạo. User có thể inject. User có thể đọc data của user khác qua những kẽ hở bạn không lường trước.
Câu hỏi: User có thể giả mạo user khác qua đâu? Có thể inject SQL/HTML/JS vào input nào? Có thể bypass authentication qua đường nào? API key có lộ ra client không? Có thể đọc file của user khác qua path traversal không?
Mỗi trục là một danh sách câu hỏi. Trả lời được càng nhiều, prompt càng đầy đủ, AI build ra càng ít bug.
Cách áp dụng đơn giản nhất
Bạn không cần là expert để làm Boundary Thinking. Bạn chỉ cần một thói quen đơn giản: viết happy path ra, rồi soi từng từ.
Bước 1: Viết feature thành 1 câu happy path. Ngắn gọn. Như user kể chuyện.
Ví dụ: "User upload ảnh để làm avatar."
Bước 2: Dưới mỗi từ trong câu đó, hỏi "cái này có thể bị abuse kiểu gì?"
User:
Là user logged-in hay anyone cũng upload được?
Có rate limit không? 1 user upload tối đa bao nhiêu file/ngày?
User bị ban có upload được không?
User mới đăng ký 5 giây trước có khác user lâu năm không?
Upload:
Max size là bao nhiêu? Server có chịu nổi không?
Validate ở client hay server? (Hint: client luôn có thể bypass)
Stream hay load nguyên file vào RAM?
Cancel giữa chừng xử lý sao?
Concurrent upload 100 request cùng lúc xử lý sao?
Upload xong rồi user xóa account thì file đi đâu?
Ảnh:
Format nào allow? JPG, PNG, WEBP, GIF, SVG?
SVG có cho không? (Hint: SVG có thể chứa script)
Validate bằng extension hay magic bytes? (Extension dễ giả)
Có strip EXIF metadata không?
Có resize không? Lưu original hay chỉ thumbnail?
Lưu ở đâu? Public folder hay protected?
Tên file đặt như nào? Giữ tên user hay generate UUID? (Hint: tên user nguy hiểm)
Làm avatar:
Avatar cũ xử lý sao khi update mới?
Có cần lưu lịch sử avatar không?
Avatar có hiển thị ở đâu khác ngoài profile không?
Người chưa login có thấy avatar của user khác không?
Mỗi câu hỏi tương ứng một dòng trong prompt. Prompt sẽ dài hơn nhiều so với "build tính năng upload ảnh". Nhưng cái bạn nhận về cũng ít bug hơn nhiều.
Template prompt copy-paste
Đây là template áp dụng Boundary Thinking khi prompt AI. Copy về dùng cho feature bất kỳ:
Tôi muốn build feature [tên feature].
Happy path: [1 câu mô tả luồng chính]
Yêu cầu xử lý các boundary case sau:
Input:
[liệt kê các giới hạn về size, format, content]
[liệt kê các giá trị edge: rỗng, null, quá dài, ký tự đặc biệt]
Concurrency:
[rate limit]
[xử lý request cancel/duplicate]
Lifecycle:
[data được tạo/update/xóa như nào]
[cleanup data cũ ra sao]
Failure:
[xử lý khi storage/network/API fail]
[retry mấy lần, log ở đâu]
Security:
[validate input ở server-side]
[phân quyền truy cập]
[strip metadata nhạy cảm nếu có]
Build từng phần một, test xong phần này mới qua phần khác.
Ví dụ cụ thể cho feature upload avatar:
Tôi muốn build feature upload avatar.
Happy path: User logged-in chọn ảnh từ máy, upload lên, ảnh được dùng làm avatar.
Yêu cầu boundary case:
Input:
Chỉ chấp nhận JPG, PNG, WEBP. Cấm SVG.
Validate bằng magic bytes, không tin extension.
Max size 5MB.
Strip EXIF metadata trước khi lưu.
Concurrency:
1 user upload tối đa 10 file/giờ.
Request cancel giữa chừng: xóa file tạm.
Lifecycle:
Khi upload mới, xóa avatar cũ khỏi storage.
Tên file dùng UUID, không dùng tên user gửi lên.
Failure:
Storage full: trả lỗi rõ ràng, không silent fail.
Network drop: cho retry 3 lần, sau đó báo lỗi.
Security:
Chỉ user logged-in upload được.
File lưu ở folder protected, serve qua signed URL.
Resize về max 500x500, lưu cả original.
Bạn thấy không? Prompt này dài. Nhưng nó loại bỏ 8/8 vấn đề mình kể ở đầu bài.
Sai lầm hay gặp khi áp Boundary Thinking
Sai lầm 1: Cố nghĩ ra tất cả boundary cùng lúc
Bạn không phải developer 10 năm kinh nghiệm. Lần đầu áp dụng, bạn sẽ chỉ nghĩ ra 30% boundary. Đó là bình thường. 70% còn lại học được qua kinh nghiệm. Đừng cố hoàn hảo. Cứ build, gặp bug, học từ bug, lần sau prompt tốt hơn.
Sai lầm 2: Boundary cho mọi thứ ngang nhau
Không phải feature nào cũng cần soi 5 trục như nhau. Feature đổi màu nền dashboard? Không cần security thinking nhiều. Feature thanh toán? Cần soi cực kỹ. Feature upload file? Cần soi cực kỹ. Feature hiển thị text? Soi nhẹ thôi.
Cân nhắc theo blast radius. Feature gì sập sẽ ảnh hưởng lớn → soi kỹ. Feature gì sập chỉ ảnh hưởng 1 user → soi vừa phải.
Sai lầm 3: Bỏ qua input từ "user thân thiện"
Bạn nghĩ "user của tôi là khách hàng ruột, ai mà lại phá app". Sai. Có ngày họ vô tình paste 50MB text vào ô comment. Có ngày họ vô tình bấm nút "Xóa tài khoản" 5 lần liên tiếp. Có ngày con họ nghịch điện thoại spam form 200 lần. User không cần ác ý để gây bug.
Sai lầm 4: Quên test sau khi prompt
Bạn prompt đầy đủ, AI build ra. Bạn nghĩ "okay đủ rồi". Sai. Phải test. Đeo mũ malicious user 15 phút, cố tình phá. Upload file rác, paste ký tự lạ, mở DevTools sửa request body, gọi API trực tiếp bằng Postman. Bạn sẽ tìm ra bug nhanh hơn 10 lần so với chờ user phát hiện.
Trước và sau khi áp Boundary Thinking
Trước:
Prompt: "Tạo form đăng ký tài khoản: email, password, confirm password, nút submit."
AI build ra. Test thử với email "abc@xyz.com" và password "123456". Chạy ngon. Ship.
Tuần sau:
User nhập email "asdf" (không có @). Tài khoản tạo thành công vì AI không validate.
User nhập password "1". Tài khoản tạo thành công.
User nhập email đã tồn tại. AI báo lỗi server 500 chứ không phải "email đã được dùng".
User submit form 50 lần. AI tạo 50 tài khoản.
User nhập password chứa script tag. Khi admin xem profile, browser chạy script đó.
Sau:
Prompt: "Tạo form đăng ký tài khoản.
Happy path: User nhập email + password, bấm submit, tài khoản được tạo, redirect sang trang chính.
Input:
Email: validate format có @, có domain. Check trùng với user đã có.
Password: tối thiểu 8 ký tự, có cả chữ và số. Confirm password phải khớp.
Cả 2 field đều không được rỗng, không quá dài (max 100 ký tự cho email, 72 cho password).
Strip whitespace đầu cuối email trước khi lưu.
Concurrency:
Rate limit 5 lần submit/phút từ cùng IP.
Disable nút submit sau khi bấm 1 lần.
Failure:
Database connection lỗi: hiện thông báo 'Hệ thống đang bận, thử lại sau'.
Email service lỗi: tạo tài khoản trước, queue email để gửi sau.
Security:
Hash password bằng bcrypt trước khi lưu, không bao giờ lưu plain text.
Trả về cùng error message cho 'email không tồn tại' và 'password sai' khi login (tránh leak info).
Escape HTML trong tất cả input trước khi render."
Cùng feature. Khác kết quả.
Bài tập 15 phút
Chọn 1 trong các feature sau (hoặc dùng feature bạn đang build):
Form gửi feedback của user (có nội dung text + ảnh đính kèm tùy chọn) Tính năng "Quên mật khẩu" (gửi link reset qua email) Tính năng search sản phẩm trong cửa hàng online
Làm theo 3 bước:
Bước 1 (3 phút): Viết happy path 1 câu cho feature đó.
Bước 2 (10 phút): Soi 5 trục. Với mỗi trục, viết ít nhất 2 câu hỏi boundary. Tổng cộng ít nhất 10 câu hỏi.
Bước 3 (2 phút): Viết prompt hoàn chỉnh theo template ở trên, nhét hết các câu hỏi đã nghĩ ra thành yêu cầu cụ thể.
Làm xong share trong comment. Mình sẽ pick vài bài soi giúp các boundary còn thiếu.
Cách rèn Boundary Thinking lâu dài
Boundary Thinking không học 1 lần là xong. Nó là cơ bắp. Càng tập càng khỏe.
Cách rèn nhanh nhất: mỗi lần build xong feature, trước khi đánh dấu done, đeo mũ malicious user 15 phút. Tự đặt câu hỏi "nếu tôi muốn phá feature này, tôi sẽ làm gì?". Rồi thử làm thật. Upload file rác. Gửi request lạ. Click loạn xạ. Mở DevTools sửa form. Gọi API trực tiếp.
15 phút này tiết kiệm cho bạn 15 giờ debug sau khi user phát hiện ra bug.
Một cách khác: đọc bug report của các app lớn. GitHub có public issue tracker của nhiều project. Đọc xem user báo bug gì, bug đó xảy ra trong điều kiện nào. Bạn sẽ học được vô số boundary mà mình không bao giờ tự nghĩ ra.
Một cách nữa: mỗi khi bạn dùng app của người khác và gặp lỗi, ghi lại. App này lỗi khi nào? Mình làm gì để gây ra lỗi đó? Bạn đang tích lũy một thư viện boundary trong đầu, miễn phí.
Kết nối với các bài trước
Đến đây mình đã đi qua 7 bài. Cùng nhìn lại:
Phase 1 dạy bạn cách nhìn: Decomposition (chia nhỏ), Pattern Recognition (nhận pattern), Abstraction (chọn level chi tiết).
Phase 2 dạy bạn cách thiết kế trước khi build: Algorithm Thinking (vẽ flow), State Thinking (liệt kê trạng thái), MVP Thinking (cắt xuống cái cốt lõi).
Phase 3 dạy bạn xử lý khi sai. Bài 7 (Debug Thinking) dạy đọc lỗi. Bài 8 này dạy lường trước cái sẽ sai. Khác biệt: bài 7 reactive (sau khi lỗi xảy ra), bài 8 proactive (trước khi lỗi xảy ra).
Combo Debug Thinking + Boundary Thinking là combo cực mạnh. Boundary giúp bạn giảm 80% bug ngay từ đầu. Debug giúp bạn xử lý 20% bug còn lại nhanh hơn.
Và đặc biệt, Boundary Thinking kết hợp với Algorithm Thinking (bài 4) sẽ cho bạn flow hoàn chỉnh:
Bước 1 (Algorithm Thinking): Vẽ happy path từng bước. Bước 2 (Boundary Thinking): Với mỗi bước, hỏi "có thể sai ở đâu?". Thêm điều kiện và fallback. Bước 3: Prompt AI theo flow đã có cả happy path lẫn boundary case.
Kết quả: AI build ra feature hoàn chỉnh, ít bug, dùng được trong production thật. Không phải demo 1 lần rồi tan vỡ.
Bài tiếp theo
Bài 9 - Isolation Thinking: "Thu hẹp để tìm lỗi."
App 10 tính năng bị lỗi. Bạn bảo AI sửa lỗi, AI sửa xong lại hỏng chỗ khác. Vòng lặp địa ngục. Tuần sau mình sẽ dạy cách thoát khỏi vòng lặp này bằng kỹ thuật cô lập vấn đề. Đây là kỹ năng cứu sống bạn khi app phức tạp lên.