Spec-Driven Development: Khi spec trở thành "source code" của kỷ nguyên AI

AI có thể viết code cực nhanh, nhưng tốc độ không đồng nghĩa với việc hiểu đúng yêu cầu. Chỉ cần prompt hơi mơ hồ, cùng một bài toán có thể cho ra nhiều cách triển khai khác nhau — kèm theo những lỗ hổng khác nhau.

Spec-Driven Development (SDD) ra đời để xử lý chính vấn đề đó. Thay vì để AI tự suy diễn, SDD đặt spec làm điểm neo bắt buộc cho toàn bộ quá trình phát triển. Đây cũng là lý do nhiều người thấy rằng “vibe coding” không còn phù hợp với công việc chuyên nghiệp.

1. Vibe coding hỏng ở đâu

Hãy tưởng tượng một tình huống rất quen thuộc.

Bạn nhờ AI tạo nhanh một API quản lý bookmark với prompt kiểu:

“Làm giúp tôi CRUD bookmark có validation cơ bản.”

Vài giây sau, AI trả về một project Express chạy ngon lành. Demo ổn, endpoint hoạt động, mọi thứ có vẻ hoàn hảo. Bạn merge.

Hai tuần sau:

  • Có người paste javascript:alert(1) vào trường URL — hệ thống vẫn lưu.
  • Một user tạo cùng một URL nhiều lần — database có duplicate.
  • API xoá bookmark nhận một id ngẫu nhiên — và xoá luôn bookmark của người khác.
  • Không pagination.
  • Không tách business logic khỏi controller.
  • Header định danh gửi gì cũng tin.

Vấn đề ở đây không phải “AI ngu” hay “dev bất cẩn”.

Vấn đề là: không có nơi nào định nghĩa rõ hệ thống phải hoạt động như thế nào.

Khi không có spec, mọi quyết định đều trở thành ngẫu hứng. Và chính phần “ngẫu hứng” đó là nơi bug và security issue xuất hiện.

2. SDD là gì?

Có thể tóm gọn SDD bằng một câu:

Trong SDD, spec là nguồn sự thật. Code chỉ là phần triển khai được sinh ra từ spec.

Nếu yêu cầu thay đổi, ta sửa spec trước rồi mới cập nhật code — không làm ngược lại.

SDD thường được chia thành ba mức độ nghiêm ngặt:

Spec-first

Spec chỉ dùng để dẫn hướng cho lần implement đầu tiên. Sau đó spec có thể bị drift theo thời gian.

Phù hợp với prototype hoặc project ngắn hạn.

Spec-anchored

Spec và code phải luôn đồng bộ. Mọi thay đổi hành vi đều cần cập nhật cả hai phía, và CI sẽ kiểm tra drift.

Đây là mức phù hợp với phần lớn production system.

Spec-as-source

Con người chỉ chỉnh sửa spec, còn code được generate hoàn toàn tự động.

Mô hình này chỉ thực sự phù hợp với một số domain hẹp như automotive hoặc OpenAPI stub generation.


Khi nào SDD đáng đầu tư?

  • Team đông hoặc thay đổi người liên tục
  • Có AI assistant trong workflow
  • Hệ thống cần audit hoặc compliance
  • Nhiều service phải phát triển song song

Ngược lại, nếu chỉ là prototype tạm thời, fix một bug nhỏ, hay CRUD đơn giản có thể giải thích trong vài chục giây thì SDD thường là overkill.

Nguyên tắc khá đơn giản:

Dùng mức độ rigor tối thiểu nhưng đủ để loại bỏ sự mơ hồ.

3. Bốn lớp giữ AI đi đúng spec

Để spec không biến thành “tài liệu viết cho có”, SDD thường tổ chức nó thành bốn lớp.

Lấy lại ví dụ hệ thống bookmark ở trên.

Lớp 1 — Constitution (hiến pháp dự án)

Đây là tập luật bất biến áp dụng cho toàn bộ project.

Ví dụ:

  • TypeScript bắt buộc bật strict và cấm any
  • Mọi mutation từ web phải đi qua API layer
  • URL input phải match ^https?://
  • Không render raw HTML từ user input

Hiến pháp không phải guideline. Nó là rule bắt buộc.

Nếu sửa constitution, toàn bộ spec liên quan phải được validate lại.


Lớp 2 — Spec

Spec chỉ mô tả:

  • WHAT
  • WHY

Tuyệt đối không nói về tech stack.

Phần quan trọng nhất trong spec là Acceptance Criteria (AC).

Ví dụ:

  • AC-2: URL dùng scheme javascript: hoặc data: phải trả về 400 URL_SCHEME_INVALID
  • AC-5: User không được tạo duplicate bookmark
  • AC-7: Xoá bookmark của người khác phải trả 404 thay vì 403 để tránh leak thông tin resource tồn tại

Mỗi AC phải là một điều có thể verify bằng test.

Không phải mô tả cảm tính kiểu:

“Validation hợp lý”

Lớp 3 — Plan và Tasks

Đây mới là nơi nói về HOW.

Plan sẽ quyết định:

  • Kiến trúc
  • Tech stack
  • Database schema
  • API contract
  • Data flow

Sau đó tasks chia nhỏ implementation thành các bước có dependency rõ ràng.


Lớp 4 — Test

Mỗi AC bắt buộc phải map được tới ít nhất một automated test.

Ví dụ AC-2 sẽ có test:

  • POST javascript:alert(1)
  • Expect 400
  • Expect error code URL_SCHEME_INVALID

Nếu một AC không có test tương ứng, merge sẽ bị block.


Điểm quan trọng nhất ở đây là khả năng truy vết.

Bất kỳ dòng code nào cũng có thể lần ngược về:

Code → AC → Constitution

4. Cùng một field, hai thế giới

Trong vibe coding, validation cho trường url thường chỉ là:

if (!url) {
  return res.status(400).json({ message: 'url is required' });
}

bookmarks.push({
  id: nextId++,
  url,
  title,
  tags,
  userId,
  createdAt,
});

Trong SDD, cùng field đó có thể trông như thế này:

@IsString()
@MaxLength(2048, { message: 'URL_TOO_LONG' })
@Matches(/^https?:\/\//i, { message: 'URL_SCHEME_INVALID' })
@Transform(({ value }) =>
  typeof value === 'string' ? value.trim() : value,
)
url!: string;

Khác biệt không nằm ở số lượng decorator.

Khác biệt nằm ở việc mỗi dòng đều có lý do tồn tại.

  • @MaxLength(2048) đến từ acceptance criteria về giới hạn URL
  • Regex validate scheme đến từ security rule trong constitution
  • @Transform xử lý edge case đã được mô tả trong spec
  • Error code được đồng bộ với frontend contract

Đó mới là cốt lõi của SDD.

Không phải “code chặt chẽ hơn”, mà là:

Mọi quyết định trong code đều có spec đứng phía sau bảo vệ.

Khi requirement thay đổi, bạn biết chính xác:

  • cần sửa chỗ nào
  • test nào sẽ fail
  • phạm vi ảnh hưởng nằm ở đâu

5. Workflow thực tế với Claude Code

GitHub Spec-Kit hiện đóng gói workflow SDD thành một chuỗi slash command khá hoàn chỉnh.

Ví dụ với Claude Code:

1. /speckit.constitution

Tạo hoặc cập nhật constitution.

Thường chỉ cần làm một lần, sau đó rất ít thay đổi.

2. /speckit.specify

Mô tả WHAT và WHY của feature.

Không nói về tech stack.

3. /speckit.clarify

Claude sẽ chủ động hỏi lại những phần còn mơ hồ trước khi cho phép implement.

Đây là bước cực kỳ quan trọng.

Nó ép requirement phải rõ ràng ngay từ đầu.

4. /speckit.plan

Lúc này mới bắt đầu nói về:

  • architecture
  • schema
  • API contract
  • infra
  • implementation strategy

5. /speckit.tasks

Tách plan thành các task có dependency rõ ràng.

6. /speckit.analyze

Đây là bước tạo khác biệt lớn nhất của SDD.

Hệ thống sẽ kiểm tra:

  • constitution
  • spec
  • plan
  • tasks

có đang mâu thuẫn với nhau hay không.

Nếu có inconsistency, code generation sẽ bị chặn.

Đây không còn là “review thủ công”.

Nó là một automated gate thực sự.

7. /speckit.implement

Sau khi toàn bộ gate pass, agent mới bắt đầu implement.

6. Những cái bẫy phổ biến

SDD không miễn phí.

Dùng sai cách đôi khi còn tệ hơn vibe coding.

Viết spec khổng lồ trước khi code

Đây là cách nhanh nhất để quay lại Waterfall.

Spec nên đi cùng feedback loop liên tục:

  • linter
  • type checker
  • test
  • CI
  • AI review

Trộn WHAT và HOW quá sớm

Khi spec đã dính chặt vào công nghệ, nó mất khả năng review độc lập.

Đổi stack cũng đồng nghĩa phải viết lại spec.

Biến IDE rules thành “spec”

Một file rule trong IDE không phải spec.

Nó:

  • không versioned
  • không truy vết
  • không có validation gate

Nó chỉ là config.

Spec dài hơn cả code

Đây thường là dấu hiệu của over-engineering.

Nếu feature đơn giản đến mức giải thích trong 30 giây là hiểu, đừng cố biến nó thành tài liệu dài 20 trang.

7. Kết luận

Spec không phải thủ tục hành chính để báo cáo với cấp trên.

Trong thời đại AI agent, spec chính là cách giữ hệ thống không trôi khỏi ý định ban đầu của con người.

Nó giúp:

  • AI implement đúng hơn
  • Team nói cùng một ngôn ngữ
  • Requirement change có thể kiểm soát được
  • Và quan trọng nhất: giảm những “surprise bug” xuất hiện sau này

Một feature nhỏ trong sprint tiếp theo là nơi rất tốt để thử SDD.

Hãy viết spec trước, định nghĩa acceptance criteria rõ ràng, để Claude Code chạy hết workflow rồi so sánh với cách làm cũ.