我讀-程式碼整潔之道---讀書筆記整理
第一章 整潔程式碼
"我可以列出我留意到的整潔程式碼的所有特點,但其中有一條是根本性的,整潔的程式碼總是看起來像是某位特別在意他的人寫的.幾乎沒有改進的餘地,程式碼作者設麼都想到了,如果你企圖改進它,總會回到原點,讚歎某人留給你的程式碼" ---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的複數形式簡潔
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的實體變數持有的物件
方法不應該呼叫任何由任何函式返回的物件的方法,一個很經典的比喻就是:不要和陌生人說話,只和朋友說話,還有一個比喻就是,人可以命令狗走路,但人不能直接命令狗的腿去走路,而應該由狗來控制自己的腿去走路.
如果程式碼中充斥著"."構成的鏈式程式設計風格的程式碼.意味著該定律被被違反了.當然如果呼叫者是資料結構,沒有行為只有資料,就可以忽略.