---
title: Clean Code 無暇的程式碼
tags: Coding Skill
---
# Clean Code 無暇的程式碼

`WHY` :
程式很多人都會寫,故事卻很少人會寫,如果能把程式寫成故事,那麼閱讀程式的人也會感到興奮。
[TOC]
---
## 第一章-有意義的命名
### 類別的命名
類別的命名應該使用名詞或名詞片語來命名
```java=
public class customer{
...
}
public class wikiPage{
...
}
public class account{
...
}
public class addressPaser{
...
}
```
:::warning
類別的名稱不應該是動詞
:::
### 方法的命名
方法應該使用動詞或動詞片語來命名
```java=
public static void setName(string _name){
...
}
string name = employee.getName();
customer.setName("leo");
if(leo.isPaid())...
```
> 根據JavaBean的標準,取出器(取出的方法)應該使用Get當字首、修改器(設定的方法)應該使用Set當字首,判定(predicate)應該使用is當字首。
### 每個概念使用一種字詞
在同一個函數庫(library)裡,如果分別使用Controller、Manager和Driver,**這三個類別混雜在一起會使人混亂**。除非你能分辨DeviceManager(裝置管理員)與ProtocolController(協定控制器)實質的差別是什麼,不然就全部改用單一種字詞來命名就好。
```java=
//錯誤示範 相似觀念用一種類別來撰寫就好
public class controller{
...
}
public class manager{
...
}
public class driver{
...
}
```
:::success
全部都改用Controller或Manager這兩個都可以代表Driver的意思。
:::
```java=
// 用一種類別來改寫即可
public class controler{
...
}
```
### 使用解決方案領域的名稱
閱讀你程式的人也會是程式設計師,所以盡量使用資訊領域的專業術語來進行命名,如`演算法`、`模型名稱`、`數學詞彙`
```java=
public static string BankersAlgorithm(){
while (P != ∅) {
found = FALSE;
foreach (p ∈ P) {
if (Mp − Cp ≤ A) {
/* p可以獲得他所需的資源。假設他得到資源後執行;執行終止,並釋放所擁有的資源。*/
A = A + Cp ;
P = P − {p};
found = TRUE;
}
}
if (! found) return FAIL;
}
return OK;
}
// 作業系統-Banker演算法 拿來預防Deadlock
BankersAlgorithm();
```
### 添加有意義的上下文
想像著你有一些變數,命名為firstName、lastName、city、state,放在一起看的話,你很清楚這是在記錄一份地址。
```java=
// 你很清楚這是一份地址與個人資料
String firstName, lastName, city, state;
```
但如果你在方法裡只單純看到state這個變數你還會推測她是地址的一部分嗎 :stuck_out_tongue:?
```java=
// 如果只有state 你還清楚嗎?
string state;
```
可以利用字首來增加上下文資訊
```java=
// 如果這樣宣告的話不是更清楚嗎?
string addrState, addrFirstName;
```
:::success
最好的方式是,產生一個名為Address的類別,讓這些變數隸屬於該類別,至少讀者會了解這些變數是來自更大結構的一部分。
:::
---
## 第二章-函數
### 只做一件事
If、Else、While都應該只有一行,而那行通常是呼叫Function來回傳T or F。
```java=
//錯誤示範
if((employee.flags & hourly_flag) && (employee.age > 65))
//正確
if(employee.isEligibleForFullBenefits())
```
:::success
**確保你的函式只做一件事,且把那件事做好**
確認只做一件事的最好方法就是,檢查可不可以從該Function中提取新的Function出來。
:::
### 降層準則
程式的閱讀應該是由上而下的敘事,而不是東跳西跳。
```java=
public void function newDayInMyLife(){
wakeUp();
brushTooth();
haveBreakfast();
...
}
public void function wakeUp(){
printf("Good morning~");
}
public void function brushTooth(){
printf("Brushing my tooth~");
}
public void function haveBreakfast(){
printf("Today I want to eat toast ^_^");
}
```
### 輸出型的參數
以下函式真的是s接在某個東西的後方嗎?還是這個函式會把某些頁尾加到s後面?
```java=
appendFooter(s); //我是輸出型函式
```
當我們回頭去看原函式的內容時會中斷我們閱讀上的思考。在物件導向的程式語言中,應該更改為以下方式來呼叫。
```java=
content.appendFooter();
```
### 函式的參數
**函式的參數數量最理想的是零個,盡量避免用多於三個參數。**
```java=
public static void ideal(){
...
}
```
:::warning
參數的傳遞順序有多種組合,因此盡量避免用過多的參數。
輸出型的參數比輸入型更難以理解,當我們閱讀一個函式時,我們習慣於->**參數是輸入到函式**的概念,而**輸出則是透過回傳值來傳遞**,我們無法預期回傳的資訊由參數(輸入的值)來傳遞。
:::
```java=
public static void draw(float x, float y, color R, color G, color B, color Alphabet){
//這麼多參數 你只要給錯順序就會出錯
}
```
### 要無副作用
:::warning
確保你的函式只做一件事,時常你所撰寫的函式**暗地裡**偷偷做了其他事情。有時候會使得同類別的其他變數,產生不可預期的改變。有時候他會將資訊轉換成參數傳遞給其他函式,或是轉變成系統的全域變數。
:::
### 函式的詞性
Function通常使用`動詞`進行命名,代表函式所要進行的動作,而所接收的`參數以名詞`命名。
```java=
public void write(String name){
...;
}
```
### 指令和查詢分離
函式最好一次僅做一件事情,例如回答某個問題或設定某個值,但這兩件事情不應該同時發生。看看以下的例子。
```java=
public boolean set(String attribute, String value);
```
:::warning
這個函式設定某個屬性的值,當設定成功則回傳Ture,設定失敗則回傳False代表該屬性不存在,這樣就會導致下面詭異的敘述。
:::
```java=
if(set("userName","leo"))...
```
這行程式碼可以表達以下兩種意思
1. 設定userName的值為leo,判斷是否成功設定
2. 判斷userName的值是否為leo
:::success
正確的方式應該要將查詢與設定的指令分開來撰寫
:::
### 提取Try/Catch區塊
Try/Catch 區塊本身是難看的,在正常的程式運作中混入錯誤處理會混淆程式結構,比較好的做法是將函式從Try/Catch中提取出來。
:skull_and_crossbones: **錯誤示範**
```java=
try{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}catch (Exception e){
logger.log(e.getMessage());
}
```
:sunglasses: **正確寫法**
```java=
public void delete(Page page){
try{
deletePageAndAllReferences(page);
}catch (Exception e){
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e){
logger.log(e.getMessage());
}
```
:::warning
如上述例子,函式應該只做一件事,而錯誤處理就是一件事,因此一個錯誤處理函式不應該再做其他事。關鍵字Try存在於一個函式裡它幾乎就代表該函式的開頭字眼,理應不該有其他程式。
:::
---
## 第三章-註解
### 用程式碼表達本意
```java=
//check to see if the employee is eliglible for full benefits
if((employee.flags & hourly-flag) && (employee.age >65))
//改寫後(直接不需要註解)
if(employee.isEligibleForFullBenefits())
```
:::warning
**註解的原則**:註解不該比Code長
:::
### 可以寫的註解
* 法律型註解(條款 or 標準)
* 資訊型註解
* 對意圖的解釋
* 闡明
* 對於後果的告誡
* 代辦事項(Todo)
* 放大重要性
### 法律型註解
*對於條款與標準的闡明*
```java=
MIT ...
```
### 資訊型註解
*對於傳入的參數進行解釋*
```java=
// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = pattern.compile("\\d*;\\d*;...")
```
### 對意圖的解釋
```java=
// This is our best attempt to get race condition
// by creating large number of threads.
```
### 闡明
有時候註解回傳可能是標準函式的一部分,或某些你不能修改的程式碼,加入註解可以降低查看原始碼的頻率
```java=
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(aa.compareTo(ab) == -1); // aa < ab
```
### 對於後果的告誡
有些程式我們並不曉得執行後會發生什麼事,然而這些事有時候並不如我們預期,例如:深度學習的訓練時間、執行緒安全問題...等
```java=
// 1. Don't run unless you have some time to kill
// 2. SimpleDateFormat is not thread safe,
// so we need to create each instance independetly.
```
### 待辦事項(Todo)
```java=
// Todo-MdM these are not needed
// We expect this to go away when we do the checkout model.
protected versionInfo makeVersion() throws Exception{}
```
### 放大重要性
```java=
String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting
// space that could cause the item to be recognized
// as another list
```
### 使用函式或變數來代替註解
```java=
// does the modeule from the global list <mod> depend on the
// subsystem we are part of?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
```
```java=
// 改寫後
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if(moduleDependees.contains(ourSubSystem)){
...
}
```
### 右大括弧( } )後的註解
我們時常會在右大括弧後撰寫括弧屬於誰的註解
``` html=
<div class="banner">
<div class="container">
<div class="banner-txt">
<h1>金魚都能懂的
<small>這個網頁畫面怎麼切</small>
</h1>
<h2>圖文滿版區塊</h2>
<p>這畫面實在常見,在各種樣版網站可說是設計常客<br>
金魚切不出來實在說不過去阿</p>
</div>
</div>
</div><!-- banner -->
```
:::warning
**解決方式**:應試著縮短你的函式來解決
:::
### 非區域性的Info
確保註解只用來描述範圍內的程式碼,不要跨區管理。
``` java=
/**
* Port on which fitnesse would run. Defaults to <b>8082</b>
*
* @param fitnessePort
*/
public void setFitnessePort(int fitnessePort){
this.fitnessePort = fitnessePort;
}
```
### 不顯著的關聯
```java=
/*
* 我每一天都想要這樣過
* 就是單純的吃飯睡覺
* 然後偶爾順便打東東
*/
public void function newDayInMyLife(){
wakeUp();
brushTooth();
haveBreakfast();
...
}
public void function wakeUp(){
printf("Good morning~");
}
public void function brushTooth(){
printf("Brushing my tooth~");
}
public void function haveBreakfast(){
printf("Today I want to eat toast ^_^");
}
```