---
# System prepended metadata

title: 程式碼品質

---

# 程式碼品質
## 程式碼品質目標
程式碼高品質或低品質其實是很主觀且決斷的事情
對於作者而言，所謂的高品質程式碼，應實現下列四項：
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，以最近經歷來說，同事設計一套系統後，由於某些方法並不會觸發時間戳記的更動，所以導致預期結果不同出現錯誤，但在自動化測試中並無法顯現，這代表程式碼並不一定可以百分百的涵蓋所有可能性。

若程式碼不可測，就不可能正確測試，所以為了確保可測，寫程式時應該隨時提醒自己『我們會如何測試它？』。

