軟體開發和測試的 30 個最佳實踐

maifans發表於2017-06-29

這些軟體開發和測試的最佳實踐,可以幫你節省時間和避免問題。

加入一個企業文化和程式設計實踐已經定型的新公司,可能會是一種令人沮喪的經歷。當我加入 Ansible 團隊後,我決定整理我多年以來所學併為之奮鬥的軟體工程實踐和準則。這是一個不明確的也不夠詳盡的準則列表,使用它們時需要智慧和靈活性。

我對測試充滿熱情,因為我相信良好的測試實踐既能確保滿足最低質量標準(可悲的是許多軟體產品做不到),並能指導和塑造開發本身。本文提到的這些準則,很多是與測試實踐和理念相關的。其中一些準則針對 Python 的,但大多數不是。(對於 Python 開發者,PEP 8 應該是程式設計風格和指南的首先。)

軟體開發和測試的 30 個最佳實踐

開發和測試的最佳實踐

1. YAGNI 原則:“You Aint Gonna Need It”。不要寫你認為將來可能需要但現在不需要的程式碼。這是為假想的未來用例編碼,這些程式碼將不可避免地變成死程式碼,或需要重寫,因為未來結果總是與想象的稍有不同。

如果你寫程式碼用於將來的用例,我將在程式碼評審中對其質疑。(你可能而且必須設計 API,並確保未來的用例可用,但這是不同的問題。)

這條原則也適用於被註釋掉的程式碼;如果一個被註釋掉程式碼塊即將進入一個釋出版本,那麼它就不應該存在。如果程式碼可能要還原,請為程式碼刪除建立一個問題單並引用提交物件的雜湊字串。YAGNI 原則是敏捷程式設計的核心要素,這個話題最好的參考書是 Kent Beck 寫的《解析極限程式設計》(《Extreme Programming Explained》)。

2 . 測試不需要測試。用於測試需要的基礎設施、框架和庫需要測試。除非你真的需要不要測試瀏覽器或外部庫。測試你寫的程式碼,而不是別人的程式碼。

3.當第三次編寫相同的程式碼時,也是將其提取成通用的輔助函式(併為其編寫測試)的正確時機。測試中的輔助函式不需要測試;但當你將它們剔除出去然後再重用它們時,它們需要測試。到你第三次編寫類似程式碼的時候,你通常會清楚地認識到你正在解決的通用問題的模型是什麼。

4. 現在來談談 API 設計(面向外部的物件API):把簡單的事情做簡單了,複雜的事情自然成為可能。首先設計簡單的用例,如果有可能最好是零配置或引數化。為更復雜和靈活的用例(如需要)增加選項或額外的 API 方法。

5. 快速失敗。檢查輸入,如果遇到無意義輸入或非法狀態則儘早失敗,最好是通過異常或錯誤響應使問題對呼叫者變得清晰。允許你的程式碼處理“有創意”的用例(例如,除非真的需要,否則在做輸入驗證的時候不要做型別檢查)。

6.單元測試測的是行為單元,而不是實現單元。我們的目標是在改動實現的情況下不改動行為,也不必更新測試,儘管這個目標不總是能實現。因此在可能的情況下將測試物件視為黑盒,通過公共 API 測試,而不呼叫私有方法或玩弄狀態位。

在一些複雜的情況可能做不到,如在特定的複雜狀態下測試行為,以找到一個偶發的錯誤。這一點對於寫測試非常有幫助,因為它迫使你在寫測試程式碼之前思考你程式碼的行為以及你將如何測試它。測試首先鼓勵更小,更模組化的程式碼單元,這通常意味著好程式碼。關於“測試優先”方法有一本很好的入門參考書,就是 Kent Beck 寫的《測試驅動開發》(《Test Driven Development by Example》)

7. 對於單元測試(包括測試基礎設施測試),所有程式碼路徑都應該被測到。100% 覆蓋是一個好的開始。你不能覆蓋所有可能狀態的排列/組合(組合性爆炸),因此這個問題需要考慮。只有當有很好理由的情況下才允許有程式碼路徑未經測試。沒有時間不是一個好理由,最終會花費更多的時間。可能的好理由包括:真正的不可測(以任何有意義的方式),現實中不可能發生,或被其它測試覆蓋。沒有測試的程式碼是一種債。測量覆蓋率和拒絕減少覆蓋率 PR(拉取請求) 是確保你在正確的方向演進的一種方式。

8. 程式碼是敵人:它可能出錯,並且需要維護。少寫程式碼,刪除程式碼,不要寫你不需要的程式碼。

9. 隨著時間的推移,程式碼註釋不可避免地成為謊言。在現實中,很少有人在事情變化的時候更新註釋。通過良好的命名法和已知的程式設計風格,努力使你的程式碼可讀和自文件化。

對於那些晦澀的程式碼,一定要寫註釋,例如偶發錯誤或意外情況的變通方案,或者必要的優化。解釋程式碼的意圖和及其原因,而不是解釋程式碼在做什麼。(順便說一句,有些觀點認為註釋變謊言是有爭議的。我仍然認為這是正確的,《程式設計實踐》(《The Practice of Programming》)的作者 Kernighan 和 Pike 同意我的觀點。)

10. 防守思維。總是考慮什麼會出錯,無效的輸入會引發什麼,什麼可能會失敗,這將有助於你在許多錯誤發生之前發現他們。

11.無狀態和無副作用的單元測試,其邏輯應簡單。將邏輯分解成單獨的函式,而不是將邏輯混合到有狀態和充滿副作用程式碼中。將有狀態程式碼和有副作用程式碼,分為較小的更容易模擬的函式和無副作用地單元測試。(測試的開銷越小意味著更快的測試)副作用確實需要測試,但是測試一次然後處處模擬它們通常是一個好的模式。

12. 全域性變數不好。函式優於型別。物件可能比複雜的資料結構更好。

13.使用 Python 內建型別及其方法將比自己編寫的型別執行快(除非你用C語言編寫)。如果效能是一個考慮因素,請嘗試弄懂如何使用標準的內建型別,而不是自定義物件。

14. 依賴注入是一個實用的程式設計模式,明確你的依賴是什麼和它們來自哪裡。(物件,方法等以引數的形式接收它們的依賴,而不是例項化新物件本身。)這確實讓 API 簽名更復雜,所以這裡需要權衡。如果一個方法最後為所有的依賴設定了10個引數,那這是一個不錯的訊號,不管為什麼你的程式碼做得太多。關於依賴注入的權威文章是 Martin Fowler 的《控制反轉容器&依賴注入模式》(《Inversion of Control Containers and the Dependency Injection Pattern》)。

15. 需要模擬測試的程式碼越多,你的程式碼就越糟糕。為了測試一個特定的行為,需要例項化和牽扯的程式碼越多,程式碼越糟糕。我們的目標是小型可測試的單元,以及更高階別的整合和功能測試,以測試各單元是否配合正確。

16. 面向外部的 API 是“預先設計”——同時要考慮未來的用例——真正重要的地方。改變 API 對我們和使用者來說是一種痛苦,造成向後不相容是可怕的(儘管有時無法避免的)。設計面向外部的 API 要細心,仍然要堅持“把簡單的事情做簡單”的原則。

17.如果函式或方法超過 30 行程式碼,考慮分解它。一個良好的模組最大約 500 行。測試檔案往往比這個要大。

18 .不要在物件建構函式中工作,這裡很難測試而且經常發生意外。不要在__init__ .py中新增程式碼(匯入名稱空間除外)。__init__ .py不是程式設計師通常期望找程式碼的地方,所以它是個“驚喜”。

19. DRY(不要重複自己)在測試中沒有在生產程式碼中要緊。單個測試檔案的可讀性比可維護性更重要(跳出模組複用的限制)。這是因為測試是單獨被執行和閱讀的,而且它們自己不是大系統的一部分。雖然在很多重複的時候建立可重用的元件更方便,但相較於產品程式碼,測試程式碼較少考慮。

20.每當你看到有需要有機會就重構。程式設計是關於抽象的,你的抽象對映越接近問題域,程式碼就越容易理解和維護。隨著系統的有機增長,需要改變結構以擴大它們的用例。系統越來越多的抽象和結構,如果不改變它們就成為技術性的債務。它會是更加痛苦的工作(更慢,越來越多的錯誤)。在特性開發估計中請考慮清除技術債務(重構)的成本。你遺留債務的時間越長,積累的利息就越高。關於重構和和測試的一本很棒的書是 Michael Feathers 的《修改程式碼的藝術》(《Working Effectively with Legacy Code》)。

21. 程式碼正確為第一位,速度快第二位。在處理效能問題時,在修復錯誤之前先要做效能剖析。通常瓶頸不是你認為的那樣。如果寫晦澀的程式碼的唯一價值就是更快而且你已經做過效能剖析並證明了,那麼它實際就是值得的。編寫測試定期檢測你要做效能剖析的程式碼,這樣可以很容易讓你知道你什麼時候測試過。測試可以留在測試套件中,以防止效能退化。(通常情況下,新增定時程式碼總會改變程式碼的效能特性,使效能工作成為令人沮喪的任務之一。)

22. 當更小、範圍有限的單元測試失敗的時候,可以給出更多有價值的資訊,告訴你具體是什麼錯誤。如果一個測試牽涉了半個系統來測試行為,那麼它需要更多的調查以確定什麼是錯誤的。一般來說,執行超過 0.1 秒的測試不是單元測試。沒有所謂的慢單元測試。用限定範圍的單元測試測試行為,你的測試行為扮演了事實上的程式碼規範。理想情況下如果有人想了解你的程式碼,他們應該能夠把測試套件轉換為行為的“文件”。Gary Bernhardt 的《快測,慢測》(《Fast Test, Slow Test》)是關於單元測試實踐的一篇很棒的演講。

23. ”非我所創“不像人們說的那麼壞。如果程式碼是我們寫的,那麼我們知道它是什麼,我們知道如何維護它,在我們可以在適當的時候自由地擴充套件和修改它。這遵循了 YAGNI 原則:我們用那些適合我們需要用例的特定程式碼,而不用我們不需要的可以做複雜事情的通用程式碼。另一方面,程式碼是敵人,擁有必要的程式碼比擁有更多的程式碼更好。引入新的依賴關係時要權衡。

24. 共享程式碼所有權是我們目標;沉默的知識不是好知識。這意味著最低限度要討論或記錄設計決策和重要的實施決策。程式碼評審(Code Review)是開始討論設計決策的最壞時刻,因為在程式碼編寫後,很難徹底更改。(當然在評審時指出並修改設計錯誤比沒有好。)

25. 生成器很棒!它們通常比迭代或重複執行的狀態物件更短更容易理解。David Beazley的《系統程式設計師的生成器訣竅》(《Generator Tricks for Systems Programmers》)是關於生成器一個很好的介紹。

26.讓我們成為工程師!讓我們考慮設計、構建健壯並實現良好的系統,而不是做膨脹的有機怪物。然而程式設計是一種平衡。我們並不總是建造火箭。過度設計(洋蔥架構)同設計不完善的程式碼一樣,處理起來非常痛苦。Robert Martin 的作品幾乎都值得一讀,《架構之潔:一個工匠的軟體結構和設計指南》(《Clean Architecture: A Craftsman’s Guide to Software Structure and Design》)是這個話題一個很好的資源。《設計模式》(《Design Patterns》)是每一位工程師都應該閱讀的經典程式設計書。

27. 間歇失敗的測試會侵蝕測試套件的價值,以至於最終每個人都忽略測試執行結果,因為總有一些失敗的事情。修復或刪除間歇性失敗測試是痛苦的,但這些努力是值得的。

28. 一般來說,特別是在測試中,在需要等待一個特定的變化時候不要採用休眠隨機時間的方式。Voodoo(Python 庫) 的 sleeps 很難理解而且使你的測試套件變慢。

29. 至少讓你的測試失敗一次。故意加入一個錯誤,並確保它失敗,或在測試的行為不完整的情況下執行測試。否則你不知道你真的在測試什麼。瞎寫的測試實際上不能測試任何東西或它很可能永遠不會失敗。

30. 最後一點:只關注特性改進是開發軟體的一種可怕方式。如果開發者的工作不能確保最好的結果,那麼不要讓他們為自己的工作感到自豪。不處理技術債務會使開發變慢並最終導致產品更糟,問題更多。

感謝 Ansible 團隊,尤其是 Wayne Witzel,為改善這個列表中的準則而提出的意見和建議。

相關文章