# 程式碼品質 ## 程式碼品質目標 程式碼高品質或低品質其實是很主觀且決斷的事情 對於作者而言,所謂的高品質程式碼,應實現下列四項: 1. 能**運作** 2. 能**持續**運作 3. 能適應持續**變化**的需求 4. **不用重新造輪子** --- ### 能運作: 所謂的能運作是指,當需求出現時(開發新功能或者修復),程式碼可以確保目標符合預期結果(解決問題),而其中『運作』指的是捕捉所有需求,書中舉例若是一項功能牽涉到效能問題時,除了預期結果達成,也就必須把效能問題考慮進來。 ### 能持續運作 『程式碼什麼時候不能持續運作?』 『難道今天運作明天就不運作了嗎?』 其中的關鍵在於**程式碼不是獨立存在的**,所以隨著環境的改變而可能中斷運作! * **程式碼彼此依賴**,而這些程式碼都會面臨更動的可能 * 新功能的出現,可能造成程式碼的修改 * 當下程式碼會隨著商業邏輯或者技術考量而產生變化 ### 持續適應需求 對於軟體開發而言,這是一個棘手的問題,原因在於我們知道軟體勢必變化,卻又不確定如何變化。但這並不代表要忽略這樣的問題 兩種極端場景: ##### 嘗試準確預測: 長時間的投入開發,確保每個程式碼細節,並且預測潛在需求。 隨著時間過去,對手已經進入市場,而且當初預測的需求,或許根本是錯誤的.... ##### 忽略演變事實: 只滿足於現況,不額外多做程式碼適應性的調整。 由於程式碼沒有適應性,所以產品內含有許多脆弱假設,所有的小問題都被整個架構綑綁。當推出商品後,使用者給予的反饋或許變化需求不大,但因為程式碼不含適應性,所以需要**重新架構**。 對於這兩者極端狀況來說,開發需要在之間取得平衡,而平衡點會隨著專案類型和公司的組織文化來決定。(之後會介紹各種技術來找到平衡點) ### 不用重新造輪子 在一個大功能之中,會拆分成許多細節來進行處理。而往往這些細節在其他功能之中可能也有使用到,所以這時候就不需要額外開發,而是**重複使用**。 * 節省時間和精力 重複使用比重複開發來得更有效率。 * 降低出錯機會 已存在使用的程式碼,通常經過徹底測試(完整性高),出錯機率降低,若是有問題也可以很快發現,並且修復。 * 運用現有的專業知識 當初開發細項的工程師,或許已經熟悉程式碼運作方式,若是需要更新修改,相對人員也可以快速處理完成。 * 讓程式碼更容易理解 以往的設計方式,或許已經讓其他工程師熟悉(可能是風格之類的),若是自己在開發一項細則,則需要時間讓其他人理解,而舊有功能則更快理解。 ## 程式碼品質支柱 前述目標其實是開發功能的大方向,而相關細則實踐,在此稱為支柱,而有其六項: 1. 可讀性 2. 避免意外驚訝 3. 不會被誤用 4. 模組化 5. 可重用(reuse)和可泛化(generalizable) 6. 可測試(testable)且正確測試 ### 可讀性: 一段程式碼應該要可以回答以下問題 * 能做什麼? * 怎麼做到的? * 需要什麼?(輸入或狀態) * 執行後可以得到什麼? 在一開始的PR時,或許會經過Code Review的流程,在這過程中需要確保另一位工程師理解程式碼內容。又或者是在開發過程中需要使用到相關功能,這時候也需要經過審視理解後,才可以使用。 若是可讀性造成問題,反而會讓其他工程師花費時間理解內容,或者沒有正確理解而導致誤用或錯過細節。 ### 避免意外驚訝: 一段程式碼在眼前時,會經由邏輯與命名對工程師建立一定的心理認知模型,若是程式碼跳脫我們的認知模型,就可能造成錯誤在軟體中蔓延。 就像是一個方法的使用,在過程中都輸入正確內容資訊,但沒有對其結果驗證是否正確,這時會讓這錯誤值在軟體中流竄,最後就會產出意想不到的結果。 作者例子是,當投入瑪格麗特時,我們預期是披薩,但送來的卻是調酒... ### 不會被誤用: 程式碼間會彼此呼叫使用,若是使用方式錯誤,或投入錯誤的參數,可能造成程式當掉、系統崩潰、資料庫損壞或者資料丟失等災難。 ### 模組化: 模組化的原因在於拆分後易於調整。 ![](https://i.imgur.com/S3OVzJQ.png) 如這兩張圖,左邊模組化高(可拆分),而右邊模組化低(難拆分),若是今天需求改變,要將尾巴修改成含有三角形圖案時,左邊會比右邊來的容易。 如同程式碼之間的關係,使用模組化區分後,利用良好的介面定義,來讓互相交流。而在修改過程中,只需要專注開發特定部分,不需要害怕其他程式碼遭受影響,意味著不用進行大量修改。 此外,**模組化也更容易理解與推理**,因為功能分解成可管理的區塊,而且彼此間的交流也有良好的定義(良好介面)。增加程式碼可運作且在未來可持續運作的機會。 ### 可重用(reuse)和可泛化(generalizable): * 可重用性(reusability) 同個程式碼,可以適用於多種不同情況。 作者以電鑽作為譬喻,電鑽可以對不同的材質(牆壁或者天花板等)進行鑽孔。而問題都是一樣的(都要鑽一個洞),但場景不同(對牆壁或者天花板)。 * 可泛化性(generalizability) 同個程式碼,可以解決概念相似的情況,但情況略有不同。 同樣的,電鑽有很多功能,不僅可以拿來鑽孔還可以拿來栓螺絲,所以只要符合旋轉這個動作的需求,都可以從電鑽中獲得。 程式碼是需要許多成本來建立,而建立後則需要維護,這意味著越多的程式碼需要顧及的範圍越多,而且出錯機率也會增加,因此應該用少量的程式碼,來完成多種需求,所以才需要可重用性與可泛化性。 承接上個觀念,**程式碼越多模組化,也更容易達成這兩著概念**。 ### 可測試(testable)且正確測試: 測試是開發流程內兩個關鍵點的主要防禦機制 * 避免錯誤或損壞的功能提交到程式碼庫 * 確保具有錯誤或損壞功能的版本會被阻止並且不會釋出 已經有太多書籍強調測試的重要性,而作者在此強調兩個觀點 * 軟體系統龐大且複雜,一個人不可能全盤了解細節 * 人,都會犯錯 程式碼品質支柱有兩項概念:**測試(testing)** 與 **可測性(testability)**。這兩者讓程式碼『可測試』與『正確測試』 * 測試 可以分成手動與自動,而身為工程師當然是寫測試來進行,而這樣的方式可以讓測試自動化。最簡單的分類如下 * 單元測試(Unit tests) 測試小型程式碼單元,例如單個函示或者類別。 * 整合測試(Integration tests) 系統通常由多個元件、模組或子系統組成。整個元件和子系統連接稱為整合,而此測試就是要**確保連接整後和還可以正確且持續運作**。 * 端對端測試(End-to-end tests) 此測試會從頭到尾的**貫穿整套軟體系統(工作流程)**,例如測試使用者的購買流程測試。 * 可測試性 被測試的程式碼有一定的測試程度。通常**越高模組化**的系統,則越容易被測試。有的程式碼在本地時,可以正常運作,且自動化測試中也沒有任何問題,但實際產品運作時,則可能會出現出乎意料的Bug,以最近經歷來說,同事設計一套系統後,由於某些方法並不會觸發時間戳記的更動,所以導致預期結果不同出現錯誤,但在自動化測試中並無法顯現,這代表程式碼並不一定可以百分百的涵蓋所有可能性。 若程式碼不可測,就不可能正確測試,所以為了確保可測,寫程式時應該隨時提醒自己『我們會如何測試它?』。