CSDN 的同學們,大家好,我是二哥呀!
在某乎上看到一個接近萬贊的高贊回答,一開始看的時候我嘴角是上揚的,還會笑出豬聲,随後情緒就急轉直下,莫名心酸!題目是這樣的:

我曾經接手過一份代碼,遇到過一個有 30 多個 if-else 嵌套 if-else 的子產品。
心裡罵罵咧咧,“誰他喵寫的這玩意!”然後翻了一遍代碼的 history。
大緻情況是這樣的:第一個程式員寫下這段代碼時,隻有 2 個 if-else;後來需求逐漸增加,先是 1 個、2 個,然後量變引起質變,于是邏輯分支快速擴張。
到這時候已經沒有人願意去重構 switch 或者設計模式了,畢竟複雜度擺在那裡,萬一崩了還得背鍋。
大概三四個程式員接手這段代碼後,就程式設計我現在這種局面了。
第一個程式員絕對沒有料到那麼簡單的邏輯在之後會變得這麼複雜,甚至在增加第一個第二個 if-else 時,也隻是很随意的加上。
是以我覺得,這鍋絕對是甲方的,讓他娘的随便改需求。這麼一想心裡就好受多了,程式設計嘛,最重要的是看得開!
于是我又追加了兩條 if-else,然後測試、送出,下班。
插一句,如果真想重構代碼,建議先看看小傅哥的《重學 Java 設計模式》,設計模式是軟體設計中常見問題的典型解決方案,它們就像能根據需求進行調整的預制藍圖, 可用于解決代碼中反複出現的設計問題,如果不懂設計模式的話,遇到這些問題就隻能抓瞎了。
設計模式,牛逼!
看完了作者的回答,二哥強忍着無名的悲傷來說兩句。
十多年的程式設計生涯裡,的确有過無數次的沖動,想要把原有的代碼重構,想要調優,最後大多數都無疾而終,尤其是随着年齡的增長,反而越來越膽小怕事,有些真的是不敢亂動,隻能忍痛讓原有的代碼更爛一些。
畢竟背鍋是大事,嘿嘿。
有時候,不能把代碼當做是藝術品,要能夠适度忍受不完美,程式能跑起來,bug 數量可控,有啥問題可以解決也是很重要的。
如果重構了,出了問題,自己背鍋是注定的,可能還會連累了測試小姐姐。
記得在外企的第二年,由于組裡面有個新人的代碼寫得實在是太爛,我就忍不住前前後後優化了一遍,畢竟作為 Team Leader,要對新人負責,要為團隊負責,結果大家猜怎麼樣?
我被上司臭罵了一頓!
原因很簡單,我特麼引入了一個 Bug,Code Review 的時候還沒有檢查出來,測試也沒有測試到,結果到了正式環境,剛巧碰到上司在日方出差,上司要給上司的上司展示成果,結果程式出了 bug,然後上司被狠狠地臭罵了一頓。
上司被批了,那自然一通越洋電話打過來,把我直接罵哭!
當時還年輕,那叫一個委屈啊。但能怎麼辦,自己的鍋不背讓誰背?
後來回洛陽後,團隊規模變小,自己重構的欲望又湧上心頭,畢竟這次沒人能管得了我,看到誰寫的代碼爛,就直接一頓操作猛如虎,重構到自己心滿意足為止。
即便是引入了新的 Bug 也沒關系,畢竟老闆也不懂,好忽悠,嘿嘿。
老闆雖然不懂代碼,但懂得寫代碼哪能沒有 Bug——經過我的不懈努力,成功給老闆灌輸了這個思想,要想不出 Bug,就增加測試團隊的人手,上司可不願意多發一份工資。
成功洗腦老闆後,我真的有一段時間是飄到了極點,狠起來連自己的代碼都重構,一遍又一遍,手頭最經常看的兩本書,一本《代碼的整潔之道》,一本《重構·改善既有代碼的設計》。
從簡單的變量命名、方法命名,到縮減方法的行數,能拆分就拆分,盡量保證每個方法的行數不超過一個小拇指那麼長。為了适配設計模式,我當時還買了一本《設計模式之禅》,真的是殚精竭慮。
現在想想那段日子真瘋狂,有時候為了修自己重構後帶來的新 Bug,真的是熬了不少夜。
但有一說一,那段日子的進步也是肉眼可見的。
不過,話又說回來,對穩定性要求比較高的項目,如果能力沒到那份上,還是盡量少重構,搞不好版本更新的日志裡就會寫下一條:XXX 程式員被祭天了!
最好是等到上司忍不住下了死指令,限爾等多少天之内,務必把這座屎山給搬走!到了那時候,再大展拳腳也不遲。
如果真的是安耐不住,一肚子的重構、調優想法無法得到施展,我給大家推薦一個好辦法,就是自己搞一個練手項目,可以是自己開發的,也可以是 GitHub 上成熟的項目,比如說我一直推薦的 vhr、mall、miaosha 等等,把源碼 fork 下然後拉下來,在本地跑一跑,嘗試去讀一讀源碼,覺得哪裡需要重構了,就動手實踐一遍,即便是出錯了,也誰都影響不到,對吧?
有些同學如果覺得自己比較厲害的話,可以去拿那些頂級的第三方類庫做實驗,重構完一定要記得測試,并且在送出 PR 的時候附帶上自己的測試報告,如果項目的作者認為你重構的有水準,沒準你一躍就成為了項目的維護者,履歷上也是加分項。
但對于公司的那堆屎山,動刀子的時候盡量猥瑣點,免得把自己埋了。
再說回 if-else 和 switch 這個題目。朋友 @yes 在回答裡提到過 Dubbo 源碼中的 ChannelEventRunnable 類的 run() 方法,我用 Sourcegraph 插件看了一下 GitHub 上 Dubbo 的源碼,還真的是挺有學習價值的。
public void run() {
if (state == ChannelState.RECEIVED) {
try {
handler.received(channel, message);
} catch (Exception e) { }
} else {
switch (state) {
case CONNECTED:
try {
handler.connected(channel);
} catch (Exception e) {}
break;
case DISCONNECTED:
try {
handler.disconnected(channel);
} catch (Exception e) {}
break;
case SENT:
try {
handler.sent(channel, message);
} catch (Exception e) { }
break;
case CAUGHT:
try {
handler.caught(channel, exception);
} catch (Exception e) { }
break;
default:
}
}
}
看到沒,這段代碼裡先用 if 做了判斷,然後才在 else 中使用 switch 做了分支判斷。為什麼不全部使用 switch 呢?
官方還特意給了個說明。
我把其中關鍵的一點摘錄出來,大家看一下就明白了。
現代 CPU 都支援分支預測(branch prediction)和指令流水線(instruction pipeline),這兩個結合可以極大提高 CPU 效率。對于像簡單的 if 跳轉,CPU 是可以比較好地做分支預測的。但是對于 switch 跳轉,CPU 則沒有太多的辦法。switch 本質上是根據索引,從位址數組裡取位址再跳轉的。
是以說,不是所有情況下,把 if-else 重構成 switch 就是最好的選擇,還是要因地制宜,是以求求你,别再問我為什麼要 if-else 走天下了!