Tôi đã sử dụng phương pháp phát triển dựa trên đặc tả (spec-driven development) được một thời gian – trong các dự án cá nhân và một vài dự án gần đây với khách hàng. Dù là Spec Kit, Kiro hay một số biến thể tự phát triển, công cụ không quan trọng bằng hình thức của quy trình làm việc. Viết đặc tả, lập kế hoạch, phân công nhiệm vụ và để một tác nhân (hoặc chính bạn) bắt đầu viết mã dựa trên các nhiệm vụ đó.
Vấn đề nằm ở nơi lưu trữ tất cả những thứ này: trong kho lưu trữ (repo), dưới dạng Markdown. Điều này rất tiện lợi cho tôi, nhưng vô dụng đối với những người khác. Quản lý dự án của tôi không mở kho lưu trữ của tôi. Trưởng nhóm triển khai của tôi làm việc trên Azure DevOps. Bộ phận tuân thủ yêu cầu các câu chuyện người dùng (stories) trong Jira kèm theo tiêu chí chấp nhận.
Tôi đã sử dụng phương pháp phát triển dựa trên đặc tả (spec-driven development) trong một thời gian – cho các dự án cá nhân và một vài dự án khách hàng gần đây. Spec Kit, Kiro, một vài biến thể tự phát triển; công cụ ít quan trọng hơn hình thức của quy trình làm việc. Viết một đặc tả, có một kế hoạch, có các nhiệm vụ, và để một tác nhân (hoặc chính bạn) bắt đầu viết mã dựa trên chúng.
Vấn đề nằm ở nơi tất cả những điều đó tồn tại: bên trong kho lưu trữ (repo), dưới dạng Markdown. Tuyệt vời cho tôi. Vô dụng cho những người khác. Quản lý dự án (PM) của tôi không mở kho lưu trữ của tôi. Trưởng nhóm triển khai của tôi làm việc trong Azure DevOps. Bộ phận tuân thủ muốn có các câu chuyện (stories) trong Jira với tiêu chí chấp nhận (acceptance criteria) và nhật ký kiểm toán (audit trail). Không ai sẽ truy cập vào một nhánh tính năng (feature branch) để đọc tệp tasks.md.
Vì vậy, bảng sprint không trống. Ai đó lặng lẽ tạo lại một nửa danh sách nhiệm vụ của tôi trong Jira. Trong vòng một ngày, hai danh sách này lệch nhau, và đến cuối sprint, không ai có thể nói cho bạn biết danh sách nào là thật.
Đó là luận điểm của bài viết này: phát triển dựa trên đặc tả không hoạt động một cách đơn độc. Trừ khi nó tích hợp vào Azure DevOps, Jira, hoặc bất kỳ công cụ nào mà nhóm của bạn thực sự sử dụng, nó vẫn chỉ là một thủ thuật năng suất cá nhân. Dưới đây là thiết kế mà tôi đã phác thảo để kết nối hai thế giới này – bởi vì tôi không nghĩ rằng ai đó đã thực sự hoàn thiện nó.
Tuyên bố miễn trừ trách nhiệm: Bài viết này được soạn thảo với sự hỗ trợ của các công cụ AI để tinh chỉnh cấu trúc và sự rõ ràng, nhưng phân tích cốt lõi và quan điểm kỹ thuật dựa trên kinh nghiệm chuyên môn của tôi với tư cách là Kiến trúc sư Dữ liệu và AI.
Vấn đề hai thế giới
Hãy để tôi vẽ bức tranh mà tôi liên tục thấy:
Nguồn: Hình ảnh do tác giả cung cấp
Ở phía dưới là nơi quy trình dựa trên đặc tả của tôi tạo ra công việc. Ở phía trên là nơi tổ chức của tôi nghĩ rằng công việc đang diễn ra. Đường chấm chấm giữa chúng là ai đó, thường là tôi, gõ lại mọi thứ vào lúc 5 giờ chiều thứ Sáu.
Tôi đã tự bảo vệ điều này. “Không sao đâu, tôi sẽ tự giữ chúng đồng bộ thủ công.” Tôi không bao giờ làm vậy. Không ai làm vậy. Hai tuần sau, đặc tả đã thay đổi, Jira đã lỗi thời, và các tiêu chí chấp nhận mà nhà phát triển đang viết mã không khớp với những gì người kiểm thử đang kiểm tra. Tôi đã chứng kiến một bản phát hành được đưa ra trong đó câu chuyện trong Jira nói “người dùng có thể xuất dưới dạng CSV” và đặc tả nói “người dùng có thể xuất dưới dạng CSV hoặc JSON” – và cả PM lẫn nhà phát triển đều không nhận ra cho đến khi một khách hàng hỏi.
Tại sao “chỉ chọn một công cụ” không phải là câu trả lời.
Mỗi khi tôi nêu vấn đề này, ai đó nói: “phát triển dựa trên đặc tả vẫn còn là lý thuyết. Nó không thể được sử dụng trong thực tế trong các dự án đời thực.” Và mỗi lần tôi muốn giải thích – nhưng thường không làm, vì nghe có vẻ quý giá – rằng tôi đã thử các giải pháp thay thế và chúng tệ hơn.
Lý do tôi thích giữ các đặc tả trong kho lưu trữ thực sự rất thực tế:
Đặc tả tại commit abc123 mô tả hệ thống tại commit abc123. Jira không có phiên bản như vậy.
Các tác nhân đọc tệp Markdown tốt hơn nhiều so với việc họ điều hướng API REST của Jira. Tôi đã thử cả hai. Không có sự so sánh nào.
Tôi có thể phân nhánh một đặc tả giống như cách tôi phân nhánh mã. Hai hướng thiết kế, hai nhánh, một PR thắng.
Các thay đổi đặc tả xuất hiện trong quá trình xem xét yêu cầu kéo (pull request review), bên cạnh mã mà chúng đang điều khiển. Đó là cách các cuộc trò chuyện thiết kế nên diễn ra.
Tôi không thể có bất kỳ điều nào trong số đó từ Jira. Nhưng tôi cũng không thể có dung lượng sprint, quản lý phụ thuộc, tổng hợp điều hành, hoặc các trạng thái quy trình làm việc thân thiện với kiểm toán từ một tệp Markdown. Cả hai công cụ đều tồn tại vì những lý do riêng. Câu hỏi mà tôi đã suy nghĩ là làm thế nào để chúng hợp tác thay vì giả vờ chỉ một trong số chúng quan trọng.
Đây là cách tôi đã nghĩ về sự phân chia:
Nguồn: Hình ảnh do tác giả cung cấp
Quy tắc mà tôi đã thống nhất: đặc tả là sự thật, công cụ là góc nhìn.
Sau rất nhiều lần thử.
Sau nhiều lần thử nghiệm và thất bại, mô hình tư duy cuối cùng mà tôi đúc kết được là: kho lưu trữ (repo) sở hữu nội dung "cái gì", công cụ quản lý dự án (PM tool) sở hữu cách thức tổ chức thực hiện "như thế nào". Mọi thứ khác đều xuất phát từ nguyên tắc cơ bản này.
Cụ thể, có ba quy tắc:
Tiêu đề, mô tả, tiêu chí chấp nhận, phân tích công việc – những nội dung này được soạn thảo bằng Markdown, trong kho lưu trữ. Đây là nguồn thông tin đáng tin cậy về nội dung công việc.
Người được giao, sprint, trạng thái, điểm câu chuyện (story points), theo dõi thời gian – những thông tin này được soạn thảo trong công cụ quản lý dự án. Đây là nguồn thông tin đáng tin cậy về trạng thái công việc.
Có một định danh ổn định tồn tại ở cả hai nơi và không bao giờ thay đổi sau khi được tạo.
Khi tôi viết ra như vậy, có vẻ hiển nhiên, nhưng tôi cam đoan rằng tôi đã mất vài tuần để cố gắng thực hiện mà không có quy tắc 3, và đó là một thảm họa.
Về mặt trực quan, sự phân chia quyền sở hữu trông như thế này:
Nguồn: Hình ảnh của tác giả
Vòng tròn ở giữa – ID được chia sẻ – là yếu tố then chốt. Mọi thứ khác đều xuất phát từ việc có một điểm chung mà cả hai hệ thống đều đồng ý.
Thiết kế 1: một công cụ đồng bộ hóa nhỏ chạy hai chiều
Đây là phần tôi luôn quay lại. Bạn cần một dịch vụ nhỏ để điều hòa hai hệ thống. Nội dung chảy từ kho lưu trữ → công cụ quản lý dự án. Trạng thái thực thi chảy từ công cụ quản lý dự án → kho lưu trữ. Không có gì ghi đè lên các trường của phía bên kia.
Một chi tiết đáng lưu ý ngay từ đầu: hướng từ kho lưu trữ đến công cụ quản lý dự án có thể nằm trong GitHub Action hoặc pipeline của ADO, vì nó được kích hoạt bởi một lần hợp nhất (merge). Hướng ngược lại thì không thể. GitHub Actions không chấp nhận webhook đến từ các hệ thống bên ngoài tùy ý, vì vậy phần từ công cụ quản lý dự án đến kho lưu trữ cần một điểm cuối HTTP liên tục – một Azure Function, một Cloudflare Worker, một container nhỏ ở đâu đó. Hai trình kích hoạt, hai nơi. Khi bạn chấp nhận điều đó, phần còn lại sẽ đơn giản.
Nguồn: Hình ảnh của tác giả
Hai trình kích hoạt. Khi hợp nhất vào nhánh chính (main), phân tích các tệp đặc tả, so sánh với ảnh chụp nhanh cuối cùng và đẩy các thay đổi nội dung vào công cụ quản lý dự án. Khi có webhook từ công cụ quản lý dự án, lấy thay đổi trạng thái đến và mở (hoặc tự động commit) một bản cập nhật frontmatter trên kho lưu trữ.
Lý do tôi chia tách theo cách này, và tôi đã học được điều này một cách khó khăn, là nếu công cụ đồng bộ hóa có quyền ghi đè nội dung ở một trong hai phía, con người cuối cùng sẽ thực hiện một thay đổi và công cụ sẽ ghi đè nó. Nếu nội dung và trạng thái được tách biệt rõ ràng, sẽ không có gì để tranh chấp.
Thiết kế 2: hợp đồng định danh
Tôi đã mất khoảng một tuần để cố gắng đồng bộ hóa mọi thứ mà không có ID ổn định. Tôi đã sử dụng đường dẫn tệp. Tôi đã sử dụng văn bản tiêu đề. Cả hai đều bị hỏng ngay lần đầu tiên ai đó đổi tên một tác vụ. Đừng làm như tôi đã làm.
Đây là những gì một tác vụ
Nguồn tin: Medium Towards AI — Tác giả: Kishan. Bản dịch tiếng Việt do AI thực hiện, có thể có sai sót.