Lập trình ở tầng ý định — Khi Spec trở thành ngôn ngữ máy hiểu được
Chương này xây dựng toàn bộ bộ công cụ để viết Executable Spec: từ triết lý thiết kế, cấu trúc 8 thành phần, đến EARS Notation — ngôn ngữ đặc tả yêu cầu được thiết kế riêng để loại bỏ sự mơ hồ.
Có một nghịch lý thú vị trong thực tế phát triển phần mềm với AI: chúng ta trao cho AI khả năng viết code phức tạp, nhưng lại không dành đủ thời gian để nói với AI chính xác điều mình muốn. Kết quả là AI "đoán" — đôi khi đúng, nhiều khi sai, và luôn luôn không nhất quán.
Executable Specification (Spec có thể thực thi) là câu trả lời cho nghịch lý đó. Đây không phải tài liệu Word mà một người đọc rồi diễn giải theo cách riêng. Đây là một hệ thống ngôn ngữ có cấu trúc chặt chẽ — đủ rõ ràng để AI biến nó thành code mà không cần đoán, đủ có thể đọc để con người review và maintain.
Trong lập trình hướng đối tượng, Interface là hợp đồng: nó định nghĩa những gì một thành phần phải làm mà không quan tâm đến cách nó làm. Hai bên — người gọi và người thực thi — đều chỉ cần biết Interface, không cần biết chi tiết bên trong của nhau. Giao tiếp được tách biệt khỏi implementation.
Executable Specification đóng vai trò y hệt trong giao tiếp giữa con người và AI Agent. Spec là hợp đồng giữa "ý định của con người" và "năng lực thực thi của AI". Con người không cần biết AI sẽ sinh code ra sao. AI không cần đọc tâm trí con người. Cả hai gặp nhau tại Spec — một văn bản đủ chính xác để không cần giải thích thêm.
Vấn đề cốt lõi: PRD truyền thống được viết cho con người — người có thể suy luận, hỏi lại, và lấp đầy khoảng trống bằng common sense. AI không làm được điều đó: nó chỉ thực thi — và khi gặp mơ hồ, nó hallucinate.
| Chiều cạnh | PRD truyền thống | Executable Specification |
|---|---|---|
| Người đọc mục tiêu | Con người — tự suy luận | AI Agent — phải thực thi trực tiếp |
| Độ rõ ràng (Precision) | Cho phép mơ hồ, con người lấp đầy | Zero ambiguity — thiếu logic = AI hallucinate |
| Ngôn ngữ | Ngôn ngữ tự nhiên, narrative | Ngôn ngữ có cấu trúc (EARS, BDD…) |
| Điều kiện biên | Ngụ ý hoặc bỏ qua | Explicit, được liệt kê đầy đủ |
| Phạm vi | Thường chỉ nói những gì làm | Bao gồm cả "Out of Scope" — những gì KHÔNG làm |
| Testability | Khó đo lường trực tiếp | Mỗi requirement → test case trực tiếp |
| Xử lý lỗi | Thường được để ngụ ý | Explicit — mọi failure path đều được đặc tả |
Đây là điểm mấu chốt mà nhiều kỹ sư bỏ qua khi lần đầu viết spec cho AI. Khi một developer con người gặp một yêu cầu mơ hồ, họ có ba lựa chọn: hỏi lại, suy luận từ context, hoặc tạm bỏ qua. Khi một AI Agent gặp yêu cầu mơ hồ, nó chỉ có một lựa chọn: đoán — và đưa đoán đó ra như thể nó là sự thật.
Hiện tượng này gọi là "confident hallucination" — AI không nói "tôi không chắc", nó nói "đây là cách tôi hiểu và đây là code tôi viết". Kết quả: code chạy được, pass basic tests, nhưng implement sai logic. Lỗi này cực kỳ nguy hiểm vì khó phát hiện hơn lỗi compile hay runtime.
"Hệ thống phải xử lý đăng nhập một cách thông minh, đảm bảo bảo mật và trải nghiệm người dùng tốt."
AI tự quyết: "thông minh" → ML model? · "bảo mật" → bcrypt? SHA256? · "trải nghiệm tốt" → auto-fill? biometric? → 3 developer, 3 implementation khác nhau.
GIVEN người dùng nhập email + password hợp lệ WHEN nhấn "Đăng nhập" THEN: Hash bcrypt (cost=12), JWT RS256 expiry=15 phút, failed≥5 → lock 30 phút, return 200+token HOẶC 401+error_code.
Mọi quyết định đã được con người đưa ra. AI chỉ còn việc implement.
Một trong những giá trị lớn nhất của Executable Spec là nó buộc bạn tách biệt hai câu hỏi hoàn toàn khác nhau: "Hệ thống phải làm gì?" (What) và "Hệ thống sẽ làm điều đó như thế nào?" (How). Spec chỉ trả lời câu hỏi What. How là trách nhiệm của AI implementation — và AI giỏi việc đó hơn bạn nghĩ, miễn là What đủ rõ ràng.
Sự tách biệt này mang lại hai lợi ích quan trọng. Thứ nhất, nó cho phép bạn thay đổi implementation mà không cần viết lại spec. Thứ hai, nó cho phép bạn test behavior thay vì test implementation — test rằng "kết quả đúng" thay vì "code chạy theo đúng cách này".
Một Executable Specification đầy đủ cần tám thành phần, mỗi thành phần giải quyết một khía cạnh riêng biệt của bài toán. Thiếu bất kỳ thành phần nào cũng tạo ra "lỗ hổng context" — những khoảng trống mà AI sẽ tự lấp đầy bằng các giả định không được kiểm soát.
| # | Thành phần | Câu hỏi trả lời | Thiếu → AI làm gì? |
|---|---|---|---|
| 1 | Context & Goal | Tại sao feature này tồn tại? | Code "đúng kỹ thuật" nhưng sai bài toán |
| 2 | Actors & Roles | Ai tương tác? Với quyền gì? | Bỏ qua phân quyền, code cho một loại user |
| 3 | Functional Requirements | Hệ thống làm gì? | Implement theo hiểu biết default của model |
| 4 | Non-functional Requirements | Tốt đến mức nào? | Performance/security theo best guess |
| 5 | Data Model | Dữ liệu có cấu trúc gì? | Tự thiết kế schema — thường không phù hợp |
| 6 | Error Handling | Khi sai thì làm gì? | Happy path only — production sẽ crash |
| 7 | Acceptance Criteria | Định nghĩa "xong" là gì? | Không có target — tests sẽ yếu và thiếu |
| 8 | Out of Scope | Hệ thống KHÔNG làm gì? | "Nhiệt tình" thêm features không cần thiết |
Thành phần này thường bị bỏ qua vì có cảm giác "không technical". Đây là sai lầm. AI model được train trên hàng triệu codebase — khi bạn cung cấp business context, model có thể align implementation với những pattern phổ biến nhất cho use case đó, thay vì chọn một pattern ngẫu nhiên.
Đây là thành phần ít phổ biến nhất nhưng lại có tác động lớn nhất đến chất lượng code sinh ra. AI model được train để "helpful" — nó có xu hướng thêm những gì nó nghĩ là hữu ích, ngay cả khi bạn không yêu cầu. Không có "Out of Scope" tường minh, AI sẽ tự quyết định biên giới của feature — và thường quyết định sai.
Sau đây là năm anti-pattern phổ biến nhất, mỗi loại đều được ghi nhận từ thực tế của các team đã áp dụng AI-assisted development. Nhận biết và tránh những lỗi này là kỹ năng phân biệt kỹ sư giỏi với kỹ sư trung bình trong kỷ nguyên AI.
Dùng những tính từ đánh giá mà không có tiêu chí đo lường. AI không có context để định nghĩa "nhanh", "thông minh", "mượt mà" — nó sẽ dùng định nghĩa của chính nó, thường là từ codebase training data phổ biến nhất.
Hai yêu cầu mâu thuẫn nhau mà không có cơ chế giải quyết. AI sẽ không báo lỗi — nó sẽ chọn một trong hai (thường là cái dễ implement hơn) và bỏ qua cái còn lại. Không có warning, không có error, chỉ có implementation sai.
Quên không cung cấp thông tin về stack hiện tại, conventions đang dùng, hoặc constraints của môi trường. AI sẽ dùng default knowledge từ training — đôi khi đúng, thường là conflict với codebase thực tế.
"Xây dựng DatePicker cho form booking." — AI sẽ chọn: react-datepicker? MUI? date-fns? Tailwind? useState? Formik? Zod? vi-VN hay English?
"DatePicker cho booking. Tech: shadcn/ui v2, Tailwind CSS v4, React Hook Form v8, Zod schema, date-fns v4, locale vi-VN. Pattern: xem /src/components/forms/TimePicker.tsx"
Những điều "hiển nhiên" với con người thường hoàn toàn không hiển nhiên với AI. Đặc biệt là các business rules ẩn, regulatory requirements, hay domain invariants.
Spec không có version, không có ownership, và thay đổi trong quá trình AI đang implement. Đây là recipe cho inconsistent codebase — code viết dựa trên spec v1 sẽ conflict với code viết dựa trên spec v2.
| # | Tên | Dấu hiệu nhận biết |
|---|---|---|
| 1 | Magic Req. | Tính từ không có số đo: nhanh, thông minh, tốt |
| 2 | Contradiction | Hai yêu cầu xung đột, không có tie-breaker |
| 3 | Context Gap | Thiếu tech stack, library, existing patterns |
| 4 | Implicit Assume | Business rule, regulatory req. không viết ra |
| 5 | Moving Target | Spec không có version, thay đổi mid-sprint |
Năm 2009, Alistair Mavin và nhóm kỹ sư tại Rolls-Royce (hàng không vũ trụ, không phải ô tô) đang đối mặt với một vấn đề sống còn: tài liệu yêu cầu cho hệ thống kiểm soát động cơ máy bay chứa hàng nghìn câu yêu cầu, và phần lớn trong số đó mơ hồ đến mức hai kỹ sư khác nhau đọc cùng một câu có thể implement theo hai cách hoàn toàn khác nhau.
Hệ quả không phải là bug — là thảm họa hàng không. Họ cần một ngôn ngữ đặc tả có cấu trúc, dễ học, và buộc người viết phải tường minh về điều kiện, hành động, và hệ thống. EARS ra đời từ nhu cầu đó: Easy Approach to Requirements Syntax.
Mấy chục năm sau, EARS tìm được ứng dụng hoàn hảo thứ hai: viết Executable Spec cho AI Agent. Lý do giống hệt — AI không khoan nhượng với sự mơ hồ, giống như một máy bay không khoan nhượng với lỗi kỹ thuật. Nếu câu yêu cầu không rõ ràng, AI sẽ chọn một cách diễn giải tùy ý — và bạn không biết nó đã chọn cái gì.
Dùng cho các yêu cầu không có điều kiện — không trigger, không trạng thái, luôn áp dụng mọi lúc. Đây là hành vi nền tảng của hệ thống. Mỗi mẫu EARS được thiết kế cho một loại yêu cầu khác nhau. Việc chọn sai mẫu không chỉ là lỗi văn phong — nó che giấu thông tin quan trọng mà AI cần để implement đúng.
Cú pháp: THE <system> SHALL <action>.
Dùng khi hành vi được kích hoạt bởi một sự kiện cụ thể — hành động của người dùng, signal từ hệ thống, hay thay đổi trạng thái. Khác với Ubiquitous: đây là hành vi xảy ra TẠI MỘT THỜI ĐIỂM cụ thể, không phải liên tục.
Cú pháp: WHEN <event>, THE <system> SHALL <action>.
Dùng khi hành vi phụ thuộc vào trạng thái hiện tại của hệ thống hoặc đối tượng. Khác với Event-driven: đây là hành vi liên tục trong khi trạng thái tồn tại, không chỉ tại thời điểm chuyển đổi. Đây là điểm khác biệt quan trọng — WHILE vs WHEN.
Cú pháp: WHILE <state>, THE <system> SHALL <action>.
Dùng cho các tính năng configurable — hành vi chỉ áp dụng khi feature flag, cài đặt, hoặc điều kiện hệ thống nhất định được kích hoạt. Rất phổ biến trong SaaS với nhiều tier.
Cú pháp: WHERE <feature> IS ENABLED, THE <sys> SHALL <action>.
Đây là mẫu quan trọng nhất và thường bị bỏ qua nhất. AI rất giỏi implement happy path nhưng thường không biết xử lý lỗi trừ khi được chỉ định rõ ràng.
Cú pháp: WHERE <error condition>, THE <sys> SHALL <response>.
Đây là bảng tham khảo nhanh để dùng ngay khi viết spec. Laminate và dán lên màn hình, hoặc thêm vào snippet của editor.
| Mẫu | Cú pháp | Khi nào dùng | Ví dụ nhanh |
|---|---|---|---|
| Ubiquitous | THE <sys> SHALL <action>. | Luôn đúng, mọi lúc | THE sys SHALL hash passwords với bcrypt ≥12. |
| Event-driven | WHEN <event>, THE <sys> SHALL <action>. | Phản ứng với sự kiện | WHEN user clicks "Submit", THE sys SHALL validate trong 1s. |
| State-driven | WHILE <state>, THE <sys> SHALL <action>. | Hành vi liên tục trong trạng thái | WHILE offline, THE app SHALL queue writes locally. |
| Optional | WHERE <feature> IS ENABLED, THE <sys> SHALL <action>. | Feature flag / gói dịch vụ | WHERE 2FA enabled, THE sys SHALL require OTP after login. |
| Unwanted ★ | WHERE <error/condition>, THE <sys> SHALL <response>. | Xử lý lỗi & edge cases | WHERE API fails 3×, THE sys SHALL refund và alert Slack. |
Đây là chi tiết nhỏ nhưng cực kỳ quan trọng. Khi AI gặp SHALL, nó hiểu đây là mandatory — không implement là bug. Khi gặp SHOULD, nó có thể skip nếu thấy phức tạp. Khi gặp MAY, nó có thể implement hoặc không, tùy "cảm hứng".
| Keyword | Nghĩa | AI behavior | Khi nào dùng |
|---|---|---|---|
| SHALL | Bắt buộc — không thể bỏ qua | Implement 100%, thiếu = bug | Mọi yêu cầu core business logic |
| SHALL NOT | Bắt buộc KHÔNG làm | Không implement, nếu làm = bug | Security constraints, prohibitions |
| SHOULD | Khuyến nghị — tốt nếu có | Có thể skip khi khó hoặc conflicting | Best practices, non-critical quality |
| SHOULD NOT | Không khuyến nghị | Tránh nhưng không phải lỗi nếu vi phạm | Deprecated patterns, anti-patterns |
| MAY | Tùy chọn — được phép nếu muốn | Implement hoặc không, tùy context | Extensions, nice-to-have features |
Một tính năng thực tế cần nhiều mẫu EARS kết hợp. Ví dụ sau mô tả toàn bộ tính năng "Đăng nhập" với tất cả 5 mẫu, bao gồm cả các edge case thường bị bỏ qua:
Phân loại mẫu EARS phù hợp và viết lại theo đúng cú pháp:
Viết Executable Spec đầy đủ cho tính năng "Quên mật khẩu" sử dụng tất cả 5 mẫu EARS.
Yêu cầu tối thiểu:
Đưa spec bài 5.3.B vào Cline với prompt:
Ghi lại phản hồi của AI và update spec.
Một sai lầm phổ biến của những người mới học SDD là áp dụng cùng một mức độ chi tiết cho mọi thứ. Họ viết Formal Spec đầy đủ 8 thành phần cho một hàm tiện ích 5 dòng code, và đồng thời chỉ viết một comment mơ hồ cho luồng thanh toán xử lý hàng triệu đồng. Cả hai đều sai.
Spec là đầu tư. Giống như mọi khoản đầu tư, ROI phụ thuộc vào việc đầu tư đúng chỗ. Mục tiêu là calibrate: viết spec đủ để AI thực thi đúng, không phải nhiều nhất có thể.
| Risk \ Logic | Logic Đơn giản | Logic Vừa | Logic Phức tạp |
|---|---|---|---|
| Rủi ro Thấp (Bug = annoyance) | 🟢 Sketch — Prompt ngắn OK | 🟡 Detailed Spec 4–5 thành phần | 🟡 Detailed Spec 6–7 thành phần |
| Rủi ro Vừa (Bug = mất UX/data) | 🟡 Detailed Spec ngắn + test cases | 🟡 Detailed Spec đầy đủ 8 thành phần | 🔴 Formal Spec + State Diagram |
| Rủi ro Cao (Bug = tiền/bảo mật) | 🟡 Detailed Spec + security review | 🔴 Formal Spec + Diagram + Review | 🔴 Formal Spec + Diagram + Audit trail |
Sketch là mức tối thiểu — một prompt có cấu trúc. Phù hợp cho utility functions, helper methods, và các đoạn code nhỏ không có business logic phức tạp.
Khi nào dùng: Logic đơn giản (1–2 branch), rủi ro thấp, có thể kiểm tra output bằng mắt trong < 30 giây.
Detailed Spec là mức tiêu chuẩn cho hầu hết các module tính năng. Bao gồm 6–8 thành phần, EARS notation, và Out of Scope rõ ràng. Phù hợp cho khoảng 70% công việc development thực tế.
Khi nào dùng: Module tính năng có 3–10 business rules, rủi ro vừa, cần unit test để verify.
Formal Spec dành cho các luồng có rủi ro cao: thanh toán, xác thực, quản lý quyền. State Diagram buộc bạn liệt kê MỌI trạng thái có thể — kể cả các trạng thái trung gian mà prose spec thường bỏ qua. Với AI, State Diagram đặc biệt có giá trị: nó là nguồn duy nhất để AI biết transition nào là hợp lệ, transition nào không.
Khi bắt đầu bất kỳ task nào, hãy đặt 3 câu hỏi: (1) Nếu AI implement sai, hậu quả là gì? (2) Có bao nhiêu branch và edge case? (3) Có ai khác cần đọc và depend vào spec này không?
| Module ví dụ | Rủi ro | Logic | Mức Spec đúng | Lý do |
|---|---|---|---|---|
| formatDate() helper | Thấp | Đơn giản | 🟢 Sketch | Bug dễ thấy, fix nhanh |
| Search/filter products | Thấp | Vừa | 🟡 Detailed | Nhiều filter combination |
| User profile update | Vừa | Vừa | 🟡 Detailed | Data integrity + audit |
| Login/Auth flow | Cao | Vừa | 🔴 Formal | Security, brute-force protection |
| Payment processing | Cao | Phức tạp | 🔴 Formal | Tiền + state machine phức tạp |
| Notification email | Thấp | Đơn giản | 🟢 Sketch | Dễ kiểm tra output |
| RBAC/Permissions | Cao | Phức tạp | 🔴 Formal | Security + many role combos |
| Dashboard charts | Thấp | Vừa | 🟡 Detailed | Nhiều data conditions |
Phần này là điểm hội tụ của toàn bộ chương. Quy trình SDD không phải là "write spec → paste vào AI → nhận code". Đó là một vòng lặp cộng tác giữa người và máy, trong đó mỗi bên đóng góp điều mà mình làm tốt nhất: con người mang ý định, kinh nghiệm, và judgment; AI mang khả năng phân tích cú pháp, tìm logic gap, và thực thi không mệt mỏi.
Bước này không có AI tham gia — và đây là chủ đích. Trước khi đưa AI vào, con người phải tự suy nghĩ về bài toán. Nhiều team mắc sai lầm bỏ qua bước này — họ đi thẳng từ "có ý tưởng" đến "chat với AI". Kết quả là spec được AI "đoán" từ một mô tả mơ hồ, và toàn bộ hệ thống được xây trên nền tảng không vững.
Đây là bước nhiều người không biết đến nhưng có impact lớn nhất. Thay vì dùng AI để viết code ngay, hãy dùng AI để "peer review" spec của bạn. AI rất giỏi tìm ra những điều bạn bỏ qua vì quá quen thuộc với domain — những assumption ẩn, edge case bị quên, và logic inconsistency.
Sau khi nhận review từ AI, đây là lúc con người đưa ra judgment. Không phải mọi issue mà AI tìm thấy đều cần giải quyết ngay — đó là lý do tại sao bước này không thể tự động hóa. Con người phải quyết định: issue này quan trọng và cần xử lý trong spec, hay là acceptable risk và sẽ handle sau?
| Loại issue | Action |
|---|---|
| Logic gap nghiêm trọng | Thêm vào spec NGAY |
| Edge case quan trọng | Thêm Unwanted pattern |
| Ambiguity có ảnh hưởng | Làm rõ trong spec |
| Out of scope (intentional) | Ghi vào Out of Scope |
| Nice to have, low risk | Ghi vào backlog |
| False positive của AI | Ignore + comment lý do |
Bước này là nơi AI thực sự tỏa sáng — nhưng chỉ khi spec ở các bước trước đã làm tốt. AI được yêu cầu implement đồng thời code và unit test. Hai thứ này phải được generate cùng nhau để đảm bảo test thực sự cover spec, không phải chỉ cover code.
Trước khi approve code do AI generate, reviewer cần verify đầy đủ những điểm sau. Đây là trách nhiệm con người — không thể outsource cho AI tự review chính nó.
| Kiểm tra | Cách verify | Fail nếu... |
|---|---|---|
| Mọi SHALL được implement | So sánh spec với code line by line | Có SHALL không có code tương ứng |
| Mọi SHALL NOT được tuân thủ | Grep code cho forbidden patterns | Tìm thấy hành vi bị cấm |
| Mọi Acceptance Criteria có test | Map test names với AC list | Có AC không có test cover |
| Unwanted patterns được handle | Check error handling code | Exception bị swallow/ignore |
| Out of Scope được tôn trọng | Review nếu AI code thêm gì | Code nằm ngoài spec tồn tại |
| EARS tags present | Search for # EARS[ | Nhiều business rule không có tag |
Đây là bài tập tổng hợp kết thúc chương. Tính năng Giỏ hàng cho e-commerce app — tính năng phổ biến nhưng có đủ độ phức tạp để thực hành đầy đủ: nhiều actors, nhiều state, edge cases thú vị, và non-trivial business rules.
Viết SPEC.md theo mẫu 8 thành phần. Trả lời 5 câu Story Decomposition trước khi viết.
Gợi ý Unwanted patterns:
Mở Cline trong VSCode. Paste spec sơ thảo và dùng prompt review mẫu từ Section 5.5.3. Ghi lại:
Dùng prompt từ Section 5.5.5 để yêu cầu Cline generate đầy đủ:
| Tiêu chí | Điểm tối đa | Cách đánh giá |
|---|---|---|
| Tất cả SHALL được implement | 30đ | 1đ/requirement, missing = -2đ |
| Unwanted patterns được handle | 25đ | 2đ/error case có handler đúng |
| Unit tests cover Acceptance Criteria | 20đ | 1đ/acceptance criterion có test |
| EARS tags trong code | 10đ | 5đ nếu ≥ 80% rules có tag |
| Out of Scope được tôn trọng | 10đ | 0đ nếu AI code ngoài scope |
| Code quality (readable, typed) | 5đ | Subjective: type hints + naming |
| Khái niệm | Điểm cốt lõi | Công cụ |
|---|---|---|
| Spec = Interface | Tách "Cái gì" khỏi "Như thế nào" | SPEC.md template |
| 8 Thành phần | Out of Scope quan trọng như In Scope | 8-component checklist |
| EARS Notation | 5 patterns loại bỏ mơ hồ | EARS Cheat Sheet |
| Levels of Depth | Calibrate theo Risk × Complexity | Risk Matrix |
| SDD Workflow | Draft → Review → Lock → Generate | SPEC.md lifecycle |
| Anti-patterns | 5 "tử huyệt" khiến AI hallucinate | Pre-spec checklist |
Test-Driven Specification