Mục lục
Có hai niềm tin đang cùng lúc lan trong giới làm phần mềm.
Niềm tin thứ nhất: spec-driven development (SDD) là quay về thời waterfall — viết tài liệu dày cả chục trang rồi mới dám code, chậm chạp và lỗi thời.
Niềm tin thứ hai, từ phe ngược lại: SDD là thuốc chữa vibe coding. Cứ viết spec thật kỹ, đưa cho AI, thế là xong.
Tôi nghĩ cả hai đều sai. Và cái sai chung của chúng là cùng một chỗ: cả hai đều coi spec là một tài liệu. Trong khi thứ bạn thật sự cần trong kỷ nguyên AI không phải tài liệu. Đó là một hệ thống quản trị — ai quyết định điều gì, quyết định được ghi ở đâu, và máy nào kiểm tra rằng code AI viết ra có tuân theo hay không.
Tôi từng nghĩ "spec càng chi tiết càng tốt". Bài này là lý do tôi không còn nghĩ vậy.
Vibe coding thực chất thiếu cái gì?
Tôi đã viết hai bài về vibe coding — về món nợ nhận thức và về những điểm dừng bị xóa mất. Bài này đi tiếp một bước: nhìn nó dưới góc độ quản trị.
Vibe coding không xấu vì AI viết code tệ. Nó là một phương thức làm việc mà ở đó mọi quyết định kỹ thuật sống trong đầu một người và trong một cuộc chat. Đóng tab là mất. Người thứ hai vào dự án không biết vì sao hệ thống thanh toán được làm kiểu này. Và tệ nhất: mỗi lần bạn mở một phiên chat mới, "chính sách" của dự án được viết lại từ đầu, bằng trí nhớ.
Điều đó đặc biệt nguy hiểm ở những chỗ mà mặc định của mô hình không trùng với chính sách của bạn. Ví dụ rõ nhất là bảo mật. Báo cáo 2025 GenAI Code Security của Veracode cho 100+ mô hình chạy 80 tác vụ lập trình và thấy: mô hình đưa ra lỗ hổng thuộc OWASP Top 10 trong 45% trường hợp. Với Java, tỉ lệ thất bại về bảo mật vượt 70%; với Python, C# và JavaScript là 38–45%. Với lỗi XSS, mô hình không bảo vệ được code trong 86% trường hợp.
Chi tiết đáng nghĩ nhất: Veracode ghi nhận mô hình lớn hơn không an toàn hơn đáng kể, và mức an toàn gần như không đổi theo thời gian dù code ngày càng "chạy được". Nói cách khác: đây không phải lỗi sẽ tự biến mất khi có mô hình mới. Đây là đặc tính bạn phải quản trị, không phải đợi.
Và bạn không thể quản trị nó bằng cách nhắc miệng trong từng prompt. Quy tắc phải nằm ở một nơi mà mọi phiên làm việc, mọi người, mọi agent đều phải đọc.
Vibe coding không thiếu tốc độ hay trí thông minh. Nó thiếu một nơi chung để ghi lại "chúng ta đã quyết định gì" — và một cơ chế để buộc code tuân theo.
SDD là gì — nói gọn, không thuật ngữ
Thoughtworks định nghĩa spec-driven development là cách làm việc với AI mà ở đó quy trình "bắt đầu từ một đặc tả chức năng có cấu trúc, rồi đi qua nhiều bước để chia nhỏ thành các phần, giải pháp và tác vụ." Nói người thường: viết rõ cái cần làm trước, rồi mới để AI làm.
Hiện có ba công cụ đại diện, mỗi cái hiểu SDD một kiểu:
| Công cụ | Cách làm | Điểm nhấn |
|---|---|---|
| Amazon Kiro | 3 giai đoạn: requirements → design → tasks | Gọn, đi từng bước |
| GitHub spec-kit | 4 giai đoạn: Specify → Plan → Tasks → Implement | Nhiều điều phối hơn, có khái niệm "constitution" — nguyên tắc bất biến |
| Tessl | Spec là thứ được duy trì, code là sản phẩm phái sinh | Cực đoan nhất: người chỉ sửa spec |
GitHub tóm tắt tham vọng này rất đẹp: chuyển từ "code là nguồn chân lý" sang "ý định là nguồn chân lý." Martin Fowler và Birgitta Böckeler còn chia SDD thành ba mức, và mức độ này mới là chỗ quan trọng:
- Spec-first — viết spec để dẫn dắt lần build đầu, xong vứt.
- Spec-anchored — spec được giữ lại, dùng tiếp khi tính năng tiến hóa.
- Spec-as-source — con người chỉ sửa spec, code được sinh ra.
Böckeler ghi nhận: mọi cách làm SDD bà thấy đều là spec-first, nhưng không phải cái nào cũng cố đạt spec-anchored. Hãy để ý điều này. Một spec bị vứt sau lần build đầu chỉ là một cái prompt dài hơn. Nó không phải quản trị.
Phần phe SDD không thích nghe
Tôi ủng hộ hướng đi này. Nhưng một bài viết chỉ khen thì không xứng với chữ "phản biện", nên đây là phần bằng chứng chống lại chính nó — và nó nghiêm túc.
Thoughtworks Technology Radar (11/2025) xếp SDD ở mức "Assess" — tức là đáng tìm hiểu, chưa phải thứ nên áp dụng đại trà. Họ nói thẳng rằng các workflow hiện tại vẫn "phức tạp và áp đặt", một số công cụ "sinh ra những file spec dài khó review", và cảnh báo giới làm nghề có thể đang "học lại một bài học đắng — rằng viết tay quy tắc chi tiết cho AI cuối cùng không mở rộng được."
Thử nghiệm thực tế của Böckeler còn cụ thể hơn:
- Agent hay lờ spec. Có lần một agent "phớt lờ ghi chú rằng đây là mô tả các class đã có, và coi chúng như một spec mới" — kết quả là code trùng lặp. Spec dài không bảo đảm agent đọc và tuân theo.
- Review spec tốn hơn review code. Bà viết: "Tôi thà review code còn hơn review đống file markdown này."
- Búa tạ đập hạt dẻ. Sửa một bug nhỏ mà công cụ sinh ra cả bộ requirements đồ sộ. Một quy trình cho mọi cỡ việc là quy trình sai cho phần lớn việc.
- Cảnh báo lịch sử. Spec-as-source có nguy cơ thừa hưởng "nhược điểm của cả MDD lẫn LLM: cứng nhắc và không tất định." MDD (model-driven development) từng hứa "vẽ sơ đồ là ra phần mềm" và thất bại vì lý do tương tự.
Đọc xong tôi phải thừa nhận: niềm tin thứ hai — "cứ viết spec kỹ" — chính là thứ bị bằng chứng bác bỏ. Spec dài mà không ai kiểm chứng, agent không chắc đọc, và người không muốn review chỉ là một dạng vibe coding mới, có thêm thủ tục.
Vậy phe "SDD là waterfall" đúng sao? Cũng không. Waterfall sai vì nó đóng băng quyết định trước khi học được gì. Còn ở đây, vòng lặp giữa spec và code chỉ mất vài phút, và bạn sửa spec ngay khi thấy sai. Vấn đề không phải có spec hay không, mà là spec đó có được kiểm chứng và có chủ sở hữu hay không.
Khung 4 lớp quản trị: spec phải cắn được
Kết luận của tôi: một spec chỉ đáng tồn tại nếu có thứ gì đó cắn được khi code vi phạm nó. Đây là bốn lớp tôi dùng, xếp từ nhẹ đến nặng.
Lớp 1 — Hiến pháp: một trang, chỉ chứa thứ không được phép sai
Không phải tài liệu kiến trúc. Là một trang những quy tắc bất biến của dự án, đặt ngay trong repo để mọi người và mọi agent đều đọc:
# Project constitution
## Security (non-negotiable)
- Every query that touches user data is parameterized. No string-built SQL.
- All user-supplied output is escaped at the render boundary.
- Secrets never enter the repo or logs.
## Architecture
- Server components by default; a client component needs a written reason.
- One price list (quoteCatalog). No second source of prices.
## Definition of done
- Every acceptance criterion has a test that fails when it is violated.
- Anything touching money, auth or personal data is human-reviewed line by line.Chú ý cách chọn nội dung: ưu tiên những chỗ mô hình có xu hướng làm sai theo mặc định (bảo mật, theo số liệu Veracode ở trên) và những chỗ sai thì đắt (tiền, đăng nhập, dữ liệu cá nhân). Hiến pháp dài quá hai trang là hiến pháp không ai đọc — kể cả agent.
Lớp 2 — Spec theo tầng rủi ro: cỡ giấy tờ phải khớp cỡ rủi ro
Đây là câu trả lời cho cảnh "búa tạ đập hạt dẻ" của Böckeler. Đừng có một quy trình cho mọi việc. Có ba cỡ:
| Tầng | Ví dụ | Mức spec |
|---|---|---|
| Thấp | Sửa chữ, chỉnh CSS, bug một dòng | Không cần spec. Mô tả trong commit là đủ |
| Trung bình | Thêm trang, thêm trường form, refactor có test phủ | Spec nửa trang: mục tiêu, ràng buộc, 3–5 tiêu chí nghiệm thu |
| Cao | Thanh toán, xác thực, dữ liệu người dùng, API công khai | Spec đầy đủ + review con người từng dòng + test bắt buộc |
Nguyên tắc: nếu review spec tốn hơn tự viết code, tầng đó không cần spec. Tiết kiệm giấy tờ ở tầng thấp chính là thứ cho phép bạn nghiêm khắc ở tầng cao.
Lớp 3 — Nghiệm thu chạy được: spec mà máy đọc được
Đây là lớp quan trọng nhất, và là chỗ SDD "thật" khác SDD "diễn". Mỗi tiêu chí nghiệm thu trong spec phải trở thành một test sẽ đỏ khi code sai. Không có test thì đó là lời ước, không phải spec.
import { describe, it, expect } from "vitest";
describe("SPEC-014 · Cancel order", () => {
it("AC-1: a customer can cancel only their own order", async () => {
const res = await cancelOrder({ orderId: "o_1", userId: "someone_else" });
expect(res.status).toBe(403);
});
it("AC-2: a shipped order cannot be cancelled", async () => {
const res = await cancelOrder({ orderId: "o_shipped", userId: "owner" });
expect(res.status).toBe(409);
});
});Hai dòng được tô sáng là chỗ mà tôi muốn bạn nhìn: mã spec (SPEC-014, AC-1) nằm ngay trong tên test. Từ đó bạn truy được từ yêu cầu đến bằng chứng, và ngược lại. Khi agent "quên" AC-2 thì CI đỏ — bạn không cần tin vào việc nó có đọc spec hay không, bạn chỉ cần tin vào test.
Cách làm này cũng giải luôn bài toán "agent lờ spec" mà Böckeler gặp: đừng cầu mong agent tuân thủ, hãy làm cho việc vi phạm không thể merge.
Lớp 4 — Cổng kiểm soát và dấu vết: ai ký, ở đâu, khi nào
Hai việc nhỏ nhưng là xương sống của quản trị:
- Spec đi cùng pull request. Cùng một PR sửa spec và code. Reviewer thấy ý định thay đổi ra sao trước khi thấy cách nó được hiện thực. Đây cũng là cách bạn đạt mức spec-anchored thay vì spec-first: spec sống cùng code, không chết sau lần build đầu.
- Mỗi spec tầng cao có một người ký tên. Không phải "team". Một người, có tên. Khi hệ thống thanh toán sai, câu hỏi "ai đã chấp nhận spec này?" phải có câu trả lời trong ba giây.
Cộng thêm cổng CI tự động: chạy toàn bộ test nghiệm thu, quét bảo mật tĩnh, và chặn merge nếu chạm vào file tầng cao mà chưa có chữ ký reviewer. Máy làm phần lặp lại; con người giữ phần phán đoán.
Bốn lớp, một nguyên tắc: ý định phải nằm ở chỗ ai cũng đọc được, và phải có thứ gì đó tự động cắn được khi code đi lệch nó. Thiếu vế thứ hai, spec chỉ là văn xuôi.
Bắt đầu từ đâu — trong một tuần, không phải một quý
Đừng cài công cụ nào cả trước khi làm được ba việc này:
- Ngày 1: viết
CONSTITUTION.mdmột trang cho dự án đang chạy. Chỉ ghi thứ từng làm bạn cháy tay hoặc cháy tiền. - Ngày 2–3: chọn một vùng tầng cao (thanh toán, đăng nhập). Viết tiêu chí nghiệm thu thành test cho nó — kể cả khi code đã tồn tại.
- Ngày 4–5: gắn test đó vào CI, và quy định spec tầng cao phải đi cùng PR.
Sau đó mới đánh giá xem Kiro hay spec-kit có đáng dùng không. Họ đều là công cụ để viết spec; chúng không thay được ba việc ở trên. Và với việc Radar mới xếp SDD ở mức "Assess", giữ cho quy trình nhẹ và tách rời công cụ cũng là một cách quản trị rủi ro — bạn không bị khóa vào một workflow còn chưa ngã ngũ.
Cái giá và phần thưởng, tính bằng tiền
Khi mọi đội đều dùng AI, tốc độ build là mặt bằng chung. Cái phân biệt bạn với người khác là bạn có chứng minh được code của mình làm đúng điều đã hứa hay không. Với khách hàng doanh nghiệp, đó là khác biệt giữa "nhanh" và "đáng tin" — và chỉ loại thứ hai được gia hạn hợp đồng.
Nếu bạn là chủ doanh nghiệp thuê một đội làm website hay hệ thống "bằng AI", đừng hỏi "các bạn làm nhanh không?". Hãy hỏi bốn câu này:
- Quy tắc bất biến của dự án tôi được ghi ở đâu? (Muốn thấy một file cụ thể.)
- Tính năng chạm vào tiền và dữ liệu khách hàng của tôi có tiêu chí nghiệm thu viết thành test không?
- Ai là người chịu trách nhiệm cho phần đó — có tên, không phải "cả team"?
- Nếu tôi đổi nhà cung cấp, người mới đọc gì để hiểu hệ thống?
Đội nào trả lời trôi chảy bằng tài liệu thật là đội đang quản trị code AI, chứ không đang hy vọng nó đúng. Chúng tôi làm việc theo cách đó — nếu bạn muốn xem một bộ hiến pháp và test nghiệm thu thật của dự án, cứ nói với chúng tôi.
Vibe coding không sai vì nó dùng AI. Nó sai khi nó không để lại gì ngoài code. Spec-driven development không cứu bạn vì nó bắt bạn viết nhiều hơn. Nó cứu bạn khi nó buộc bạn viết ra điều mình đã quyết — và để máy canh giữ điều đó.
Nguồn số liệu và trích dẫn: Veracode — 2025 GenAI Code Security Report; Thoughtworks Technology Radar — Spec-driven development (11/2025, Assess); Birgitta Böckeler, martinfowler.com — Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl (15/10/2025); GitHub Blog — Spec-driven development with AI (2/9/2025); GitHub spec-kit.

