Kỹ thuật phần mềm

Phát triển theo đặc tả: viết đặc tả trước khi để AI làm

Phát triển theo đặc tả (spec-driven development) là viết ra điều cần làm trước, rồi để AI dựa vào đó mà làm. Bài này giải thích ba mức độ, các công cụ Kiro, Spec Kit và Tessl theo bài phân tích của Martin Fowler, khi nào nó đáng và khi nào không, kèm mẫu đặc tả nhẹ năm mục dùng được ngay.

  • #spec-driven development
  • #đặc tả
  • #AI agent
  • #quy trình
  • #Spec Kit
  • #Kiro

Bản trình chiếu

Bấm vào slide rồi dùng phím mũi tên để chuyển trang. Phím L đổi ngôn ngữ, phím F để toàn màn hình.

Mở toàn màn hình

Dành cho lập trình viên và người làm sản phẩm đang dùng AI để viết code. Từ kỹ thuật nào cũng có giải thích ngắn.

Phần mô tả ba mức độ, ba công cụ và các hạn chế dựa vào bài phân tích của Martin Fowler (mình đọc trực tiếp). Phần "đặc tả nhẹ năm mục" và ví dụ là gợi ý của mình, chưa được kiểm chứng trên nhiều dự án. Ví dụ "tính năng hoàn tiền" được dựng lại cho dễ hình dung.


Tóm tắt nhanh#

  • Phát triển theo đặc tả (spec-driven development, SDD) là viết trước điều cần làm, rồi để AI làm dựa trên đó.
  • Martin Fowler chia ba mức: spec-first (viết trước, có thể bỏ sau), spec-anchored (giữ lại để bảo trì), spec-as-source (chỉ sửa đặc tả, mã là sản phẩm sinh ra).
  • Hầu hết cách làm hiện nay mới ở mức spec-first.
  • Có hạn chế thật: quy trình nặng cho việc nhỏ, nhiều tệp lặp lại khó duyệt, và tác nhân vẫn có lúc bỏ qua đặc tả.
  • Cách thực dụng: đặc tả nhẹ năm mục cho việc vừa và lớn, bỏ qua cho việc nhỏ.

Từ khó, nói dễ#

Thuật ngữ Nói dễ hiểu
Đặc tả (spec) Bản mô tả có cấu trúc về hành vi mong muốn của một tính năng, viết bằng ngôn ngữ thường.
Tác nhân lập trình (coding agent) AI không chỉ trả lời mà còn tự đọc, sửa tệp và chạy lệnh để làm việc.
Tiêu chí chấp nhận (acceptance criteria) Những điều phải đúng để coi tính năng là xong, thường viết thành ví dụ cụ thể.
Bộ nhớ chung (memory bank) Tệp ngữ cảnh áp dụng cho mọi lần làm việc, như quy tắc và mô tả dự án.
Mã có sẵn (brownfield) Dự án đã chạy lâu, khác với dự án mới tinh.
Không xác định (non-deterministic) Cùng đầu vào nhưng mỗi lần ra một kết quả hơi khác.

1. Vì sao có chuyện đặc tả#

Khi nhờ AI bằng một câu ngắn, bạn trao cho nó quyền tự điền những chỗ trống. Với việc nhỏ thì không sao. Với việc lớn hơn, chỗ trống nào cũng là một lần AI đoán, và bạn chỉ biết nó đoán sai khi đọc kết quả.

Một ví dụ từ chính blog này: khi người dùng báo "tính năng tìm kiếm không hoạt động", agent kiểm tra phần logic và thấy đúng, trong khi vấn đề thật là kết quả nằm bên dưới màn hình đầu nên người dùng không thấy gì thay đổi (xem bài Ba tiếng làm việc với một AI agent). Nếu có một dòng đặc tả như "gõ xong, kết quả phải hiện ra mà không phải cuộn", lỗi đó có thể đã được bắt sớm. Mình nói "có thể" vì đó là suy luận sau sự việc, không phải kết quả thử nghiệm.

Ý tưởng của phát triển theo đặc tả là biến những dòng như vậy thành một bước chính thức trước khi để AI làm.


2. Ba mức độ#

Theo Martin Fowler:

Mức Ý nghĩa Đặc tả sau khi làm xong
Spec-first Viết đặc tả trước, dùng nó để hướng dẫn việc làm Có thể bỏ
Spec-anchored Giữ đặc tả lại, dùng nó để phát triển và bảo trì tính năng Giữ và cập nhật
Spec-as-source Đặc tả là sản phẩm chính, con người chỉ sửa đặc tả, không sửa mã Là nguồn, mã được sinh ra

Ông ghi nhận phần lớn các cách làm SDD hiện nay chỉ ở mức spec-first, và kế hoạch giữ đặc tả về lâu dài thường còn mơ hồ.

Ông cũng phân biệt đặc tả với bộ nhớ chung: bộ nhớ chung (quy tắc, mô tả sản phẩm và mã) áp dụng cho mọi phiên làm việc, còn đặc tả chỉ áp dụng cho tính năng đang làm hoặc đang đổi. Vì vậy tệp như AGENTS.md (xem bài Sổ tay cho AI) và đặc tả bổ sung cho nhau.


3. Ba công cụ, theo bài của Fowler#

Fowler xem xét ba công cụ. Mô tả dưới đây là phần tóm tắt của mình.

Công cụ Quy trình Ghi chú của Fowler
Kiro Yêu cầu, thiết kế, việc cần làm. Có tính năng "steering" làm bộ nhớ chung Nhẹ hơn, chủ yếu spec-first
Spec Kit (GitHub) Nguyên tắc chung, đặc tả, kế hoạch, việc cần làm, rồi triển khai, mỗi bước ra tệp Markdown GitHub nói hướng tới spec-anchored, nhưng Fowler thấy nó vẫn là spec-first vì tạo một nhánh cho mỗi đặc tả
Tessl (bản thử riêng) Nhắm rõ tới spec-anchored và thử spec-as-source. Tệp sinh ra được đánh dấu là không sửa tay Fowler thấy kết quả không xác định từ cùng một đặc tả

Các công cụ này mới và đổi nhanh, nên đừng coi mô tả trên là bản chốt.


4. Những quan sát và lời phê bình của Fowler#

Điều mình thấy đáng đọc nhất là Fowler không chỉ ca ngợi. Ông nêu ra:

  • Một quy trình cho mọi cỡ việc. Kiro biến một lỗi nhỏ thành bốn câu chuyện người dùng và 16 tiêu chí chấp nhận. Spec Kit làm ông thấy là quá tay cho một tính năng cỡ vừa trong mã có sẵn, nơi viết thẳng với AI sẽ nhanh và dễ kiểm soát hơn.
  • Gánh nặng duyệt. Spec Kit sinh nhiều tệp Markdown lặp lại, dài dòng, mệt để duyệt. Ông thà duyệt mã.
  • Cảm giác kiểm soát giả. Tác nhân có lúc bỏ qua nội dung đặc tả, như tạo lại một lớp đã có dù bước nghiên cứu đã ghi nó, hoặc áp dụng quá tay các nguyên tắc chung.
  • Mã có sẵn khó hơn. Hai trong ba công cụ khó đưa vào dự án đã chạy, nên khó đánh giá.
  • Ranh giới chức năng và kỹ thuật không rõ trong thực tế.
  • "Spec" đang bị dùng lỏng. Từ này có nguy cơ thành đồng nghĩa với "lời nhắc chi tiết".
  • Bài học từ phát triển theo mô hình (MDD). MDD từng không thành công cho ứng dụng kinh doanh. Fowler lo spec-as-source thừa hưởng cả sự cứng nhắc của MDD lẫn tính không xác định của mô hình ngôn ngữ.

Dù vậy, ông vẫn thấy suy nghĩ theo kiểu spec-first có giá trị: viết cẩn thận đặc tả trước khi nhờ tác nhân thường có ích. Cái ông nghi ngờ là các quy trình nặng nhiều tệp.


5. Khi nào đáng dùng, khi nào không#

Bảng này dựa trên lời của Fowler cộng với suy luận của mình.

Tình huống Đặc tả Vì sao
Sửa một lỗi nhỏ Không Công viết đặc tả vượt xa công làm
Thay đổi cỡ vừa trong mã có sẵn Nhẹ, hoặc không Duyệt nhiều tài liệu có thể tốn hơn viết thẳng với AI
Tính năng mới cỡ vừa đến lớn Có, mức nhẹ Nhiều chỗ trống cho AI đoán, nên chốt trước
Thay đổi có rủi ro cao (tiền, quyền, dữ liệu cá nhân) Có, kèm tiêu chí chấp nhận rõ Cần viết ra điều không được xảy ra
Vấn đề lớn, chưa rõ Chưa, cần tìm hiểu người dùng trước Đặc tả không thay được việc hiểu vấn đề
Cần kiểm soát từng bước nhỏ Ít, làm từng bước Đặc tả lớn có thể đi ngược cách làm lặp nhỏ

6. Mẫu đặc tả nhẹ năm mục#

Gợi ý của mình, không phải chuẩn của công cụ nào. Một tệp ngắn, một trang, đặt cạnh mã.

  1. Vì sao. Một hai câu về vấn đề và người dùng. Không phải giải pháp.
  2. Hành vi mong muốn. Mô tả điều người dùng thấy và làm, bằng câu thường.
  3. Không làm. Những thứ cố ý để ngoài phạm vi. Mục này chặn AI "làm thêm".
  4. Ví dụ chấp nhận được. Ba đến sáu ví dụ cụ thể: đầu vào, kết quả mong đợi, kể cả ca lạ (rỗng, rất dài, sai kiểu).
  5. Câu hỏi còn mở. Điều bạn chưa biết. Bắt AI hỏi lại thay vì đoán.

Ví dụ minh họa (dựng lại): tính năng hoàn tiền.

  • Vì sao: Khách hủy đơn muốn nhận lại tiền mà không phải gọi hỗ trợ.
  • Hành vi: Trong trang đơn hàng, đơn chưa giao có nút "Hủy và hoàn tiền". Bấm xong, khách thấy trạng thái "đang hoàn tiền" và nhận email.
  • Không làm: Hoàn tiền một phần, đơn đã giao, đổi sang phương thức thanh toán khác.
  • Ví dụ chấp nhận: đơn chưa giao, 200 nghìn, hoàn đủ. Đơn đã giao: nút không hiện. Bấm hai lần liên tiếp: chỉ hoàn một lần. Cổng thanh toán lỗi: hiện thông báo, không đổi trạng thái đơn.
  • Câu hỏi mở: Hoàn về đâu nếu thẻ đã hết hạn? Ai duyệt nếu quá một triệu?

Ba ví dụ chấp nhận ở giữa chính là thứ kiểm thử có thể bám theo, và là thứ người duyệt dùng để đối chiếu. Chúng cũng nối với danh sách duyệt code do AI viết: điểm 1 (khớp yêu cầu) và điểm 9 (test kiểm gì).


7. Quy trình bốn bước dùng được ngay#

  1. Viết đặc tả nhẹ (khoảng 10 đến 20 phút). Nhờ AI hỏi lại những chỗ mơ hồ, nhưng bạn quyết định.
  2. Cho AI lập kế hoạch ngắn dựa trên đặc tả, và đọc kế hoạch trước khi cho làm.
  3. Làm từng bước nhỏ. Mỗi bước một thay đổi nhỏ, chạy thử, rồi đối chiếu ví dụ chấp nhận.
  4. Cập nhật hoặc bỏ đặc tả. Nếu tính năng sống lâu, sửa đặc tả khi hành vi đổi. Nếu không, xóa đi để khỏi lỗi thời.

Điểm 3 và 4 khớp với lo ngại của Fowler: giữ bước nhỏ và nhớ rằng đặc tả cũng có thể cũ.


8. Nhìn cân bằng#

  • Phần lớn bằng chứng là bài phân tích của một người. Fowler dùng thử các công cụ trên một số việc, không phải khảo sát lớn. Mình chưa thấy nghiên cứu độc lập đo hiệu quả của SDD.
  • Các công cụ đổi nhanh. Mô tả Kiro, Spec Kit, Tessl có thể đã khác khi bạn đọc. Các nguồn thứ cấp còn ghi khác nhau về ngày ra mắt và số liệu mức dùng, nên mình không trích.
  • "Spec" có nhiều nghĩa. Đừng nhầm đặc tả với lời nhắc dài hay với tài liệu thiết kế.
  • Đặc tả không thay được việc hiểu người dùng. Nó giúp chốt điều cần làm, không giúp tìm ra điều đáng làm.
  • Mẫu năm mục là gợi ý của mình, chưa kiểm chứng trên nhiều nhóm.

9. Kết luận#

Viết đặc tả trước không phải thủ tục mới cho vui. Nó là cách chốt những chỗ trống mà AI sẽ tự điền. Nhưng như Fowler nhắc, đừng dùng búa tạ cho việc nhỏ: một đặc tả nhẹ, vài ví dụ chấp nhận, và từng bước nhỏ thường đủ.

Việc nhỏ làm ngay: chọn một tính năng bạn định nhờ AI làm trong tuần này, viết năm mục trên trong mười phút, rồi so sánh kết quả với cách bạn thường làm.


Nguồn tham khảo#

Câu hỏi thường gặp

Phát triển theo đặc tả là gì?
Là viết trước một đặc tả, tức mô tả có cấu trúc về hành vi mong muốn của tính năng, rồi để tác nhân lập trình AI làm dựa trên đó. Đặc tả thành nguồn chân lý cho cả người và AI, thay vì một lời nhắc dài. Thuật ngữ còn đang thay đổi nghĩa.
Đặc tả khác tệp AGENTS.md thế nào?
AGENTS.md hay các tệp ngữ cảnh chung là bộ nhớ cho mọi lần làm việc: quy ước, lệnh, kiến trúc. Đặc tả chỉ áp dụng cho một tính năng hoặc một thay đổi cụ thể. Hai thứ bổ sung cho nhau.
Khi nào phát triển theo đặc tả không đáng?
Theo phân tích của Martin Fowler, khi việc nhỏ như sửa một lỗi, khi thay đổi cỡ vừa trong mã có sẵn mà việc duyệt tài liệu sinh ra tốn hơn viết thẳng, hoặc khi vấn đề lớn và chưa rõ nên cần kỹ năng sản phẩm hơn là đặc tả. Đặc tả dài dòng còn có thể làm tăng gánh nặng duyệt.
Có cần công cụ như Spec Kit hay Kiro không?
Không bắt buộc. Cốt lõi là thói quen viết rõ điều cần làm, điều không làm và ví dụ chấp nhận được trước khi nhờ AI. Một tệp đặc tả nhẹ trong kho mã đã đủ để bắt đầu, công cụ chỉ thêm quy trình và tệp.