为何要成为“不嵌套主义者”

什么是 “不嵌套主义者”

不嵌套主义者绝不嵌套他们的代码！
好吧，尽可能不要

嵌套深度

如果我们把每个左括号作为新一层嵌套的标志，下列代码块就是一个嵌套深度为 4 的方法：

Copy
public int calculate(int bottom, int top)
{ // 1 😎
if (top > bottom)
{ // 2 🤨
int sum = 0;
for (int number = bottom; number <= top; number++)
{ // 3 🤔
if (number % 2 == 0)
{ // 4 😡
sum += number;
}
}
return sum;
}
else
{
return 0;
}
}

这个方法还算逻辑简单的。嵌套深度大起来，会显著影响阅读代码和理清逻辑。我们需要尽可能将嵌套深度控制在 2 以下。

消除嵌套的两种方法

提炼。即把方法中处理同一件事的代码块抽取成子方法。

反转。即通过反转if-else语句，使方法尽早return。

1. 提炼

让我们使用提炼把上述代码的结构优化一下：

Copy
int filterNumber(int number)
{
if (number % 2 == 0)
{
return number;
}
return 0;
}
public int calculate(int bottom, int top)
{ // 1
if (top > bottom)
{ // 2
int sum = 0;
for (int number = bottom; number <= top; number++)
{ // 3
sum += filterNumber(number);
}
return sum;
}
else
{
return 0;
}
}

看起来并没有好太多。。。但至少现在我能一眼看出for循环内部是让筛选过的数字进行累加。

2. 反转

接下来我们使用反转。当你把 “正面” 条件的分支放在更深的地方时，代码就会嵌套很多层。若我们把 “负面” 条件的分支放到前面，就可以使程序尽早返回，同时不必嵌套else部分的代码。

让我们试试看：

Copy
int filterNumber(int number)
{
if (number % 2 == 0)
{
return number;
}
return 0;
}
public int calculate(int bottom, int top)
{ // 1
if (top <= bottom)
{ // 2
return 0;
}
// 反转后，既然程序能运行到这里，说明 top > bottom，那么就可以少一层缩进
int sum = 0;
for (int number = bottom; number <= top; number++)
{
sum += filterNumber(number);
}
return sum;
}

当我们有多处条件检验时，通过不断反转条件，尽量让 “负面” 条件提前处理，尽早返回。如此，我们就会形成一种 “验证守护”。就好像，事先声明方法的要求，符合要求后，再执行实现方法功能的核心部分。

这样，“正面” 条件的代码都在下面，而 “负面” 条件的代码，它们都进行了缩进。

我们在阅读方法的核心部分时，也不用再记当前方法的状态。

练手

让我们看看下面这段 “杰作”：

Copy
private static String getValueText(Object value) {
final String newExpression;
if (value instanceof String) {
final String string = (String)value;
newExpression = '"' + StringUtil.escapeStringCharacters(string) + '"';
}
else if (value instanceof Character) {
newExpression = '\'' + StringUtil.escapeStringCharacters(value.toString()) + '\'';
}
else if (value instanceof Long) {
newExpression = value.toString() + 'L';
}
else if (value instanceof Double) {
final double v = (Double)value;
if (Double.isNaN(v)) {
newExpression = "java.lang.Double.NaN";
}
else if (Double.isInfinite(v)) {
if (v > 0.0) {
newExpression = "java.lang.Double.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Double.NEGATIVE_INFINITY";
}
}
else {
newExpression = Double.toString(v);
}
}
else if (value instanceof Float) {
final float v = (Float) value;
if (Float.isNaN(v)) {
newExpression = "java.lang.Float.NaN";
}
else if (Float.isInfinite(v)) {
if (v > 0.0F) {
newExpression = "java.lang.Float.POSITIVE_INFINITY";
}
else {
newExpression = "java.lang.Float.NEGATIVE_INFINITY";
}
}
else {
newExpression = Float.toString(v) + 'f';
}
}
else if (value == null) {
newExpression = "null";
}
else {
newExpression = String.valueOf(value);
}
return newExpression;
}

我直接头皮发麻，但其实这段代码虽然看起来复杂，但逻辑还是清晰的，只是。。。所有的处理逻辑都在一个方法里，看起来很 “唬人”。

观察到原代码中对value的重复判断以及newWxpression的重复赋值，而且对Double和Float的处理逻辑是一致的，哦，重复代码的警笛响了！

以下是重构后的代码（这里使用了 jdk 新版本的特性，switch 可以返回值）：

Copy
private static String getValueText(Object value) {
final String newExpression = switch (value) {
case String string -> '"' + StringUtil.escapeStringCharacters(string) + '"';
case Character character -> '\'' + StringUtil.escapeStringCharacters(value.toString()) + '\'';
case Long aLong -> value.toString() + 'L';
case Double aDouble -> getNewExpression(aDouble);
case Float aFloat -> getNewExpression(aFloat);
case null -> "null";
default -> String.valueOf(value);
};
return newExpression;
}
// getNewExpression() 的具体实现

重构后的代码更简洁，可读性也更高了。当然示例代码比起if-else更适合使用switch处理。如果针对if-else类型，还可以使用反转。

最后

可以理解为如果细节展现的过多，那么代码复杂度就会高，可读性也就会差；

嵌套深度尽可能维持在 2 以下，如果超过 2，就要考虑重构代码；

有两种方法可以减少嵌套深度：提炼与反转。反转让 “负面” 条件的代码尽早返回，使得方法的核心部分都在下面；提炼方便我们理清代码逻辑，又不必知道细节；

见名知意的方法名很重要；

一个方法最好只做一件事，如果方法过长（50 行是一个比较好的评判标准），为了可读性有必要抽取代码为子方法；

方法复用越多，参数越少，说明提炼的越好。

这在协作开发时尤为重要，除了首要保证功能的实现，其次就是好的可读性。