編碼之道:小函數的大威力

 

一屏之地,盡收眼底!對的!要的就是短小精悍!c++

翻開項目的代碼,到處可見成百上千行的函數,函數體裏面switch-case、if、for等交錯在一塊兒,一眼望不到頭的感受。有些變態的函數,長度可能得按千米計算了。神啊,請賜予我看下去的勇氣吧!先不論邏輯如何,首先這長度直接就把人給嚇到了。這些超大號函數是怎麼來得呢?redis

  • 直接從別處COPY一段代碼,隨便改改便可,形成大量重複代碼。
  • 缺乏封裝,甚至說就沒有封裝,徹底就是隨意亂加一氣,形成各個抽象層次的代碼混合在一塊兒,混亂不堪。
  • 成篇的異常處理和特殊處理,核心邏輯或許就是函數體開頭、中間或結束那麼幾行而已。

這些超長的函數,給咱們形成了很大的麻煩:閱讀代碼找BUG幾乎是不可能的事情,沒有調試器估計撞牆的心都有了;重複代碼形成修改困難,漏掉任何一處早晚是要出問題的;各個層次的代碼混在一塊兒,閱讀代碼至關吃力,人的臨時記憶是有限的,不斷在各個層次之間切換,一下子就給繞暈了。ide

 

更多內容:http://game-lab.org/posts/zoc-cleancode-5/函數

 

解決這些問題最重要的就是要保持函數的短小,短小的函數閱讀起來要好得多,同時短小的函數意味着較好的封裝。下面談談關於函數,應該遵循的一些原則:post

1. 原則:取個描述性的名字

  • 取個一眼就看出函數意圖的名字很重要
  • 長而具備描述性的名稱,要比短而讓人費解的好(長度適中,也不能過度長)
  • 使用動詞或動詞+名詞短語

編碼之道:取個好名字中已經介紹過,好名字的重要性,再也不贅述。ui

2. 原則:保持參數列表的簡潔

  • 無參數最好,其次一元,再次二元,三元儘可能避免
  • 儘可能避免標識參數
  • 使用參數對象
  • 參數列表
  • 避免輸出和輸入混用,沒法避免則輸出在左,輸入在右
bool isBossNpc(); void summonNpc(int id); void summonNpc(int id, int type); void summonNpc(int id, int state, int type); // 還能記得參數順序嗎? void showCurrentEffect(int state, bool show); // Bad!!! void showCurrentEffect(int state); // Good!! void hideCurrentEffect(int state); // 新加個函數也沒多難吧? bool needWeapon(DWORD skillid, BYTE& failtype); // Bad!!! 

3. 原則:保持函數短小

  • 第一規則:要短小
  • 第二規則:還要更短小
  • 要作到「一屏之地,盡收眼底」更好

4. 原則:只作一件事

  • 函數應該只作一件事,作好這件事
  • 且只作這一件事

5. 原則:每一個函數位於同一抽象層級

  • 要確保函數只作一件事,函數中的語句都要在同一個抽象層級上
  • 自頂下下讀代碼

6. 原則:無反作用

  • 謊話,每每名存實亡

7. 原則:操做和檢查要分離

  • 要麼是作點什麼,要麼回答點什麼,但兩者不可兼得")
  • 混合使用---反作用的肇事者

8. 原則:使用異常來代替返回錯誤碼

  • 操做函數返回錯誤碼輕微違法了操做與檢查的隔離原則
  • 用異常在某些狀況下會更好點
  • 抽離try-cacth
  • 錯誤處理也是一件事情,也應該封裝爲函數
bool RedisClient::connect(const std::string& host, uint16_t port) { this->host = host; this->port = port; this->close(); try { redis_cli = new redis::client(host, port); return true; } catch (redis::redis_error& e) { redis_cli = NULL; std::cerr << "error:" << e.what() << std::endl; return false; } return false; } 

9. 原則:減小重複代碼"

重複是一些邪惡的根源!!!this

10. 原則:避免醜陋不堪的switch-case

  • 天生要作N件事情的貨色
  • 屢次出現就要考慮用多態進行重構

BAD:編碼

bool saveBinary(type, data) { switch (type) { case TYPE_OBJECT: .... break; case TYPE_SKILL: ... break; .... } } bool needSaveBinary(type) { switch (type) { case TYPE_OBJECT: return true; case TYPE_SKILL: ... break; .... } } 
class BinaryMember { BinaryMember* createByType(type){ switch (type) { case TYPE_OBJECT: return new ObjectBinaryMember; case TYPE_SKILL: return new SkillBinaryMember; .... } virtual bool save(data); virtual bool needSave(data); }; class ObjectBinaryMember : public BinaryMember { bool save(data){ .... } bool needSave(data){ .... } };"))) 

最後

上面提到的原則,若要理解的更加深入,建議去閱讀《代碼整潔之道》,裏面有許多詳盡的例子,對於寫過幾年代碼的人來講,總會發現一些本身所在項目常常犯的毛病。spa

知道了這些原則,咱們應該這樣作:調試

當在添加新函數的時候:

  • 剛下手時違反規範和原則不要緊
  • 開發過程當中逐步打磨
  • 保證提交後的代碼是整潔的便可

重構現有的函數,有下面狀況的,見一個消滅一個:

  • 冗長而複雜
  • 有太多縮進和嵌套循環
  • 參數列表過長
  • 名字隨意取
  • 重複了三次以上
相關文章
相關標籤/搜索