1. 程式人生 > >斷舍離 ——《程式碼整潔之道》讀書筆記

斷舍離 ——《程式碼整潔之道》讀書筆記

注:只看了書的前十章

第一章 為什麼要整潔程式碼

1、程式碼永不消失

程式碼就是銜接人腦理解需求的含糊性機器指令的精確性的橋樑。哪怕未來會有對現在高階程式語言的再一次抽象——但這個抽象規範自身仍舊是程式碼。

所以既然程式碼會一直存在下去,且自己都幹了程式設計師這一行了,就好好的對待它吧。

2、讀遠比寫多

當你錄下你平時的編碼的過程,回放時,你會發現讀程式碼所花的時間遠比寫的多,甚至超過 10:1。所以整潔的程式碼會增加可讀性

3、對抗拖延症——稍後等於永不

糟糕的程式碼會導致專案的難以維護。而當你正在寫糟糕的程式碼的時候,心裡卻聖潔的想:“有朝一日再回來整理”。但現實是殘酷的,正如勒布朗(LeBlanc)法則

:稍後等於永不(Later equals never)

4、精益求精

寫程式碼就跟寫文章一樣,先自由發揮,再細節打磨。追求完美

大師級程式設計師把系統當作故事來講,而不是當作程式來寫。

第二章 命名

1、名副其實

(×)圖表:mapTable

(√)圖表:chart

2、避免誤導

(1)不要用專有名詞

const var = 0;

(2)避免細微之處有不同

XYZControllerForEfficientKeepingOfStrings

XYZControllerForEfficientHoldingOfStrings

(3)避免廢話

如果是一個數組型別,就沒必要叫 ProductArray

如果返回值就是資料,就沒必要叫 ProductData

(4)便於搜尋

(×)

if ( team.length === 3 )

(√)

const MAX_NUM_OF_TEAM = 3 ;
……
if ( team.length === MAX_NUM_OF_TEAM )

MAX_NUM_OF_TEAM 比 3 好檢索

早期流行一種匈牙利語標記法

如 arru8NumberList 的字首 "arru8" 表示變數是一個無符號8位整型陣列;

3、避免思維對映

類名、引數名用名詞:

member leader

方法名用動詞:

getTeam

根據 Javabean 的標準,方法名可以加上 get set is 字首

4、每個概念用一個詞

get fetch 選一個,不要混著用

5、新增有意義的語境

如果你要記錄 member 的詳細住址,會設定瞭如下幾個變數:

(×)

addrStreet addrHouseNumber addrCity addrState

(√)

new Address {
    street,
    houseNumber,
    city,
    state
}

第三章 函式

儘量短小

函式的縮排層級儘量控制在 1-3 層

例如要依次讀取 A、B、C 三個檔案:

(×)

function test() {
    readFileA(function (err, data) {
        // todo
        readFileB(function (err, data) {
            // todo
            readFileC(function (err, data) {
                // todo
                //……
            });
        });
    });
}

(√)

function test() {
    try {
        let re_a = await readFileA();
        let re_b = await readFileB();
        let re_c = await readFileC(); 
    } catch (err) { 
    }
}

只做一件事

判斷標準:是否可以再拆出一個函式

(×)

function test() {
    //......
    Session.save();
    //......
}

(√)

function test() {
    //......
    saveSession();
    //......
}

function saveSession(){ 
    Session.save();
}

每個函式即是一個抽象層,如上面的例子,Session 跟 test 函式不是一個抽象層,所以抽離出來。

函式名代替註釋

長且具有描述性的名字比描述性的長註釋好

(×)

//取出所有滿員的團
getSpecialGroup()

(√)

getGroupOfFullMember()

引數順序太多記不住怎麼辦?

方法一:體現在函式名上

(×)assertEqual(expected, actual)

(√)assertExpectedEqualsActual(expected, actual)

現代 IDE 已經具有滑鼠移在呼叫函式名上可以浮窗顯示形參列表了。

方法二:讓引數可以打亂傳
function getMember({isNew = false,isActivate = false}){
    console.log("isNew:"+isNew,", isActivate:"+isActivate)
}

getMember({isNew:true,isActivate:false}) //isNew:true , isActivate:false
//不會因為傳參的順序記錯而出錯
getMember({isActivate:false,isNew:true}) //isNew:true , isActivate:false

//也支援預設引數
getMember({})   //isNew:false , isActivate:false

這裡用到了 ES6 的新特性:解構賦值

let obj = {a:1,b:2};  
let {a,b} = obj;  // a = 1, b = 2
方法三:減少引數

最理想的引數數量 < 3,最好不用輸入引數。

A、布林值引數 一拆二

(×)getMember(isNew)

(√) getNewMember() getOldMember()

這裡的前提是獲取新、老會員的方法體程式碼不一樣,不然還是共用在一個方法通過布林值比較好。

B、二元函式變一元

function addSuffix(origin,suffix){
    return origin+"+"+suffix;
} 
console.log(addSuffix("hello","world"));

console.log("hello".addSuffix("world"));

異常代替返回錯誤碼

抽離 try / catch 塊

避免重複

否則得修改多處地方

結構化程式設計

(1)每個函式都應該有一個入口和一個出口

關於(只能有一個 return 語句,在結尾處 / 儘快 return,即有多個 return 語句)的爭論:

《結構化程式設計》的建議:

function test(is) {
    let result;
    if(is){
        result = true;
    }else{
        result = false;
    } 
    return result;
}

《重構》的建議:

function test(is) {
    if(is){
        return true;
    }else{
        return false;
    } 
}

(2)迴圈中儘量避免有 break 或 continue ,而且決不能出現 goto 語句。

第四章 註釋

儘量用程式碼表達,而不是用註釋

就像上文提到的用詳盡的函式名代替註釋,或者:

//程式碼過於追求簡潔而導致這裡要加詳細的註釋
if( smodule.getDependSubsystems().contains(subSysMod.getSubSytem()) )

//還不如這裡做拆分,取易懂的變數名方便理解,就可以不用加註釋或者少加
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if( moduleDependees.contains(ourSubSystem) )

原因是:註釋存在的越久,隨著程式碼的不斷迭代,會離程式碼的距離越來越遠,這個時候好的做法是同時維護程式碼 + 註釋,而註釋越多越複雜,維護的成本自然就上升了。

註釋不能美化糟糕的程式碼,儘量去優化程式碼

好註釋

(1)法律資訊、許可證、版權、著作權、外鏈文件的 url

(2)對意圖的解釋

(3)警示

(4)TODO 註釋

壞註釋

(1)只有自己看得懂

(2)多餘的註釋

a、不能提供比程式碼更多的資訊

b、讀的時間比直接看程式碼還久

(3)歧義性

(4)循規蹈矩的註釋

例如用第三方工具生成的註釋,很多都是累贅的廢話

(5)日誌式、署名式註釋

(×)

//  write by colin
//  fix #201 bug

(√) 交給 git 記錄

(6)慎用位置標記

// **********************

及時清理不需要的註釋

(1)越堆越多

(2)導致以後因看不懂而不敢刪

第五章 格式

向報紙學習

(1)從上往下讀,從左往右讀

(2)原始檔在最頂端給出高層次的概念和演算法,細節往下逐次展開,直到最底層的函式和細節。

垂直格式

(1)善用空白行:人會更容易將目光聚焦到空白行之後的那一行

(2)函式:呼叫者放在被呼叫者上面

橫向格式

(1)縮排

(2)IDE 中不會出現橫向滾動條

第六章 物件和資料結構

資料結構物件的區別

資料結構暴露其資料,沒有提供有意義的函式。

物件把資料隱藏在抽象之後,暴露操作資料的函式。

//資料結構
function Point(x,y){
    this.x = x;
    this.y = y;
}  

//物件
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  getX() {
    return this.x;
  }
  setX(x) {
    this.x = x;
  }
}

第七章 錯誤處理

使用異常而非返回碼

(上文有述)

定義異常類

如引用了一個第三方庫,它會 throw 自己的幾種異常值,但我們可以定義一個異常類,封裝好它的幾種異常值為一種專門異常,然後再二次 throw 交給上層捕獲。

別返回 null 值

null 值會造成很多不必要的 if 判斷

function getCurrentMember(){  
    let a = DB.getCurrentMember(); 
    if (a){
        return a;
    }else{
        return null;
    }
}

方法一:拋異常

function getCurrentMember(){ 
    let a = DB.getCurrentMember(); 
    if (a){
        return a;
    }else{
        throw Error("no member")
    } 
}

方法二:返回特例值


function getCurrentMember(){ 
    let a = DB.getCurrentMember(); 
    if (a){
        return a;
    }else{
        return {};
        //return {name : "default"};
    } 
}