提高编程能力,特别是调试能力,是每位新手程序员提升能力的重点。以下是一些高效提升debug技能的方法:
理解你的程序
高效 debug 的前提是充分理解你自己的程序。每段代码负责什么工作、有哪些 corner case 的处理、对输入有哪些假设,等等。如果你对你的程序非常熟悉并充分理解,那可能大部分 bug 你看到症状之后一眼就能猜出个大概位置,这可以极大提升你的 debug 效率。对于比较 “业务” 的代码来说这一点尤其重要。对于算法/数据结构类的代码,bug 可能相对不那么容易一眼看出来。但充分理解你的代码依然可以指导你进一步收集信息。
编程语言通常会对常见的错误场景提供各类警示。以月兔为例,月兔会给我们提供很多的检查,例如,如果我们声明了一个标识符应当为变量,那么月兔会检查这个变量是否被修改,因为一种出错的场景就是在写循环的时候可能忘记对计数器进行迭代。
月兔会检查我们的返回值是否和类型声明相同。即使我们的返回类型声明的是单值类型,我们也不会主动地舍弃这些运算结果,而是需要我们手动添加声明来做到这个也是为了避免我们可能会漏写返回类型的声明。
虽然并不是每一个警示都需要被清除,但是审视警示信息可以减少代码发生错误的可能性,避免踩进大家都掉过的坑中。
学会收集信息
当一眼瞪不出 bug 的可能来源时,就需要收集更多的信息来辅助判断了。这部分主要是技巧的积累,这里推荐几个小技巧:
- 崩溃类的 bug 通过 stack trace 可以快速定位
- 优秀的 debugger 可以帮助你非常轻松的检视程序的中间状态
- 大不了用最原始的 print debugging,除了写起来麻烦一些同样是获取信息的合理手段
当程序涉及的数据总量不大时,往往调试运行一次,收集一些信息就能够判断 bug 的成因了。但当程序涉及的数据量很大,难以逐一确认时,就需要运用对程序的理解,来尝试从大量数据中挖掘出不对劲的地方了。这其中非常常用而重要的一种,就是利用程序的性质来 debug。一段程序在正确运行时,在程序的不同位置,各种数据往往需要满足各种各样的性质(例如:二叉搜索树的左子树的元素永远应当比父节点小)。在程序没有 bug 时,这些性质是一定会得到满足的,因此为了效率我们并不会写代码去检查它们。然而,当程序出错时,往往就会有某个地方的性质被破坏了。此时,不妨插入一些代码,在程序的各个位置显式检查各种性质是否得到满足。这样可以快速地缩小 bug 可能的范围。
学会使用调试器等工具
除了插入代码以外,最高效的方式便是通过调试器在代码中添加断点,在运行中查看程序状态并实时计算。为此需要熟练掌握所使用的语言的调试器。例如,C语言对应的gdb等。MoonBit在开发模式的构建下同样可以利用调试器。目前,该功能已支持源码映射、基于源码设置断点、输出sourcemap等,在浏览器中进行源码调试。对于那些熟悉或正在学习MoonBit的开发者来说,使用调试器能够提高调试效率并更深入地理解MoonBit代码的运行过程。
MoonBit 现已支持在浏览器中进行源码调试
使用调试器允许我们在运行中看到实时的运行数据,更好理解运行过程。
写单元测试
测试代码有很多种方式,但其中最对 debug 最有用的无疑是单元测试。根据程序应当满足的性质,在每一处小模块都加入一些单元测试,往往能在更早的阶段发现 bug。而且单元测试的天性使得,当一个单元测试失败时,你马上就能知道 bug 的成因在哪、是程序的哪些性质没有得到满足。例如下面是在 moonbit 里写的测试:
fn iter_range(from :Int, to :Int, f : (Int) ->Unit) ->Unit {
let mut i = from
while i < to {
f(i)
i = i + 1
}
}
fn sum(from :Int, to :Int) ->Int {
let mut n = 0
iter_range(from, to, fn{ x => n = n + x })
n
}
fn assert_equal[T:Eq +Show](actual:T, expected:T) {
if actual != expected {
abort("assert_equal, actual: \\(actual), expected: \\(expected)\\n")
}
}
test "sum 1 to 100" {
assert_equal(sum(1, 100), 5050)
}
从测试结果我们很容易就能发现实际值比预期值少了100,于是很快就能定位到是第三行的while i < to {应该改为while i <= to {。
➜ mytest git:(master) ✗ moon test
inline test main ... FAILED
assert_equal, actual: 4950, expected: 5050
error while executing at wasm backtrace:
0: 0x8ad - <unknown>!<wasm function 39>
1: 0x8c3 - <unknown>!<wasm function 40>
Caused by:
wasm trap: wasm `unreachable` instruction executed
debugging in the large
除了 debug 自己写的程序,在协作开发时,可能还需要 debug 别人写的代码。此时需要用到一些其他的技巧,例如:
- 使用 git blame和二分调试快速找到某段代码是什么时候、由谁、为了什么目的引入的
- 如果怀疑调用的他人的代码出错了,可以运用上面的、基于程序需要满足的性质调试的思路。在每次调用他人的代码时,检查结果是否符合预期、满足 API 要求满足的性质
希望这些建议对你有所帮助!