無瑕的程式碼 (2):有意義的命名

Evan Tsai
嗨,世界
Published in
9 min readMay 20, 2020

--

There are only two hard things in Computer Science: cache invalidation and naming things.

~~~~ Phil Karlton

前言

無論變數、函式、類別、參數、檔案、套件,由於寫扣過程中會不斷進行命名這個動作,因此精進命名技能對於程式碼品質的提升會有明顯的助益。本章討論好的命名有哪些原則,以及這些原則所能帶來的效益。

Photo by Ben White on Unsplash

讓名稱代表意圖

事物的名稱會大幅影響閱讀理解的效率,最好的命名要能從名稱中取得程式碼的重要資訊。無論是變數、函式、或類別名稱,它應該要傳達以下資訊

  1. 為什麼在這
  2. 它做什麼的
  3. 如何使用它

盡量透過命名取代掉多餘的註解,因為一個名稱若還需要註解,那麼這個名稱就不具備展現意圖的能力。編寫程式當中若發現有更好的命名時,就應該替換掉原本的名稱,長期的效益下,往往省下來的時間會比花掉的還多。

// bad
int h; //working hours
// good
int workingHoursInDays;

避免誤導

閱讀程式碼,若把某一個變數或函式的意圖搞錯,會造成時間的浪費甚至給閱讀者額外的壓力。編寫程式碼中,應該避開使用那些與原意圖相違背的常見字。

比如:

  1. hp、aix、sco 是 Unix 平台的名稱,若把三角形斜邊 (hypotenuse) 的程式縮寫成 hp 可能會誤導原意。
  2. 某些字對工程師有特定意義,可能會導致錯誤的認知。舉例來說在 Java 下變數名稱單純的陣列如果叫做 AccountList,可能會被誤以為是 List 型別。當然這個例子還有另一個問題就是不應該把變數型別放到名稱內。
  3. 如果名稱只有一點點不同就需要特別小心。ex. ControllerForEfficientHandlingOfStrings vs ControllerForEfficientsStorageOfStrings。
  4. 小寫 l 與大寫 O,他們很容易跟數字 0 與 1 搞混,所以避免使用 lOl 、l1 這類的變數名稱。
[討論]還有一種情況就是不同程式語言同樣的字詞但指的卻是不同東西: 比如說在 Rails 裡面講的 hash 以及處理網址的 hash。遇到這種情況時需要先把溝通的環境闡釋清楚,才能減低誤會發生的狀況。

產生有意義的區別

對相近類型或意圖的變數或函式進行命名時,除了有意義外還需要能做出區別,才能讓讀者比較輕鬆的辨識變數之間的不同之處。

使用代表意圖的單字

public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}

以此範例來說,懶得想變數名稱時,函式的參數隨便取個 a1, a2 或是 a, b,如果是比較簡單的函式當然不會太難理解。但假設今天函式內還包含更多其他變數或類別名稱,然後每個都是 a1, b, cc,我們要如何區別?比較好的方式是找出每個變數存在的理由並以此命名,以此例來說 a1 是來源字元陣列,可以叫做 source,a2 是目標字元陣列,那不妨叫做 destination。

避免無法區別的雜訊字

Product / ProductInfo / ProductData / theProduct 有什麼差別?Info / Data 就是所謂的雜訊字,變數基本上就是用來儲存 info / data 的所以多加這樣的字大多時候無法讓變數名稱本身有更好的解釋能力。

同理 variable 不應該出現在變數名稱裡 & table 不應該出現在表格名稱裡

[討論]1. 一種狀況是,兩個變數代表的是幾乎一樣的東西,只是一個從函式外面傳入的參數,一個是函式內部處理用的變數,此時傳進來的參數可以用類似前述的 source+variable_name 來命名,內部變數則需要思考用圖,如果是要產生回傳值用的可以用 result+variable_name 來做個區別
2. table 當然是看場合使用,如 Database 的 schema 全都是是 table ,這時候大家都加上 table 跟沒有一樣,所以就不需要加上去。但如果一個場域有 table 又有 chair 這樣的命名就可以做出區別。
3. 命名/寫扣最高境界就是言簡意賅,也許我們都應該讀讀海明威的小說。 https://www.enchantingmarketing.com/write-like-hemingway/

使用完整單字/有意義的字詞

寫程式是一個人與人社交的活動,所以名稱能不能讓一般讀者發音以及直接理解相當重要,不要考驗讀者的英文能力。對母語非英文的我們更是如此,無法發音或是非完整單字,我們都需要另外花時間去想原來的單字是什麼,然後在對應回我們自己的母語,那麼何不一開始就使用完整的精確的名稱?

// bad
private Date genymdhms;
//good
private Date generatedTimestamp;
[討論]const obj = {}; Q: 用 obj 作為 object 的縮寫是好的命名嗎?
A: 為何需要縮寫?除非 object 是系統關鍵字不能使用,不然還是希望使用完整單字。再來是否有筆 object 更能精確描述當下變數使用目的的字詞?如果這個變數名稱就是指一個很泛用的物件那使用 object 完全沒問題,如果指的其實是一台車的屬性,那就叫做 car。
onChange={(e) => {...}} Q: 用 e 作為 event 的縮寫是好的命名嗎
A: 這裡的變數命名牽扯到一些慣例,如果作為網頁的 event handler 系統一定會回傳一個 Event 的物件,慣例上大家都用 e 作為 event,如果是在大家幾乎都能理解的狀況下使用這種縮寫不會增加認知負擔。但當然還是建議可以使用全名 event。
.btn {
display: inline-block;
}
Q:// 按鈕的命名用 button 還是 btn?
A: 除非我們都是 native speaker,不然能不縮寫就不縮寫,縮寫除了讀者還需要展開單字之外,命名上的問題還有可能因為每個人的縮寫不同造成 這裡是 btn 那裡變成 bttn 之類的奇妙現象,所以能用完整的全名就用全名。
[討論]
思維的轉換,不僅會造成自己額外的時間成本,也會增加團隊成員的溝通成本

使用可被搜尋的名字

命名的長度應該與其視野 ( scope ) 的大小相對應

全域常數或變數因為本身特性關係,在程式中都可以被使用到,最好給予一個容易搜尋到的名稱,盡量不要使用單一字母或是大家都會用的字來做命名,它們無法容易地在檔案中被搜尋。另一方面當一個變數只有在非常小的範圍內比如說一個小型迴圈內使用時,我們幾乎不可能去搜尋這種變數,也因此不用考慮可搜尋性。

for (int j=0; j<34; j++) {
s += (t[j]*4)/5;
}

相較於下者,何者比較容易理解呢?

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;
}

避免編碼

早期使用的 “匈牙利標誌法” 名稱的第一個字母編入資料型態,可能會造成現在的不方便,因現在的 IDE 提供強大功能能協助記憶加上偵測型態。加上改變型態時需要重新命名,若沒有會使程式更難理解。

[討論] 已知底層的程式語言還是會使用到。

類別的命名

應該使用名詞來命名且不應該是動詞,避免使用 manager, processor, data, info

方法的命名

應該使用動詞, 根據 javabean 的標準,取出應使用 get當字首、修改 set 、判定 is。

[討論]
在 ruby 下有另一套規則,取出修改直接使用 attribute accessor,判定使用 ?
https://rubystyle.guide/#bool-methods-prefix

每個概念使用一種字詞

替單一抽象概念選一個字詞,並堅持持續使用它。

若不同類別的取得方法採用 fetch、retrieve 和 get 就會使讀者造成困擾

[討論]講起來簡單做起來宇宙困難的事,對於 domain keyword 大家要有個共通的詞庫會比較好達到,對於技術層面的 keyword 也會需要一個統一的 guideline 來處理。

別說雙關語

避免同一個字詞在不同地方代表不同的目的/行為模式。

相似類別的 add 函式,其行為應該要類似,例如說不要一個類別下的 add 是 mutable 另一個是 immutable 這會造成使用上的困擾。

使用解決方案領域的命名

盡量使用電腦科學領域的術語,經常使用問題領域的詞彙來命名是不明智的作法。

Ex. AccountVisitor vs JobQueue , AccountVisitor(帳戶造訪者) 對於熟悉 Visitor模式的程式設計師來說是有意義的,JobQueue(工作序列) 哪個工程師不知道?

若無法使用程式設計師熟悉的術語,那就使用問題領域的語術吧…

[討論]對於 feature team 來說,我們的解決方案領域其實就是商業領域,所以命名時還是需要知道商業邏輯。

足夠的上下文資訊

一個單字不夠,你可以用兩個

有些時候僅用單字命名會容易混淆,此時可以多加一些資訊來讓名稱更有描述力。ex. state 在英文裡有多種意義,如果今天我們想要講的是地址下的州,命名上可以叫做 addressState,就可以避免被誤解。

結語

命名是門藝術,除了上述提到的原則之外,也盡量試著與團隊成員或是其他可能碰到程式碼的人交流,看看怎樣的名稱是大家都可以接受可以看得懂的。命名不可能一次就完美,因此可能需要後續修改,但只要測試要做好,就不怕未來改名字時會讓系統壞掉了。

--

--