如何少写bug?debug毫无头绪的时候能做点什么?
惭愧啊, 我上学也有几年了, 但是一次写对程序的概率. 总是把90%的时间用于debug, 我感到非常痛心. 而且不是那种软件级别的代码, 连30行的R代码, 核心逻辑只有15行的, 我都能debug几小时. 因此来想想这两个问题.
如何少写bug?
- 首先得理解了算法和逻辑, 确保逻辑是完善的, 当然这一点可能做不到, 但能考虑多完善就多完善吧. 如果有些情况我不想考虑, 至少我可以assert一下吧.
- 代码写个大纲, 方便对照逻辑.
- 变量名起得长一点, 要有意义, 而且也有区分度, 免得变量名不对导致的问题(是的, 虽然年纪不轻了, 但变量名的错误我现在还常常犯).
- 注意变量的更新, 可以在旁边打个草稿列出需要维护的变量, 确定每个变量是否正确更新.
- 函数尽量不要有副作用, 也就是不要修改全局变量. 一个函数有副作用一定要写在注释中. 写完一个函数要检查它是不是有副作用.
- 测试. 每一个函数, 如果行数稍微多一点, 10 行肯定算多了, 就要做测试. 测试我几乎没做过, 做起来很不熟练, 技术也很差. 至少目前, 我得考虑各种情况, 来做测试, 并且测例也要写在代码文件中(当然, 这毫无疑问会使代码文件变长, 但 who care 呢).
debug毫无头绪的时候能做点什么?
- 或许你的代码真的没错, 是这个算法本来就有这些问题, 比如共轭梯度下降, 它可能就是收敛得很慢, 慢得让你以为它不收敛. 这种情况, 本来应该能从printf中得到的, 但我几个小时没发现, 说明我没有在合适的位置printf来检查行为. 另一个办法是, 与肯定正确的代码结果相对比, 比如用别人实现好的对比一下. 比如GMM, 我写得又不对.
- 如果确认的确是自己错了, 那么考虑什么情况导致了这个异常行为. 并且记录下来. 有哪些情况会导致这个异常行为的发生.
- 单步调试, 以及assert, 看看哪些地方开始就已经不符合预期了. 如果是一个循环, 看看前几次循环是否符合期望. 如果是累积起来才能看到异常, 那么可以考虑条件断点或者printf大法了. 打断点, 或者在合适的位置加printf, 这是需要仔细考虑的, 不过如果做到了, 那就成功了一大半.说实话, 如果算法用到了递归, 比如深搜这种, 我debug更是痛苦无比, 因为各种调用关系太麻烦了, 就连构造最小最简单的例子, 都要跟踪半天. 这也是我烦恼的一点. 也没有想到解决办法. 说实话, 循环中出现的问题, 我也没想到很好的办法, 比如为啥我写的优化函数不收敛, 我只能看出最容易分析的.
- 找到了不符合预期的地方, 那么很好, 有两个可能, 逻辑错了, 比如有些边界情况没考虑. 或者逻辑没错, 但代码没正确实现逻辑, 比如没及时更新变量.
- 如果还是不行, 那就不要死磕了. 我们应该列出, 我们还能做的事情, 如果这些事情都做了, 那就可以无悔了, 其孰能讥之乎? 说不定过几天看, 就发现了呢, 比如某天吃饭心情好的时候看, 或许一下子就明白了. 这也侧面告诉我们, 不要赶ddl, 这样才有几乎预留停下来的时间.
- debug本身也是学习的机会, 比如思考算法本来正常的行为是什么, 在哪些地方打断点, printf, 这都是有助于加深理解的. 再比如, 与正确代码作对比, 我们可以学些库函数的用法. 与别人正确的代码作对比, 也是学习的机会. 再比如, 如果发现不会单步调试已经成了自己的瓶颈(比如我), 那么就好好学下怎么单步调试. 如果你觉得现在debug就像无头苍蝇(这个你就是我), 你真的该停下来了. 想一想你能学点什么, 让这个过程更有意义. 比如我的调试技术很不行, 比如R, 函数debug每次都让我很头疼, 这说明我对调试掌握得完全不够, 需要看RStudio的文档.
- 对读别人代码感到排斥, 有以下可能. 你不敢, 因为你没有充分理解这个算法, 怕看不懂别人的代码. 你不愿意, 因为不想付出时间. 这很可能说明你心中有觉得更紧急更需要做的事, 那就先做它们吧.
希望我能对这个话题不断更新.