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