布爾值的陷阱:如何避免編程中的邏輯混亂
布爾值看起來簡單,但用起來卻常常讓人頭疼。它們表面上只是“真”或“假”,但一旦深入使用,就會發現它們很容易引發混亂。
多年的編碼經驗讓我對布爾值保持警惕。也許你習慣了它們,但我發現,能不用就盡量不用;如果非用不可,也得格外小心。
為什么?因為布爾值容易讓邏輯變得復雜。命名糟糕、否定邏輯、嵌套條件,這些都會讓代碼變得難以理解。更糟的是,我們常常把多個布爾條件組合在一起,讓閱讀者的大腦不得不做多重判斷。
但布爾值畢竟是編程中不可或缺的一部分。因此,我總結了五條使用布爾值的規則:
保持積極 正面條件優先 禁止復雜表達式 拒絕布爾參數 布爾值是未來復雜性的陷阱
保持積極
在處理布爾變量時,我盡量保持它們的命名是積極的,這意味著當變量為True時,事情是正常工作和發生的。因此我更喜歡這樣的表達式:
if UserIsAuthorized {
// 執行某些操作
}
而不是:
if !UserIsNotAuthorized {
// 執行某些操作
}
前者可讀性更強,更容易理解。需要處理雙重否定會讓大腦感到痛苦。雙重否定意味著要思考兩件事而不是一件。
正面條件優先
本著保持積極的精神,如果你必須使用if... else
結構,請將正面條件放在前面。我們的思維方式喜歡沿著"快樂路徑"前進,所以將否定條件放在前面可能會讓人感到不適。換句話說,不要這樣做:
if not Authorized {
// 不好的情況
} else {
// 好的情況
}
而應該將正面條件放在前面:
if Authorized {
// 一切正常
} else {
// 滾開!!
}
這樣更容易閱讀,而且你不需要處理not
操作。
禁止復雜表達式
解釋性變量被嚴重低估了。我理解這一點——我們想要快速前進。但停下來把事情寫清楚總是值得的,就像你的數學老師過去常說的"展示你的解題過程"。我遵循這樣的規則:只在命名變量之間使用&&
和||
,永遠不要使用原始表達式。
我經常看到這樣的代碼:
if (user.age > 18 && user.isActive && !user.isBanned && user.subscriptionLevel >= 2) {
grantAccess();
}
相反,你應該考慮那些不得不閱讀這段可怕代碼的人,并像這樣寫出來:
const isAdult = user.age > 18;
const hasAccess = !user.isBanned;
const isActive = user.isActive;
const isSubscriber = user.subscriptionLevel >= 2;
const canAccess = isAdult && hasAccess && isActive && isSubscriber;
if (canAccess) {
grantAccess();
}
這樣可讀性極強,而且清楚地表明了它在做什么以及期望什么。不要害怕讓解釋性變量變得明確。我懷疑有人會抱怨:
const userHasJumpedThroughAllTheRequiredHoops = true;
我知道這需要更多的輸入,但清晰度比節省幾個按鍵要寶貴得多。此外,這些解釋性變量是單元測試的絕佳候選者。它們還使日志記錄和調試變得更加容易。
拒絕布爾參數
沒有什么比布爾參數更能讓人每分鐘產生更多"這到底是怎么回事?"的疑問了。看看這個例子:
saveUser(user, true, false); // 這到底意味著什么?
當你編寫函數時,參數是有命名的,看起來沒問題。但當你要調用它時,維護者必須查找函數聲明才能理解傳遞了什么。
相反,為什么不完全避免使用布爾值,而是聲明一個描述性的enum
類型來解釋參數的作用?
enum WelcomeEmailOption {
Send,
DoNotSend,
}
enum VerificationStatus {
Verified,
Unverified,
}
然后你的函數可以這樣寫:
function saveUser(
user: User,
emailOption: WelcomeEmailOption,
verificationStatus: VerificationStatus
): void {
if (emailOption === WelcomeEmailOption.Send) {
sendEmail(user.email, 'Welcome!');
}
if (verificationStatus === VerificationStatus.Verified) {
user.verified = true;
}
// 保存用戶到數據庫...
}
你可以這樣調用它:
saveUser(newUser, WelcomeEmailOption.Send, VerificationStatus.Unverified);
這難道不是對大腦更友好嗎?這個調用就像文檔一樣。它清晰明了,維護者可以立即看到調用做了什么以及參數的含義。
布爾值是未來復雜性的陷阱
enum
的一個優點是它們可以擴展。想象一下,你有一個食品飲料系統,有小型和大型飲料。你可能會得到:
var IsSmallDrink: boolean;
你圍繞這個布爾變量構建了你的系統,甚至在數據庫中為這些信息創建了布爾字段。但后來老板過來告訴你:"嘿,我們要開始賣中號飲料了!"
哦豁,這將是一個重大變更。突然間,一個簡單的布爾值變成了一個負債。但如果你避免了布爾值,而是從一開始就使用:
enum DrinkSize {
Small,
Large
}
那么添加另一種飲料尺寸就會容易得多。
聽著,布爾值既強大又簡單。我年紀足夠大,還記得語言甚至沒有布爾類型的時候。我們不得不使用整數來模擬它們:
10 LET FLAG = 0
20 IF FLAG = 1 THEN PRINT "YOU WILL NEVER SEE THIS"
30 LET FLAG = 1
40 IF FLAG = 1 THEN PRINT "NOW IT PRINTS"
50 END
所以我理解它們的吸引力。但使用布爾值最終會充滿危險。有例外情況嗎?當然有,有些簡單情況實際上就是非真即假,而且永遠如此——比如isLoading
。但如果你趕時間,或者放松警惕,或者可能有點懶惰,你很容易陷入編寫復雜、難以理解的代碼的陷阱中。所以在使用布爾變量之前,請謹慎小心。
原文地址:https://www.infoworld.com/article/3990923/booleans-considered-harmful.html
作者:Nick Hodges