“相對於任何宏偉景願,對細節的關注甚至是更為關鍵的專業性基礎。首先,開發者通過小型實踐獲得可用於大型實踐的技能和信用度。其次,宏偉建築中最細小的部分,比如關不緊的門,有點兒沒有鋪平的地板,甚至是凌亂的桌面,都會將整個大局的魅力毀滅殆盡。這就是整潔代碼之所系”----沒有比書中的這段話更能說明這本書的意義了。

《代碼整潔之道》是第1期書山有路活動選出的讀本。相對於記住那些如何寫出整潔代碼的那些法則,養成保持代碼整潔、提高代碼質量的習慣和思維更為重要。全書大致分為三個部分,第一部分1-10章都是介紹如函數、類、命名、單元測試等保持整潔的建議。第二部分11-13章從系統設計的層面提倡用AOP、IOC等方式保持整潔,或者合適的時候使用並發編程。第三部分14-17章以及后面的附錄,作者以JAVA源碼(全書都是以JAVA代碼示例)來實際講解如何保持整潔。
later equals never
我們都曾經瞟一眼自己親手造成的混亂,決定棄之不顧,走向新的一天。我們都曾經看到自己的爛程序居然能運行,然后斷言能運行的爛程序總比什么都沒有強,我們都曾經說過有朝一日再回頭清理。當然,在那些日子里。我們都沒有聽過布朗法則:later equals never 稍后等於永不.
ps:看到這句話確實有點慚愧。印象最深的感覺就是,我們去看一兩年前自己的代碼。那是寫的什么玩意,真的是自己都看不下去。要讓我改,我情願再實現一個。所以時刻保持好的習慣是多么重要。不要想着以后再解決。就像領導說,這個事情以后再考慮,然后就沒有然后了。
第一章 整潔代碼
怎樣是整潔的代碼?
Bjarne Stroustrup(C++發明者) 說:
“我喜歡優雅和高效的代碼,代碼邏輯應當直接了當,叫缺陷難以隱藏;盡量減少依賴關系,使之便於維護;依據某種分層戰略完善錯誤處理代碼;性能調至最優,省得引誘別人做沒有必要的優化,搞出一堆混亂來,整潔的代碼之做好一件事。”
Ron Jeffries 對整潔代碼的理解:
1.能通過所有的測試。
2.沒有重復代碼。
3.體現系統中的全部設計理念。
4.包含盡量少的實體、比如類、方法、函數等。
“在以上諸項中,我最在意的是代碼重復。如果一段代碼重復出現,就表示某種想法未在代碼中得到良好的提現。我會盡力去找出到底那是什偶么,然后在盡力的更清晰的表達出來。”
PS:總結下就是整潔的代碼1.職責明確,沒有多余,2.減少依賴,便於維護。3.高效。
第二章 有意義的命名
1.名副其實。 說起來很簡單。選個好名字需要花時間,但省下的時間比花掉的多。注意命名,一旦有好的命名,就換掉舊的。
int d;// 消失的時間,以日計。
int elapsedTimeInDays;
PS:Ctrl+R+R蠻好用的。實際中的情況要比我貼出來的復雜。我們定義不同的變量,能夠看到名字就知道是什么意思,這是最基本的要求了。
2.避免誤導。比如不是List類型,就不要用個accountList來命名,這樣形成誤導。
3.做有意的區分。
Public static void copyChars(char a1[],char a2[]){
for(int i=0;i<a1.length;i++)
{
a2[i]=a1[i];
} }
如果參數名稱改為source和destination ,這個函數就會像樣很多。廢話都是冗余的,Variable一詞 永遠不應當出現在變量名中。Table一詞永遠不應當出現在表名中。NameString 會比 Name好嗎,難道Name 會是一個浮點數不成?如有一個Customer的類,有又一個CustomerObject的類。是不是就凌亂了。
4.使用便於搜索的的名稱
單個字母或者數字常量是很難在一大堆文章中找出來。比如字母e,它是英文中最常用的字母。長名勝於短名稱,搜得到的名稱勝於自編的名稱。 竊以為單字母的名稱僅用於短方法中的本地變量。名稱長短應與其作用域大小相對應。
5.類名應該是名詞或短語,像Customer,Account,避免使用Manager,Processor,Data或者Info這樣的類名。類名不應當是動詞。方法名應該是動詞或動詞短語,如postPayment ,deletePage或Save,屬性訪問、修改和斷言應該根據其值來命名,並加上get,set,is這些前綴。
6.別扮可愛,耍寶,比如誰知道HolyHandGrenada 函數是干什么的,沒錯這個名字挺伶俐,但是不過DeleteItems或許是更好的名字。
7.每個概念對應一個詞。並且一以貫之。
在一堆代碼中有Controller,又有manager,driver。就會令人困惑。比如DeviceManager和Protal-Controller之間又什么本質區別?
第三章 函數
1.函數的第一規則是要短小,第二條規則是還要更短小。
2.函數應該做一件事。做好這件事。只做這一件事。
3.盡量少的函數參數。有兩個參數的函數要比一元函數的難懂。如果需要三個或者三個以上的參數應該封裝成類了。
4.不要重復自己。
PS:如果一段相同的代碼出現了兩次,你是不是覺得自己改做些什么了。
第四章 注釋
注釋的恰當用法是彌補我們在用代碼表達意圖時遭遇的失敗。作者認為注釋是一種失敗,我們總無法找到不用注釋就能表達自我的方法,所以總要有注釋,這並不值得慶賀。寫注釋的常見動機之一是糟糕代碼的存在。
帶有少量注釋的整潔而有表達力的代碼,要比帶有大量注釋的零碎而復雜的代碼像樣的多。與其花時間編寫解釋你搞出的糟糕的代碼注釋,不如花時間清潔那堆糟糕的代碼。
PS:這段話看起來可能有些過激。我們確實可以通過好的編碼習慣減少不必要的注釋。不過現在自動生成文檔的技術都是從代碼的注釋中提取的。如果是這種情況,上司肯定是要求你寫完備的注釋的。
好注釋:
1. 法律信息。有時,公司代碼規范要求編寫與法律有關的注釋。例如版權和著作申明。
2.提供信息的注釋。
// returen an instance of the Responder being tested
protected abstract Responder responderInstance();
不過作者認為 將函數名 重新命名為 responderBeingTested 注釋就是多余的。
3.對意圖的解釋。 有時注釋不僅提供了有關實現的有用信息,而且還提供了某個決定后面的意圖。
4.闡釋。 有時注釋把某種晦澀難明的參數或返回值的意義翻譯為某種可讀形式。也會是有用的。特別是參數或者返回值是某個標准庫的一部分,或者你不能修改代碼,那幫助闡釋其含義的代碼就會有用,例如:
assertTrue(bb.compareTo(ba)==1);//bb>aa
assertTrue(a.compareTo(b)==-1);//a<b
直接看方法可能不明確,但有注釋就明白多了。我看這2,3,4都是一個意思。就是說明是干嘛的。
5.警示,告訴別人要注意這個方法之類的。
6.放大。有的代碼可能看着有點多余,但編碼者當時是有他自己的考慮,這個時候需要注釋下這個代碼的重要性。避免后面被優化掉。
第五章 格式
縱向格式:
1. 函數與函數之間留空行。
2.變量聲明:變量聲明應該盡可能靠近其使用位置。因為函數很短,本地變量應該在函數的頂部出現。
3.實體變量 應該在內的頂部,相當於我們的field 字段,會被使用的多。
4.相關函數,如果某個函數調用另外一個,就應該把他們放在一起,而且調用者應該盡可能放在被調用者的上面。這樣這個程序就會自然有序。(之前我喜歡把private的方法 放到一起。當然這確實沒有什么實際的意義)
5.“相關概念的代碼放在一起。相關性越強,比如一個大功能邏輯靠在一起。” (更多的時候我喜歡用 region 來收起來。)
橫向格式:
1.一行的長度,作者建議是上限是120個字符
PS 平時我們都是按照自己的屏幕大小來決定,當然太長了,自己也不便閱讀,又不是壓縮的js文件
2.賦值語句兩端留空。
3.不在函數名和左括號間加空格。因為函數與其參數密切相關。
4.縮進。源文件是一種繼承結構,而不是一種大綱結構,繼承結構中的每一層級都圈出一個范圍, 也就是代碼塊,其中有聲明語句和執行語句。要體現這種繼承結構,就要對源代碼進行縮進處理。但有時候我們會把if語句,while循環,或小函數寫成一行,但這樣沒有層級的概念,不便閱讀,還是縮進的好。
第六章 對象和數據結構
1.過程式代碼(函數編程)便於在不改動既有數據結構的前提下添加新函數,面向對象代碼便於在不改動既有函數的前提下添加新類。反過來講也說的通,過程式代碼難以添加新的數據結構,因為必須修改所有函數,面向對象代碼難以添加新函數,因為必須修改所有類。所以在設計的時候要分析好是以后是要添加新函數還是要添加新的數據結構。
2.德墨忒爾律:模塊不應該了解它所操作對象內部情形。比如C的方法f只能調用以下對象的方法。
- C
- 由f創建的對象
- 作為參數傳遞給f的對象
- C的實體變量持有的變量
var outpath=cxt.getOptions().getScart().getAbsolutePath();
這個代碼就違反了上面的德墨忒爾律,調用了返回值的方法。這樣就是暴露了內部結構。
第七章 異常處理
1.try代碼就像是事務,catch代碼塊將程序維持在一種持續狀態。在編寫可能拋出異常的代碼時,最好先寫出try-catch-finally 語句。
2.根據需要定義異常類。對錯誤分類的方式有多種,可以依據來源,是組件還是其他地方,或者依據類型,是設備錯誤還是網絡錯誤。不過在我們定義異常類的時候,最重要的考慮是如何捕獲它們。
3.別返回null值。程序中不斷的看到檢測null值的代碼,一處漏掉檢測就可能會失控。返回Null,作者認為這種代碼很糟糕。建議拋出異常 或者返回特定對象(默認值)。更早的發現問題。同理,也應該避免傳遞Null值給其他的方法。
PS:在大多數的編程語言中,沒有良好的方法能對付由調用者意外傳入的null值。我們發布產品應該有容錯的機制,程序不能輕易的就崩掉,有異常應該及時記錄下來或給出提示。
第八章 邊界
有時候我們在使用第三方程序包或者開源代碼的時候,或者依靠公司其他團隊的代碼,我們都得干凈利落的的整合進自己的代碼中。這一章就是介紹保持保持軟件邊界整潔的實踐手段和技巧。
1.對第三方進行學習性測試,當第三方程序包發布了新的版本,我們可以允許學習性測試,看看程序包的行為有沒有發生改變。
2.使用尚不存在的代碼,有時候我們的第三方,還沒有開發好API,但又不能影響到我們的開發進度,所以我們先可以定義好自己想要的接口。如果第三方ok了,我們再對接起來。
3.通過接口管理第三方邊界,可以使用ADApter模式將我的接口轉換為第三方提供的接口。這個是要注意,第三方的代碼和自己的代碼混合太多,這樣不便管理。
第九章 單元測試
敏捷和TDD運動鼓舞了許多程序員編寫自動化單元測試,單元測試是確保代碼中的每個犄角旮旯都如我們所願的工作。
TDD三定律
- 除非這能讓失敗的單元測試通過,否則不允許去編寫任何的生產代碼。
- 只允許編寫剛好能夠導致失敗的單元測試。 (編譯失敗也屬於一種失敗)
- 只允許編寫剛好能夠導致一個失敗的單元測試通過的產品代碼。
PS:什么是生產代碼,這里有點迷惑。
測試代碼和生產代碼一樣重要,它可不是二等公民,它需要被思考、被設計和北照料。它該像生產代碼一樣保持整潔。單元測試讓你的代碼可擴展,可維護,可復用。原因很簡單,有了測試,你就不擔心對代碼的修改,沒有單元測試,每次修改可能帶來缺陷,一個測試,一個斷言。一個測試,對應一個概念。我們不想要超長的測試函數。
測試還應遵守以下5條規則。
1.快速 測試應該能快速運行,太慢了你就不會頻繁的運行,就不會盡早的發現問題。
2.獨立。測試應該相互獨立,某個測試不應該為下個測試設定條件。當測試相互依賴,一個沒通過導致一連串的測試失敗,使問題診斷變的困難。
3.可重復。測試應該可以在任何環境中重復通過。
4.自足驗證 測試應該有布爾值輸出,無論通過或失敗,不應該是查看日志文件去確認
5.及時。單元測試應該恰好在使其通過的生產代碼之前編寫。
第十章 類
1.類應該短小
2.單一權責原則(SRP):類或模塊應有且只有一條加以修改的理由。系統應該有許多短小的類而不是巨大的類組成。
PS:每個達到一定規模的系統都會包括大量邏輯和復雜性。管理這種復雜性的首要目標就是加以組織,以便開發者在哪兒能找到東西,反之,
擁有巨大、多目的的類的系統,總是讓我們在目前並不需要了解的一大堆東西中艱難的跋涉。
3.內聚:如果一個類中的每個變量都被每個方法所使用,則該類具有最大的內聚性。內聚性高,意味着類中的方法和變量相互依賴,相互結合成一個邏輯整體。
4.為了修改而組織。開放閉合原則(OCP):類應當對擴展開放,對修改封閉。我們可以借助接口和抽象類來隔離這些細節帶來的影響。
第十一章:系統
將系統的構造和使用分開:構造和使用是不一樣的過程。
PS:修建一棟大樓的時候,起重機和升降機在外面,工人們穿着安全服在忙碌。當大樓建設完成,建築物變得整潔,覆蓋着玻璃幕牆和漂亮的漆色。在其中工作的人,看完完全不同的景象。軟件也是如此,將關注的方面分離。
1.工廠,有時候應用程序需要確定何時創建對象,我們可以使用抽象工廠模式。將構造的細節隔離於應用程序之外。
2.依賴注入(DI/IOC)。在依賴管理情景中,對象不應該負責實例化對自身的依賴,反之,它應該將這份權責移交給其他有權利的機制,從而實現控制的反轉。
PS 現在的依賴注入組件比較多了,Autofac,Ninject等。
3.擴容:“一開始就做對的系統”純屬神話,反之,我們應該只實現今天的用戶的需求。然后重構,明天再擴容系統,實現新用戶的需求。這就是迭代和增量敏捷的精髓所在。 就像城市不斷的再拆掉,再建設。
4.面向切面編程。AOP中,被稱為方面(aspect)的模塊構造指明了系統中哪些點的行為會以某種一致的方式被修改,從而支持某種特定的場景。這種說明是用某種簡潔的聲明(Attribute)或編程機制來實現的。
PS:MVC的Filter是個很好的AOP,可以從權限驗證,方法進入前,方法進入后,返回結果前,返回結果后等這幾個橫切面進行編程。更好的組織代碼。第十,十一章講的設計只是一少部分。更多的可能要去參考專門講設計模式之類的書。
第十二章 迭進
1.簡單設計規則 1:運行所有測試。
緊耦合的代碼難以編寫測試。同樣編寫測試越多,就會越遵循DIP之類的原則,使用依賴注入,接口和抽象等工具盡可能減少耦合。如此一來設計就會有長足進步。遵循有關編寫測試並持續運行測試的、明確的規則,系統就會更貼近OO低耦合度、高內聚的目標。
2.簡單設計規則2 重構:
在重構過程中,可以應用有關優秀軟件設計的一切知識,提升內聚性,降低耦合度。換句話說:消除重復,保證表達力,盡可能的減少類和方法的數量。
3.不可重復。重復是良好設計系統的大敵。它代表着額外的工作、額外的風險和額外不必要的復雜度。重復有多種表現。雷同的代碼行是一種。另外的比如:
int size();
bool isEmpty();
這兩個方法可以分別實現,但可以在isEmpty中使用size消除重復。
bool isEmpty(){
return size()==0;
}
不但是從代碼行的角度,也要從功能上消除重復。
第十三章: 並發編程
並發是一種解耦策略,它幫助我們把做什么(目的)和何時(時機)做分解開。在單線程應用中,目的與時機緊密耦合,很多時候只要查看堆棧追蹤即可斷定應用程序的狀態。而解耦目的與時機能明顯地改進應用程序的吞吐量和結構。從結構的角度看,應用程序看起來更像是許多台協同工作的計算機,而不是一個大循環。單線程程序許多時間花在等待Web套接字I/O結束上面。
- 並發會在性能和編寫額外代碼上增加一些開銷。
- 正確的並發是復雜的,即使對於簡單的問題也是如此。
- 並發缺陷並非總能重現,所以常被看做偶發事件而忽略,而未被當做真的缺陷看待。
- 並發常常需要對設計策略的根本性修改。
一些基礎定義:
在並發編程中用到的幾種執行模型。
1)生產者-消費者模型
一個或多個生產者線程創建某些工作,並置於緩存或者隊列中。一個或者多個消費者線程從隊列中獲取並完成這些工作。生產者和消費者之間的隊列是一種限定資源。
2)讀者-作者模型。
當存在一個主要為讀者線程提供信息源,但只是偶爾被作者線程更新的共享資源,吞吐量就會是個問題。增加吞吐量,會導致線程飢餓和過時信息的積累。協調讀者線程不去讀取正在更新的信息,而作者線程傾向於長期鎖定讀者線程。
3)宴席哲學家。
許多企業級應用中會存在進程競爭資源的情形,如果沒有用心設計,這種競爭會遭遇死鎖,活鎖,吞吐量和效率低等問題。
PS:這里對並發的講解還不是那么的清晰,要掌握怎么正確使用並發,自己還是需要去專門看看這方面的書。
小結:書十三章之后的部分是一些java源碼的優化過程的講解,我不太懂java,這里先略過。本書最有價值的地方在於讓我們程序員要有些整潔代碼的習慣。從細微的變量命令,到函數、類的設計、以及整個系統的構造。不能忽略每一道工序。壞的代碼就像沼澤,會讓人越陷越深,很難改動,所以我們從一開始就要寫整潔的代碼。而至於設計模式或並發編程,從其他的書籍
學習
更全面。這本書滿足不了我們的需求。
PS:書山有路活動是讀書群的朋友共同選出來一起讀的一本書。《代碼整潔之道》是第一期。我是讀書人,這本書一共讀了七天。每天大概一個多小時。但是今天整理筆記,基本上全書又過了一遍。筆記內容也是依據我自己的判斷。如果你想獲得全面的了解,還是要請看原書。我們第二期正在讀的書籍是《失控》,歡迎有興趣的朋友加入。qq群452450927