---
# System prepended metadata

title: 易讀程式美學

---

## 第二章 簡化迴圈與邏輯

目的：讓程式易於理解，減少程式中的「心理負擔 (mental baggage)」

---

## 提高控制流程可讀性

讓所有條件判斷、迴圈與其他改變流程的敘述「盡量自然」


### 1. 條件式中條件順序

A. 
```
if ( length >= 10 ) // good
```
```
if ( 10 <= length ) // bad
```

B.
```
if( bytes_received < bytes_expected )
```
```
if( bytes_expected > bytes_received )
```

通用原則：

| 左側 | 右側 |
| -------- | -------- |
|「比較對象表示式」，數值較有變化     |「比較對象基準式」，數值為固定常數     |

### 2. if / else 區塊順序

在不考慮巢狀結構情況下，避免難以閱讀的順序呈現，以下條件有助於決定區塊順序：

> 這些條件偶有牴觸，但沒有牴觸時都是可遵循原則

* 先處理**肯定**條件而非**否定**條件
* 先處理**簡單**的情況 -> 先處理邏輯簡單或者判斷條件少的情況
* 先處理比較**有趣**或**明顯**的情況 -> 先處理那些在業務邏輯中比較重要或者用戶最關心的情況

### 3. 有效使用三元運算子

縮短其他人理解程式所需時間優先度高於減少程式行數；只在簡單情況下使用三元運算子

A.

```
time_str += (hour >= 12) ? 'pm' : 'am'
```

B.

```
const accessLevel = user.isActive ? user.age > 18 ? 'full' : 'restricted' : 'none';
```

### 4. early return

透過「把守條件（guard clauses）」儘早由函數中返回，提高程式碼可讀性並降低複雜性

### 5. 減少巢狀結構

多層巢狀除了難懂以外，每多一層巢狀結構就需要使用者將更多環境資訊「推進堆疊」，盡可能使用「線性」程式流程，避免巢狀結構

Before: 不同的條件式的表達方式不一致，部分與 SUCCESS 比較另外與非SUCCESS比較
```
if (user_result === SUCCESS) {
    if(permission_result !== SUCCESS) {
        reply.error("error reading permissions")
        reply.done()
        return
    }
    reply.error("")
} else {
    reply.error(user_result)
}
reply.done()
```

After: 先處理失敗情況並 early return

> 修改程式碼要以全新角度審視，退一步以整體的角度考慮程式碼

```
if (user_result !== SUCCESS) {
    reply.error(user_result)
    reply.done()
    return
} 

if(permission_result !== SUCCESS) {
    reply.error("error reading permissions")
    reply.done()
    return
}

reply.error("")
reply.done()
```

---

## 分解巨大表示式
將巨大表示分解為更容易消化的大小

### 1. 解釋性變數（ explaining variable ）

解釋變數主要是為了可讀性和明確性，用於使條件或複雜表達式使其變得清晰

Before:
```
if ( line.split(:)[0].strip() === 'root' ) {...}
```

After:
```
const userName = line.split(:)[0].strip()
if( userName === 'root' ) {...}
```

### 2. 摘要變數（ summary variable ）

這類變數目的是用較短、較易於瞭解的名稱取代較大的程式碼，通常保存資料或結果的計算值，有助於提高效率

Before:
```
if ( request.user.id === document.owned_id ) {...}
...
if ( request.user.id !== document.owned_id) {...}
```

After: 雖然表示式不算大，但包含五個變數增加了理解成本；利用加入摘要變數除了能易於理解也能清楚表示出「使用者是否擁有這份文件」概念

```
const isUserOwnsDocument = request.user.id === document.owned_id

if (isUserOwnsDocument) {...}
...
if (!isUserOwnsDocument) {...}
```

### 3. 利用笛摩根定律

笛摩根定律包括兩個基本的轉換原則：

* NOT (A AND B) 等於 (NOT A) OR (NOT B)
* NOT (A OR B) 等於 (NOT A) AND (NOT B)

Before:
```
 if (!(user.isActive && user.hasPaid)) 
```

After: 利用笛摩根定律讓布林值表示式更有可讀性
```
 if (!user.isActive || !user.hasPaid) 
```

### 4. 分解巨大的敘述

程式每個敘述單獨看都不大，但集合起來有機會成為一個巨大敘述體，如同範例程式碼，觀察後可以發現許多敘述都相同，可以抽取出來成為摘要變數

Before:
```
var update_highlight = function (message_num) {
 if ($("#vote_value" + message_num).html() === "Up") {
    $("#thumbs_up" + message_num).addClass("highlighted");
    $("#thumbs_down" + message_num).removeClass("highlighted");
 } else if ($("#vote_value" + message_num).html() === "Down") {
    $("#thumbs_up" + message_num).removeClass("highlighted");
    $("#thumbs_down" + message_num).addClass("highlighted");
 } else {
    $("#thumbs_up" + message_num).removeClass("highighted");
    $("#thumbs_down" + message_num).removeClass("highlighted");
 }
};
```

After:
```
var update_highlight = function (message_num) {
 var thumbs_up = $("#thumbs_up" + message_num);
 var thumbs_down = $("#thumbs_down" + message_num);
 var vote_value = $("#vote_value" + message_num).html();
 var hi = "highlighted";

 if (vote_value === "Up") {
    thumbs_up.addClass(hi);
    thumbs_down.removeClass(hi);
 } else if (vote_value === "Down") {
    thumbs_up.removeClass(hi);
    thumbs_down.addClass(hi);
 } else {
    thumbs_up.removeClass(hi);
    thumbs_down.removeClass(hi);
 }
};
```

---

## 變數與可讀性
在「分解巨大表示」利用**解釋性**與**摘要**變數提高程式碼的可讀性，但濫用變數也將造成以下問題：
* 變數越多越難記住所有變數
* 變數存活範圍越大，就必須記得越久
* 變數越常改變，越難記得目前的數值

### 1. 消除變數

消除程式碼中對可讀性沒有幫助的變數

#### 不必要的暫存變數

```
const now = Date.now()
lastViewTime = now
```

`now` 不是必要變數原因：
* 不是分解複雜表示式的結果
* 程式沒有更加明確
* 只使用一次，沒有消除任合重複程式碼

結論：不使用 `now` 程式碼一樣容易理解
```
lastViewTime = Date.now()
```

#### 消除中間結果

Before:
```
var remove_one = function (array, value_to_remove) {
    var index_to_remove = null;
    for (var i = 0; i < array.length; i += 1) {
        if (array[i] === value_to_remove) {
            index_to_remove = i;
            break;
        }
    }
    if (index_to_remove !== null) {
        array.splice(index_to_remove, 1);
    }
}
```

After: 移除只是用來保存中間的結果的 `index_to_remove` 變數
```
var remove_one = function (array, value_to_remove) {
    for (var i = 0; i < array.length; i += 1) {
        if (array[i] === value_to_remove) {
            array.splice(i, 1);
            return;
        }
    }
}
```

#### 消除控制流程變數

減少使用額外的變數來控制流程

Before:
```
function findElement(arr, target) {
    let found = false;
    let i = 0;
    while (i < arr.length && !found) {
        if (arr[i] === target) {
            found = true;
        } else {
            i++;
        }
    }
    return found;
}
```

After:
```
function findElement(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return true;  // 直接返回，消除了控制流變數
        }
    }
    return false;
}
```

### 2. 縮限變數的範圍

不限於全域變數，對所有變數而言「縮限範圍」都是很好的作法，將變數的作用域限制在它們被使用的最小可能範圍內。

> 盡可能減少可以看到變數的程式碼行數

Before:
```
let submitted = false
const submit = function (formName) {
    if(submitted) return
    
    ...
    submitted = true
}
```
After:將變數放在閉包中
```
const submit = (function () {
    let submitted = false
    
    return function(formName) {
        if(submitted) return
        ...
        submitted = true
    }
}())
```



### 3. 偏好單次寫入的變數

盡量使用常數，或是減少變數的改變次數也有很大幫助

> 操作變數地方越多，越難記得變數目前的數值

---

refer: https://mcusoft.wordpress.com/wp-content/uploads/2015/04/the-art-of-readable-code.pdf