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