“每个大程序里都有一个不安分的小程序”,想要成为一名好的开发人员,你得先真正学会 Debug 才行。
以下为译文:
总结我的编程生涯,可以得出如下两个硬道理:
-
任何代码都可能出错,而且肯定会出错。
-
代码有味道。
而中和这些苦涩现实的唯一方法就是调试(Debug)。
没错,就是调试。在刚开始接触编程的时候,没人会喜欢调试。相反,调试往往是沮丧和恐惧的根源。很多人心里说:“我要浪费多少时间才能改好这个bug?”我们更加愿意去构建很酷的东西。(试问谁不喜欢构建很酷的东西!?)
然而,所有我们钦佩的开发人员都认为调试很重要。那是因为他们知道调试对他们来说是提高自身实力的宝贵资源。在有些情况下,调试能够反映出开发人员的能力。
调试的真正问题在于无法规划所需的时间。所有编程活动——从设计到开发都可以有时间规划。但调试却完全不同,调试可能会花费一个小时、一天、甚至不知不觉一周就过去了,却还是未能发现问题的根源并修复问题。
这就是你应该把调试作为学习良机的原因。当然调试很痛苦,但是通过正确的方法你也可以控制调试。
以下是一些可以更好地进行调试的方法。
首先了解系统
我们经常会犯一个错误:“先理解出现的问题”然后再“理解系统”,实际的顺序应该反过来。
曾经有一段时间,我调试过一个 SAP 智能表单的问题。表单上的有些值显示不正确,我花了两天的时间调试整个表单,仍然茫无头绪。不用说,我的心情很沮丧。这个表单看起来似乎根本没问题,后来我突然来灵感了——
我注意到主代码中有两处同时调用了该表单。通过进一步分析,我发现问题出在调用该表单的代码中。我迅速解决了这个问题。我之前两天的努力全白费了,事实上只需要在别的地方做一处小小的改动就可以解决问题。
那么,我哪里做错了?
如果不理解这个系统的工作原理,那么我就无法调试这个问题。一旦明白了系统以后,问题就很明显了。
请时刻牢记,你需要了解系统的应有行为、系统的设计,以及在某些情况下系统如此设计的原因 。如果你不理解系统的某些部分,那么问题往往就会出现在你不了解的这些部分上。
分而治之
我付出了很大代价才明白了这个道理。
有一段时间,我参与了一个大型的 SAP 数据迁移项目。当时的时间非常紧迫,而且有很多代码块同时开发。在这个节骨眼上,我遇到了数据损坏的问题。
一些迁移的客户数据遭到了破坏,从几百万条记录中找出哪些出了问题是一项无比艰巨的任务。虽然我知道问题是什么,但是问题出在什么地方才是关键所在。
最后,我通过原型制作解决了这个问题。我创建了一个很小的原型,通过一套子数据集重现了类似的症状。我通过这个原型找到了根本原因,并最终解决了问题。
解决问题的关键在于缩小搜索范围。
大型系统都很复杂,系统的运行涉及许多因素。从整体上来看系统,你很难区分出影响特定问题的细节。
解决方法是分而治之。不要着急着眼于整个系统,将有问题的组件或模块从代码中分离出来,才能进行严谨的调试。
分离问题有许多优点。你可以只关注与问题相关的部分,由于代码量很少,所以你可以快速解决问题。
请时刻牢记,通过剥丝抽茧,逐步缩小范围,最终 bug 将无藏身之处。
一次只做一个改动
就好比使用步枪,一枪一个眼;而不是霰弹枪,一枪炸一片。
想象一下,你去牙医那里拔牙。你的牙医为了“设法”找到问题,开始对着你满嘴的牙下手。他手中的锤子每敲一下,你就痛苦地呻吟一声。
这时,你有何感受?你心里肯定在犯嘀咕:“什么情况啊?这个混蛋会不会给人看病啊?”
错误的调试方式亦是如此。
一次只改动一个地方。如果你手中拿的是一把好步枪,那么你可以更好地修复 bug。我见过有些开发人员试图通过交换或修改其他组件来修复错误的代码。可能在他们改动了三四处代码后,发现可以正常工作了。这看似很酷,但是他们完全不知道哪个部分有问题。更糟糕的是,他们所做的这些改动很有可能破坏本来运行得好好的代码。
在很多情况下,你需要改动系统的不同部分,来看看是否对这个问题有影响。一般来说,这是一个警告,你并没有充分地了解代码,而是在猜测。你只是在改变问题发生的条件,而不是寻找问题自然发生的原因。
这种做法会隐藏第一个问题的原因,还会导致更多问题的发生。这是开发人员都需要避免的错觉陷阱。
请时刻牢记,如果一次只做一个改动,那么你可以确切地知道哪个参数有效。如果改动看似没有效果,那么请立即恢复原样!
检查是否真的是由于你的改动才修复了 bug
调试的一个黄金法则是:如果不是你的改动修复了 bug,那么这个 bug 就没有得到修复。
每个人都愿意相信 bug 不药而愈了——“似乎我们无法再重现这个 bug”——“这个 bug 出现了几次,但后来不知道发生了什么,不再重现了。”当然,合乎逻辑的结论是,“也许这个 bug 不会再发生。”但是,你猜怎么着?它还会发生。
很抱歉让你们失望,但现实世界中没有神仙。
如果你觉得你已经修复了某个 bug,那么你需要确保去掉你的改动后,这个 bug 会再次重现;还要确保加上改动后,bug 就会被修复。你需要重复从改好到重现再到改好的过程,只改动与修复 bug 有关的代码,否则就不能证明你修复了这个 bug。
修复 bug 往往很有意思。有时 bug 只是“隐藏了起来”而不是真正被解决了。有时候,当我们意识到我们的改动与要修复的 bug 无关时,产品早已发给了客户,显然他们不会满意这种产品。所以,千万不要掉入这样的陷阱中。
请时刻牢记,如果去掉你改动的代码,那么系统就会重现之前的 bug,只有这样才能确保你的修改确实有效。
最后,记录你的解决方案
这项工作看似无关紧要,但其实是一个常常被忽略的非常重要的问题解决工具。问题在生活、工作甚至是长期的人际关系中反复重演,我们没必要一次又一次地重复造轮子。
关于解决方案日志中实际记录的信息类型而言,考虑到业务之间的需求不同,所以很难下结论。但是,通常可以考虑如下几项:
-
问题编号
-
汇报人(记录通话的人)
-
汇报人的分机号码或电话号码
-
创建日期/时间
-
概要描述
-
影响/重要性
-
故障类型
-
系统的所有者
-
当前状态(开放,正在处理,已关闭)
-
下一步
-
下一步的日期
-
完成日期
-
解决方案,开发请求编号或供应商支持请求的链接
不要在同一个地方跌倒两次。为了提高工作效率,请记录所遇到的问题以及最终的解决方案。遇到问题时,我们不必再说:“我以前见过这个问题,但我不知道怎么解决。”你可以快速查找过去使用过的解决方案。不用说,这种做法不仅可以节省你的时间,还可以大大地提高你的自尊和自信。
请记住,调试是一个学习的良机。当然,有时你会意识到你犯了一个本不应该犯的愚蠢错误。但这也是成长为一名更好的开发者的必经之路,因为你可以借机学习新知识。
Richard Pattis 有一句话说得很好:
新手调试时会添加矫正代码;而专家调试时会删除有缺陷的代码。