今天,知了姐来跟大家聊聊“在代码中避免间接性”这个话题。
(以下为译文,作者 | Matthew Rocklin)
我经常在代码中看到,作者为了抽象,而把细节放入某个外部函数中。请看下面的例子。
加入间接代码之前:
# main.py
if x.startswith("foo"):
do_something_with(x)
加入间接代码之后:
# main.py
if is_foolike(x):
do_something_with(x)
# utils.py
def is_foolike(x):
return x.startswith("foo")
这样做的理由可能包括:
l 如果这个代码重复多次,它可以使代码更紧凑
l 如果此代码重复多次,则会为该逻辑创建一个位置集中管理,以便将来可以集中更改
l 它隐藏了可能与功能要点无关的细节。这就像散文中的脚注。
l 它使用函数名称作为内联文档为一组操作命名
l 如果经常使用,感觉代码更干净,更抽象
在学校我们被教导这样做——找到一些功能,抽象出去,然后继续。
避免间接的情况
然而,这种添加间接代码的行为也需要付出代价。当其他人阅读这段代码时,他们需要在多个文件定义的多个函数之间跳转。这种非线性的阅读过程更加耗费阅读者的精力。
在写代码的过程中,加入这种间接代码并没有任何问题,写代码的人的脑海中建立了一个抽象模型,因此将这种抽象写入代码是有意义的,感觉很好。但是,当其他人需要快速检查和理解一段代码时,就会遇到大麻烦。这发生在两种重要的情况下:
1. 在审核期间,要求审核人在合并到主项目之前验证代码是否合理。该评论者可能花费的时间大约是原作者在该代码上花费的十分之一。
2. 同时调试未来的问题。这段代码最终将涉及一个错误,一些完全不同的开发人员将不得不浏览这段代码来弄清楚发生了什么。他们必须在几分钟内理解这段代码的一小部分,以确定相关的内容。他们将无法投入时间来理解其背后的完整思维过程,而功能定义网络可以大大减缓这一过程。
与原始开发相比,现代社区代码中的审查和调试往往是瓶颈。因此,我经常鼓励开发人员避免抽象,并“内联此函数定义”。
我们仍然应该使用函数
需要明确的是,有很多理由将复杂逻辑分成多个功能,特别是在代码重复时,或者某些重要政策可能在未来发生变化时。因此,这里需要找到一个平衡点。
大多数情况下,我希望作者能够意识到除了原作者之外,每个阅读代码的人都会感受到间接代码带来的附加成本。