1. 程式人生 > >我讀-程式碼整潔之道---讀書筆記整理

我讀-程式碼整潔之道---讀書筆記整理

第一章 整潔程式碼

  "我可以列出我留意到的整潔程式碼的所有特點,但其中有一條是根本性的,整潔的程式碼總是看起來像是某位特別在意他的人寫的.幾乎沒有改進的餘地,程式碼作者設麼都想到了,如果你企圖改進它,總會回到原點,讚歎某人留給你的程式碼" ---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的實體變數持有的物件

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

  如果程式碼中充斥著"."構成的鏈式程式設計風格的程式碼.意味著該定律被被違反了.當然如果呼叫者是資料結構,沒有行為只有資料,就可以忽略.

 ...未完