Skip to content

Viết Spec Tốt ​

Bạn hiếm khi viết spec từ trang trắng. Bạn mô tả một thay đổi bằng ngôn ngữ thông thường, /opsx:propose phác thảo các yêu cầu và kịch bản, và sau đó bạn làm cho chúng trở nên tốt. Trang này nói về phần cuối đó — "tốt" trông như thế nào, và làm thế nào để định hướng AI hướng tới điều đó.

Đây là phần bổ trợ cho Reviewing a Change: review là phát hiện các điểm yếu trong bản nháp, viết là biết một bản mạnh được tạo nên từ những gì.

Spec là hành vi, không phải mã nguồn ​

Spec nói hệ thống của bạn làm gì, theo cách bất kỳ ai cũng có thể kiểm tra — chứ không phải nó được xây dựng như thế nào. Nó được tạo nên từ yêu cầu (các phát biểu về hành vi) và kịch bản (các ví dụ cụ thể chứng minh chúng).

markdown
### Requirement: Session Timeout
The system SHALL expire a session after 30 minutes of inactivity.

#### Scenario: Idle timeout
- GIVEN an authenticated session
- WHEN 30 minutes pass with no activity
- THEN the session is invalidated and the user must re-authenticate

Giữ phần cách làm — hàng đợi, thư viện, schema bảng — trong design.md hoặc mã nguồn. Khi hành vi và triển khai bị trộn lẫn vào một yêu cầu, yêu cầu đó không còn khả thi để kiểm thử và bắt đầu lỗi thời ngay khi mã nguồn thay đổi.

Điều gì tạo nên một yêu cầu tốt ​

Một yêu cầu tốt là một hành vi, được phát biểu rõ ràng đến mức bạn có thể giao cho người khác kiểm thử.

  • Một phát biểu, một SHALL/MUST. Nếu một yêu cầu có ba điều khoản "và cũng", thì thực ra nó là ba yêu cầu. Hãy tách chúng ra.

  • Có thể quan sát được. Ai đó bên ngoài mã nguồn phải có thể biết liệu nó có đúng hay không. "The system SHALL show an error banner when the upload exceeds 10 MB" là có thể quan sát được. "The system SHALL handle large uploads gracefully" thì không.

  • Mức độ mạnh phù hợp. OpenSpec sử dụng các từ khóa RFC 2119, và chúng có nghĩa khác nhau:

    KeywordMeaning
    MUST / SHALLMột yêu cầu cứng. Không thể thương lượng.
    SHOULDMột khuyến nghị mạnh, có chỗ cho ngoại lệ có lý do chính đáng.
    MAYThực sự tùy chọn.

    Ưu tiên dùng MUST/SHALL mặc định. Chỉ dùng SHOULD khi bạn thực sự có ý "trừ khi có lý do tốt để không."

Tiêu chí cho một yêu cầu: một người kiểm thử chưa bao giờ thấy mã nguồn có thể biết liệu nó đã đạt hay chưa? Nếu không, nó cần được làm sắc bén hơn.

Điều gì tạo nên một kịch bản tốt ​

Kịch bản là nơi một yêu cầu chứng minh giá trị của mình. Mỗi kịch bản là một GIVEN / WHEN / THEN cụ thể có thể trở thành một bài kiểm thử tự động.

  • Nó kiểm tra yêu cầu của mình. Một kịch bản chỉ lặp lại yêu cầu bằng cách khác không kiểm tra được gì. Hãy biến nó thành một tình huống cụ thể với một kết quả cụ thể.
  • Phủ các trường hợp quan trọng, không chỉ đường dẫn thành công. Đăng nhập hợp lệ thì dễ. Input trống, token hết hạn, nhấp lần thứ hai, thứ gì đó đi sai — đó là nơi các bug tồn tại, và nơi một kịch bản đáng giá nhất.
  • Đặt tên trường hợp trong tiêu đề. "Scenario: Rejects an expired token" cho người review biết điều gì được bao phủ ngay lập tức; "Scenario: Test 2" thì không.

Một thói quen hữu ích: trước khi phê duyệt, hãy hỏi trường hợp duy nhất mà tôi sẽ khó chịu nếu thấy nó bị hỏng là gì? — và đảm bảo một kịch bản nêu rõ nó.

Chọn đúng loại delta ​

Một thay đổi mô tả các chỉnh sửa của nó đối với spec bằng ba loại section. Sử dụng đúng loại giữ cho các spec đã lưu trữ của bạn trung thực:

  • ## ADDED Requirements — hành vi hoàn toàn mới chưa từng tồn tại trước đó.
  • ## MODIFIED Requirements — hành vi đã tồn tại và đang thay đổi. Bao gồm toàn bộ phiên bản mới; một ghi chú ngắn về những gì đã thay đổi giúp người review.
  • ## REMOVED Requirements — hành vi đang bị loại bỏ, kèm một dòng giải thích lý do.

Khi lưu trữ, ADDED được thêm vào spec chính, MODIFIED thay thế phiên bản cũ, và REMOVED bị loại khỏi nó. Xóa yêu cầu cuối cùng mà một capability có và bạn sẽ ngừng sử dụng nó: thay vì để lại một spec trống rỗng, archive sẽ xóa openspec/specs/<capability>/spec.md. Vì đó là bước archive duy nhất xóa một file, nó phải được yêu cầu — thêm retire_capabilities: true vào .openspec.yaml của thay đổi, bên cạnh schema: mà file đó đã cần. Không có nó thì archive sẽ hủy và báo cho bạn biết. Retirement xóa toàn bộ file, nên nó cũng bị từ chối khi spec còn chứa bất cứ thứ gì ngoài tiêu đề, ## Purpose, và các khối yêu cầu của nó — một section ## Notes, một bình luận dưới một yêu cầu. Việc hủy sẽ nêu tên các dòng đó; di chuyển chúng vào ## Purpose hoặc một yêu cầu, hoặc xóa spec thủ công. Đối với spec trong checkout của người gọi, đầu ra archive cũng nêu tên lệnh git checkout khôi phục file đã commit; các store được chọn nhận hướng dẫn khôi phục theo phạm vi checkout thay thế. Nếu bạn đánh dấu một thay đổi thực sự là ADDED, bạn sẽ có hai yêu cầu cạnh tranh; nếu bạn mô tả hành vi mới là MODIFIED, sẽ không có gì để thay thế. Khi không chắc, hãy mở spec hiện tại và xem liệu yêu cầu đã ở đó chưa.

Một section nữa đáng biết. Khi delta của bạn tạo ra một capability chưa tồn tại, hãy mở đầu bằng ## Purpose — một hoặc hai câu về mục đích của capability đó. Archive sử dụng nó làm Purpose của spec chính mà nó tạo ra; bỏ qua nó và bạn sẽ nhận được một placeholder TBD để điền thủ công. Một spec hiện có đã có Purpose, nên Purpose của delta bị bỏ qua ở đó — chỉnh sửa openspec/specs/<capability-path>/spec.md trực tiếp để thay đổi. Ở đây, <capability-path> là thư mục tương đối so với specs/, chẳng hạn như user-auth trong một dự án phẳng hoặc identity/user-auth trong một dự án được tổ chức theo domain.

Điều chỉnh kích thước thay đổi phù hợp ​

Lỗi viết phổ biến nhất không phải là một yêu cầu diễn đạt kém — mà là một thay đổi đang cố gắng làm ba thay đổi.

Một thay đổi tốt có một ý định duy nhất mà bạn có thể nói trong một câu. "Thêm công tắc dark-mode." "Giới hạn tần suất endpoint đăng nhập." "Di chuyển phiên khỏi cookies." Nếu mô tả thay đổi cần nhiều "và cũng", đó là tín hiệu để tách nó ra.

Dấu hiệu một thay đổi quá lớn:

  • Phạm vi của đề xuất đọc như một danh sách các tính năng không liên quan.
  • Review nó sẽ mất cả buổi chiều, nên không ai làm.
  • Hai người không thể làm việc trên nó mà không xung đột.
  • Một nửa các tác vụ có thể phát hành độc lập.

Các thay đổi nhỏ hơn dễ review hơn, dễ xây dựng trong một phiên tập trung, và dễ suy luận sau sáu tháng khi archive là tất cả những gì còn lại. Bạn luôn có thể chạy nhiều thay đổi song song — xem Editing & iterating và Workflows.

Điều ngược lại cũng xảy ra: sửa một lỗi chính tả một dòng không cần ba yêu cầu và một tài liệu thiết kế. Hãy phù hợp nghi thức với mức độ quan trọng.

Cách định hướng AI hướng tới bản nháp tốt ​

Vì /opsx:propose làm bản nháp đầu tiên, chất lượng của những gì bạn nhận lại phụ thuộc vào chất lượng của những gì bạn đưa cho nó. Bạn không cần viết yêu cầu bằng tay — bạn cần định hướng AI tốt:

  • Nêu rõ ý định và ranh giới. "Thêm công tắc dark-mode theo cài đặt hệ điều hành khi tải lần đầu — đừng đụng vào API theme hiện có." Phần ngoài phạm vi quan trọng ngang với phần trong phạm vi.
  • Nêu các trường hợp bạn quan tâm. "Đảm bảo có kịch bản cho người dùng đã chọn theme thủ công." AI bao phủ những gì bạn chỉ vào.
  • Rồi chỉnh sửa. Nó là Markdown thuần. Làm sắc bén một SHALL mơ hồ, xóa một kịch bản không kiểm tra được gì, thêm trường hợp nó bỏ lỡ — hoặc yêu cầu AI: "yêu cầu timeout mơ hồ, chốt nó ở 30 phút."

Nháp, làm sắc bén, lặp lại. Vài vòng như vậy tạo ra một spec mà bạn tin tưởng, đó là mục đích chính.

Danh sách kiểm tra nhanh ​

Tiếp theo đi đâu ​

  • Reviewing a Change — lượt xem hai phút bắt được những gì đã lọt qua.
  • Concepts — mô hình sâu hơn đằng sau spec, thay đổi và delta.
  • Examples & Recipes — các thay đổi thực tế từ đầu đến cuối.