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

大師級的程序員把系統看成故事來說,而不是看成程序來寫。

有意義的命名

作有意義的區分

  • 若是同一做用範圍內兩樣不一樣的東西不能重名,那其意思也應該不一樣纔對。那麼這兩樣東西應該取不一樣的名字而不是以數字區分。若是如下代碼參數名改成 sourcedestination,這個函數就會像樣許多java

    public static void copyChars(char a1[], char a2[]){
        for(int i = 0; i < a1.length; i++){
            a2[i] = a1[i];
        }
    }
  • 以數字系列命名(a一、a二、a3...)是依意義命名的對立面。這樣的名稱徹底沒有提供正確的信息
  • Info和Data就像a、an和the同樣,是意義含糊的廢話。但有時候只要體現出有意義的區分,使用a和the這樣的前綴就沒錯
  • 廢話都是冗餘的。Variable一詞永遠不該當出如今變量名中。Table一詞永遠不該該出如今表名中

避免思惟映射

例如循環中的 i、j、k 等單字母名稱不是個好選擇;讀者必須在腦中將它映射爲真實概念。最好用 filter、map 等方法代替 for循環程序員

類名與方法名

  • 類名和對象名應該是名稱或名詞短語
  • 方法名應該是動詞或動詞短語

每一個概念對應一個詞

例如 fetchretriveget 表達同一個意思,應該選定一個,而後在各個類中使用相同的方法名。算法

別用雙關語

避免將同一單詞用於不一樣的目的。同一術語用於不一樣概念,基本上就是雙關語了。app

使用解決方案領域名稱

記住,只有程序員纔會讀你的代碼。因此,儘管用那些計算機科學(Computer Science,CS)術語、算法名、模式名等。函數

動詞與關鍵字

給函數取個好名字,能較好地解釋函數的意圖,以及參數的順序和意圖。fetch

  • 對於一元函數,函數和參數應當造成一種很是良好的動詞/名詞形式。編碼

    // good
    write(name)
    // better
    // 更具體,它告訴咱們,"name"是一個"field"
    writeField(name)
  • 函數名稱的關鍵字(keyword)形式。使用這種形式,把參數的名稱編碼成了函數名spa

    // bad
    assertEqual(expected, actual);
    
    // good 
    // 這大大減輕了記憶參數順序的負擔
    assertExpectedEqualsActual(expected, actual);

函數

短小

  • 函數第一條規則是要短小。第二條規則不是要短小。越短小越好,20行封頂
  • ifelsewhile等語句,其中的代碼應該只有一行。該行大抵應該是一個函數調用語句。由於塊內的函數擁有較具體說明性的名稱,從而增長了文檔上的價值

只作一件事

確保函數不能被再拆分日誌

參數

最理想的參數數量是零,其次是一,再次是二,應儘可能避免三code

  • 不要傳遞標識參數,標識參數大聲宣佈函數不是作一件事。若是標識爲 true 將會這樣,標識爲 false 則會那樣
  • 二元函數:有些時候兩個參數正好。例如 Point p = new Point(0, 0);由於點天生擁有兩個參數。但大多數狀況下應該儘可能利用一些機制將二元函數轉換成一元函數。例如,把writeField 方法寫成outputStream的成員之一

    // bad
    writeField(outputStream, name);
    // good
    outputStream.writeFiled(name);
  • 參數對象:若是函數看起來須要兩個、三個、或三個以上參數,就說明其中一些應該封裝爲類了

    // bad
    Circle makeCircle(double x, double y, double, radius);
    // good
    Circle makeCircle(Point center, double radius);

    從參數封裝成對象,從而減小參數數量,看起來像是在做弊,但實則並不是如此。當一組參數被共同傳遞,就像上例中的x和y那樣,每每就是該有本身名稱的某個概念的一部分

無反作用

  • 確保函數功能就像函數名描述的同樣,不要作函數名描述之外的事情。應該爲起一個更能描述函數功能的函數名

    public UserValidator {
        private Cyptographer cyptographer;
        
        public boolean checkPassword(String userName, String password) {
            User user = UserGateway.findByName(userName);
            if(user != User.NULL) {
                String codePhrase = user.getPhraseEncodedByPassword();
                String phrase = cyptographer.decrypt(codePhrase, password);
                if("Valid Password".equals(phrase)) {
                    // 反作用在於對這個調用
                    // checkPassword函數,顧名思義,就是用來檢查密碼。該名稱並未暗示它會
                    // 初始化該次會話。能夠重命名爲 checkPasswordAndIntializeSession
                    Session.initialize();
                    return true;
                }
            }
            return false;
        }
    }
  • 普通而言,應該避免使用輸出參數,若是函數必需要修改某種狀態,就修改所屬對象的狀態

    // bad
    // 讀者會弄不清s是輸入參數仍是輸出參數
    // 也會弄不清這函數是把s添加到什麼東西后面,仍是把什麼東西添加到s後面
    appendFooter(s); 
    // 函數簽名
    public void appendFooter(StringBuffer report){}
    
    // good
    report.appendFooter();

分隔指令與詢問

函數要行作什麼事( 例如 user.setName('xxx') )、要麼回答什麼事( 例如 user.isVip() )。一個函數裏不要把兩件事都幹了。

如何寫出好函數

  • 分解函數
  • 修更名稱
  • 消除重複

註釋

好的註釋

  • 法律信息
  • 警示性註釋
  • TODO 註釋雖好,但也要按期查看,刪除再也不須要的

壞的註釋

  • 循規式註釋。 例如每一個函數都要有Javadoc或每一個變量都要有註釋的規矩全然是愚蠢好笑的。這類註釋徒然讓代碼變得散亂
  • 註釋掉的代碼。 如今已經有源代碼控制系統,不要的代碼應該當即刪掉
  • 不明顯的聯繫。 註釋及其描述的代碼之間的聯繫應該顯而易見。註釋的做用是解釋未能自行解釋的代碼。若是註釋自己還須要解釋,就太遺憾了
  • 切斷代碼間的聯繫

    // bad
    public class ReportConfig {
        //
        // The class name of the reporter listener
        //
        private String m_className;
        
        //
        //The properties of the reporter listener
        //
        private List<Property> m_properties = new ArrayList<Property>();
        
        public void addProperty(Property property) {
            m_properties.add(property);
        }
    }
    
    // good
    public class ReportConfig {
        private String m_className;
        private List<Property> m_properties = new ArrayList<Property>();
        
        public void addProperty(Property property) {
            m_properties.add(property);
        }
    }

格式

垂直距離

  • 變量聲名。 大多數狀況下變量聲名應該儘量靠近其使用的位置。可是在類內,變量聲名應該統一放在頂部,由於這樣讀者能夠一眼看出這個類有什麼變量。
  • 相關函數。 若某個函數調用了另外一個函數,就應該把它們放到一塊兒,並且調用者應該儘量放在被調用者上面。這樣程序就有個天然順序。
  • 概念相關。 概念相關的代碼應該放到一塊兒。相關性越強,彼此之間的距離就該越短

    public    class Assert {
        static public void assertTrue(String message, boolean codition(){}
        static public void assertTrue(boolean codition(){}
        static public void assertFalse(String message, boolean codition(){}
        // .....
    }

    這些函數有關極強的概念相關性,由於他們擁有共同的命名模式,執行同一基礎任務的不一樣變種。互相調用是第二位的。即使沒有互相調用。也應該放在一塊兒。

更多例子查看 p79 -- 5.25 垂直順序

錯誤處理

抽離Try/Catch代碼塊

函數應該只作一件事,錯誤處理就是一件事。

// bad
public void delete(Page page) {
    try{
        deletePage(page);
        registery.deleteReference(page.name);
        configKeys.deleteKey(page.name.makeKey();
    }catch(Expection e){
        logError(e);
    }
}

// good
public void delete(Page page) {
    try{
        // 將上例的操做,封裝到一個方法
        deletePageAndAllReferences(page);
    }catch(Expection e){
        logError(e);
    }
}

使用非受控異常

受控異常:Checked Exception(FileException、SQLException等),這類異常必須寫 try/catch,或者 throw拋出,不然編譯通不過。

非受控異常:Unchecked Exception,這類異常也叫作運行時異常(與非受控異常 字數相等),這類異常不須要 try/catch,也不須要 throw拋出。即便 throw 了,上層調用函數也非必須捕獲,編譯能經過。

受控異常的代價就是違反開放/閉合原則。若是你在方法中拋出受控異常,這意味着每一個調用該函數的函數都要修改,捕獲新異常,或在其簽名中添加合適的throw語句。對於通常的應用開發,受控異常依賴成本要高於收益成本,儘可能 try/catch 處理,不要拋出。

給出異常發生的環境說明

應建立信息充分的錯誤信息,並和異常一塊兒傳遞出去。在消息中,包括失敗的操做和失敗類型。若是你的應用程序有日誌系統,傳遞足夠的信息給catch塊,並記錄下來。

依調用者須要定義異常類

// bad
ACMEPort port = new ACMEPort(12);
try {
    port.open();
} catch(DeviceResponseException e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} catch(ATM1212UnlockedException e) {
    reportPortError(e);
    logger.log("Unlock exception",e);
} catch(GMXError e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} finally {
    // .....
}

經過打包調用API,確保它返回經過用異常類型,從而簡化代碼

// good
LocalPort port = new LocalPort(12);
try {
    port.open();
} catch(PortDeviceFailure e) {
    reportError(e);
    logger.log(e.getMessage(),e);
} finally {
    // .....
}

public class LocalPort{
 private ACMEPort innerPort;
 
 public LocalPort(int portNumber){
     innerPort = new ACMEPort(portNumber);
 }
 
 public open() {
  try {
       innerPort.open();
   } catch(DeviceResponseException e) {
           // 自定義的異常類
       throw new PortDeviceFailure(e);
   } catch(ATM1212UnlockedException e) {
       throw new PortDeviceFailure(e);
   } catch(GMXError e) {
       throw new PortDeviceFailure(e);
   }
 }
}

將第三方API打包是個良好的實踐手段。當你打包一個第三方API,你就下降了對它的依賴。

其餘

  • try catch語句塊的範圍不要太大,這樣不利於對異常的分析
  • 別返回null值,這樣能夠減小調用者的防護性檢測。與其返回null,不如拋出異常,或是返回特例對象(特例對象詳見 p101)
  • 別傳遞null值,傳遞null就要求被調用函數須要一系列防護性檢測,也就意味着程序有更大可能出錯
相關文章
相關標籤/搜索