編碼之道:小函式的大威力
阿新 • • 發佈:2018-12-30
一屏之地,一覽無餘!對的!要的就是短小精悍!
翻開專案的程式碼,處處可見成百上千行的函式,函式體裡面switch-case、if、for等交錯在一起,一眼望不到頭的感覺。有些變態的函式,長度可能得按公里計算了。神啊,請賜予我看下去的勇氣吧!先不論邏輯如何,首先這長度直接就把人給嚇到了。這些超大號函式是怎麼來得呢?
- 直接從別處COPY一段程式碼,隨便改改即可,造成大量重複程式碼。
- 缺少封裝,甚至說就沒有封裝,完全就是隨意亂加一氣,造成各個抽象層次的程式碼混合在一起,混亂不堪。
- 成篇的異常處理和特殊處理,核心邏輯或許就是函式體開頭、中間或結束那麼幾行而已。
這些超長的函式,給我們造成了很大的麻煩:閱讀程式碼找BUG幾乎是不可能的事情,沒有偵錯程式估計撞牆的心都有了;重複程式碼造成修改困難,漏掉任何一處遲早是要出問題的;各個層次的程式碼混在一起,閱讀程式碼相當吃力,人的臨時記憶是有限的,不斷在各個層次之間切換,一會兒就給繞暈了。
解決這些問題最重要的就是要保持函式的短小,短小的函式閱讀起來要好得多,同時短小的函式意味著較好的封裝。下面談談關於函式,應該遵循的一些原則:
1. 原則:取個描述性的名字
- 取個一眼就看出函式意圖的名字很重要
- 長而具有描述性的名稱,要比短而讓人費解的好(長度適中,也不能過分長)
- 使用動詞或動詞+名詞短語
在編碼之道:取個好名字中已經介紹過,好名字的重要性,不再贅述。
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. 原則:減少重複程式碼"
重複是一些邪惡的根源!!!
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){
....
}
};")))
最後
上面提到的原則,若要理解的更加深刻,建議去閱讀《程式碼整潔之道》,裡面有許多詳盡的例子,對於寫過幾年程式碼的人來說,總會發現一些自己所在專案經常犯的毛病。
知道了這些原則,我們應該這樣做:
當在新增新函式的時候:
- 剛下手時違反規範和原則沒關係
- 開發過程中逐步打磨
- 保證提交後的程式碼是整潔的即可
重構現有的函式,有下面情況的,見一個消滅一個:
- 冗長而複雜
- 有太多縮排和巢狀迴圈
- 引數列表過長
- 名字隨意取
- 重複了三次以上