在前兩篇文章中,我們分別討論了:
- 第一部分:確保需求本身的質量(文檔化、正確性、完整性)
- 第二部分:確保需求能被正確理解(無歧義性、一致性)
現在,需求已經"合格"且"清晰"了,但還有一個關鍵問題:需求能否被正確實現和驗證? 本文重點討論可測試性、可追溯性和可行性,這三個特質確保需求不僅能被理解,還能被實際執行、驗證和維護。
可測試性
我們必須有一些想法來測試需求是否得到滿足。如果可以通過實際和客觀的方式來確定實現的解決方案是否符合需求,則該需求是可測試的。可測試性對於人類生成的代碼和人工智能生成的代碼都至關重要。我們的信心必須主要來自驗證代碼行為。針對清晰、可測試的需求進行嚴格測試是確保代碼可靠且符合目的的主要機制。可測試的需求為驗證提供了藍圖。
可測試性要求小粒度、可觀察性和可控性。這三個特性就像測試的三根支柱,缺一不可。
小粒度
這裏的小粒度需求意味着它產生了一個小的被測代碼單元。這就是可分解性、簡單性和模塊化變得重要的地方。具有單一職責的小型、定義良好且簡單的代碼單元比大型、整體化和複雜的組件更容易理解、全面測試和推理。如果人工智能生成了一個龐大且糾纏的函數,即使它在"快樂路徑"上"工作",驗證其所有內部邏輯和邊緣情況也極其困難。你無法確定其中可能潛伏着什麼意外行為。為了實現小粒度,將大型需求分解為更小、更易於管理的子需求。每個子需求最好描述一個單一、連貫的功能及其自身的可測試結果。就像拆積木,小塊比大塊更容易測試。
可觀察性
可觀察性是指根據輸入確定組件的內部狀態及其輸出的難易程度。這在測試執行之前、期間和之後都是如此。本質上,你可以"看到"軟件在做什麼以及它的結果是什麼嗎?為了測試,我們需要能夠觀察行為或狀態。如果一個動作的效果完全是內部的且不可見,那麼測試就很難進行。就像黑盒子,你看不到裏面發生了什麼,就很難測試。
為了實現可觀察性,我們需要清晰且全面的日誌記錄,通過 getter 或狀態端點公開相關狀態。我們需要返回詳細且結構化的錯誤消息,實現事件發佈,或者有效地使用調試器。這樣我們就可以驗證中間步驟,理解執行流程,並診斷測試失敗的原因。
具體要求包括:
-
描述外部行為:關注系統可以被看到的行為,而不是它內部是如何實現的(除非內部的"如何"影響了需要約束的非功能性需求,如性能)。
-
指定輸出:詳細説明任何輸出的格式、內容和目的地(UI 顯示、API 響應、文件生成、數據庫條目、日誌消息)。
- 示例:在成功註冊後,系統必須返回一個帶有 JSON 正文的 HTTP 201 響應,其中包含
user_id和email。
- 示例:在成功註冊後,系統必須返回一個帶有 JSON 正文的 HTTP 201 響應,其中包含
-
定義狀態變化:如果狀態變化是一個重要的結果,指定如何觀察該狀態。
- 示例:在訂單提交後,訂單狀態必須是"PENDING_PAYMENT",並且可以通過
/orders/{orderId}/status端點檢索到該狀態。
- 示例:在訂單提交後,訂單狀態必須是"PENDING_PAYMENT",並且可以通過
-
要求記錄關鍵事件:以 INFO 級別記錄關鍵狀態變化和決策點。系統必須在成功登錄時記錄一個帶有
event_type='USER_LOGIN_SUCCESS'和user_id的審計事件。
可控性
可控性是指我們"引導"組件進入特定狀態或條件的難易程度。我們能夠多容易地為組件提供必要的輸入(包括依賴項的狀態),以執行測試並將其與測試無關的外部因素隔離?我們可以通過依賴注入(DI)、設計清晰的 API 和接口、為依賴項使用模擬對象或樁以及提供配置選項等技術來實現這一點。這使我們能夠輕鬆地設置特定場景,獨立地測試單個代碼路徑,並創建確定性的測試。就像做實驗,你需要能控制變量,才能得出準確的結論。
可控性差導致的問題
如果可控性不好,會遇到以下問題:
硬編碼依賴項
它們可能會迫使你將單元測試與真實的依賴項一起進行。這會使單元測試變成緩慢且可能不可靠的集成測試。你無法輕鬆地模擬依賴項的錯誤條件。就像測試一個函數,但函數內部硬編碼了數據庫連接,你就必須真的連數據庫才能測試。
依賴全局狀態
如果一個組件讀取或寫入全局變量或單例,測試很難隔離。一個測試可能會改變全局狀態,導致後續測試失敗或行為不可預測。在測試之間重置全局狀態可能很複雜。全局狀態就像公共廁所,一個人用了,下一個人用的時候可能就不乾淨了。
缺乏清晰的輸入機制
如果一個組件的行為是由複雜的內部狀態變化觸發的,或者依賴於不透明的數據源,而不是清晰的輸入參數,那麼很難將其置於特定測試所需的特定狀態。就像黑盒子,你不知道怎麼控制它,就很難測試。
後果
- 測試緩慢:需要設置數據庫、調用真實 API 或等待真實超時的測試運行緩慢,這會阻礙頻繁執行。
- 測試不穩定:依賴外部系統或共享狀態的測試可能會因測試代碼之外的因素(例如,網絡問題、API 速率限制)而間歇性失敗。
- 難以編寫和維護:複雜的設置和非確定性行為導致測試難以編寫、理解和調試失敗。測試的"準備"階段成為一個巨大的努力。
可追溯性
軟件需求的可追溯性意味着能夠正向和反向跟蹤需求的生命週期。你應該能夠將特定需求與實現和驗證它的設計元素、代碼模塊和測試用例聯繫起來。反之,查看代碼片段或測試用例時,你應該能夠追溯它所滿足的需求。可追溯性告訴我們代碼存在的原因以及它應該實現的業務規則或功能。如果沒有這種聯繫,代碼可能會迅速變成開發者不敢觸碰的"魔法"。就像給代碼貼上標籤,你知道它是為什麼而寫的。
1. 調試和根本原因分析
當人工智能生成的代碼出現錯誤或意外行為時,追溯到源需求通常是第一步。需求有缺陷嗎?人工智能誤解了一個正確的需求嗎?可追溯性指導調試過程。有了可追溯性,你就能快速找到問題的根源。
2. 維護和變更影響分析
需求不可避免地會變化。如果需求 REQ-123 被更新,可追溯性允許你快速識別特定的代碼部分(可能是人工智能生成的)。與 REQ-123 相關的測試需要審查、修改或重新生成。沒有可追溯性,找到所有受影響的代碼部分將是一個耗時且容易出錯的手動搜索。就像改一個需求,你知道要改哪些代碼和測試,不會漏掉。
3. 驗證和覆蓋
可追溯性有助於驗證我們的需求是否有代碼和測試。你可以檢查是否有需求被遺漏,或者是否有生成的代碼無法追溯到有效需求。這樣你就知道哪些需求實現了,哪些還沒實現。
可行性
如果需求可以在項目的給定約束內實際實現,則該需求是"可行的"。這些約束通常包括可用時間、預算、人員技能、現有技術棧、架構模式、安全策略、行業法規、性能目標和部署環境。
明確約束的必要性
為了確保人工智能助手生成可行的代碼,需求必須明確説明相關約束。這些約束作為護欄,引導人工智能朝着不僅是技術上可能,而且對於你的特定項目背景也是實用和適當的解決方案發展。也許你的公司標準化了使用 FastAPI 框架用於 Python 微服務。也許某些服務直接訪問數據庫被安全策略禁止。也許你的部署目標是一個低內存容器環境,或者人工智能建議的特定外部(付費)API 超出了項目預算。就像給 AI 畫個框,告訴它只能在這個框裏發揮,這樣生成的代碼才符合你的實際情況。
總結
當為人工智能生成的代碼編寫需求時,基本原則保持不變,但重點轉向:
- 極度明確性:精心涵蓋邊緣情況、錯誤和非功能性需求。不要指望 AI 能自己推斷,要把能想到的都寫清楚。
- 無歧義性和精確性:使用清晰、機器可解釋的語言。避免模糊表達,用具體的數據和規則。
- 約束定義:通過指定架構、技術棧、模式和非功能性需求來引導人工智能。告訴 AI 你的邊界在哪裏。
- 可測試性:定義清晰、可衡量的驗收標準。小粒度、可觀察性和可控性很重要。讓需求可測試,代碼才能可靠。
- 結構化輸入:以最佳方式為人工智能呈現需求。用模板、格式化的方式組織需求,AI 更容易理解。
本質上,人工智能代碼生成的需求意味着更加深思熟慮、詳細和指令性。這是關於為人工智能提供一個高保真度的藍圖,以減少猜測。一個最大化生成正確、安全、高效和可維護代碼的概率的藍圖。與項目目標和技術標準一致的代碼。這涉及增強完整性、無歧義性和可測試性等特質的重要性。這也涉及演變對"可理解性"的解釋,以適應人工智能"開發人員"。
簡單來説,以前的需求文檔是給人看的,現在還要給 AI 看。AI 不像人那樣有常識和經驗,所以需求必須寫得更詳細、更明確、更結構化。 雖然寫需求的工作量增加了,但換來的是更可靠的代碼生成,這個投入是值得的。
目前看來,精心編寫軟件需求也可以減少人工智能生成代碼中的幻覺現象。然而,僅通過需求本身並不能完全消除幻覺。輸入提示(包括需求)的質量和結構顯著影響人工智能產生幻覺的可能性。幻覺還源於模型限制、訓練數據的痕跡以及提示上下文的邊界。這些因素超出了本文的範圍。
最後,記住一個原則:好的需求文檔,不僅能讓 AI 生成好代碼,也能讓人更好地理解系統。 這是一舉兩得的事情,值得花時間做好。