今天的主題會參考這本非常有名的書 Clean Code。
寫程式到最後,除了最基本的,商業功能要能正常運作以外,其實大部分的時候都是在追求,如何讓 code 好讀、好維護、好重構,而這也是我這個系列文的核心目標。
而這本書也是朝向這個目標,是需要對自己有要求,期待朝向「更好」的 developer 邁進。因此,今天來好好討論,有哪些小細節可以優化。
名字就是認識這個變數最快的途徑,只要名字取得好,光是把所有變數看過一次,就可以大致了解這段程式的目的。
Naming 一直都不是一件容易的事情,因為英文真的不好,有時候要表達一個抽象的概念時,也不容易用英文去形容,而且命名即便取不好,程式仍然可以正常執行,所以就更不容易好好看待這件事。
命名的最高指導原則:好猜
沒錯就是這麼簡單兩個字!我都做不到QQ
無論是變數命名或函式命名,都要遵循這個原則,更精確來說,是要讓人「容易理解意圖」,那要怎麼衡量有沒有符合這個原則呢?
基本上在 Day 7 的時候討論過一次了,這邊簡單複習一下:
很多時候程式寫得正火熱(?),邏輯已經想到第 30 步了,但連第 1 個變數都還沒想好名字,很容易就隨便給了個「好像有意義但其實沒意義」的命名,大致分成下列幾類:
data
、element
、value
、temp
、item
:完全不知道是什麼num
、arr
、list
、func
:只看得出變數類型i
、index
:十分氾濫是不是很常見?
沒錯我在鐵人賽趕稿的時候,就是這個死樣XD:
const arr = [
{ id: 'item1', name: 'TV', price: 13500 },
{ id: 'item2', name: 'washing machine', price: 8200 },
{ id: 'item3', name: 'laptop', price: 25000 }
];
const displayArr = arr.map(item => item.name).join('、');
但如果是在那種意圖單純、用一次就結束的 scope 內,還算是比較可以接受(好啦是我自己可以接受啦QQ),比如上例第 6 行的 item
:
const displayArr = arr.map(item => item.name).join('、');
item
只有在 map
function 內的 scope 使用,所以只要使用一次的情況下,其實本身就比較容易理解,很多時候我甚至會直接用物件解構賦值,省掉一次命名:
const displayArr = arr.map(({ name }) => name).join('、');
要改進的話,array 的變數可以在後面加個 s
或 list
複數型來表示,這樣如果跑 map
,裡面的 iterator 就可以變成單數型:
const products = [
{ id: 'item1', name: 'TV', price: 13500 },
{ id: 'item2', name: 'washing machine', price: 8200 },
{ id: 'item3', name: 'laptop', price: 25000 }
];
const displayArr = products.map(product => product.name).join('、');
但規則盡量是統一的,不要偶爾
s
偶爾list
註解也是寫在程式碼裡面的一部分,只是不會被執行,那註解跟一般的程式語法比起來,差別、用意究竟是什麼?
程式語法是寫給電腦看的,而註解是寫給人看的。
當這個區分出來之後,我們的問題就更清晰了一點:
如果註解是寫給人看的,那人們期待在註解上面看到什麼?
畢竟語法是寫給電腦看的,很多時候在不同的情境下,我們其實想表達不同的意思,但語法就是那幾個 if
、for
、switch
在排列組合,有點像是只用 500 個英文單字過生活一樣(?)
因此,很自然就會衍生出,使用註解來解釋這段 code 的意圖,這個用意。
這一點雖然很直覺,但別忘記,變數名稱也是可以自由命名的,所以其實不是只有 500 個單字能用,你仍然可以用變數名稱來解釋變數的意圖,用函式名稱來解釋整段 code 的意圖。
正是基於這一點,網路上看到有些 developer 對於「把註解拿來解釋程式意圖」這件事,是抱有稍微貶意的,因為他們認為「就是因為你變數命名沒做好,才會需要仰賴註解」。
關於這種批評我是覺得大可不必,原因如下:
不可否認大部分的時候,JavaScript 的新手的確是因為對於命名不夠熟悉,或者單純就是函式拆得不夠細微,再加上可能詞彙量有限(比如只會用 if
/else
不會用 switch
),所以呈現出來的程式也比較冗雜與單一。
const color = 'red';
let colorCode;
// 根據 color 對應的顏色轉成色碼
if (color === 'blue') {
colorCode = '#0000FF';
} else if (color === 'green') {
colorCode = '#00FF00';
} else if (color === 'red') {
colorCode = '#FF0000';
}
console.log(colorCode); // '#FF0000'
比較適合改成:
const color = 'red';
let colorCode;
switch (color) {
case 'blue':
colorCode = '#0000FF';
break;
case 'green':
colorCode = '#00FF00';
break;
case 'red':
colorCode = '#FF0000';
break;
}
console.log(colorCode); // '#FF0000'
但基本上肯學習的新手都會自己找學習資源,如果這時候在網路上又看到有人說:「啊寫註解就是因為菜」,有些人會不小心得到一個結論:即便不是很擅長命名,也不要去寫註解。
當然我沒有要一竿子打翻一船人,也是會有人因此而認真去練習命名,減少註解的數量。
但我個人比較傾向從鼓勵、優化的角度來看這件事,畢竟程式語言也是語言的一種,沒有人剛開始學語言就很溜的。
因為變數命名都是用英文,所以外國人當然會覺得不用寫註解啊((誤
其實最主要還是因為,英文跟中文有一些概念沒有對應的詞彙,所以要對應就沒那麼方便。
再加上,台灣人天生看中文就比較快啊,一些比較難懂的概念,使用熟悉的中文來寫註解,會比用不熟的英文來幫變數命名,還容易理解。
如果是站在學習的角度,的確可以自己好好練習在沒有註解的情況下,用命名來解釋 code;但公司不是請人來學習的,真的用英文表達不清的情況下,請直接寫中文註解,下一個接手的人會感謝你的!
通常命名取得好,大多也是在解釋「這個變數」或者「這個函式」所做的事情,很像是在幫每一片拼圖命名,但很多片拼圖拼出來的一個局部,整體在做什麼,就很難用命名來解決。
比如下面這段程式碼:
const studentList = [
{ name: 'Joey', grade: 25 },
{ name: 'Susan', grade: 49 },
{ name: 'Allen', grade: 64 },
{ name: 'Alice', grade: 100 }
];
const passList = [];
studentList.forEach(student => {
const adjustedScore = Math.sqrt(student.grade) * 10;
const isHigherThan60 = adjustedScore > 60;
if (isHigherThan60) {
passList.push(student);
}
});
const displayPassList = passList.map(student => student.name).join('、');
console.log(`恭喜以下同學:${displayPassList}`);
執行結果
恭喜以下同學:Susan、Allen、Alice
如果多了註解來說明就會清楚得多:
const studentList = [
{ name: 'Joey', grade: 25 },
{ name: 'Susan', grade: 49 },
{ name: 'Allen', grade: 64 },
{ name: 'Alice', grade: 100 }
];
const passList = [];
// 計算每個學生調分後是否及格,並列出及格名單
studentList.forEach(student => {
const adjustedScore = Math.sqrt(student.grade) * 10;
const isHigherThan60 = adjustedScore > 60;
if (isHigherThan60) {
passList.push(student);
}
});
const displayPassList = passList.map(student => student.name).join('、');
console.log(`恭喜以下同學:${displayPassList}`);
今天講的都是偏向變數命名、註解寫法,這系列的討論其實都是很小細節的東西,但如果真的有做好,看程式碼也能夠讓人感到如沐春風!
期許每天都在每個細節上更進步一點,每天多懂一點,才趕得上忘記的速度((誤
名字的背後
藏著淺顯的暗示
與深層的靈魂
就是因為你變數命名沒做好,才會需要仰賴註解
我也是不太認同這句話,從團隊合作的立場,我們需要盡可能地讓團隊中的每一個人能夠快速理解程式碼的內容。另一方面,不好的變數命名如果沒有辦法在 code review 階段抓出的話,那麼也是團隊共同的問題,不是個人的問題。