我讀<代碼整潔之道>--讀書筆記整理


第一章 整潔代碼

  "我可以列出我留意到的整潔代碼的所有特點,但其中有一條是根本性的,整潔的代碼總是看起來像是某位特別在意他的人寫的.幾乎沒有改進的余地,代碼作者設么都想到了,如果你企圖改進它,總會回到原點,贊嘆某人留給你的代碼" ---Michael Feathers 

  "整潔的代碼只做好一件事" ---Bjarne Stroustrup 

第二章 有意義的命名

  1 變量名要名副其實

  即顧名思義,變量.函數或者類的名稱應該能告訴你,它為什么會存在,他做什么事,應該怎么用,名稱應該盡量不需要注釋補充亦能表示其含義.

  類名和對象應該是名詞或者名詞短語,方法名應該是動詞或者動詞短語.

  2 避免誤導

//不好的命名
public boolean stateMachine(int smStatus) {
//...
}
public boolean doAction() {
//...
}
//好的命名
public boolean moveStateMachineToStatus(int smStatus) {
//...
}
public boolean doNextStepInProviderTechnique() {
//...
}

  3 做有意義的區分

    4 使用能讀得出來的名稱

  名字中不要有冗余,定義一個List類型變量,xxxList不如xxx的復數形式簡潔,List字眼就屬於冗余字段.

   5 命名布爾變量

   使用常見的布爾含義的命名: 如done;error;found;success;

   可以添加前綴is,has來轉換布爾,但是isSuccess不如success可讀性好

   避免使用負向變量命名:如notFound,設想if(!notFound)是不是覺得別扭.負向條件比正向條件更加難於理解,應該盡量避免.

第三章    函數  

  1 短小

  這里是指一個函數的代碼量,不是指函數名稱要短小.函數應該是在做一件從語義上無法再次拆解的事情.

  2 一個函數只做一件事

  函數應該做一件事,做好這一件事,而且只做這一件事.

寫多長的函數比較合適?

  約定:不超過一屏幕,長度並不是問題,關鍵在於函數名稱和函數體之前的語義距離,函數應該很短,也可以較長.只要保證函數只做這一件事.

拆分函數的技巧?

  尋找注釋,如果函數中某部分代碼前面有一行注釋,一般可以把這段代碼替換成一個函數.此外,當感覺需要注釋來說明一些什么的時候,就要考慮把要說明的這些東西封裝成一個獨立的函數.

  3 每個函數一個抽象層級

  這一點需要多一些說明,要確保函數只做一件事,函數的語句要在同一個抽象層級上.

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String fileId = request.getParameter("FILEID"); 
    if (fileId == null) {
        throw new ServletException("Invalid FileId"); }
    String partNum = request.getParameter("PART"); int part = 1;
    if (partNum != null) {
        part = Integer.valueOf(partNum).intValue(); }
        boolean isLast = "Y".equals(request.getParameter("LAST"));
        boolean getName="Y".equals(request.getParameter("GETNAME"));
        String fileName = 
        MitoDownloadCache.INSTANCE.getFileName(fileId, part); 
   if (fileName== null) {
        throw new ServletException("Invalid FileName"); }
    MitoConfig mitoCfg = new MitoConfig();
    File file = new File(mitoCfg.getPrintClientDataPath()+"/"+fileName); 
   if (!file.exists()) {
        throw new ServletException("File " + file.getAbsolutePath() + " not found");
}
    if (getName) {
        doDownloadFilename(request, response, file.getName());
} else {
    if (isLast) {
        MitoDownloadCache.INSTANCE.removeFileList(fileId); }
        doDownload(request, response, file); file.delete();
   }

  上面這個方法是我copy來的,不用研究這個函數的內容,只看一下結構,這個函數中既有比較高抽象層次的函數doDownloadFilename,doDownload,還有getFileName這種位於中間抽象層次的函數,更有很多像equals等較低抽象層次的概念.混雜了不同抽象層次,讀起來就比較吃力,現在來改造一下:

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    File file = fileToDownLoad(request); 
    if (downloadByName(request)) {
        doDownloadFilename(request, response, file.getName()); 
    }  else {
        removeFromCacheIfLast(request);
        doDownload(request, response, fileToDownLoad(request)); 
        file.delete();
} }

  將功能拆分成紅能更小更細的函數,封裝成私有方法,起一個見名知義的函數名,使整個方法能夠位於同一個抽象層次,然后在每個函數內部都跟着下一級抽象層次的函數,這樣能給閱讀帶來極大的便利.(上面函數中私有方法這里不再展示)

  4 使用描述性名稱

   函數越小,功能越集中,就越容易取一個合適的名字.另外不要害怕長名稱,描述性很好的長名稱比含糊不清的短名稱要好.不要害怕花時間給函數起名字,起一個合適的名字本身就是編程的一部分.

  5 函數輸入參數

  最理想的函數參數是0個(無參函數),其次是一個,二個,應該盡量避免三個以上的參數.

      盡量避免標示參數,即用一個boolean類型作為入參,比如下面這個函數,eat(true);這樣讀起來就有些摸不着頭腦,可能讀者需要去查看eat方法eat(boolean isHuntry)才能比較理解這個函數.

      如果函數必須要三個或三個以上的參數,就說明其中一些參數需要封裝成類,如果很難封裝成類,就說明這個函數的設計有問題.

  6 函數輸出參數

  面向對象語言的方法應該盡量避免使用輸出參數,當然那些util類型的工具類除外.this也有輸出函數的意味,如果函數必須要修改某種狀態,就修改所屬對象的狀態吧.

  7 抽離try/catch代碼塊

   try/catch代碼塊比較丑陋,搞亂了代碼結構,最好把try/catch代碼塊的主體部分抽離出來,另外形成函數.

函數應該只做一件事,錯誤處理就是一件事,因此,如果處理錯誤(帶try/catch)的函數應該只處理錯誤,該函數只有try/catch不應該再包含其他內容.

  8 使用更少的代碼

//實例1:不好的代碼格式
public int size() {
  if (root == null) {
    return 0;
  }else {
    return root.numSiblings(); 
  }
}
//實例1:好的代碼風格
public int size() {
  return root != null ?root.numSiblings() : 0; 
}
//實例2 不好的代碼
   List<Integer> list = Arrays.asList(1, 2, 3));
   list = Collections.unmodifieableList(list); 
   return list;
//實例2 好的代碼
   import static java.util.Arrays.*;
   import static java.util.Collections.*;
   ...

   return unmodifiableList(asList(1, 2, 3))

實例2中給出的例子有些函數式編程的味道,這里只是提供一種思路,但大多數情況我們一般寫出來的都是上面的格式.

第四章 注釋

  不寫無意義的注釋,什么是無意義的注釋,如下:

  //當我寫這段代碼的時候,只有老天和我自己知道我在做什么

     //現在,只剩老天知道了

       ...

    //我不對以下代碼負責

   //使他們逼我寫的,是違背我意願的  

  注釋盡量做到簡潔

  最好不寫注釋

  代碼結合命名應該良好的描述功能,寫注釋說明表達意圖的失敗以及對代碼功能的不自信,糟糕的代碼是注釋存在的動機之一.

  什么是必須要寫的注釋

    警告他人 版權協議 公共API 

第五章 格式 

  1 格式的目的

  代碼格式很重要,代碼格式關乎溝通,而溝通是開發者的頭等大事.

  2 垂直格式

    閱讀代碼都是從上往下,從左往右讀的.在一個類中,在封包聲明,導入聲明,和每個函數之前都應該使用一個空白行來隔開.這可以給閱讀帶來愉悅的感受.

  空白行隔開了概念,每個空白行都是一條線索,標示出一個新的獨立的概念,閱讀代碼的時候,我們的目光總是容易停留在空白行的前一行或后一行.

    變量聲明 :變量聲明應該盡量靠近其使用位置.類變量應該聲明在類的頂部,

    相關函數:某函數A調用了函數B,應該盡量把他們放到一起,讓A位於B的上方.

    概念相關:概念相關的代碼應該放到一起,相關性越強,他們之間的距離就應該越短.

    避免過度縮進(代碼梯子):

//不好的代碼風格
public void startCharging() {
   if (customer.hasFunds()) {
      if (!station.isCharging()) {
         if (!station.currentlyBooked()) { 
            reallyStartCharging();
            return; 
         }
      }
   }
   throw new UnableToStartException(); 
}
//好的代碼風格
public void startCharging() {
   if (!customer.hasFunds()) throw new UnableToStartException
   if (station.isCharging()) throw new UnableToStartException
   if (station.currentlyBooked()) throw newUnableToStartException
    reallyStartCharging(); 
}

  3 橫向格式

  一行代碼應該多寬?

  應當遵循無需拖動滾動條到右邊的原則,每行代碼控制在100個字符以內是良好的風格.

第六章 對象和數據結構

  數據抽象

  當我們構建一個實體類時,會為變量設置為private,再為變量提供set/get方法,想一想為什么要這么做?實際上,即使變量都是私有,我們通過變量的set/get方法操作變量時,實現仍然被曝光了,那為何不直接將變量設置為public,然后直接操作變量呢?

  隱藏實現並非只是在變量之間放上一個函數層那么簡單,更大的意義在於抽象,類並不是簡單的用set/get方法將變量推向外間,而是暴露抽象接口,使得用戶無需了解數據的實現就能夠操作數據本體.  

  德墨忒爾定律(The Law of Demeter)

  得墨特定律認為,模塊不應該了解它所操作的對象的內部情況.對象隱藏數據,曝光操作.

  類C的方法f只應該調用以下對象的方法

  1. C
  2. 由f創建的對象
  3. 作為參數傳遞給f的對象 
  4. 由C的實體變量持有的對象

  方法不應該調用任何由任何函數返回的對象的方法,一個很經典的比喻就是:不要和陌生人說話,只和朋友說話,還有一個比喻就是,人可以命令狗走路,但人不能直接命令狗的腿去走路,而應該由狗來控制自己的腿去走路.

  如果代碼中充斥着"."構成的鏈式編程風格的代碼.意味着該定律被被違反了.當然如果調用者是數據結構,沒有行為只有數據,就可以忽略.

 

 

 ...未完


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM