第一章 整潔代碼
"我可以列出我留意到的整潔代碼的所有特點,但其中有一條是根本性的,整潔的代碼總是看起來像是某位特別在意他的人寫的.幾乎沒有改進的余地,代碼作者設么都想到了,如果你企圖改進它,總會回到原點,贊嘆某人留給你的代碼" ---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只應該調用以下對象的方法
- C
- 由f創建的對象
- 作為參數傳遞給f的對象
- 由C的實體變量持有的對象
方法不應該調用任何由任何函數返回的對象的方法,一個很經典的比喻就是:不要和陌生人說話,只和朋友說話,還有一個比喻就是,人可以命令狗走路,但人不能直接命令狗的腿去走路,而應該由狗來控制自己的腿去走路.
如果代碼中充斥着"."構成的鏈式編程風格的代碼.意味着該定律被被違反了.當然如果調用者是數據結構,沒有行為只有數據,就可以忽略.