《代码整洁之道》
目錄
一,整潔代碼
1,什么是整潔代碼
二,有意義的命名
1,名副其實
2,避免誤導(dǎo)
(1)特殊字母、詞匯
(2)很長很接近的詞
3,使用讀得出來的名稱
4,使用可搜索的名稱
5,匈牙利標(biāo)記法
6,每個概念對應(yīng)一個詞
三,函數(shù)
1,函數(shù)應(yīng)該盡量短小
2,一個函數(shù)只做一件事
3,每個函數(shù)都在同一抽象層級
4,函數(shù)參數(shù)
5,無副作用
四,注釋
1,好注釋
2,壞注釋
五,格式
1,垂直格式
2,水平格式
六,對象和數(shù)據(jù)結(jié)構(gòu)
1,數(shù)據(jù)抽象
2,對象和結(jié)構(gòu)體
3,demeter律
七,錯誤處理
1,使用異常而非返回碼
2,別返回NULL值,別傳遞NULL值
八,邊界
1,整潔的邊界
九,單元測試
1,TDD三定律
2,保持測試整潔
3,每個測試一個概念
4,FIRST原則
十,分離構(gòu)造和使用
1,將系統(tǒng)的構(gòu)造和使用分開
2,依賴注入/控制反轉(zhuǎn)
十一,跌進(jìn)
1,簡單設(shè)計四原則
十二,味道與啟發(fā)
一,整潔代碼
1,什么是整潔代碼
這里提到了2個關(guān)鍵詞:優(yōu)雅,高效。
優(yōu)雅就不說了,現(xiàn)在討論整潔代碼的時候,大家都會提到這個詞,代碼是一種藝術(shù)。
高效,這個觀點(diǎn)還挺有意思的,尤其是這句:
性能調(diào)至最優(yōu),省得引誘別人做沒規(guī)矩的優(yōu)化,搞出一堆混亂來。
二,有意義的命名
1,名副其實
我理解這個和代碼自注釋意思差不多,當(dāng)命名做的好,能描述清楚這個變量、函數(shù)是做什么的,就不需要注釋。
寫代碼最難的就是命名,命名最難的就是名副其實。
2,避免誤導(dǎo)
這里提到了2個問題:
(1)特殊字母、詞匯
特殊字母比如l、O等,用來做變量名簡直是神坑。
特殊詞匯,比如UNIX的專有詞匯aix,又比如編程通用專有詞匯List
(2)很長很接近的詞
2個很長的詞,都是7個單詞拼接起來的,只有中間有一個單詞不一樣,相似度很高,造成閱讀障礙。
3,使用讀得出來的名稱
編程是一種社會活動,代碼不僅要給人看,還要給人讀(朗讀的讀)
4,使用可搜索的名稱
這相當(dāng)于一種編程技巧,不要用過短且普遍存在的詞做變量名,除非它的作用域非常小。
比如一個小循環(huán),循環(huán)變量用i, j是可以接受的,因為作用域僅限這幾行。
5,匈牙利標(biāo)記法
以前有一種標(biāo)記法,變量的名稱前面加前綴,i表示int,u表示unsigned,g表示全局變量等等。
匈牙利標(biāo)記法風(fēng)靡一時是因為,以前的編譯器不做類型檢查。
現(xiàn)在的編譯器做智能化程度比較高,成熟的公司還會用腳本掃描代碼不符合自己的編碼規(guī)范的地方,匈牙利標(biāo)記法慢慢的被遺棄了。
6,每個概念對應(yīng)一個詞
不要用多個相近的詞表示同一個概念。
三,函數(shù)
在C語言中,最重要的實體就是函數(shù)。
1,函數(shù)應(yīng)該盡量短小
if、else、while語句,其中的代碼行應(yīng)該只占一行,一個函數(shù)調(diào)用語句。
一方面,這個標(biāo)準(zhǔn)很高,并不容易做到,難點(diǎn)在于很容易造成命名困難和過長參數(shù)列表,
另一方面,這個標(biāo)準(zhǔn)和我們重構(gòu)的標(biāo)準(zhǔn)有沖突。我們平常做函數(shù)級重構(gòu),要把圈復(fù)雜度降到7以下,所以拆函數(shù)的時候會把代碼一段一段的摳出來,而不是縱橫交錯,把每個if else里面的語句提出來,更不會把while里面的語句提出來。
2,一個函數(shù)只做一件事
這也是做設(shè)計的時候,多次被提到的概念。
要判斷函數(shù)是否不止做了一件事,還有一個辦法,就是看它是否能再拆出一個函數(shù)。
這個標(biāo)準(zhǔn)也挺高的。
3,每個函數(shù)都在同一抽象層級
這一點(diǎn)如果沒有可以練習(xí)的話,也是很難做到的。一般人習(xí)慣性的只把較大較復(fù)雜代碼塊提煉出函數(shù),如果語句很簡單,只有兩三行,就沒有提煉,也就造成抽象層級參差不齊。
抽象層級一致對于自頂向下閱讀代碼很有幫助。
4,函數(shù)參數(shù)
(1)向函數(shù)傳入布爾值簡直就是駭人聽聞的做法,這相當(dāng)于大聲宣布本函數(shù)不止做一件事。
(2)利用一些機(jī)制減少函數(shù)參數(shù)數(shù)量,比如變成成員函數(shù)。
這在C++中比較好實現(xiàn),在C語言中結(jié)構(gòu)體放函數(shù)指針,寫法復(fù)雜一點(diǎn)。
5,無副作用
這其實也是“一個函數(shù)只做一件事”。
函數(shù)有副作用,就會造成時序性耦合。
在LLT中,如果要盡可能覆蓋所有代碼行,前面用例的執(zhí)行就很容易造成后面的用例失敗,因為很多函數(shù)都有副作用,全局變量太多了。
在博弈型算法的開發(fā)過程中,我也深有體會,盡量讓底層的搜索函數(shù)、復(fù)雜計算函數(shù)等大函數(shù)無副作用,把修改全局變量的代碼都分離出來,集中在離main函數(shù)盡可能近的地方,代碼的耦合性會小一點(diǎn),穩(wěn)定性強(qiáng)一點(diǎn)。
四,注釋
注釋的恰當(dāng)用法是彌補(bǔ)我們在用代碼表達(dá)意圖時遭遇的失敗。
注釋存在的時間越長,就會越來越不準(zhǔn)確,因為程序員不喜歡維護(hù)注釋。
1,好注釋
(1)對意圖的解釋
不是解釋這句代碼是做什么的,而是在寫代碼的時候,面臨必要的選擇的時候,作者是怎么想的。
(2)闡釋
對一些不好理解的參數(shù)或返回值的意義翻譯成可讀形式。
(3)警示
用于警示其他的程序員。
(4)TODO注釋
這個我一般用一長串///這種注釋代替,尤其是短期內(nèi)馬上就要修改的地方的標(biāo)記。
2,壞注釋
大多數(shù)注釋都屬此類。
五,格式
1,垂直格式
垂直區(qū)隔:相關(guān)的內(nèi)容緊密聯(lián)系在一起,不同的概念用空行隔開。
2,水平格式
(1)行寬
一行120個字符,是顯示器的寬度,便于閱讀。無需左右滾動是原則。
(2)水平間隔
緊密聯(lián)系相關(guān)的事物連接在一起,相關(guān)性弱的事物用空格隔開。
這里有作者喜歡的風(fēng)格:
return b*b - 4*a*c;毫不夸張的說和我個人喜歡的風(fēng)格完全一樣!
不過大部分代碼格式化工具都不會做成這樣,所以我們的編碼規(guī)范也不是這樣,而是如下:
return b * b - 4 * a * c;六,對象和數(shù)據(jù)結(jié)構(gòu)
1,數(shù)據(jù)抽象
這里糾正了一個我也一直持有的錯誤的思想:給類的私有數(shù)據(jù)成員隨意添加共有的get和set方法,只要能用到。
之所以有這個想法是覺得,相比于共有數(shù)據(jù)成員,用get和set的話,就相當(dāng)于一個統(tǒng)一的接口,如果未來這個數(shù)據(jù)成員發(fā)生了變化,是比較容易修改的。
但實際上,隱藏實現(xiàn)并非只是在變量上放一個函數(shù)層那么簡單,隱藏實現(xiàn)關(guān)乎抽象!
2,對象和結(jié)構(gòu)體
過程式代碼難以添加新數(shù)據(jù)結(jié)構(gòu),因為必須修改所有函數(shù),面向?qū)ο蟠a難以添加新函數(shù),因為必須修改所有類。
我理解這其實就是數(shù)據(jù)和函數(shù)的矩陣式結(jié)構(gòu):過程式代碼是函數(shù)包含數(shù)據(jù),面向?qū)ο笫菙?shù)據(jù)包含函數(shù)。
就好像我有人民幣紙幣和硬幣,還有美元紙幣和硬幣,我還有倆錢包,如果我經(jīng)常用硬幣很少用紙幣,那么硬幣放一個錢包紙幣放一個錢包,如果我經(jīng)常用人民幣很少用美元,那么我人民幣放一個錢包美元放一個錢包,幣種和面額的關(guān)系就類似于數(shù)據(jù)和函數(shù)的關(guān)系。
3,demeter律
方法不應(yīng)調(diào)用由任何函數(shù)返回的對象的方法。
書中例子:
String outputDir = ctxt.get*().get*().get*()
然后用outputDir拼湊成絕對路徑,用來創(chuàng)建臨時文件。
優(yōu)化方案:ctxt類直接定義一個創(chuàng)建臨時文件的方法A。
這一塊我感覺挺疑惑的,這個例子的意思應(yīng)該不是在A里面調(diào)用get*().get*().get*()吧?
我理解這個例子反映的應(yīng)該是兩個問題,ctxt類直接定義一個創(chuàng)建臨時文件的方法A解決了暴露outputDir的問題,而get鏈的問題應(yīng)該是通過別的方法解決,比如層層傳遞,每個類的方法中只能訪問它的父類的共有方法。
七,錯誤處理
1,使用異常而非返回碼
C語言沒有這個機(jī)制,C語言的設(shè)計機(jī)制就是靠返回碼來運(yùn)行的。
2,別返回NULL值,別傳遞NULL值
八,邊界
1,整潔的邊界
邊界上的代碼需要清晰的分割和定義了期望的測試。
依靠你能控制的東西,好過依賴你控制不了的東西,免得日后受它控制。
九,單元測試
1,TDD三定律
(1)在編寫不能通過的單元測試前,不可編寫生產(chǎn)代碼
(2)只可編寫剛好無法通過的單元測試
(3)只可編寫剛好足以通過當(dāng)前失敗測試的生產(chǎn)代碼。
這3個定律,語法非常接近高等數(shù)學(xué)中的ε-δ語言,用嚴(yán)謹(jǐn)?shù)恼Z法用一種不直觀的表述形式精確的闡述一個概念。
通俗的理解就是,測試代碼和生產(chǎn)代碼一起寫,每一個小周期非常小,書中說的是30秒。
2,保持測試整潔
臟測試等同于沒測試(或者更壞)。
測試的可讀性甚至比生產(chǎn)代碼的可讀性還重要
3,每個測試一個概念
一個測試不要做兩件事,一個測試其實就是一個函數(shù)。
4,FIRST原則
快速、獨(dú)立、可重復(fù)、自足驗證、及時
可重復(fù)指的是每次運(yùn)行結(jié)果都一樣,不依賴于環(huán)境,也沒有隨機(jī)行為。
自足指的是結(jié)果只有2種,用例success或fail,不需要看其他信息,比如打印。
十,分離構(gòu)造和使用
1,將系統(tǒng)的構(gòu)造和使用分開
構(gòu)造和使用混雜,會違反單一職責(zé)原則。比如:
Node getNode() {if(node == NULL) return new Node();else return node; }這樣會造成耦合、很難測試。
2,依賴注入/控制反轉(zhuǎn)
依賴注入/控制反轉(zhuǎn)_nameofcsdn的博客-CSDN博客
十一,跌進(jìn)
1,簡單設(shè)計四原則
運(yùn)行所有重復(fù),不可重復(fù),表達(dá)了程序員的意圖,盡可能減少類和方法的數(shù)量
最近參加的演進(jìn)式設(shè)計培訓(xùn)中,簡單設(shè)計四原則有個差不多的表述:通過所有測試、盡量消除重復(fù)、盡可能清晰的表達(dá)、沒有冗余,重要程度依次降低,主觀性依次增加。
十二,味道與啟發(fā)
這一章對應(yīng)《重構(gòu):改善既有代碼的設(shè)計》這本書,講到了很多代碼壞味道。由于之前看過這本書,所以這里我再把印象中沒有的拎出來:
(1)一個源文件中存在多種語言
(2)不恰當(dāng)?shù)撵o態(tài)方法
這個應(yīng)該是只有面向?qū)ο蟠a才涉及的,類的靜態(tài)方法是用類調(diào)用的,而不是用對象調(diào)用的,所以不能實現(xiàn)多態(tài)。
如果一個方法不在類中,那應(yīng)該就是看實際作用范圍,如果只在本文件中作用,那就用static修飾,防止被隨意extern
(3)掩蔽時序耦合
書中的例子是,三個無參的函數(shù)依次調(diào)用,閱讀者看不出來他們之間的時序耦合,改成返回值傳遞做入?yún)⒌膶懛?#xff0c;就一眼看出來了。
不過這一條看的沒什么感覺,還沒有實際體會到這方面,一般也不敢隨意去挪動代碼順序。
(4)在較高層放置可配置數(shù)據(jù)
總結(jié)
- 上一篇: python读取npy文件
- 下一篇: 优先级调度算法和高响应比优先调度算法