《代碼整潔之道》讀書筆記

看完《代碼整潔之道》以後我受益不淺,但等到本身實踐時卻很難按照書中給的建議編寫出整潔的代碼。一方面是規則太多,記不住,另外一方面書上引用了大量示例代碼對這些規則進行佐證,在我記不住時亦不方便查閱。因而我把書中的規則摘了出來並加以必定的解釋,閒暇時候多過幾遍,但願這些規則時刻警示我,成爲個人習慣。php

想看此書卻還沒開始的人也能夠從這篇筆記出發,對筆記中列出的規則有疑問再翻書找答案,相信會比直接啃書來的快一些。java

ps: 未必要嚴格遵循書中的規則,代碼不是八股文。程序員

命名

1.避免誤導

  • "一組帳號"別用accountList表示,List對程序員有特殊含義,能夠用accountGroupbunchOfAccounts、甚至是accounts
  • 不使用區別較小的名稱,ZYXControllerForEfficientHandlingOfStrings和ZYXControllerForEfficientStorageOfStrings難以辨別
  • 不使用小寫l、大寫O做變量名,看起來像常量一、0

2.作有意義的區分

  • 不以數字系列命名(a一、a二、a3),按照真實含義命名
  • Product/ProductInfo/ProductData 意思無區別,只統一用一個
  • 別寫冗餘的名字,變量名別帶variable、表名別帶table

3.使用可搜索的名稱

  • 單字母名稱和數字常量很難在上下文中找出。名稱長短應與其做用域大小相對應,越是頻繁出現的變量名稱得越容易搜索(越長)

4.命名時避免使用編碼

  • 把類型和做用域編碼進名稱裏增長了解碼負擔。意味着新人除了瞭解代碼邏輯以外,還須要學習這種編碼語言
  • 別使用匈牙利語標記法(格式:[Prefix]-BaseTag-Name 其中BaseTag是數據類型的縮寫,Name是變量名字),純屬多餘
  • 沒必要用"m_"前綴來代表成員變量
  • 接口和實現別在名稱中編碼。接口名IShapeFactory前導"I"是廢話。若是接口和實現必須選一個編碼,寧肯選實現,ShapeFactoryImp都比對接口名稱編碼來的好

5.類名、方法名

  • 類名應當是名詞或名詞短語,方法名應當是動詞或動詞短語

6.每一個概念用一個詞

  • fetch、retrieve、get約定一個一直用便可

7.別用雙關語

  • add方法通常語義是:根據兩個值得到一個新的值。若是要把單個值加入到某個集合,用insertappend命名更好,這裏用add就是雙關語了。

8.添加有意義的語境

  • 不多有名稱能自我說明,須要用良好命名的類、函數、或者命名空間來放置名稱,給讀者提供語境,若是作不到的話,給名稱添加前綴就是最後一招了。

函數

1.越短小越好

  • if/else/while語句的代碼塊應該只有一行,該行應該是一個函數調用語句。
  • 函數的縮進層級不該該多於一層或兩層。

2.只作一件事

  • 若是函數只是作了該函數名下同一抽象層上的步驟,則函數只作了一件事。
  • 要判斷函數是否不止作了一件事,就是要看是否能再拆出一個函數

3.每一個函數一個抽象層級

4.switch語句

  • 把switch埋在較低的抽象層級,通常能夠放在抽象工廠底下,用於建立多態對象。

5.使用描述性的名稱

  • 函數越短小、功能越集中,就越便於取個好名字。
  • 別懼怕長名稱,長而具備描述性的名稱,要比短而使人費解的名稱好,要比描述性的長註釋好。
  • 別懼怕花時間取名字。

6.函數參數

  • 參數越少越好,0參數最好,儘可能避免用三個以上參數
  • 參數越多,編寫組合參數的測試用例就越困難
  • 別用標識參數,向函數傳入bool值是很差的,這意味着函數不止作一件事。能夠將此函數拆成兩個。
  • 若是函數須要兩個、三個或者三個以上參數,就說明其中一些參數應該封裝成類了
  • 將參數的順序編碼進函數名,減輕記憶參數順序的負擔,例如,assertExpectedEqualsActual(expected, actual)

7.反作用(函數在正常工做任務以外對外部環境所施加的影響)

  • 檢查密碼而且初始化session的方法 命名爲checkPasswordAndInitializeSession而非checkPassword,即便違反單一職責原則也不要有反作用
  • 避免使用"輸出參數",若是函數必須修改某種狀態,就修改所屬對象的狀態吧

8.設置(寫)和查詢(讀)分離

  • if(set("username", "unclebob")) { ... } 的含義模糊不清。應該改成:算法

    if (attributeExists("username")) { 
        setAttribute("username", "unclebob");
        ...
    }

9.使用異常代替返回錯誤碼

  • 返回錯誤碼會要求調用者馬上處理錯誤,從而引發深層次的嵌套結構:編程

    if (deletePate(page) == E_OK) {
        if (xxx() == E_OK) {
            if (yyy() == E_OK) {
                log();
            } else {
                log();
            }
        } else {
            log();
        }
    } else {
        log();
    }
  • 使用異常機制:session

    try {
        deletePage();
        xxx();
        yyy();
    } catch (Exception e) {
        log(e->getMessage());
    }
  • try/catch代碼塊醜陋不堪,因此最好把try和catch代碼塊的主體抽離出來,單獨造成函數數據結構

    try {
        do();
    } catch (Exception e) {
        handle();
    }
  • 函數只作一件事,錯誤處理就是一件事。若是關鍵字try在某個函數中存在,它就該是函數的第一個單詞,並且catch代碼塊後面也不應有其餘內容。
  • 定義錯誤碼的class須要添加新的錯誤碼時,全部用到錯誤碼class的其餘class都得從新編譯和部署。但若是使用異常而非錯誤碼,新異常能夠從異常class派生出來,無需從新編譯或部署。這也是開放閉合原則(對擴展開放,對修改封閉)的範例。

10.不要寫重複代碼

  • 重複是軟件中一切邪惡的根源。當算法改變時須要修改多處地方

11.結構化編程

  • 只要函數保持短小,偶爾出現的return、break、continue語句沒有壞處,甚至還比單入單出原則更具備表達力。goto只有在大函數裏纔有道理,應該儘可能避免使用。

12.如何寫出這樣的函數

  • 並不須要一開始就按照這些規則寫函數,沒人作獲得。想些什麼就寫什麼,而後再打磨這些代碼,按照這些規則組裝函數。

註釋

  • 若編程語言足夠有表現力,咱們就不須要註釋。
  • 註釋老是一種失敗。
  • 代碼在演化,註釋卻不老是隨之變更。
  • 不許確的註釋比沒註釋壞的多。

1.用代碼來闡述

  • 建立一個與註釋所言同一事物的函數便可併發

    // check to see if the employee is eligible for full benefits
    if ((employee.falgs & HOURLY_FLAG) && (employee.age > 65))

    應替換爲app

    if (employee.isEligibleForFullBenefits())

2.好註釋

  • 法律信息
  • 提供基本信息,如解釋某個抽象方法的返回值
  • 對意圖的解釋,反應了做者某個決定後面的意圖
  • 闡釋。把某些晦澀的參數或者返回值的意義翻譯成可讀的形式(更好的方法是讓它們自身變得足夠清晰,可是相似標準庫的代碼咱們沒法修改):less

    if (b.compareTo(a) == 1) //b > a
  • 警示。// don't run unless you have some time to kill
  • TODO註釋
  • 放大 一些看似不合理之物 的重要性

3.壞註釋

  • 自言自語
  • 多餘的註釋。把邏輯在註釋裏寫一遍不能比代碼提供更多信息,讀它不比讀代碼簡單。一目瞭然的成員變量別加註釋,顯得不少餘。
  • 誤導性註釋
  • 遵循規矩的註釋。每一個函數都加註釋、每一個變量都加註釋是愚蠢的
  • 日誌式註釋。有了代碼版本控制工具,沒必要在文件開頭維護修改時間、修改人這類日誌式的註釋
  • 能用函數或者變量表示就別用註釋:

    // does the module from the global list <mod> 
    // depend on the subsystem we are part of?
    if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())

    能夠改成

    ArrayList moduleDependees = smodule.getDependSubsystems();
    String ourSubSystem = subSysMod.getSubSystem();
    if (moduleDependees.contains(ourSubSystem))
  • 位置標記。標記多了會被咱們忽略掉:
    ///////////////////// Actions //////////////////////////
  • 右括號註釋

    try {
        while () {
            if () {
                ...
            } // if
            ...
        } // while
        ...
    } // try

    若是你想標記右括號,其實應該作的是縮短函數

  • 署名 /* add by rick */ 源代碼控制工具會記住你,署名註釋跟不上代碼的演變。
  • 註釋掉的代碼。會致使看到這段代碼其餘人不敢刪除
  • 信息過多。別在註釋中添加有趣的歷史話題或者無關的細節
  • 沒解釋清楚的註釋。註釋的做用是解釋未能自行解釋的代碼,若是註釋自己還須要解釋就太遺憾了。
  • 短函數的函數頭註釋。爲短函數選個好名字比函數頭註釋要好。
  • 非公共API函數的javadoc/phpdoc註釋。

代碼格式

1.垂直格式

  • 短文件比長文件更易於理解。平均200行,最多不超過500行的單個文件能夠構造出色的系統
  • 區隔: 封包聲明、導入聲明、每一個函數之間,都用空白行分隔開,空白行下面標識着新的獨立概念
  • 靠近: 緊密相關的代碼應該互相靠近,例如一個類裏的屬性之間別用空白行隔開
  • 變量聲明應儘量靠近其使用位置:循環中的控制變量應該老是在循環語句中聲明。
  • 成員變量應該放在類的頂部聲明,不要四處放置
  • 若是某個函數調用了另一個,就應該把它們放在一塊兒。咱們但願底層細節最後展示出來,不用沉溺於細節,因此調用者儘量放在被調用者之上。
  • 執行同一基礎任務的幾個函數應該放在一塊兒。

2.水平格式

  • 一行代碼沒必要死守80字符的上限,偶爾到達100字符不超過120字符便可。
  • 區隔與靠近: 空格強調左右兩邊的分割。賦值運算符兩邊加空格,函數名與左圓括號之間不加空格,乘法運算符在與加減法運算符組合時不用加空格(a*b - c)
  • 沒必要水平對齊。例如聲明一堆成員變量時,各行不用每個單詞都對齊。
  • 短小的if、while、函數裏最好也不要違反縮進規則,不要這樣:
    if (xx == yy) z = 1;

對象和數據結構

對象:暴露行爲(接口),隱藏數據(私有變量)
數據結構:沒有明顯的行爲(接口),暴露數據。如DTO(Data Transfer Objects)、Entity

1.對象與數據結構的反對稱性(參考書中代碼清單6-5)

  • 使用數據結構便於在不改動如今數據結構的前提下添加新函數;使用對象便於在不改動既有函數的前提下添加新類
  • 使用數據結構難以添加新數據結構,由於必須修改全部函數;使用對象難以添加新函數,由於必須修改全部類。
  • 萬物皆對象只是個傳說,有時候咱們也會在簡單數據結構上作一些過程式的操做。

2.Demeter定律(最少知識原則)

  • 模塊不該該瞭解它所操做對象的內部情形
  • class C的方法f只應該調用如下對象的方法:

    • C
    • 在方法f裏建立的對象
    • 做爲參數傳遞給方法f的對象
    • C持有的對象
  • 方法不該調用 由任何函數返回的對象 的方法。下面的代碼違反了demeter定律:
    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
  • 一個簡單例子是,人能夠命令一條狗行走(walk),可是不該該直接指揮狗的腿行走,應該由狗去指揮控制它的腿如何行走。

異常處理

異常處理很重要,但若是異常處理四處分散在代碼中 致使邏輯模糊不清,它就是錯的。

1.使用異常而不是返回錯誤碼

  • 若是使用錯誤碼,調用者必須在函數返回時馬上處理錯誤,但這很容易被咱們忘記。
  • 錯誤碼一般會致使嵌套if else

2.先寫try-catch語句

  • 當編寫可能會拋異常的代碼時,先寫好try-catch再往裏堆邏輯

3.使用unchecked exception(java獨有)

4.在catch裏儘量的記錄錯誤信息,記錄失敗的操做以及失敗的類型。

5.根據調用者的須要 定義不一樣的異常處理類

  • 同一個try裏catch多個不一樣exception,可是catch處理的事(打日誌等)是一致的,能夠考慮用函數打包一下這個異常處理,針對不一樣異常僅需throw成同一個異常而不作任何處理,在外層catch時統一處理(打日誌等)。若是僅想捕獲一部分異常而放過其餘異常,就使用不一樣的函數打包這個異常處理

6.特例模式: 建立一個類來處理特例。

try {
    MealExpendses expenses = expenseRepotDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();// 若是消耗了餐食,計入總額
} catch (MealExpensesNotFound e) {
    m_total += getMealPeDiem();// 若是沒消耗,將員工補貼計入總額
}

異常打斷了業務邏輯。能夠在getMeals()裏不拋異常,而是在沒消耗餐食的時候返回一個特殊的MealExpense對象(PerdiemMealExpense),複寫getTotal()方法。

MealExpendses expenses = expenseRepotDAO.getMeals(employee.getID());
m_total += expenses.getTotal();

publc class PerDiemMealExpenses implements MealExpenses {
    public int getTotal() {
        //return xxx; //返回員工補貼
    }
}

7.別返回null值

  • 返回null值只要一處沒檢查null,應用程序就會失敗
  • 當想返回null值的時候,能夠試試拋出異常,或者返回特例模式的對象。

8.別傳遞null值

  • 在方法中傳遞null值是一種糟糕的作法,應該儘可能避免。
  • 在方法裏用if或assert過濾null值參數,可是仍是會出現運行時錯誤,沒有良好的辦法對付調動者意外傳入的null值,恰當的作法就是禁止傳入null值。

邊界

將第三方代碼乾淨利落地整合進本身的代碼中

1.避免公共API返回邊界接口,或者將邊界接口做爲參數傳遞給API。將邊界保留在近親類中。

2.不要在生產代碼中試驗新東西,而是編寫測試來理解第三方代碼。

3.避免咱們的代碼過多地瞭解第三方代碼中的特定信息。

單元測試

1.TDD(Test-driven development)三定律

  • First Law: You may not write production code until you have written a failing unit test.
  • Second Law: You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
  • Third Law: You may not write more production code than is sufficient to pass the currently failing test.

2.保持測試整潔

  • 髒測試等同於沒測試,測試代碼越髒 生產代碼越難修改。
  • 測試代碼和生產代碼同樣重要。
  • 整潔的測試代碼最應具備的要素是:整潔性測試代碼中不要有大量重複代碼的調用。

3.每一個測試一個斷言

  • 每一個測試函數有且僅有一個斷言語句。
  • 每一個測試函數中只測試一個概念。

4.整潔的測試依賴於FIRST規則

  • fast: 測試代碼應該可以快速運行,由於咱們須要頻繁運行它。
  • independent: 測試應該相互獨立,某個測試不該該依賴上一個測試的結果,測試能夠以任何順序進行。
  • repeatable: 測試應能夠在任何環境中經過
  • self-validating: 測試應該有bool值輸出,不該經過查看日誌來確認測試結果,不該手工對比兩個文本文件確認測試結果。
  • timely: 及時編寫測試代碼。單元測試應該在生產代碼以前編寫,不然生產代碼會變得難以測試。

1.類的結構組織(順序):

  • 公共靜態常量
  • 私有靜態變量
  • 私有實體變量
  • 公共函數
  • 私有工具函數

2.類應該短小

  • 對於函數咱們計算代碼行數衡量大小,對於類咱們使用權責來衡量。
  • 類的名稱應當描述其權責。類的命名是判斷類長度的第一個手段,若是沒法爲某個類命以準確的名稱,這個類就太長了。類名包含模糊的詞彙,如Processor、Manager、Super,這種現象就說明有不恰當的*權責彙集狀況。
  • 單一權責原則: 類或者模塊應該有一個權責——只有一條修改的理由(A class should have only one reason to change.)。
  • 系統應該由許多短小的類而不是少許巨大的類組成。
  • 類應該只有少許的實體變量,若是一個類中每一個實體變量都被每一個方法所使用,則說明該類具備最大的內聚性。建立最大化的內聚類不太現實,可是應該以高內聚爲目標,內聚性越高說明類中的方法和變量互相依賴、互相結合造成一個邏輯總體
  • 保持內聚性就會獲得許多短小的類。若是你想把一個大函數的某一小部分拆解成單獨的函數,拆解出的函數使用了大函數中的4個變量,沒必要將4個變量做爲參數傳遞到新函數裏,僅需將這4個變量提高爲大函數所在類的實體變量,可是這麼作卻由於實體變量的增多而喪失了類的內聚性,更好多作法是讓這4個變量拆出來,擁有本身的類。將大函數拆解成小函數每每是將類拆分爲小類的時機。

3.爲修改而組織(參考書中代碼清單10-九、10-10)

  • 類應當對擴展開放,對修改封閉(開放閉合原則)
  • 在理想系統中,咱們經過擴展系統而非修改現有代碼來添加新特性。

系統

1.將系統的構造與使用分開

  • 將所有構造過程搬遷到main或者被稱之爲main的模塊中,涉及系統其他部分時,假設全部對象都已經正確構造。
  • 有時應用程序也須要負責肯定什麼時候建立對象,咱們可使用抽象工廠模式讓應用自行控制什麼時候建立對象,但構造能力在工廠實現類裏,與使用部分分開。
  • 依賴注入(DI),控制反轉(IoC)是分離構造與使用的強大機制。

emergent design

四條規矩幫助你建立優良的設計

1.運行全部測試

  • 緊耦合的代碼難以編寫測試,測試寫的越多,就越會遵循依賴注入之類的規則,使代碼加減小耦合。
  • 測試消除了對清理代碼就會破壞代碼的恐懼。

2.消除重複

  • 兩個方法提取共性到新方法中,新方法分解到另外的類裏,從而提高其可見性。
  • 模板方法模式是消除重複的通用技巧

    // 原始邏輯
    public class VacationPolicy() {
        public void accrueUSDivisionVacation() {
            //do x;
            //do US y;
            //do z;
        }
        public void accrueEUDivisionVacation() {
            //do x;
            //do EU y;
            //do z;
        }
    }
    
    // 模板方法模式重構以後
    abstract public class VacationPolicy {
        public void accrueVacation() {
            x();
            y();
            z();
        }
        private void x() {
            //do x;
        }
        abstract protected void y() {
        
        }
        private void z() {
            //do z;
        }
    }
    public class USVacationPolicy extends VacationPolicy {
        protected void y() {
            //do US y;
        }
    }
    public class EUVacationPolicy extends VacationPolicy {
        protected void y() {
            //do EU y;
        }
    }

3.表達意圖

  • 做者把代碼寫的越清晰,其餘人理解代碼就越快。
  • 太多時候咱們深刻於要解決的問題中,寫出能工做的代碼以後,就轉移到下一個問題上,沒有下足功夫調整代碼讓後來者易於閱讀。多少尊重一下咱們的手藝,花一點點時間在每一個函數和類上。

4.儘量少的類和方法

  • 爲了保持類和函數的短小,咱們可能會早出太多細小的類和方法。
  • 類和方法數量太多,有時是由毫無心義的教條主義致使的。

5.以上4條規則優先級依次遞減。重要的是測試、消除重複、表達意圖。

併發編程

1.防護併發代碼問題的原則與技巧

  • 遵循單一職責原則。分離併發代碼與非併發代碼
  • 限制臨界區數量、限制對共享數據的訪問。
  • 避免使用共享數據,使用對象的副本。
  • 線程儘量地獨立,不與其餘線程共享數據。
相關文章
相關標籤/搜索