Local Debug Notes
随便写写,未来可能看到类似的内容就会更新,反正写出来总比不写好。被人骂了就看别人的经验更新更新,终归比闭门造车好。
笔者作为各种简单代码长期的受害者,在生活中多少有一点「先看懂至少大部分代码,再改」的强迫症,主要原因还是下面几点:虽然理想中的模块是那种对外留一个漂亮的接口,对内留下脏活累活。但是一来 infra 工程师(也包括正常的工程师)都不是生活在真空球形教科书代码世界的;二来很多项目因为要 Rush 多少留下一些技术债务;三来很多模块如果要设计成高性能的,就会有抽象泄漏(不如说据我观察,除了很多常见的设计 Pattern,比如 SQL、KV 这种接口,其他你一旦对性能之类的产生一点要求,就一定需要了解一些内部的实现,甚至根据用户语义对实现开洞)。
在一些大项目内,可能有限的时间只够你学会用别人的模块,看懂别人的代码将会变成一个很难完成的任务。完成这些不是不可能,但花费的精力可能远大于你的工作量。哎其实我也想全懂,但这篇文章就是给不能全懂的人写的。我们能理解别的 Layer 传来的内容,能够进行一些必要的 validity check,但可能不完全理解这块逻辑。二来即使是自己熟悉的模块,也会有一些自己未预料到的或者不熟悉的行为,在简单梳理代码后还有疑惑的地方,需要手动 debug 一下。
总之,Debug 的时候在遇到问题+能够复现问题或者 Local Dev 有单测的情况下,跑起来是一个合理的行为…吗?当然是。测试不够多的项目实际上是搬起石头砸自己的脚,有能复现行为的 case 会让你舒服很多(或者说是必须的)。复现之后,加 LOG 本身是一个猜想(我觉得什么地方有问题,所以加一个可观测 point 验证)-> 验证的循环。哪怕这个问题是个并发问题,加日志了好的情况能让你感受到 ordering,坏的情况下你也能知道这是一个并发问题。
日志的格式可以(或者最好)是项目中的 LOG。启用LOG_DEBUG
之类的项目自带日志通常有点好处,因为它自带 threadId 、时序,也能和项目的其它事件同步,起到一定的时序效果。std::cout
瞎用的时候就比较容易遇到线程问题了,比如 cout << a << b
的 operator<<
中间可能插入别的事件,这是个弱智问题,但是xjb直接改代码看日志的时候,笔者常常犯这种弱智问题,影响自己的 debug 体验。当然 LOG_DEBUG
级别开低的话,这样日志可能比较多,可以自己根据实际状况权衡。题外话,笔者在的所有项目里,LOG_DEBUG
感觉用的都不太好,开了之后日志总是不必要的过多,笔者也没想清楚这类怎么 trade-off。
日志本身意味着「猜测行为」,那么最初定位的时候应该讲究「宜粗不宜细」。这个「粗」「细」是根据自己的(每次的)猜测来的。比方说我们预期 Call FunctionA,但是 A 并没有被 Call;或者某个变量最后的值不如预期我们就可以有下列的行为:
- 在 A 父级函数的地方打日志
- 或许 A 和一些对象有关,可以利用 C++ 的特特性,给一些构造函数/析构函数加上一些信息。
- 准备一些 toString() debugString() 的状态 dump 函数( 比如之前我改的时候 dump 了半天 https://github.com/apache/arrow/issues/45273 )
如果一轮 debug 后自己感到费解,就可以更「粗」一些,否则可以更细一些。
日志的增删本身也要和 git 一起版本化起来,最好找个地方记录下来一些现象,笔者有的时候会感觉查了半天结论又变模糊了,这固然是因为笔者并不聪明,但也是因为这个流程很容易「堕落」:操作者很容易随便不经思考改两行代码,然后发现又不对,反复的变更一些不稳定的结论。最好能把日志收集起来,先认真扫几眼。然后一边相信问题总能被查出来一边改吧。