“现在开始,把代码里的 ‘else’ 丢掉!”


我在职业生涯中见过的大多数代码都遵循帕累托法则 。在任何一个代码库中,有 80% 的代码负责处理“正常路径”(即软件预期要做的事情),而其余的 20% 则用于处理错误、异常和边界情况。


条件判断与编程心理

当我们学习编程时,很自然地会遇到 if-else 语句(这个语句在多数流行的编程语言中都存在)。我们的大脑会立即将 if 与正常路径联系起来,而将 else 与边界情况联系起来。编程中普遍存在一种心理机制:通过在一个大的 if 代码块中包裹主要逻辑,程序员会觉得更加安心。他们会认为这样做能够防止不当输入或异常情况影响代码的主要功能。至于其他情况相对次要,放到 else 里面就好了。

理想很丰满,现实很骨感

这种做法经常会导致以下这样的代码:

-----------------------------------------------
if (someConditionIsMet) {
// ...
// ...
// ...
// 接下来是 100 行代码
// ...
// ...
// ...
// 还有 100 行
// ...
// ...
// ...
} else {
// 现在,处理边缘情况
}

return someResult;
-----------------------------------------------

其中一个问题是,边界情况的处理被放在了最后,这使得在上面的代码块添加代码时,很容易忽视 else 内的逻辑。我敢说,大多数人读这段代码时甚至不会注意到 else的存在,直到一个边界情况迫使他们更深入地阅读这段代码。

多层嵌套的陷阱

更糟糕的是,人们倾向于在多个层级上也运用这种模式。因此,写出来的代码更像是这样:

-----------------------------------------------
if (someConditionIsMet) {
// ...
// ...
// ...
// 接下来是 100 行代码
// ...
// ...
// ...
// 还有 100 行
if (someOtherConditionIsMet) {
// ...
// ...
// ...
// 接下来是 100 行代码
// ...
// ...
// ...
if (yetAnotherConditionIsMet) {
// ...
// ...
// ...
// 接下来是 100 行代码
// ...
// ...
// ...
} else {
// 现在,处理边缘情况
}
// ...
// ...
// ...
} else {
// 现在,处理边缘情况
return someOtherResult;
}
// ...
// ...
// ...
} else {
// 现在,处理边缘情况
}

return someResult;
-----------------------------------------------

代码的多层嵌套是存在很大隐患,也给代码库增加了很多不必要的复杂性。人脑一次只能处理几件不同的事情,因此当面临需要深入分析多个代码层级时,很容易忘记上一层的关键逻辑,导致一些不必要的错误。这也通常是一些 Pull Request 的代码审查发现问题被打回去重新修改的一个主要原因。我们的业务流程已经足够复杂了,多层嵌套又进一步增加了其复杂度。

"箭头代码"的解决方案

我并非第一个提到这种现象的人。实际上,这种深层次嵌套的代码甚至有一个名称。几十年前,Jeff Atwood 就为这种代码风格创造了 [箭头代码](https://blog.codinghorror.com/flattening-arrow-code/) 这个术语。

幸运的是,解决方案相当简单。

抛弃 else,优先处理边缘情况

如果我们抛弃 `if-else` 块中的 `else` 并优先处理这块逻辑,情况会怎么样呢?如果我们不期望某件事经常发生,难到不是先检查它、并在发生时立即返回更好吗?

先处理小的边界情况,如果必要的话提前返回;否则,将主流程保留在函数的最外层。

-----------------------------------------------
if (!someConditionIsMet) {
// 首先处理那个边缘情况
return someResultOrNothing;
}

// 主流程可以继续,不需要额外的保护块
// ...
// ...
// ...
// 再加 100 行代码
// ...
// ...
// ...
// 还有 100 行

return someResult;
-----------------------------------------------

同样的思路也可以应用于处理多个边缘情况:

-----------------------------------------------
if (!someConditionIsMet) {
// 首先处理那个边缘情况
return someResultOrNothing;
}

if (!someOtherConditionIsMet) {
// 首先处理那个边缘情况
return someResultOrNothing;
}

if (!yetAnotherConditionIsMet) {
// 首先处理那个边缘情况
return someResultOrNothing;
}

// 主流程可以继续,不需要额外的保护块
// ...
// ...
// ...
// 再加 100 行代码
// ...
// ...
// ...
// 还有 100 行

return someResult;
-----------------------------------------------

抛弃 else 就可以减少嵌套层数,有效降低代码复杂度。能够让代码更简单,结构更清晰,更容易维护。由于边界情况在函数的顶部得到了处理,开发者在后续添加新代码时不太可能忽略这些情况,从而降低了引入错误的风险。处理边缘情况并早期返回可以减少不必要的计算,从而可能提高代码的执行效率。简化的代码结构更容易被同事理解,从而使代码审查过程更加顺畅,减少了潜在错误和不良实践的传播。

实际工作中你是否见到过多层嵌套导致代码复杂度较高难以维护的情况?你是否认可文中通过边界情况提前处理来减少 else 使用的建议?还有哪些可以有效解决多层嵌套的方法?