知识共享许可协议

扁平化箭形代码

作为《嵌套条件的重构》 的姊妹篇,补充说明箭形代码的缺点,并以函数分解的方式扁平化箭形代码。

原文:Flattening Arrow Code

经常看到这种代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (rowCount > rowIdx) {
if (drc[rowIdx].table.columns.contains("avalId")) {
do {
if (attributes[attrVal.attributeClassId] == null) {
// do stuff
}
else {
if (!Array.isArray(attributes[attrVal.attributeClassId])) {
// do stuff
}
else {
if (!isChecking) {
// do stuff
}
else {
// do stuff
}
}
}
rowIdx++;
}
while (rowIdx < rowCount && parseInt(drc[rowIdx], 10) === Id);
}
else {
rowIdx++;
}
}
return rowIdx;

太多的条件嵌套使代码变成一个箭头的样子:

1
2
3
4
5
6
7
8
9
if
if
if
if
do something
endif
endif
endif
endif

当你在1280x1024 分辨率下阅读代码时会超出右边边界。这就是箭头反模式。

我重构的首要任务之一是把这样的箭形代码 “扁平化”。那些锋利的尖钩很危险!箭形代码有着很高的圈复杂度 -- 衡量贯穿代码有多少不同路径:

研究表明程序的质量与它的圈复杂度(WIKI, 百度百科)有关。低圈复杂度的程序更简单易懂,修改时的风险也更低。模块的圈复杂度与它的可测试性也是高度相关的。

在适当的地方,我通过以下方式扁平化箭形代码:

  1. 替换条件为 guard clauses,这个代码..
1
2
3
if (SomeNecessaryCondition) {
// function body code
}

.. 改成 guard clause 会更好:

1
2
3
4
if (!SomeNecessaryCondition) {
throw new RequiredConditionMissingException;
}
// function body code
  1. 用函数来分解条件块。在上例中,我们把 do..while 循环里的条件分解。
1
2
3
4
5
do {
validateRowAttribute(drc[rowIdx]);
rowIdx++;
}
while (rowIdx < rowCount && parseInt(drc[rowIdx], 10) === Id);
  1. 将否定检查转为肯定检查。主要规则是把肯定比较置前,让否定比较自然落到 else 中。我认为这样可读性肯定更好,更重要的是,避免 “我永远不会不做” 句式。
1
2
3
4
5
6
if (Array.isArray(attributes[attrVal.attributeClassId])) {
// do stuff
}
else {
// do stuff
}
  1. 总是尽快从函数返回。一旦工作完成,马上退出。这个并非永远合适的 -- 你可能需要清理资源。但无论如何,你必须放弃只应在底部有一个出口的错误想法。

目的是让代码在垂直方向滚动多些...而不是在水平方向上。

嵌套条件的重构

嵌套的条件判断会导致方法的正常执行路径不明晰,使代码可读性下降。本文提供一种对嵌套条件重构的方法,能有效提升代码的可读性。

原文:http://sourcemaking.com/refactoring/replace-nested-conditional-with-guard-clauses

条件判断会导致方法的正常执行路径不明晰。

特例一概使用 Guard Clauses

阅读全部