Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。
删除死代码
前些天,
Ned Batchelders
的关于删除代码的文章在
Hacker News
上广为流传,虽然这已经是
2002
年写的文章了。这里我想呼应一些
Ned
的观点,并站在比他更强的立场上:一旦你知道不再需要这段代码就立即删除它,不要有疑问。同时我也会提供一些关于如何鉴定死代码的建议。
这是软件工程方面
“eating your vegetables”
系列的第二篇文章,我目前还不知道这个系列会持续多久,所以敬请期待!
死代码可能永远存在
死代码,也就是程序永远不可能执行到的代码,对于程序的维护性是非常不利的。有多少次你想添加一个看起来很简单的功能或完善,却因为代码的复杂性而纠缠不清?如果添加一个功能或修复一个
bug
可以像你计划中预想的那样迅速,生活该变得多么美好。
每次你想做出改变时,你必须要考虑它与现有的功能、已知的
bug
以及程序的限制该如何交互。如果围绕你要添加功能的代码量越少,那么你所考虑的东西越少也越不容易出错。死代码是非常有害的,因为看起来你要考虑如何与之交互,但由于它是死的,所以它只会导致你分心,也不会有任何益处。
死代码可能永远存在的这一事实可能是你当前工作中潜在的威胁。如果这部分永不被调用的代码一直没被删除,那么你的程序规模就会一直增加。在你意识到这之前,可能看起来真正有用的几千行代码已经被非常多的无用代码所包围,而这些代码都没有任何意义。
It’s Got to Go
Ned
(
Batchelder
,不是
Stark
)对于我这里将要论述的会更加谨慎和小心:
如果你说有一个类,它包含了非常多的函数,有一天你发现有一个函数不再被调用了,那么你是保留还是删除?
这个问题的答案并不唯一,因为它取决于这个类和方法,答案在于你将来是否会重新调用它。
来自
http://nedbatchelder.com/text/deleting-code.html
.
我说:最好的代码就是你不曾拥有的代码。
对于那些没有我大胆的人来说,记住使用版本控制工具来备份以防你再次需要那些代码。
这也就是说我从没有添加过任何我之前删除过的代码,至少不是字面意义上逐行逐字地将之前删除的添加回去。
当然我这里并不想讨论诸如恢复误删的代码之类的事情——我们都是人类,我也犯过非常多的错误。我的意思是指,我从来没有在删除掉一些代码之后的数周或数月内再将其添加回来,并且告诉自己:这个一年前或数月前编写的代码非常好,所以现在把它重新加回来吧。代码库一直会随着时间而发展,所以那些老代码根本不适应新的想法,技术和框架。我可能会参考之前的版本,尤其是它非常地微妙,但是却从不会将其直接拷回来。
所以,请你和你的团队务必在注意到死代码时及时地将其删除。
How did we get here?
Nes
的文章很详尽地阐述了死代码是如何产生的
——
或许那个修改代码的人并没有意识到这段代码永远不会有用了,所以他会将其注释掉或者进行条件编译。也许那个修改代码的人并没有充分认识到这段代码真的死了。
我添加一个新的假设:我们或许都仅仅因为懒,有些事情不做往往是很容易的(比如说保存那些代码),相比于去做而言(如删除它)。
懒怠虽然是
程序员的三大美德
之一,但是
Larry Wall
提出的
Laziness
并不是指这种,而是另一种——为了减少整体工作量而付出努力的品质。通过这个观点,删除死代码是一种真正的
Laziness——
做一些简单的事情以防止今后陷入困境。我们可以在日常习惯中多多发展一些这样的懒怠,这种懒怠我认为是一种
“
有纪律的懒怠
”
。
How do we get out of here?
我花费大量的时间用
Python
编程,不幸的是,
IDE
往往不能正确地分析整个代码库并从中自动找出永不被调用的代码。但是结合着之前的规则及一些运行时工具,我们可以从两方面解决这个问题。
对于简单的例子来说,良好的语感可以帮助你在修改代码时辨别并删除掉死代码。假设你正在修改一个特定的函数,而根据当前代码的语境其中有一个
if/else
分支永远不会执行,这个非常容易推断并删掉,但是确实需要比平常花费多一些的工夫。
直到你在日常的编程中培养出了这种习惯,你可以在提交变更之前添加一步:复查变量的代码以检测是否有死代码。这可以在你提交给同事
review
之前来做,这样他们就不用在阅读你的修改时再次重复相同的工作。
另一种死代码发生的场景就是当你在修改时移除了最后可能使用到的类或函数,而你此时并不清楚这是最后一次使用。这种情形比较难以在日常编程中发现,除非你有异常清晰的记忆并且对代码库非常熟悉。
这种情况可以让运行时工具来帮助我们,我们使用了
Ned
的
coverage.py
包来告知程序中的死代码。通常这个包都是在测试中用来检测我们的测试用例是否正确地执行,但是我们这里也可以用它来查看是否存在死代码:
首先我们创建了一个
Coverage
对象,并提供了一些选项以便于阅读产生的报告。首先,我们指定了数据存储路径(我们将在后面使用它来产生一份关于死代码的漂亮的
HTML
报告),并且要求其自动载入和添加数据到文件中(
auto_data=True
)。然后我们指定它不要影响到标准库以及预装的包
——
这并不是我们的代码,所以我们希望其中大部分代码都不要被我们使用。然后指定它执行分支覆盖(
if
语句的
true
和
false
的场景都要被测试到)。在最后,我们了源代码的路径,这样它可以链接到被检测的代码。
当程序运行后,我们可以产生一份
HTML
格式的报告:
这会产生一份类似下面格式的报告:
那些标红的行就是在程序执行过程中没有被使用到的语句——这些就是死代码的候选行。
使用这个方法来发现并删除死代码时有三点需要注意:
1
、当审查
coverage
执行结果时一定要小心
——
在一次运行过程中某行或者某个函数未被执行并不代码它就真的是死代码,永远不被触及。你必须再次检查代码库以决定是否真的是无用的代码。
2
、执行覆盖测试意味着你的程序要做更多的工作,因而在这种模式下程序会执行的更慢,我建议在生产环境中不要总是这样运行,但是在测试环境中这样并没有太大问题。当然,如果性能是非常重要的,那么你就要仔细衡量下相应的影响。
3
、最后,不要依赖覆盖报告去查询死代码,一些代码可能是死的,但是也有可能是活的,一定要小心!
Parting Thoughts
亲爱的读者,我非常抱歉。我遗漏了
Ned
文章中非常重要的一点,他说:
这个问题没有唯一的答案,因为它取决于相应的类和方法。
[…]
一个粗略的答案是:如果这个类是某个框架的一部分,那么保留它;如果是应用的一部分,那么删除它。
如果你是某个库或者框架的作者,而不是某个应用,那么关于死代码的问题可能棘手也可能简单。大体上,你不能移除公共
API
中的代码(除非有大的版本变更)。所有
API
的代码都是活的,即使你不再使用它。但是在公共接口背后,仍然可能有死代码,而这些代码仍需要被移除。
英文原文:https://late.am/post/2016/04/28/delete-your-dead-code.html
译者:dlgao