程式碼可讀性介紹 vol. 2 : “命名與註釋” 篇

前言

大家好,我是通訊 App「LINE」Android 團隊的石川,本文是「程式碼可讀性介紹」系列連載的第二篇,介紹本系列的第二章與第三章:於程式中撰寫的自然語言「命名」與「註釋」原則。

本系列的上一篇文章為程式碼可讀性介紹 vol. 1 : “導入與原則” 篇

第二章:命名

撰寫程式時,需要為 class 與 resource 等命名。命名的名稱若符合正確、明確、易於描述等條件,程式碼將變得更為易讀。本文針對什麼樣的命名可讓程式碼容易閱讀,把焦點特別放在 type (class、interface、trait 等)、value (變數、欄位、參數等),以及 procedure (function、method、subroutine 等),進行解說。但本文所解說的內容僅為一般原則,如果命名規則已經針對使用語言、平台或專案而規定,則請優先以該規定為準。

此處列舉下列三項命名重點:

  1. 名稱表達的內容
  2. 文法
  3. 詞彙的選擇

1. 名稱表達的內容

名稱應能表達命名對象 (type、value、procedure) 「是什麼 / 做什麼」,換言之,名稱不需要表達命名對象什麼時候使用 / 在哪裡使用 / 如何使用 / 由誰使用。例如,寫「收到新訊息數據時,顯示該內容」的程序時,比起著重在「收到新訊息數據時」,著重「顯示內容」應該更好。也就是說,在這種情況下,命名「showMessageText」 會比「onNewMessage」來得好。

將呈現名稱的內容,設定為「是什麼 / 做什麼」的好處之一,是可讓命名對象的責任明確化。被命名為「showMesasgeText」時,就表示被賦予至參數的 MessageData 不應該被用於顯示以外的目的。但是,若命名為「onNewMessage」時,該 MessageData 即使被用在其他目的,應該也不會覺得奇怪。若責任的界線模糊不清,就會導致重複操作的程序錯誤,或過度複雜的依賴關係。

但是,這個原則也有例外。被定義為 callback 的抽象方法 (Abstract Method) 等,在宣告時,有可能還沒決定要做什麼。此時,就有必要以「onNewMessage」等的「在什麼時候 / 哪裡被呼叫」的資訊來命名。但就算是抽象方法,若目的明確,就應該以「做什麼」來命名。

2. 文法

名稱不單是由詞句的排列組合所構成,而是依照接近英文文法的規則,以語順和語形來決定。在多數情況下,value 和 type 使用名詞或名詞句型,程序則使用祈使句型 (以動詞開頭)。例如,「已收到訊息的文字」這個 value 可用「receivedMessageText」這個名詞句來表達,「儲存已收到的訊息」程序則可用「saveReceivedMessage」這個祈使句來表達。

依照程式語言的差異,其他命名亦使用形容詞、形容詞句、第三人稱形式的動詞 / 助動詞,以及使用上述規則之疑問句、包含介詞的副詞句等。例如,形容詞 / 形容詞句用以表達性質或狀態的 type 與 value (例如:「Iterable」、「FINISHED」)、第三人稱形式的動詞 / 助動詞,並使用上述疑問句,用以表達真值的 value 與程序 (例如:「contains」、「isTextVisible」)、包含介詞的副詞句用以表達 type 變換與 callback 的程序 (例如:「toInt」、「onFinished」)。

若不依據文法命名,則可能使該名稱受到誤解。例如,click event 的 listener 就應該命名為「ClickEventListener」,但若命名為「ListnerClickEvent」,則可能被解釋為「Listener 這個 UI 上的 click event」或「Listener 所發佈的 click event」。為了避免這樣的狀況發生,名詞句的最後單字應該是最重要的單字,祈使句的第一個單字則應該是表示該程序的動詞。

3. 詞彙的選擇

選擇名稱中所使用的單字時,重點有:「選擇不會模擬兩可的單字」、「避免使用令人混淆的縮寫」、「明確表達數量單位」、「選擇肯定性的單詞」。以下針對「選擇不會模擬兩可的單字」與 「避免使用令人混淆的縮寫」兩點,進行解說。

3-A: 選擇不會模擬兩可的單字

選擇組成名稱的單字時,應該採用資訊量較多的單字。例如,「checkMessage」可能會被解讀為:要檢查訊息是否存在、檢查格式是否正確、詢問伺服器是否有新訊息,或是要過濾滿足某些條件的訊息。而上述各種情況,應該分別命名為「existsMessage」、「isMessageFormatValid」、「queryNewMessage…」、「takeMessageIf…」等。而回到本段開頭的論點,為了尋找資訊量較多的單字,可參考字典或同義字辭典,以找出最適合的單字。

3-B: 避免使用令人混淆的縮寫

若在名稱中使用縮寫,將容易使閱讀程式碼的負擔變大。從縮寫中讀取意義時,是擷取記憶多於認知,但擷取記憶會對思考造成龐大的負擔。也就是說,從「instanceManager」這個名稱來理解名稱的意義,會比看到「im」這個名稱時回想其定義簡單。當然,如果是採用已經廣為人知的縮寫就沒有問題。例如,「TCP」或「URL」等,也許不知道其完整名稱的人反而比較多。另外,「string」的「str」這類易於理解的縮寫,也許可在有限的範圍內使用。另一方面,定義專案中獨有的縮寫時,可能需要使用該縮寫的命名。在此情況下,最好利用註釋向新成員解說,或準備用語集,以幫助新成員理解該縮寫。

第三章:註釋

複雜的程式碼或龐大的程式碼等,難以透過直覺理解的程式碼,可以藉由撰寫註釋來協助解讀者理解。而在撰寫註釋的過程中,也可能會想到使程式碼更加易於閱讀的點子。反過來說,已經很容易理解的程式碼,或許也可依據 coding convention,省略註釋。本章將解說什麼樣的程式碼應該寫什麼樣的註釋。

除工具用途等特殊註釋以外,註釋可分類為 documentation 與 inline comment。(在某些情況下,block comment 及 multiline comment 會明確與 inline comment 分開定義,但此處為了讓介紹單純化,暫時忽略其差異。)

1. Documentation

Documentation 是指寫在 type、value、procedure 等的宣告或定義中,依照特定形式的註釋。藉由查看 documentation,不須看程式碼的細節或 reference source,便可了解規格。Documentation 需要摘要,若有必要,則加上詳細得說明或「@return」等標籤。

1-A: 摘要

Documentation 的開頭,必須為簡單描述對象「是什麼 / 做什麼」的摘要。依 coding convention 的差異,摘要不一定要是完整的句子,而改採用經省略的字句來寫。例如:type 或 value 的摘要,使用省略名詞句的字句 ; 而程序的摘要,則使用省略第三人稱主詞的字句。如果 coding convention 中未定義摘要的形式,則依照該語言的標準函式庫 (Standard Library) 即可。說個題外話,在撰寫本文時,也有注意到要以各段落開頭的字句,作為主要資訊。

1-B: 詳細內容

如果有無法以摘要完整說明的事項,便可用更為詳盡的字句來說明。例如,有時須針對補充規格或典型的範例、return value、限制與例外等,進行補充說明。比如說,有個同時具有 return value 與 side effect 的函數,在這種情況下,函數的名稱與 documentation 的摘要,大多是將焦點放在說明 size effect 的部分。若此時 return value 為 true / false,那麼該何時為真,將變得很難懂。在這種情況下,加上說明 return value 的說明,應該可以讓程式碼的規格變得容易理解。

2. Inline comment

Inline comment 是用來摘要其週邊的程式碼及補充說明的註釋,具有輔助閱讀程式碼的功用。與 documentation 不同,摘要並非必須。此類註釋是用於分割大量程式碼,或說明非直覺性的程式碼等。

2-A: 分割大量程式碼

出現大量程式碼時,可藉由使用空行與 inline comment 整理程式碼,將使程式碼的流程由上而下,變得容易理解。這個註釋內容為程式碼的概要,能呈現該程式碼「做什麼 / 是什麼」即可。

2-B: 說明非直覺性的程式碼

有些程式碼乍看之下不知作用為何 (例如:為了迴避函式庫錯誤而撰寫的程式碼等),若寫下該程式碼存在的理由,將使其變得更容易理解。此種註釋的撰寫基準,以「誰在未來可能修正錯誤 / 重構 (refactoring)」來判斷即可。

結語

本文為「命名與註釋」篇,介紹程式中撰寫的自然語言「命名」與「註釋」原則。

下一篇文章將介紹第四章與第五章的內容:「狀態與程序」篇,請持續鎖定本系列文章。