這篇文章主要探討了在編程中如何處理“正常路徑”和“邊緣情況”。作者指出,大多數代碼庫中 80% 的代碼負責處理預期的“正常路徑”,而剩下的 20% 負責處理錯誤和異常。常見的做法是使用 if-else 語句,將正常路徑放在 if 塊中,而邊緣情況放在 else 塊中。這樣的做法經常導致代碼復雜性增加,特別是當出現多層嵌套時問題尤為嚴重。作者提出了一個解決方案,即優先處理邊界情況并提前返回,以減少代碼的復雜性和提高可維護性。這種方法不僅可以使代碼結構更清晰,還可以降低引入錯誤的風險。
原文鏈接:https://preslav.me/2023/09/22/ditch-that-else/
作者 | Preslav 譯者 | 明明如月
出品 | CSDN(ID:CSDNnews)
我在職業生涯中見過的大多數代碼都遵循帕累托法則。在任何一個代碼庫中,有 80% 的代碼負責處理“正常路徑”(即軟件預期要做的事情),而其余的 20% 則用于處理錯誤、異常和邊界情況。
條件判斷與編程心理
當我們學習編程時,很自然地會遇到 if-else 語句(這個語句在多數流行的編程語言中都存在)。我們的大腦會立即將 if 與正常路徑聯系起來,而將 else 與邊界情況聯系起來。編程中普遍存在一種心理機制:通過在一個大的 if 代碼塊中包裹主要邏輯,程序員會覺得更加安心。他們會認為這樣做能夠防止不當輸入或異常情況影響代碼的主要功能。至于其他情況相對次要,放到 else 里面就好了。
理想很豐滿,現實很骨感
這種做法經常會導致以下這樣的代碼:
if(someConditionIsMet) { // ... // ... // ... // 接下來是 100 行代碼 // ... // ... // ... // 還有 100 行 // ... // ... // ... } else{ // 現在,處理邊緣情況 }returnsomeResult;
其中一個問題是,邊界情況的處理被放在了最后,這使得在上面的代碼塊添加代碼時,很容易忽視 else 內的邏輯。我敢說,大多數人讀這段代碼時甚至不會注意到 else的存在,直到一個邊界情況迫使他們更深入地閱讀這段代碼。
多層嵌套的陷阱
更糟糕的是,人們傾向于在多個層級上也運用這種模式。因此,寫出來的代碼更像是這樣:
if(someConditionIsMet) {// ...// ...// ...// 接下來是 100 行代碼// ...// ...// ...// 還有 100 行if(someOtherConditionIsMet) {// ...// ...// ...// 接下來是 100 行代碼// ...// ...// ...if(yetAnotherConditionIsMet) {// ...// ...// ...// 接下來是 100 行代碼// ...// ...// ...} else{// 現在,處理邊緣情況}// ...// ...// ...} else{// 現在,處理邊緣情況returnsomeOtherResult;}// ...// ...// ...} else{// 現在,處理邊緣情況}returnsomeResult;
代碼的多層嵌套是存在很大隱患,也給代碼庫增加了很多不必要的復雜性。人腦一次只能處理幾件不同的事情,因此當面臨需要深入分析多個代碼層級時,很容易忘記上一層的關鍵邏輯,導致一些不必要的錯誤。這也通常是一些 Pull Request 的代碼審查發現問題被打回去重新修改的一個主要原因。我們的業務流程已經足夠復雜了,多層嵌套又進一步增加了其復雜度。
"箭頭代碼"的解決方案
我并非第一個提到這種現象的人。實際上,這種深層次嵌套的代碼甚至有一個名稱。幾十年前,Jeff Atwood 就為這種代碼風格創造了 [箭頭代碼](https://blog.codinghorror.com/flattening-arrow-code/) 這個術語。
幸運的是,解決方案相當簡單。
拋棄 else,優先處理邊緣情況
如果我們拋棄 `if-else` 塊中的 `else` 并優先處理這塊邏輯,情況會怎么樣呢?如果我們不期望某件事經常發生,難到不是先檢查它、并在發生時立即返回更好嗎?
先處理小的邊界情況,如果必要的話提前返回;否則,將主流程保留在函數的最外層。
if(!someConditionIsMet) {// 首先處理那個邊緣情況returnsomeResultOrNothing;}// 主流程可以繼續,不需要額外的保護塊// ...// ...// ...// 再加 100 行代碼// ...// ...// ...// 還有 100 行
returnsomeResult;
同樣的思路也可以應用于處理多個邊緣情況:
if(!someConditionIsMet) {// 首先處理那個邊緣情況returnsomeResultOrNothing;}if(!someOtherConditionIsMet) {// 首先處理那個邊緣情況returnsomeResultOrNothing;}
if(!yetAnotherConditionIsMet) {// 首先處理那個邊緣情況returnsomeResultOrNothing;}
// 主流程可以繼續,不需要額外的保護塊// ...// ...// ...// 再加 100 行代碼// ...// ...// ...// 還有 100 行
returnsomeResult;
拋棄 else 就可以減少嵌套層數,有效降低代碼復雜度。能夠讓代碼更簡單,結構更清晰,更容易維護。由于邊界情況在函數的頂部得到了處理,開發者在后續添加新代碼時不太可能忽略這些情況,從而降低了引入錯誤的風險。處理邊緣情況并早期返回可以減少不必要的計算,從而可能提高代碼的執行效率。簡化的代碼結構更容易被同事理解,從而使代碼審查過程更加順暢,減少了潛在錯誤和不良實踐的傳播。
實際工作中你是否見到過多層嵌套導致代碼復雜度較高難以維護的情況?你是否認可文中通過邊界情況提前處理來減少 else 使用的建議?還有哪些可以有效解決多層嵌套的方法?