Clean Code - 有意義的命名

👀 5 min read 👀

大家好,我是 Cindy,相信看過 Clean Code - 無瑕的程式碼 這篇文章的朋友們已經知道什麼是 Clean Code 了,接下來就會需要探討究竟要如何實現?

無瑕的程式碼-敏捷軟體開發技巧守則 (Clean Code: A Handbook of Agile Software Craftsmanship) 第二章有意義的命名,就像我之前說的最簡單最困難,相信已經身為工程師的大家們,寫個 method 是一件相當容易的事情,但寫個好的 method 名稱,似乎就不那麼容易了,本篇文章會針對書中提到的幾個重點做說明並用 Ruby 來做舉例。

讓名稱代表意圖—-使之名符其實

變數、方法或類別的名稱,它應該要告訴我們,它為什麼會在這出現、它要做什麼用、該如何使用它。如果一個名稱需要註解的輔助,那麼這個名稱就不具備展現意圖的能力。

當我們試著說出下面這段程式碼的目的,竟然是一件相當困難的事情,會出現我都看得懂,但不知道他要幹嘛的感覺。我想當我們閱讀程式碼出現這樣的想法的時候,其實就表示說這可能是一段命名不好的程式碼。

1
2
3
4
5
6
7
8
9
def get_them
list1 = []
the_list.each do |x|
if x[0] == 4
list1.push(x)
end
end
list1
end

雖然程式碼很短很簡單,但問題不在於程式碼的簡易度,而是在於程式碼的隱含性(implicity),即程式碼的上下文資訊(context)未能由程式本身明確地展現出來的程度,這邊突然想起知識的詛咒,就好像撰寫程式碼的工程師心裡知道一切,但是並沒有把心裡知道的寫出來。

我們把上面那段程式碼做一些修改:

1
2
3
4
5
6
7
8
9
def get_flagged_cells
flagged_cells = []
game_board.each do |cell|
if cell[STATUS_VALUE] == FLAGGED
flagged_cells.push(cell)
end
end
flagged_cells
end

這邊只是將程式碼的命名做修改,看起來就不太一樣了,比前一個例子清楚一些,那我們再做一些修改:

1
2
3
4
5
6
7
8
9
def get_flagged_cells
flagged_cells = []
game_board.each do |cell|
if cell.flagged?
flagged_cells.push(cell)
end
end
flagged_cells
end
1
2
3
4
5
6
7
8
9
def get_flagged_cells
flagged_cells = []
game_board.each do |cell|
next unless cell.flagged?

flagged_cells.push(cell)
end
flagged_cells
end

大家發現了嗎?這邊看起來就是有一個遊戲版,然後我們用這個方法取得在遊戲版上被標記的格子。我們只是做重新命名,就看出程式碼的目的了!

最後還可以改成下面這樣:

1
2
3
def get_flagged_cells
game_board.select(&:flagged?)
end

產生有意義的區別

如果說我們發現程式碼中出現兩個名稱是看不出區別的,那就不應該同時用這兩個名稱,例如 InfoData 是不可區分的無意義字詞,在沒有特別約定的情況下,money_accountmoneycustomer_infocustomeraccount_dataaccountthe_messagemessage 都是沒有區別的!

使用能唸出來的名稱

大家可以想像一下,如果工程師們在討論一個念不出來的方法,究竟要怎麼討論?

使用可被搜尋的名稱

假設某個數字在程式碼中有特別的意義,WORK_DAY_PER_WEEK = 5,像這樣的寫法會比直接在程式碼中用 5 去做計算來的好,因為如果我們用 WORK_DAY_PER_WEEK 去搜尋整個專案,絕對會比用 5 搜尋來得好!

類別的命名

類別和物件應該使用名詞或名詞片語來命名,類別的名稱也不應該是動詞。大家可以參考 類別(Class)與模組(Module) 這篇文章,類別其實就像是一個模子,當我們 new 出一個實體(Instance),表示為具有類別上狀態與行為的物件,這就是個名詞無誤。

方法的命名

方法應該使用動詞或動詞片語來命名。方法就是個行為,行為是動作,所以應該要是動詞。

每個概念使用一種字詞

例如 fetchretrieveget 這三個都是取得的意思,應該要統一用一種。

別說雙關語

避免使用同一個字詞來表示兩種不同的目的,例如專案中已經大量使用 add 表示相加或相連兩個現有的值,然後形成新的值,而我們今天要撰寫一個新的方法是會將單一參數放入一個集合容器中,那我們可以取名為 add 嗎?如果我們用 add 了是不是就表示整個專案裡的 add 會有兩種意思?這就是一種雙關語的表現,所以我們應該取名為 insertappend 之類的。

使用解決方案領域的命名

解決方案領域其實就是工程師領域的意思,因為在寫跟在看程式碼的人都是工程師,所以如果可以應該盡量使用工程師一看就懂的字詞,例如 JobQueue 之類的。

使用問題領域的命名

問題領域就是這個專案在做什麼領域的事情,例如如果是電商網站那就是電商的領域,如果是遊戲網站就會是遊戲的領域。如果沒有工程師熟悉的字詞可以使用的時候,或某段程式碼與問題領域的概念更接近,我們就會使用該問題領域的術語來命名。

添加有意義的上下文資訊 (Context)

例如有一個變數 state,如果單看這個變數會看不出這是什麼,如果改成 addr_state 至少可以知道這是地址的州,更好的做法是將零碎的變數給予更大的類別 Address。但記得如果小的名稱就能清楚表達的話,就不需要加入其他 context。

總結

我想從這章節可以學到很多實際上的做法,也會讓我省思過去是不是寫了不好的命名,也確實遇過書中寫到面對不好命名的窘境,雖然說程式碼是要讓電腦去執行的,但其實也是要讓人類去閱讀的,因為撰寫程式碼的也是人類,所以能夠表達清楚,是讓後面接手的人可以更好維護的方法之一。