# 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 精準程式碼:換句話說 (撰寫中)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up