iT邦幫忙

2021 iThome 鐵人賽

DAY 26
1
Modern Web

Javascript 從寫對到寫好系列 第 26

Day 26 - Clean Code 邁向更好讀、好維護的程式

前言

今天的主題會參考這本非常有名的書 Clean Code

寫程式到最後,除了最基本的,商業功能要能正常運作以外,其實大部分的時候都是在追求,如何讓 code 好讀、好維護、好重構,而這也是我這個系列文的核心目標。

而這本書也是朝向這個目標,是需要對自己有要求,期待朝向「更好」的 developer 邁進。因此,今天來好好討論,有哪些小細節可以優化。

命名的藝術

名字就是認識這個變數最快的途徑,只要名字取得好,光是把所有變數看過一次,就可以大致了解這段程式的目的。

Naming 一直都不是一件容易的事情,因為英文真的不好,有時候要表達一個抽象的概念時,也不容易用英文去形容,而且命名即便取不好,程式仍然可以正常執行,所以就更不容易好好看待這件事

命名的最高指導原則:好猜

沒錯就是這麼簡單兩個字!我都做不到QQ

無論是變數命名或函式命名,都要遵循這個原則,更精確來說,是要讓人「容易理解意圖」,那要怎麼衡量有沒有符合這個原則呢?

  • 找同事來看一遍你的 code(並計算他每分鐘說 WTF 的次數)
  • 三個月後再看一次還要看得懂

Clean Code

命名的要領

基本上在 Day 7 的時候討論過一次了,這邊簡單複習一下:

  • 駝峰式命名(camelCase)
  • 函式命名要用:動詞 + 名詞,不同動詞用於不同意義

常見但意義不明的命名

很多時候程式寫得正火熱(?),邏輯已經想到第 30 步了,但連第 1 個變數都還沒想好名字,很容易就隨便給了個「好像有意義但其實沒意義」的命名,大致分成下列幾類:

  • dataelementvaluetempitem:完全不知道是什麼
  • numarrlistfunc:只看得出變數類型
  • iindex:十分氾濫

是不是很常見?

沒錯我在鐵人賽趕稿的時候,就是這個死樣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 的變數可以在後面加個 slist 複數型來表示,這樣如果跑 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

註解的用意

註解也是寫在程式碼裡面的一部分,只是不會被執行,那註解跟一般的程式語法比起來,差別、用意究竟是什麼?

程式語法是寫給電腦看的,而註解是寫給人看的。

當這個區分出來之後,我們的問題就更清晰了一點:

如果註解是寫給人看的,那人們期待在註解上面看到什麼?

註解用來解釋意圖

畢竟語法是寫給電腦看的,很多時候在不同的情境下,我們其實想表達不同的意思,但語法就是那幾個 ifforswitch 在排列組合,有點像是只用 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}`);

結語

今天講的都是偏向變數命名、註解寫法,這系列的討論其實都是很小細節的東西,但如果真的有做好,看程式碼也能夠讓人感到如沐春風!

期許每天都在每個細節上更進步一點,每天多懂一點,才趕得上忘記的速度((誤

名字的背後
藏著淺顯的暗示
與深層的靈魂

參考資料

Clean Code


上一篇
Day 25 - 演算法入門理解
下一篇
Day 27 - Clean Coder 時間管理與專業人士
系列文
Javascript 從寫對到寫好30

1 則留言

1
TD
iT邦新手 4 級 ‧ 2021-11-06 10:26:26

就是因為你變數命名沒做好,才會需要仰賴註解

我也是不太認同這句話,從團隊合作的立場,我們需要盡可能地讓團隊中的每一個人能夠快速理解程式碼的內容。另一方面,不好的變數命名如果沒有辦法在 code review 階段抓出的話,那麼也是團隊共同的問題,不是個人的問題。

我要留言

立即登入留言