斷舍離 ——《程式碼整潔之道》讀書筆記
注1:只看了書的前十章
注2:原書使用的語言為 Java,我改成了 JavaScript
第一章 為什麼要整潔程式碼
1、程式碼永不消失
程式碼就是銜接 人腦理解需求的含糊性 和 機器指令的精確性 的橋樑。哪怕未來會有對現在高階程式語言的再一次抽象——但這個抽象規範自身仍舊是程式碼。
所以既然程式碼會一直存在下去,且自己都幹了程式員這一行了,就好好的對待它吧。
2、讀遠比寫多
當你錄下你平時的編碼的過程,回放時,你會發現讀程式碼所花的時間遠比寫的多,甚至超過 10:1。所以整潔的程式碼會增加 可讀性 。
3、對抗拖延症——稍後等於永不
糟糕的程式碼會導致專案的 難以維護 。而當你正在寫糟糕的程式碼的時候,心裡卻聖潔的想:“有朝一日再回來整理”。但現實是殘酷的,正如 勒布朗(LeBlanc)法則
:稍後等於永不(Later equals never)
4、精益求精
寫程式碼就跟寫文章一樣,先自由發揮,再細節打磨。 追求完美 。
大師級程式設計師把系統當作故事來講,而不是當作程式來寫。
第二章 命名
1、名副其實
(×)圖表:mapTable
(√)圖表:chart
推薦一個根據中文意思幫你起英文變數名的網址: ofollow,noindex" target="_blank">http://unbug.github.io/codelf/

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"));
異常代替返回錯誤碼
見我另一篇 《 如何做好錯誤處理?(PHP篇)》 裡面有體現
抽離 try / catch 塊
見我另一篇 《 如何做好錯誤處理?(PHP篇)》 裡面有體現
避免重複
否則得修改多處地方
結構化程式設計
(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; } }
這兩者寫法現在仍有爭議,詳細的討論可以點選: https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement
(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; } }
ES6暫不提供私有變數(雖然有提案),但可以通過其他辦法變相實現: https://juejin.im/entry/572c0b2d2e958a00667a081d
第七章 錯誤處理
使用異常而非返回碼
(上文有述)
定義異常類
如引用了一個第三方庫,它會 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"}; } }