编程调试技巧:验证假设与二分查找法

编程的早期阶段,常常感到困惑,尽管能感觉到问题的存在,但总是需要花费大量时间来追踪问题发生的位置和原因。即使是在像C++这样的强类型语言中,寻找简单的bug也常常让花费数小时。调试是程序员工作中最困难的部分之一。市面上有无数的编程课程和书籍,但关于正式调试的内容却寥寥无几。当出现问题时,它可能存在于自己编写的代码中,别人编写的代码中,其他公司编写的代码中,用户的浏览器中,网络问题中,或者任何其他的问题中。即使是最优秀的程序员,也需要花费时间来找出问题所在并修复它。

随着编写代码的时间越来越长,逐渐积累了一些技巧。在这个博客系列中,将分享一些用来诊断和修复bug的策略。讨论的一些技术和策略是特定于JavaScript的,而其他的则可以应用于任何编程语言。

验证假设

将要讨论的所有技术都是同一个理念的不同变体:验证假设。正如之前所说,在编程初期是一个糟糕的调试者。直到在高中的第三次编程课上,才开始真正问自己为什么调试如此困难。下意识地分析和剖析用来修复bug的方法。过了一段时间,一个模式出现了。发现花费在外围问题上的时间远远超过了需要解决的问题本身。当首先确定假设,然后制定如何验证它们时,效率大大提高了。

专注于问题本身的最佳方式是验证假设。假设有用户报告说点击“添加”按钮并没有弹出“添加”表单。一系列假设立即浮现在脑海中:

  • 控制器绑定了“添加”按钮的点击事件到一个方法。
  • 当执行该方法时,它将打开“添加”表单。
  • 当被告知时,“添加”表单将正确打开。

然后,每个假设都应该被验证。如果假设被验证是有效的,那么bug很可能不在那里。如果它无效,那么bug很可能就在那里。验证假设的一些方法包括:

  • 确保在点击时确实调用了该方法。
  • 确保点击事件拼写正确。
  • 检查事件是否绑定到正确的方法。
  • 测试其他点击监听器以确保它们被调用。
  • “添加”表单真的能打开吗?

调试企业级应用程序可能是一个艰巨的任务。有如此多的不同文件和类,以至于调试上述问题可能会让人感到不知所措。验证假设的最好之处在于它是可操作的和简洁的。它将范围限制在仅与相关的内容上,并提供了可以运行的真实测试,以便快速识别问题。其有效性仅受限于假设定义得有多好,以及验证有多直接。

找出假设是什么并没有真正的技巧。这需要时间和实践。然而,使用了一些不同的技术来验证假设。

二分查找法

即使不是程序员的人也经常使用二分查找。当有人猜测一个数字并得到“更高”或“更低”的反馈时,他们正在使用二分查找来缩小解决方案。二分查找背后的理念很简单:将数据集分成两部分,并找到包含正在寻找的内容的那一半。

同样的原理可以应用于调试。假设有一个非常长且复杂的方法,它产生了一个bug。需要找出bug在方法中的哪个部分开始发生。而不是检查每一行,将方法的后半部分注释掉。如果bug不再发生,那么知道它一定是由注释掉的东西引起的。然后注释掉前半部分,取消注释后半部分,并注释掉最后四分之一。如果它仍然发生,知道问题在后半部分的开始。这个过程可以重复,直到找到罪魁祸首的行或表达式。想象一下,如果逐行检查前半部分,会浪费多少时间。它可能会有一些结果,但由于bug在后半部分,没有理由真正考虑前半部分。

二分查找技术可以应用于任何语言。一个缺点是它天真地假设所有方法都足够模块化,可以运行一个部分并产生相同的结果。通常有这样的情况,前半部分的变量在后半部分使用,所以完全注释掉前半部分不是一个选项。二分查找仍然允许忽略大块的代码来找到根本原因。

debugger语句

设置断点以获取当前调用堆栈和局部/全局变量是任何调试工具的基石。JavaScript调试器确实允许在检查代码时设置断点。然而,大多数人并不知道debugger语句。

debugger语句允许将断点定义为代码本身的一部分。如果需要在多个不同的浏览器中检查同一行,将debugger语句放入代码中比尝试在每个单独的开发者工具中设置断点要容易得多。

要在JavaScript代码中放置debugger语句,只需做:

debugger;

使用debugger语句有各种各样的用途。如果在编码时需要设置存根方法稍后填充,会添加一个debugger语句来提醒自己稍后填充。也不喜欢在这些类型的调试行前放置空白,以便在将代码提交到源代码控制之前轻松发现并更正。

已经多次指出,debugger语句是一个语句,不是一个表达式。这是因为经常看到人们这样写:

if (this.someMethod() && this.someOtherMethod() && debugger && this.someOtherOtherMethod())

他们希望在执行someOtherMethod之后查看调用堆栈,但不想逐步执行每个之前的方法。上面的代码是无效的JavaScript语法,将抛出运行时异常。有效的代码版本使用自动执行的闭包:

if (this.someMethod() && this.someOtherMethod() && (function(){debugger;})() && this.someOtherOtherMethod())

对于非JavaScript程序员来说,它看起来可能很奇怪,但它是有效的。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485