关于代码调试de那些事
写代码最完美的就是, 想清楚, 码, 运行, perfect, DONE, 下班.
当然, 那是完美的状态. 大多数时候只存在于理想中.
现实是, 我们会被各种坑, 被环境坑, 被语言坑, 被依赖坑, 被第三方库坑, 被编辑器坑, 被自己坑(三个月前的自己/昨天的自己/几分钟前的自己), 被数据库坑, 被缓存坑, 被队友坑(这个比较惨), 被需求变更坑(这个也是)……
所以, 总是避免不了代码调试.
今天顺带过了下«想计算机科学家一样思考Python>>, 里面每一章最后都有关于调试的一些观点, 例如阅读 - 深思 - 修改/运行/回退
, 所以决定来写写关于代码调试的一些东西.
其实, 代码调试是论如何排查问题
的一个过程, 根据一切蛛丝马迹, 推断出问题所在, 并消灭之.(破案的即视感)
下面是一些关于一些自己在写代码和调试的总结
1.你得明白你在做什么, 保持清醒
代码调试有时候会让你陷入无尽的自我怀疑/迷茫/愤怒/沮丧/窘迫/挫败(无限负能量), 很容易被这些情绪左右, 不清醒, 陷入怀疑自我(一定是我调用的方式不对), 或者怀疑一切(一定是数据库问题, 不对, 缓存问题, 不对, 接口问题, 好像不对, 数据问题), 或者胡乱改代码(改-跑-错了-再改-跑-又错-再改, 传说中的随机行走编程), 或者……(挣扎吧……)
此刻, 保持清醒的自我是非常重要的, 要明确: 我在做什么
, 问题是什么症状
, 原来逻辑是什么
, 最有可能出问题的是哪里
?
2.想清楚了再写代码
如果连需求是什么, 想要做什么都没整明白, 就吭哧吭哧开写, 意图在实践中摸索通向胜利的道路, 是很愚蠢的行为.
需要去理解需求, 自己要做什么, 然后, 在大脑中构造, 现在有什么, 为了完成需求需要做什么, 完成大体的组成结构/步骤流程的思考后, 再着手去做.
大到整体设计, 小到一个函数, 都可以这么处理
例如, 遇到复杂问题, 可以先写注释, 完整所有函数整体设计, 然后再填充细节
def dosomething():
“””
“””
# step1: call func test()
# step2: parse url to
# step3: judge
# step4: convert and return
3.关于脚手架
代码
在逻辑的关键位置, print/assert关键信息, 用于在调试中迅速确认问题. (一些中间值/状态/条件判断结果)
当然, 信息除了关键
这个特性, 还需要足够丰富
和显眼
, 一遍一次性定位问题. (既要好看又要有用)
你需要确定下如何用顺手的编辑器快速输入这些代码, 可以用各类语言的snippets
例如, 在写python时候, 我很喜欢prt
print "TRACK ================= result", type(result), result, result == "test"
4.写完一段代码第一时间自己review一下
事实证明, review的效果比写完直接跑再来调, 效率高多了.
刚写完一段代码, 思路还很清晰, 跳到开始, review过程中注意各类变量, 条件判断, 函数调用, 上下文, 一致性, 错误处理等, 花不了多少时间, 却能发现一些显而易见
的问题, 省下很多无谓的调试时间(没问题不需要调试!).
5.review中注意, 代码是抠
过来的么?
很多时候从其他地方copy代码过来(一行或几行, 有时候只是一个函数调用或一个判断), 但是很容易忘了根据当前情况修改一些必要的值, 导致问题
例如函数调用, 这个地方调用参数可能跟你copy这行代码需要参数不一样, 但是放在这里并不会报错(一切运作正常), 最终结果并不对…..
好了, 开始调试
6.搞明白问题的表现是什么(症状)
运行代码, 报错了, 有些人会瞬切回编辑器, 开始改代码(作高效状)……>_<#
问题是: 报错提示你看了么, 看明白了么?
现在大部分语言, 其报错提示已经很明显了, 精确到行/变量, 虽然整个异常栈信息可能很长(非常长), 但是都有其特征(在最前或在最后,或在中间靠后, 有关键字), 仔细看下报错信息, 精确制导才是王道.
所以, 你需要从错误信息中先确认
错误类型
发生错误的地方
很多语法问题可以根据这个信息直接定位
7.调试过程中, 需要时刻注意
改的是不是正确的目录下正确的文件?(大坑)
保存了么(编译了么)?(又一个坑)
服务重启了么?
跟数据库有没有关系/跟缓存有没有关系, 要不要清?
……
以上问题, 随便碰上一个你都可能发现, 自己书写的代码和当前运行来调试的代码不一样.(会浪费你巨量的时间)
自己调试半天怎么还是一样的结果
我一直在修改, 但是没有什么区别(出现这种情况要自问一下了)
可以显示在代码头部打印或者故意出错, 确认是同一套代码
8.环境/数据一致性
当你发现在本地无法复现别人报过来的问题(在我电脑上是正常的), 这时候, 需要考虑是否是环境和数据的问题.
9.先不要动代码, 假设代码是正确的
遇到问题, 不要急着修改代码, 需要假设, 代码是正确的, 然后去复现, 复现之后定位.
10.首先要怀疑自己
你不能一旦代码跑不动就怀疑是别人的问题, 然后抛给别人, 这样做同样是很不负责任而且很愚蠢的.
首先, 你需要怀疑自己, 排查问题, 当确定不是自己的问题之后, 将问题定位, 输入, 预期结果, 现在的异常结果都处理好, 生成一个问题, 抛给对应负责人. (一切没有价值的怀疑都是无意义的)
程序员都是好人, 每次都在想: 一定是我的问题
11.对于莫名其妙的问题, 多试几种情况
有时候碰上一些诡异的问题, 例如有一种情况的输入会报错, 这时候, 再跳过去修改代码前, 可以多尝试几种输入, 涉及边界/异常/正常等情况, 排除法, 精确制导.
例如, 可以变换输入值的范围(扩大或缩小, 可能用二分法), 变换输入类型和格式
12.先回到正确的代码
如果这段代码是由于修改导致的, 可以注掉此次变更代码, 同样的输入再次验证定位
13.如果一段代码是没动过
的代码
如果你确保确实没动过
, 此时, 先不要怀疑自己, 更大的可能是别人的问题
.
可能情况: 依赖出了问题(调用函数返回数据不对/异常? 依赖请求挂了? ……), 数据出了问题(表结构变更/服务返回数据变更), 环境问题(数据库/缓存)
14.bug总是倾向于集中出现在一起
很多时候, bug是扎堆的, 可以回忆下之前修改的地方, 确认问题.
15.对于很长很长, 上二分法
可能函数很长, 或者调用链很长, 不易调试.(光打调试信息就得打得手疼)
找到关键变量, 上二分法
, 无上利器.
16.print or debug?
个人偏好简单粗暴的print
, 主要是用的vim+sinppet, 快速高效.
当然, 如果用IDE, 用 debug
吧
17.十分十分诡异的问题
上debug
, 打断点, 一点点调试吧, 只能这样了.
18.当一个问题超过半小时
歇一歇, 走动走动, 打个水, 呼吸下新鲜空气.
这时候有利于脱出情境, 去掉挫败感/愤怒/迷信等
很多时候突然灵感一到, 瞬间明了(这种感觉很奇妙)
19.关于google
有些错误信息, 如果觉得比较独特诡异, 可以google下, 你会找到更多的一些信息的.
20.关于求助
实在搞不定, google大神也搞不定, 此时可能需要求助了.
前提, 你自己能把问题想清楚, 并且逻辑清晰地描述出来.(什么业务什么位置的什么逻辑, 报错类型和报错信息, 输入输出, 迄今做了哪些尝试等等) 要学会聪明地问问题, 高效, 尊重自己也尊重别人.
如果你自己都没整明白怎么问, 别人也无能为力.
遇到很多人, 直接上来就一句xxx出问题了
, 没有前置条件后置结果中间症状……
不过, 如果你会聪明地问, 那就放心大胆地问吧, 不用磨磨唧唧的, 程序员大都是善良的孩子.
21.吃一堑长一智
被坑了就要总结总结, 有个记录, 不被同一个问题坑两次.
如果被坑了就忘, 还需要去反复求助, 那这属于坑队友的行为(鄙视下)
好了, 就这些:)