代碼編程:(五)函數規範

關於函數,摘抄了他人的一些規則,加入了少許自己的理解。
1 函數一定要“短小”
函數第一個規則就是要短小。每一行儘量不要超過150字符;每個函數保持20行左右最佳,不要超過100行。
遇到if else while等分支/循環語句,儘量保持裏面的代碼塊只有一行,這行代碼應該是一個函數調用語句。
2 函數保持“只做一件事”
函數應該做一件事,只做一件事,做好這件事。
一個函數所實現的功能越多,牽扯的功能越多,越容易讓人難以閱讀和理解。
如果一個函數實在要同時完成多個工作,那麼多個工作應該再用其他函數實現,在該函數內部實現多個函數同一個層級的調用。這樣可以保持函數的短小,還可以通過函數名快速理解大抵處理的內容。
3 每個函數一個抽象層級
想要確保函數只做一件事情,那麼函數中的語句都要在同一抽象層級上。通過其他函數調用,使函數不要嵌套太多,嵌套越多,層級越多,理解起來越複雜。
如果讓層級減少,就需要提供更多函數切分層級;這些函數最好遵循“向下規則”.
要讓代碼擁有自頂而下的閱讀順序。讓每個函數後面都跟着位於下一抽象層級的函數,這樣就便於查看函數時候,遵循抽象層級,自上而下閱讀了。
4 switch語句
這個有點斤斤計較了,不過應該有一定道理。
傳送門:switch語句
5 使用描述性的名稱
比如獲取蔬菜信息的方法用vegetablesInfosFromServer名稱比getData要好很多。這樣別人通過函數名,就明白函數要做什麼事情。要遵循 沃德原則:“如果每個例程都讓你感到深合己意,那就是整潔的代碼”。
所以,起名字不要冗雜,但也別怕長。英語不好的小夥伴,還是要多提升英語能力,做到命名準確,言簡意賅。
6 函數參數
最理想零參數;其次一個參數;再次兩個參數;避免三個參數。除非有足夠的理由,才使用三個以上的參數。
在OC編程過程中,一點要在參數前面的方法中,描述參數的含義。OC不同於其他語言,每個參數都可以單獨在方法中對應描述。
7 無副作用
副作用是一種謊言。函數承諾只做一件事,但還是會做其他被藏起來的事。有時,它會對自己類中的變量做出未能預期的改動。有時,它會把變量搞成向函數傳遞的參數或是系統全局變量。無論哪種情況,都是具有破壞性的,會導致古怪的時序性耦合及順序依賴。
傳送門:無副作用
8 分割指令與詢問
函數要麼做什麼事,要麼回答什麼事,但是兩者不可兼得。函數應該修改某對象的狀態,或者返回該對象的有關信息。兩個都幹常常會導致混亂。
9 使用異常替代返回錯誤碼
這個單獨拉出來。

從指令式函數返回錯誤碼輕微違反了指令與詢問分隔的規則。它鼓勵了在if語句判斷中把指令當作表達式使用。

  1. if (deletePage(page) == E_OK) 

這不會引起動詞/形容詞混淆,但卻導致更深層次的嵌套結構。當返回錯誤碼時,就是在要求調用者立刻處理錯誤。

  1. if (deletePage(page) == E_OK) {  
  2.   if (registry.deleteReference(page.name) == E_OK) {  
  3.     if (configKeys.deleteKey(page.name.makeKey()) == E_OK){  
  4.       logger.log("page deleted");  
  5.     } else {  
  6.       logger.log("configKey not deleted");  
  7.     }  
  8.   } else {  
  9.     logger.log("deleteReference from registry failed");  
  10.   }  
  11. } else {  
  12.   logger.log("delete failed");  
  13.   return E_ERROR;  

另一方面,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化:

  1. try {  
  2.   deletePage(page);  
  3.   registry.deleteReference(page.name);  
  4.   configKeys.deleteKey(page.name.makeKey());  
  5. }  
  6. catch (Exception e) {  
  7.   logger.log(e.getMessage());  
抽離try catch塊:

Try/catch代碼塊醜陋不堪。它們搞亂了代碼結構,把錯誤處理與正常流程混爲一談。最好把try和catch代碼塊的主體部分抽離出來,另外形成函數。

  1. public void delete(Page page) {  
  2.   try {  
  3.     deletePageAndAllReferences(page);  
  4.   }  
  5.   catch (Exception e) {  
  6.     logError(e);  
  7.   }  
  8. }  
  9.  
  10. private void deletePageAndAllReferences(Page page) throws Exception {  
  11.   deletePage(page);  
  12.   registry.deleteReference(page.name);  
  13.   configKeys.deleteKey(page.name.makeKey());  
  14. }  
  15.  
  16. private void logError(Exception e) {  
  17.   logger.log(e.getMessage());  

在上例中,delete函數只與錯誤處理有關。很容易理解然後就忽略掉。deletePageAndAllReference函數只與完全刪除一個page有關。錯誤處理可以忽略掉。有了這樣美妙的區隔,代碼就更易於理解和修改了。


錯誤處理就是一件事

函數應該只做一件事。錯誤處理就是一件事。因此,處理錯誤的函數不該做其他事。這意味着(如上例所示)如果關鍵字try在某個函數中存在,它就該是這個函數的第一個單詞,而且在catch/finally代碼塊後面也不該有其他內容。


錯誤依賴磁鐵

返回錯誤碼通常暗示某處有個類或是枚舉,定義了所有錯誤碼。

  1. public enum Error {  
  2.    OK,  
  3.    INVALID,  
  4.    NO_SUCH,  
  5.    LOCKED,  
  6.    OUT_OF_RESOURCES,   
  7.    WAITING_FOR_EVENT;  

這樣的類就是一塊依賴磁鐵(dependency magnet);其他許多類都得導入和使用它。當Error枚舉修改時,所有這些其他的類都需要重新編譯和部署。 這對Error類造成了負面壓力。程序員不願增加新的錯誤代碼,因爲這樣他們就得重新構建和部署所有東西。於是他們就複用舊的錯誤碼,而不添加新的。

使用異常替代錯誤碼,新異常就可以從異常類派生出來,無需重新編譯或重新部署。


10 別重複做一件事
一段相同的代碼在不同地方多次重複出現是很可怕的,會讓代碼顯得臃腫,同時修改和維護也會造成很多負擔。

11 結構化編程
適當的使用break,continue,return語句。準確表達自己的函數邏輯。


發佈了3 篇原創文章 · 獲贊 14 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章