請停止使用嵌套的 if,改為此方法
嵌套 if 的典型用例:您希望對某些數據執行各種檢查,以確保其有效,然后再最終對其進行有用的操作。
不要這樣做!:
// JavaScript
function sendMoney(account, amount) {
if (account.balance > amount) {
if (amount > 0) {
if (account.sender === 'user-token') {
account.balance -= amount;
console.log('Transfer completed');
} else {
console.log('Forbidden user');
}
} else {
console.log('Invalid transfer amount');
}
} else {
console.log('Insufficient funds');
}
}
這里有一個更好的方法:
// JavaScript
function sendMoney(account, amount) {
if (account.balance < amount) {
console.log('Insufficient funds');
return;
}
if (amount <= 0) {
console.log('Invalid transfer amount');
return;
}
if (account.sender !== 'user-token') {
console.log('Forbidden user');
return;
}
account.balance -= amount;
console.log('Transfer completed');
}
看看它清潔了多少?我們沒有嵌套 if,而是使用多個 if 語句來執行檢查,如果條件不滿足則立即返回。在這種模式中,我們可以將每個 if 語句稱為保護子句。
如果您經常使用 Node.js,您可能在 Express 中間件中看到過以下流程:
// JavaScript
function authMiddleware(req, res, next) {
const authToken = req.headers.authorization;
if (!authToken) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (authToken !== 'secret-token') {
return res.status(401).json({ error: 'Invalid token' });
}
if (req.query.admin === 'true') {
req.isAdmin = true;
}
next();
}
這比前面好多了,對吧?:
// JavaScript
function authMiddleware(req, res, next) => {
const authToken = req.headers.authorization;
if (authToken) {
if (authToken === 'secret-token') {
if (req.query.admin === 'true') {
req.isAdmin = true;
}
return next();
} else {
return res.status(401).json({ error: 'Invalid token' });
}
} else {
return res.status(401).json({ error: 'Unauthorized' });
}
};
你永遠不會超出一層嵌套。我們可以避免回調地獄中出現的混亂情況。
如何將嵌套的 if 轉換為保護子句
這樣做的邏輯很簡單:
1.找到最里面的/成功if
這里我們可以清楚地看到它是 cond3 if。在此之后,如果我們不再進行任何檢查并采取我們一直想要采取的行動。
// JavaScript
function func(cond1, cond2, cond3) {
if (cond1) {
if (cond2) {
if (cond3) {
console.log('PASSED!');
console.log('taking success action...');
} else {
console.log('failed condition 3');
}
} else {
console.log('failed condition 2');
}
} else {
console.log('failed condition 1');
}
}
2.將最外層的if取反并返回
否定 if 條件以將 else 語句的主體放在那里并在后面添加 return。
刪除 else 大括號(保留正文,它仍然包含以前嵌套的 if,并將右 if 大括號移到 return 之后。
所以:
// JavaScript
function func(cond1, cond2, cond3) {
if (!cond1) { // ?? inverted if condition
// ?? body of former else clause
console.log('failed condition 1');
return; // ?? exit on fail
}
// ?? remaining nested ifs to convert to guard clauses
if (cond2) {
if (cond3) {
console.log('PASSED!');
console.log('taking success action...');
} else {
console.log('failed condition 3');
}
} else {
console.log('failed condition 2');
}
}
3. 對每個嵌套的 if 執行同樣的操作,直到成功 if
// JavaScript
function func(cond1, cond2, cond3) {
if (!cond1) {
console.log('failed condition 1');
return;
}
if (!cond2) {
console.log('failed condition 2');
return;
}
// ?? remaining nested ifs to convert
if (cond3) {
console.log('PASSED!');
console.log('taking success action...');
} else {
console.log('failed condition 3');
}
}
最后:
// JavaScript
function func(cond1, cond2, cond3) {
if (!cond1) {
console.log('failed condition 1');
return;
}
if (!cond2) {
console.log('failed condition 2');
return;
}
if (!cond3) {
console.log('failed condition 3');
return;
}
console.log('PASSED!');
console.log('taking success action...');
}
提示:
安裝 JavaScript Booster 擴展后,在 VS Code 中反轉 if 語句很容易。
在這里,我們只需將光標放在 if 關鍵字上并激活“顯示代碼操作”命令(默認情況下為 Ctrl + .)
提示:
將保護子句拆分為多個函數以始終避免 else
如果我們在 if/else 中檢查數據后想做其他事情怎么辦?例如:
// JavaScript
function func(cond1, cond2) {
if (cond1) {
if (cond2) {
console.log('PASSED!');
console.log('taking success action...');
} else {
console.log('failed condition 2');
}
console.log('after cond2 check');
} else {
console.log('failed condition 1');
}
console.log('after cond1 check');
}
在此函數中,無論 cond1 的值如何,“after cond1 check”行仍將打印。如果 cond1 為 true,則 cond2 值也類似。
在這種情況下,使用保護子句需要做更多的工作:
如果我們嘗試使用保護子句,我們最終會重復 if/else 檢查之后的行:
function func(cond1, cond2) {
if (!cond1) {
console.log('failed condition 1');
console.log('after cond1 check');
return;
}
if (!cond2) {
console.log('failed condition 2');
console.log('after cond2 check');
console.log('after cond1 check');
return;
}
console.log('PASSED!');
console.log('taking success action...');
console.log('after cond2 check');
console.log('after cond1 check');
}
func(true);
因為必須打印這些行,所以我們在返回之前在保護子句中打印它們。然后,我們在所有(!)以下保護子句中打印它。再次,在主函數體中,如果所有的保護子句都通過了。
那么我們能做些什么呢?我們怎樣才能在使用保護子句的同時仍然堅持 DRY 原則呢?
好吧,我們將邏輯拆分為多個函數:
// JavaScript
function func(cond1, cond2) {
checkCond1(cond1, cond2);
console.log('after cond1 check');
}
function checkCond1(cond1, cond2) {
if (!cond1) {
console.log('failed condition 1');
return;
}
checkCond2(cond2);
console.log('after cond2 check');
}
function checkCond2(cond2) {
if (!cond2) {
console.log('failed condition 2');
return;
}
console.log('PASSED!');
console.log('taking success action...');
}
讓我們將其應用到我們之前看到的 Express 中間件中:
// JavaScript
function authMiddleware(req, res, next) {
checkAuthValidTokenAdmin(req, res, next);
}
function checkAuthValidTokenAdmin(req, res, next) {
const authToken = req.headers.authorization;
if (!authToken) {
return res.status(401).json({ error: 'Unauthorized' });
}
checkValidTokenAdmin(req, res, next);
}
function checkValidTokenAdmin(req, res, next) {
const authToken = req.headers.authorization;
if (authToken !== 'secret-token') {
return res.status(401).json({ error: 'Invalid token' });
}
checkAdmin(req, res, next);
}
function checkAdmin(req, res, next) {
if (req.query.admin === 'true') {
req.isAdmin = true;
}
next();
}
在某種程度上,我們用責任鏈模式替換了 if/else 語句。
當然,對于像基本 Express 請求中間件這樣的簡單邏輯來說,這可能有點過分了,但這里的優點是它將每個額外的檢查委托給一個單獨的函數,分離職責并防止過度嵌套。
總結
在代碼中使用嵌套的 if 可能會導致代碼復雜且難以維護。相反,我們可以使用保護子句來使我們的代碼更具可讀性和線性性。我們可以將保護子句應用于不同的場景,并將其拆分為多個功能,以避免重復和職責分割。通過采用這種模式,我們最終會編寫出更干凈、更易于維護的代碼。