Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là PM tại một công ty fintech ở TP.HCM. Sprint review diễn ra, đội engineering tự hào demo tính năng "Đăng nhập bằng số điện thoại". Mọi thứ chạy mượt trên máy của lập trình viên. Nhưng đến tuần sau, bộ phận chăm sóc khách hàng báo cáo dồn dập: người dùng nhập sai mã OTP ba lần thì app trắng màn hình, người dùng ở vùng sóng yếu thì OTP đến trễ và bị tính là hết hạn, còn QA thì chưa kiểm thử trên iOS thấp hơn phiên bản 15. Đội kỹ thuật ngơ ngác: "Nhưng tính năng chạy được mà, sao lại bảo chưa xong?"
Câu chuyện trên xảy ra hàng ngày, ở rất nhiều đội sản phẩm Việt Nam, và gần như luôn xuất phát từ cùng một gốc rễ: không có định nghĩa chung về thế nào là "xong". Mỗi người trong đội mang trong đầu một định nghĩa "xong" khác nhau. Với lập trình viên, "xong" là code chạy. Với QA, "xong" là không còn bug nghiêm trọng. Với PM, "xong" là khách hàng dùng được và đạt mục tiêu kinh doanh. Khi những định nghĩa ngầm này va vào nhau, kết quả là tranh cãi, rework, và niềm tin bị bào mòn.
Là một Technical PM, bạn không viết code, nhưng bạn là người gác cổng chất lượng đầu ra. Hai công cụ mạnh nhất bạn có để loại bỏ sự mơ hồ này chính là Acceptance Criteria (AC) — tiêu chí nghiệm thu cho từng story, và Definition of Done (DoD) — định nghĩa "xong" chung cho toàn đội. Nắm vững hai khái niệm này, bạn biến những cuộc tranh luận cảm tính thành những thỏa thuận rõ ràng có thể kiểm chứng. Đây chính là nội dung của Bài 18.
Khái niệm cốt lõi
Acceptance Criteria là gì
Acceptance Criteria (AC) là tập hợp các điều kiện cụ thể, có thể kiểm chứng mà một user story phải thỏa mãn để được coi là hoàn thành. AC trả lời câu hỏi: "Làm sao chúng ta biết tính năng này hoạt động đúng như mong đợi?"
Điểm mấu chốt: AC gắn với một story cụ thể. Mỗi story có bộ AC riêng. AC mô tả hành vi mà hệ thống phải có, chứ không mô tả cách triển khai kỹ thuật. AC là hợp đồng giữa PM (người định nghĩa "đúng") và engineer (người xây dựng), đồng thời là cơ sở để QA viết test case.
Một AC tốt có ba đặc tính: cụ thể (không mơ hồ), kiểm chứng được (đúng/sai rõ ràng, không phụ thuộc cảm tính), và độc lập với giải pháp (mô tả "cái gì", không quy định "làm như thế nào").
Format Given-When-Then
Cách viết AC phổ biến và mạnh nhất là theo cấu trúc Given-When-Then (Cho trước — Khi — Thì), xuất phát từ phương pháp BDD (Behavior-Driven Development). Cấu trúc này buộc bạn nghĩ rõ ràng về bối cảnh, hành động và kết quả:
- Given (Cho trước): trạng thái/bối cảnh ban đầu của hệ thống và người dùng.
- When (Khi): hành động mà người dùng thực hiện.
- Then (Thì): kết quả mong đợi mà hệ thống phải tạo ra.
Given: người dùng đã đăng nhập và đang xem trang chi tiết một sản phẩm còn hàng
When: người dùng nhấn nút "Thêm vào giỏ"
Then: sản phẩm xuất hiện trong giỏ với số lượng 1, và biểu tượng giỏ hàng cập nhật số đếm tăng lên 1
Sức mạnh của format này là nó ép bạn suy nghĩ các trường hợp biên (edge case). Khi viết xong nhánh "happy path" (luồng thành công), bạn tự nhiên đặt câu hỏi: còn nếu sản phẩm hết hàng thì sao? Nếu người dùng chưa đăng nhập? Mỗi câu hỏi đó là một AC mới:
Given: người dùng đang xem một sản phẩm đã hết hàng
When: người dùng nhấn nút "Thêm vào giỏ"
Then: hệ thống hiển thị thông báo "Sản phẩm tạm hết hàng" và nút bị vô hiệu hóa
Không phải mọi AC đều cần Given-When-Then. Với những điều kiện đơn giản, bạn có thể dùng checklist dạng gạch đầu dòng ("Trường email phải validate đúng định dạng", "Thời gian phản hồi dưới 2 giây"). Nhưng với hành vi có tương tác, Given-When-Then là lựa chọn rõ ràng nhất.
Definition of Done là gì
Nếu AC là tiêu chí riêng của từng story, thì Definition of Done (DoD) là tập tiêu chuẩn chất lượng chung áp dụng cho mọi story trong đội. DoD trả lời câu hỏi: "Một hạng mục công việc bất kỳ phải thỏa mãn những điều kiện chất lượng nào trước khi được coi là thực sự hoàn thành và sẵn sàng đưa lên production?"
Một DoD điển hình của đội phát triển có thể bao gồm:
- Code đã được review và merge (không còn nhánh chờ).
- Đã viết unit test, độ phủ test đạt ngưỡng đã thỏa thuận (ví dụ ≥ 70%).
- Đã qua kiểm thử QA, không còn bug mức nghiêm trọng (severity cao).
- Tài liệu kỹ thuật / API doc đã cập nhật.
- Đã kiểm tra trên các trình duyệt / thiết bị mục tiêu.
- Đã deploy thành công lên môi trường staging.
- Đáp ứng tiêu chuẩn hiệu năng và bảo mật cơ bản đã đặt ra.
AC và DoD bổ sung cho nhau như thế nào
Hãy hình dung: AC là "tính năng này có làm đúng việc cần làm không?", còn DoD là "công việc này có được làm đủ chất lượng để giao không?". Một tính năng có thể pass hết AC (chạy đúng logic nghiệp vụ) nhưng vẫn vi phạm DoD (chưa có test, chưa review) — và ngược lại. Cả hai phải đồng thời được thỏa mãn.
| Tiêu chí | Acceptance Criteria | Definition of Done |
|---|---|---|
| Phạm vi | Riêng từng story | Chung toàn đội |
| Trả lời | Tính năng làm đúng việc không? | Công việc được làm đủ chất lượng chưa? |
| Tần suất thay đổi | Viết mới cho mỗi story | Ổn định, ít thay đổi |
| Ai viết chính | PM (cùng đội) | Cả đội thống nhất |
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT và tính năng "Đặt hàng nhanh"
Một sàn thương mại điện tử giả định tên ChợViệt (quy mô khoảng 200 nghìn đơn/tháng) muốn ra mắt tính năng "Đặt hàng nhanh" — cho phép khách mua sản phẩm chỉ với một lần nhấn, không qua giỏ hàng. PM ban đầu viết story rất sơ sài: "Là người dùng, tôi muốn đặt hàng nhanh để tiết kiệm thời gian."
Engineer triển khai đúng một nút "Mua ngay" gọi thẳng API tạo đơn. Demo chạy ngon. Nhưng khi ra mắt, hàng loạt vấn đề: khách chưa lưu địa chỉ thì đơn tạo ra thiếu thông tin giao hàng; khách chưa chọn phương thức thanh toán mặc định thì đơn bị treo; sản phẩm có nhiều phân loại (size, màu) thì hệ thống tự chọn bừa.
PM ngồi lại, viết AC theo Given-When-Then:
Given: người dùng đã đăng nhập, đã có địa chỉ mặc định và phương thức thanh toán mặc định
When: người dùng nhấn "Mua ngay" trên sản phẩm không có phân loại
Then: hệ thống tạo đơn với địa chỉ và thanh toán mặc định, chuyển tới màn xác nhận trong dưới 2 giâyGiven: người dùng đã đăng nhập nhưng CHƯA có địa chỉ mặc định
When: người dùng nhấn "Mua ngay"
Then: hệ thống mở màn nhập địa chỉ trước, sau khi lưu mới tiếp tục tạo đơn
Given: sản phẩm có nhiều phân loại (size/màu) và người dùng chưa chọn
When: người dùng nhấn "Mua ngay"
Then: hệ thống hiển thị popup chọn phân loại, nút xác nhận bị khóa cho tới khi chọn đủ
Bài học: AC viết tốt buộc cả đội nhìn thấy các nhánh nghiệp vụ trước khi code, thay vì phát hiện chúng qua phản ánh của khách hàng. Chi phí sửa một edge case lúc viết AC gần như bằng không; chi phí sửa nó trên production là hotfix khẩn cấp lúc nửa đêm.
Ví dụ 2 — Startup SaaS B2B và DoD bị "co giãn"
Một startup SaaS giả định tên DataNhanh (công cụ phân tích dữ liệu cho doanh nghiệp vừa) đang trong giai đoạn chạy nước rút trước vòng gọi vốn. Áp lực thời gian khiến đội liên tục "bỏ qua tạm" vài bước: "Lần này release gấp, viết test sau", "Chưa kịp cập nhật doc, để đó". Mỗi lần như vậy DoD bị bẻ cong một chút.
Ba tháng sau, hậu quả lộ ra: không ai dám đụng vào module báo cáo vì nó không có test, mỗi thay đổi nhỏ đều gây lỗi dây chuyền. Onboarding lập trình viên mới mất gấp đôi thời gian vì doc lỗi thời. Velocity của đội tụt 30% so với quý trước, dù số người không đổi. Đây chính là nợ kỹ thuật sinh ra từ việc DoD không được tôn trọng.
PM cùng Engineering Manager quyết định "tái lập" DoD và dán nó công khai trên bảng Jira, biến nó thành điều kiện không thể thương lượng để đóng một story. Họ cũng thêm một quy ước quan trọng: nếu thực sự cần bỏ qua một mục DoD vì lý do khẩn cấp, việc đó phải được ghi nhận thành một ticket nợ kỹ thuật ngay lập tức, có người chịu trách nhiệm và hạn xử lý — chứ không "bỏ qua âm thầm".
Bài học: DoD chỉ có giá trị khi nó được tôn trọng nhất quán. Một DoD bị co giãn tùy tâm trạng còn tệ hơn không có DoD, vì nó tạo ảo giác về chất lượng. Việc đánh đổi (trade-off) chất lượng đôi khi là cần thiết, nhưng phải là quyết định hiển thị và có chủ đích, không phải một sự xói mòn ngầm.
Ví dụ 3 — Đội của Grab và AC cho yêu cầu phi chức năng
Tại một đội thuộc Grab (bối cảnh phỏng đoán dựa trên đặc thù sản phẩm gọi xe), tính năng "Hiển thị tài xế gần nhất theo thời gian thực" được phát triển. PM ban đầu chỉ viết AC chức năng: "Khi mở app, hiển thị các tài xế trong bán kính 3km." Demo đẹp với 5 tài xế trên bản đồ.
Nhưng giờ cao điểm tại Hà Nội, khu vực có hàng trăm tài xế, bản đồ load chậm và giật. Vấn đề ở đây không phải logic sai — mà là thiếu AC phi chức năng (non-functional) về hiệu năng. PM bổ sung:
Given: khu vực có trên 200 tài xế đang hoạt động trong bán kính
When: người dùng mở màn hình bản đồ
Then: bản đồ hiển thị tối đa 50 tài xế gần nhất, thời gian tải dưới 1.5 giây ở mạng 4GGiven: kết nối mạng của người dùng bị gián đoạn giữa chừng
When: dữ liệu vị trí tài xế không cập nhật được trong 10 giây
Then: app hiển thị trạng thái "Đang cập nhật..." thay vì màn hình trắng hoặc dữ liệu cũ gây hiểu lầm
Bài học: AC không chỉ là logic nghiệp vụ. Với Technical PM, các tiêu chí phi chức năng — hiệu năng, độ tin cậy, xử lý lỗi mạng, khả năng chịu tải — thường là phần phân biệt một tính năng "demo được" với một tính năng "dùng được ở quy mô thật". Đây là lúc hiểu biết kỹ thuật của bạn tạo ra khác biệt rõ rệt so với PM thông thường.
Hướng dẫn từng bước
Dưới đây là quy trình thực hành để viết AC và thiết lập DoD trong đội của bạn.
Bước 1 — Bắt đầu từ user story và mục tiêu. Trước khi viết AC, hãy chắc chắn bạn hiểu vì sao story tồn tại. Một AC tốt phải phục vụ mục tiêu của story, không phải liệt kê tính năng cho có.
Bước 2 — Viết nhánh "happy path" trước. Mô tả luồng thành công lý tưởng theo Given-When-Then. Đây là xương sống.
Bước 3 — Truy vấn các edge case một cách có hệ thống. Tự hỏi: Nếu dữ liệu rỗng thì sao? Nếu người dùng chưa đăng nhập? Nếu mạng lỗi? Nếu nhập sai định dạng? Nếu thao tác đồng thời? Mỗi câu trả lời "thì hệ thống phải..." là một AC mới.
Bước 4 — Thêm AC phi chức năng nếu cần. Với tính năng nhạy về hiệu năng, bảo mật hay độ tin cậy, hãy viết AC cụ thể kèm con số (thời gian phản hồi, số lượng đồng thời, hành vi khi lỗi).
Bước 5 — Rà soát cùng engineer và QA trước khi bắt đầu code. Đây là bước quan trọng nhất và hay bị bỏ. Engineer sẽ chỉ ra edge case kỹ thuật bạn chưa thấy; QA sẽ biến AC thành test case. AC phải được "ký duyệt" ba bên trước khi vào sprint.
Bước 6 — Thiết lập DoD ở cấp đội (làm một lần, định kỳ rà lại). Tổ chức một buổi họp cùng cả đội để liệt kê các tiêu chuẩn chất lượng chung. Viết ra, dán công khai, và biến nó thành điều kiện bắt buộc trong định nghĩa quy trình (ví dụ: một cột "Done" trên Jira chỉ được kéo vào khi mọi mục DoD được tick).
Bước 7 — Dùng AC + DoD làm checklist nghiệm thu. Khi nghiệm thu, đối chiếu từng AC của story và từng mục DoD. Chỉ khi cả hai đều đạt, story mới được đóng.
Lỗi thường gặp & mẹo
Lỗi 1 — AC mơ hồ, không kiểm chứng được. Viết "Trang phải tải nhanh" là vô nghĩa — "nhanh" là bao nhiêu? Hãy thay bằng "Thời gian tải dưới 2 giây ở mạng 4G". Quy tắc: nếu hai người có thể bất đồng về việc AC đã đạt hay chưa, AC đó chưa đủ cụ thể.
Lỗi 2 — Nhồi quá nhiều logic vào một story. Khi một story có tới 20 AC, đó là dấu hiệu story quá lớn và cần chia nhỏ (story slicing — nội dung của Bài 16). AC giúp bạn phát hiện điều này sớm.
Lỗi 3 — Viết AC theo cách triển khai thay vì hành vi. "Dùng Redis để cache kết quả" không phải AC — đó là quyết định kỹ thuật của engineer. AC đúng là "Kết quả tìm kiếm lặp lại trong 5 phút phải trả về dưới 200ms". Hãy mô tả cái gì, để engineer quyết định làm thế nào.
Lỗi 4 — Coi DoD là việc riêng của engineer. DoD là cam kết của cả đội, bao gồm cả tiêu chí mà PM quan tâm (ví dụ: đã cập nhật tài liệu hướng dẫn người dùng, đã bật cờ tính năng / feature flag). PM phải tham gia định nghĩa DoD.
Lỗi 5 — Để DoD bị xói mòn âm thầm. Như ví dụ DataNhanh, mọi ngoại lệ với DoD phải được ghi nhận công khai thành nợ kỹ thuật. Mẹo: thêm một mục bắt buộc "Nếu bỏ qua mục DoD nào, đã tạo ticket nợ kỹ thuật chưa?".
Mẹo vàng: Hãy phân biệt rạch ròi — AC trả lời "tính năng có đúng không", DoD trả lời "công việc có đủ chất lượng không". Khi có tranh cãi lúc nghiệm thu, hỏi ngay: đây là vấn đề AC hay vấn đề DoD? Câu hỏi đó thường gỡ được nút thắt.
Bài tập thực hành
Bài 1 — Viết AC đầy đủ. Cho story sau: "Là người dùng đã đăng nhập, tôi muốn đặt lại mật khẩu qua email để khôi phục tài khoản khi quên mật khẩu." Hãy viết tối thiểu 5 AC theo format Given-When-Then, bao gồm ít nhất 2 edge case (ví dụ: email không tồn tại, link reset hết hạn) và 1 AC phi chức năng (ví dụ: thời gian gửi email).
Bài 2 — Soạn DoD cho đội của bạn. Giả định bạn là PM của một đội 6 người (4 dev, 1 QA, 1 designer) làm app di động. Hãy liệt kê 8–10 mục DoD phù hợp. Đánh dấu mục nào là bắt buộc tuyệt đối và mục nào có thể đánh đổi trong tình huống khẩn (kèm điều kiện ghi nhận nợ kỹ thuật).
Bài 3 — Phân loại tình huống. Với mỗi tình huống sau, xác định đó là vi phạm AC hay vi phạm DoD: (a) Tính năng tính phí ship sai công thức với đơn trên 500k. (b) Tính năng chạy đúng nhưng chưa có unit test nào. (c) Nút "Lưu" hoạt động nhưng chưa được kiểm thử trên Android 10. (d) Logic giảm giá đúng nhưng tài liệu API chưa cập nhật.
Tóm tắt
Bài 18 đã trang bị cho bạn hai công cụ nền tảng để loại bỏ sự mơ hồ trong định nghĩa "xong":
- Acceptance Criteria (AC) là tiêu chí nghiệm thu riêng cho từng story, trả lời câu hỏi "tính năng này có làm đúng việc không". Format Given-When-Then giúp bạn mô tả hành vi rõ ràng và ép suy nghĩ các edge case. AC phải cụ thể, kiểm chứng được, và mô tả hành vi chứ không phải cách triển khai.
- Definition of Done (DoD) là tiêu chuẩn chất lượng chung cho cả đội, áp dụng cho mọi story, trả lời câu hỏi "công việc này có đủ chất lượng để giao chưa". DoD phải được thống nhất, công khai, và tôn trọng nhất quán — mọi ngoại lệ phải được ghi nhận thành nợ kỹ thuật.
- Một story chỉ thực sự "xong" khi thỏa mãn đồng thời toàn bộ AC riêng của nó và toàn bộ DoD chung của đội.