---
# System prepended metadata

title: B0003 易讀程式碼的特性：精準

---

# B0003 易讀程式碼的特性：精準
## 精簡與準確
**「精準」** 是另一個讓程式碼易讀的重要特性。要注意的是，這裡說的精準，指的是**精簡**與**準確**。必須兩個概念都要具備，才算得上是符合這個特性。

### 精簡
**精簡**一個容易想像的概念。假設有兩段程式碼可以完成同一件事情，其中一個用了2000行，另一個用了20行，請問哪一個比較易讀呢？在大多數的情況下，**精簡**的20行會比**繁複**的2000行來得更容易掌握、更容易理解。所以說，很多時候使用較少的程式碼可以達成易讀。這真是多麼簡單的概念！

### 準確
不過，只抓住**精簡**的原則是不夠的。當我們只考慮要用較少程式碼完成目標的時候，可能會省略了關鍵程式碼。怎麼說呢？觀察[[B0001 易讀程式碼的特性：白話]]與[[B0002 易讀程式碼的特性：直觀]]提到的例子，不難發現那些完全不白話、完全不直觀的寫法，所需要的程式碼都是比較少的，但他們都是易讀的反指標。所以不能一味地追求精簡，而是在精簡程式碼的過程中，守住一個底線，一個至少能**清楚明白表達行為**的底線，稱作**準確**。

這裡雖然用白話與直觀作為舉例，但**準確**並不是白話與直觀的綜合體。仔細分辨的話，白話與直觀屬於語意上的易讀特性，而準確則是邏輯行為上的易讀特性。有時候程式碼已經白話且直觀了，卻可能邏輯行為讓人難以掌握，變得很**失焦**，也就是**不準確**。

雖說白話且直觀與準確是兩回事，但準確的程式碼通常會需要白話與直觀的支持；換句話說，一個在語意本身不易讀的程式碼，自然無法讓邏輯行為易讀。

### 精簡與準確並進
前面提到要避免精簡過頭導致不準確，但這不表示他們是敵人似地兩者只能追求一個達成。這兩者其實是兩個不同維度上的概念，一段程式碼可能既精簡又準確，也可能不精簡也不準確。要如何在這兩個概念都做好，是重要的課題。

## 做到「精準」
試想去聽一場2小時的演講，講者東聊一段爸爸的趣聞、西說一段女兒的經歷，最後用5分鐘講了他工作的心得。請問要如何解讀這場演講呢？如果講題是要聊他的爸爸（或許他爸爸是個名人），似乎不需要談到女兒及工作；如果講題是分享工作心得，這時間的比重也太少，反而覺得他的家人更像是講題；就算講題是介紹自己，也應該多花點心思在自己本身，而不是在家人、工作上。不管講題是什麼，內容是如此**不精準**，讓人很難理解講者所要表達的重點；不管演講的場地有多盛大、門票有多貴、講者多有名，這都不是一個好理解的演講。

程式碼跟演講的概念是一樣的。當一份程式碼**包含太多不需要的概念**、**著墨太多不重要的細節**、或者**保留完全不必要的行為**，肯定是不易讀的吧！必須避開這些問題，才能產出**精準的程式碼**。那要怎麼辦到呢？可以參考下面5個方法：**去蕪存菁、截彎取直、畫地自限、名符其實、換句話說**。

### 方法1：去蕪存菁
顧名思義，**去蕪存菁**著重在保留重要的、移除不需要的，以盡量做到**精簡**。建議利用下面要點來做到去蕪存菁：
* 一段程式碼如果移除掉沒有影響到行為或邏輯表達，就直接移除。
* 如果整段程式碼都難以直接移除，就設法整理成好閱讀的樣子；像是打包程式碼[^1]、或調整程式碼的順序等等。

[^1]: [C0001 不白話的程式碼：文字加密](/tdzMvnrYTrySrC5iokmOtQ)提過的**打包行為**，在這裡可以派上用場。

這看起來不難辦到，但為什麼繁複的程式碼還是很多呢？這可能還是要歸咎於惰性。從上述的要點來看，通常是在程式碼完成了之後才執行，才能比較好判斷哪邊是蕪、哪邊是菁，然後才好做移除或整理；然而因為惰性，當一份程式碼完成了、通過測試了、可以運作了，很多時候都不會再回頭看一眼，自然就停留在不理想的狀態了。

### 方法2：截彎取直
都說要每個程式碼單位（像是 Function、Class）都是盡量小的好。然而，過度地將權責做小，可能會做出過多的程式碼單位，導致做任何事情都需要透過許多單位來執行，無法簡單地完成。這是一種**過度設計**，即使是一件小事也需要複雜的手段，想當然是不**精簡**的；此外，如果這些單位的關係錯綜複雜，難以一目了然地看懂邏輯行為，也是違背了**準確**概念。

我們可以把這樣的設計稱作是**繞彎**，彷彿是有話不好好說，非要拐彎地說的樣子。要解決彎來彎去的程式碼，就要**截彎取直**，要點如下：
* 與去蕪存菁類似，可以移除的單位就移除，不要留戀。
* 尋找邏輯類似、接近的單位，將他們合併成較大的單位。不僅是可以讓使用端變單純，而被合併的單位間，也可能因為合併而簡化交互流程。

### 方法3：畫地自限
程式碼的組成是很自由的，要怎麼擺程式碼、在什麼邏輯位置完成程式碼，都是自由的。就因為自由，程式碼就容易失焦，讓人摸不清邏輯，導致**不準確**。

什麼樣的程式碼是失焦的？**放任問題產生了之後，再事後解決就是失焦**。比方說，數值不做成常數，卻要在執行的過程檢查數值不變；比方說，呼叫順序不做好限制，卻要在執行的過程檢查是否遺漏呼叫；比方說，Function（或者稱 Method）不隱藏或限制存取，卻要在執行的過程檢查是否被誤用。這些例子都代表著，程式碼顯式地（explicitly）提供了自由使用的空間，但卻隱式地（implicitly）不接受某些使用方式[^2]。

[^2]: 雖然是盡量只讓專有名詞保留用英文，但這裡的**顯式地**、**隱式地**實在難以準確表達，只好分別附註英文explicitly、implicitly。說來有趣，寫文章跟寫程式碼是同一回事，都要講求準確。

如果自由是個問題，那麼就**限制**它吧！這樣的方法稱作**畫地自限**。執行畫地自限的要點只有1項：
* 顯式地（explicitly）將**限制**寫進去程式碼。

### 方法4：名符其實
**準確**的程式碼要注重**名符其實**。所謂的**名**，指的是名稱、命名；所謂的**實**，指的是實作、權責。名與實必須要能對應上，才是準確的程式碼。舉例來說，一個名叫「機車」的程式碼，就不應該有四輪的實作；如果強迫讓機車也能支援四輪，就會失焦。相反地，四輪交通工具的實作就該名叫「汽車」，如果對兩輪、四輪都叫汽車，一樣也是失焦的。這樣的失焦會讓人摸不清邏輯，不容易理解。

要做到名符其實，可以參考以下要點[^3]：
* 賦予實作、權責一個名稱時，務必符合實作、權責本身的概念。
* 對一個實作細節命名時，不應該採取遠大於、或遠小於該名稱概念的權責。
* 如果程式碼非得擴展出遠大於或遠小於該名稱概念的權責，可以設法改名稱以符合權責；倘若改名稱是不允許的，可以另起爐灶做另一包程式碼，不要與原本名稱混雜一起。

[^3]: 附帶一提，**名符其實**不只是易讀性的問題，更是**抽象化**的基本原則之一。關於抽象化，未來會有更多介紹。

### 方法5：換句話說
一個描述方式如果不容易理解，那就換方式來講，就有機會比較**精準**了。這就是**換句話說**的概念。比如行人問路要告訴對方如何到目的地，與其提供哪一條路左轉右轉、哪個地標迴轉等等的說了一堆，不如提供搭某一班直達公車的方法來得簡潔有效。當解決方案拘泥在「步行」上，不管怎麼處理都有一定的複雜度，但如果把「大眾交通工具」納入辦法中，就能找到更精簡與準確的辦法。程式碼也是一樣的，當拘泥在某個假設、前提、設計下，可能會寫得很囉唆，但跳脫出來換個假設、前提、設計，就有機會做得更精準。

這是所有讓程式碼精準的方法中，最難的一個了。因為每一個考慮**換句話說**的程式碼，所處的情境幾乎是不可能一模一樣，或許要改變 Data Structure、或許要調整 Design Pattern、或許要調整行為規格、等等；而且考慮的範圍，可能小至最枝微末節的程式碼，也可能大至整體的軟體架構。也就是說，題目變化可能無限多種，而方法、考量也可能是無限多種，完全不存在一個明確的方法或步驟可以遵循。要**換句話說**得好，只有靠大量的知識與經驗，才能辦到。因此，原則上是沒有要點可以參考的。如果一定要勉強提供一個呢？大概只能這樣說：
* 想想有沒有跳脫當前設計的做法吧！

## 言之有物、簡單扼要、不說廢話
回顧讓程式碼白話與直觀，會發現方法是單純的；相較之下，讓程式碼精準的方法更為多元、靈活。所以說，理論上，應該不只有上述提到的觀念與方法才是。我們可以仔細觀察這些方法，歸納出更通用的原則，才不至於拘泥於方法本身；而這個原則就是 **「言之有物、簡單扼要、不說廢話」**。嚴格來說，即使不記得上述方法，只要能掌握這些原則，一樣可以做到精準的程式碼！

## 延伸閱讀
C0006 精準程式碼：去蕪存菁 （撰寫中）
C0007 精準程式碼：截彎取直 （撰寫中）
C0008 精準程式碼：畫地自限 （撰寫中）
C0009 精準程式碼：名符其實 （撰寫中）
C0010 精準程式碼：換句話說 （撰寫中）
