今天小編分享的科技經驗:再寫if-else,就把你消滅,歡迎閲讀。
翻開市面大部分編程教程,最早能夠接觸到的條件語句基本都是 if-else。
作為高級編程語言都有的必備功能,if-else 在嵌入式編程過程中幾乎是必用。但任何東西都有限度,濫用它,只會讓代碼逐漸抽象、離譜,最後成為 " 屎山 "。
接手項目的程式員叫苦不迭;忍無可忍的 CTO 對着滿屏的 if-else 終于下定決心,下次逮着誰再寫 if-else,罰款 1000 塊。
那麼,為什麼 if-else 這麼不受待見,怎麼幹掉它,不濫用它?
if-else 的兩宗罪
實際上,我們并不是杜絕 if-else,而是建議少用。
究其原因,一共有兩點:一是影響程式的運行效率;二是影響代碼的可讀性,增大運維難度。
目前,大部分人的觀點是 if-else 的分支預測(Branch Prediction)會降低執行效率。
CPU 執行一條指令分為 IF、ID、EX、WB 四個階段。分階段執行(也就是 pipeline 流水線執行)會先給出一個預測結果,讓流水線直接執行,執行對了則繼續;執行錯了,則退回去重新執行,直到對為止。
比如説,在這樣的代碼中:
int a = 0;
a += 1;
a += 2;
a += 3;
CPU 并非運行完 a=0 後執行 a +=1,而是在運行 a=0 讀執行後,馬上運行 a +=1 的讀執行。
流水線執行的好處很多,但對 if 語句來説,就會開啓分支預測,如果預測失敗,就會影響執行時間。但未對數組排序前提下,分支預測大概率會失敗,從而導致指令執行結束後重新讀取下一條指令,無法發揮流水線效果。説白了,只有分支預測一直成功,CPU 執行效率才會大幅提升。
不過,需要強調的是,所有帶有跳轉結構的語句比如 if、switch、for 都會出現這種情況。而且,現代 CPU 性能遠超過去,只有存在巨大量 if-else 情況才會影響性能,即便出現性能問題,也得在一個結構清晰的架構上分析性能是不是才能更好的定位,實在不行也可以把這段替換成匯編。
所以相比來説,導致程式運行效率下降并非 if-else 的最大原罪,而是影響可讀性。
代碼本質是給人看的,人得能看懂,從上到下掃一下大概就明白設計意圖、思路就是好代碼,這樣的代碼也是基本沒有 if-else 的,如果還需要反復從上看到下,再從下看到上,這樣的代碼即使這次看懂隔一段時間也會忘記的。也就是 " 高内聚,低耦合 "。
許多工程師在接手老項目時,時常會收到撲面而來的是,代碼中可能會充斥着大量的 if/else ,嵌套 6、7 層,一個函數幾百行,多層判斷讓人無從下手,絕望、憤怒、無奈噴湧而出,而且這樣的慘案已經不止發生過一次。總結成一句話就是,看死人。
這種代碼風格早在幾十年前就被國外所批判,并被稱之為 " 箭頭代碼 "(Arrow-code)。
實際工作中,我們能見到一個方法包含 10 個、20 個甚至更多的邏輯分支的情況。而更為致命的情況就是 if-else 的多層嵌套。
代碼的多層嵌套擁有很大隐患,也給代碼庫增加了很多不必要的復雜性。
人腦一次只能處理幾件不同的事情,因此當面臨需要深入分析多個代碼層級時,很容易忘記上一層的關鍵邏輯,導致一些不必要的錯誤。
還是那句話,代碼是給人看的,我們的業務流程已經足夠復雜了,多層嵌套還會進一步增加了其復雜度。
遇到這種情況怎麼辦,不要慌,直接幹掉 if-else。
怎麼幹掉 if-else
第一種方法:排非策略
優化前:
優化後:
第二種方法:三元運算符
三元運算符相比 if-else 來説,只需一行語句,代碼簡練精煉。
示例一:
示例二:
第三種方法:使用 switch、key-value 和 Map
Switch 顯然更簡單,而且,不同的條件分支之間沒有嵌套,并且它們彼此獨立,邏輯很清楚。不過,代碼本身也會有點多。
此時 key- value 和 Map 就是很好的方法。
第四種方法:邏輯與運算符
有些時候我們可以使用邏輯與運算符來簡化代碼。
第五種方法:使用 includes 處理多重條件
第六種方法:責任鏈模式和策略模式
責任鏈模式是實現了類似 " 流水線 " 結構的逐級處理,通常是一條鏈式結構,将 " 抽象處理者 " 的不同實現串聯起來。策略模式的目的是将算法的使用與定義解耦,能夠實現根據規則路由到不同策略類進行處理。
當然,并不是説用 if-else 就很 low,用設計模式就高大上,二者擅長場景不同,if-else 足以滿足大部分日常需求的開發,且簡單、靈活、可靠,而設計模式則是為了更簡潔、拓展性好、性能更優、可讀性更好等。
抛棄 else 吧,你會打開新世界
當然,無論哪種重構方法,都只是優化。歸結起來,最簡單的方法就是在寫代碼之處,抛棄 else。抛棄 else 就可以減少嵌套層數,有效降低代碼復雜度。讓代碼更簡單,結構更清晰,更容易維護。
如果我們抛棄 if-else 塊中的 else 并優先處理這塊邏輯。先處理小的邊界情況,如果必要的話提前返回;否則,将主流程保留在函數的最外層。
同樣的思路也可以應用于處理多個邊緣情況:
由于邊界情況在函數的頂部得到了處理,開發者在後續添加新代碼時不太可能忽略這些情況,從而降低了引入錯誤的風險。處理邊緣情況并早期返回可以減少不必要的計算,從而可能提高代碼的執行效率。
簡化的代碼結構更容易被同事理解,從而使代碼審查過程更順暢,減少潛在錯誤和不良事件傳播。
當然,也有工程師認為,不要過度關注 if-else 的數量和層數,而是要關注語義是否清晰,單純減少 if-else 但又讓代碼更難閲讀反而是畫蛇添足。亂優化 if-else 沒什麼好處,應該優化的是本身條件的分割,把流理清楚後 if-else 比啥都清晰。
所以最好的做法是寫一篇詳細文檔,從最原始的數學模型開始,表明采取何種計算策略,策略如何推倒,然後給整個方法加上注釋附上文檔地址,并且在每個分支的地方加上注釋指明對應到文檔中哪個公式。
總結起來,就是一句話:沒有對與錯,而是要看場景,任何時候,只要能讓代碼更易讀和維護,就成功了。