5

应该怎样debug,难道就是到处加 console.log 吗?

 3 years ago
source link: https://zhuanlan.zhihu.com/p/338091580
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

应该怎样debug,难道就是到处加 console.log 吗?

problem solver

在使用 console.log 又调试了一晚上。感慨生命就这么被浪费了。debugging 是怎么一个过程,如何能改进这个过程?

State

debugging 的起点是错误报告。错误报告来源于对可见 state 的观察。最常见的就是界面上某个字段和预期不相符,或者界面某个地方白屏了。

State Inspection 是第一步需要做的事情。除了界面上可见 state,还有一些 state 需要手工获取,包括:

  • 通过 chrome inspector 查看 DOM State
  • 通过 js api 查看界面背后的依赖订阅关系
  • 通过数据库工具查看数据记录
  • 通过 HTTP GET /internal/state 查看服务器内存中的状态

查看更多关联的 state 是为了两个目的:

  • 比对“预期”和“事实”,找到更多值得注意的地方
  • 获得更多的关联上下文,精确定位到订单号,手机号等具有更广泛关联意义的标识符

这一步经常遇到两种问题:

  • 即便就出现在你的开发机上,很多“可不见”state 都不是那么容易提取的。或者缺乏日常唾手可得的提取方式。至少不缓存在你大脑的 L2 缓存里,需要借助 L3 缓存(google)去获得。
  • 很多问题出现在客户的机器上,你并不在场,无法立即执行 state inspection。这就是 core dump 等机制的价值,能够把“犯罪现场”给留下来。

你能够想出你的系统有多少个模块是有状态的吗? 能够立马想出 inspect 每个状态最简单直接的方式吗? 你客户遇到了问题之后,你能够用什么手段拿到这些 state?

Trace

Trace 是指 state 的变化过程。log 是用来记录 Trace 的。如果我们要依赖 log,特别是临时添加 log 来获得某个 state,这说明 state inspection 的机制是严重不足的。

仅仅依靠 State,有的时候我们就可以立即凭直觉找到问题所在。但更多的时候会纳闷为啥 state 变成了这样,中间是哪个环节掉链子了。Trace 包括各种形式的:

  • 手写的文件日志
  • 临时加的 console.log
  • 分布式追踪,zipkin 等
  • 自动加的 function tracing
  • 系统 api 的追踪,strace 等
  • Enhanced Debugging with Traces

Trace 通过忠实记录 state 的变化过程,回答以下两类问题:

  • 我不清楚这个过程是咋实现的,通过 Trace 来反推实现
  • 我有一个大概的 idea 这个过程是咋样的,通过检察 trace 来发现异常的地方,从而找到 bug 根源

很多时候我们认为我们很清楚执行的每一步是怎样的。但是调试地时候才惊讶地发现执行过程充满了未知的细节。Rubber Duck Debugging 的原理就是把你假设的执行过程都说出来,这个过程就是在复述 Trace 的过程,也是比对假设和事实的过程。

这一步经常遇到:

  • Trace 压根就没有记录,代码也不是我写的,或者不知道在哪里。如果你是一个咨询师,只知道客户用的是 Java,你能够带上哪些 toolbox 去查问题?
  • Stack trace 是最常见最易用的一种 Trace。但是在 generator / async 等场景下,stack trace 就缺失了,然后立马就会回忆起同步编程的美好。但是 state 随更长时间周期的修改(例如上一次 react render 对下一次 react render 的影响),总是用 stack trace 回答不了的。没有 stack trace 的时候,拿什么串起来线索,id 是什么?
  • 缺少大容量的存储。日志一刷就没了,无论是前端日志,还是控制台日志,经常一多就滚没了。
  • 缺少查阅和索引,即便是把所有日志都记录下来了。一条条去翻也太慢了。没法和代码直接映射起来,往往在对应的代码里,加上带条件的 console.log 是获取这个 spot 的 trace 的最佳办法。
  • 性能拉跨。各种日志手段都会拖慢执行。
  • 很多问题出现在客户的机器上,你并不在场。无论是前端还是后端,日常能开的日志还是太少了。当然不开日志主要原因还是性能拉跨。

你能够根据现有的 Trace 完整地把执行原理给讲一遍吗? 你能够在每种编程语言上都找到立即可用的 function tracing 的工具吗? 你能快速从 Trace 的海洋中检索出有效的信息吗? 你能从生产环境获得可以定位问题的 Trace 吗?

即便 State / Trace 都有很完备的机制和手段,仍然不够。因为不同的人,需要的细节程度是不同的。这个是因为分工导致的。框架的维护者需要能够定位到根本原因,但是框架的使用者只需要知道是我业务代码没写对,还是框架出bug了。操作系统的维护者需要能够定位到硬件是不是出bug了,而大部分软件开发人员都默认假设硬件是bug free的。

需要区分细节程度还有一个原因显然是因为性能。像 strace 这样的东西没有办法日常开,但在现在的硬件能力下,rpc 日志已经可以全量记录了。无论是 State 还是 Trace,都要准备多份,一些是必须的,一些是按需的。

对不同的读者,提供的阅读工具也会有不同。越是日常使用的,越要方便易用。甚至可能需要 web 界面。

明确定义“知识边界”。不要假定所有人可以 debug 所有的 stack。总是有一些人需要负责底层,有一些人只要写最上层的业务的。这个“知识边界”怎么能清晰划分。如何能够快速定位到是底层模块的问题,还是业务系统的问题? 这个快速定位是否可以做成自动化无需人参与的?

插件化架构使得这个问题更加复杂。如果仅仅只是 client <-> server 的关系,无论是你用一个硬件,用一个操作系统,用一个框架,用一个后端服务,只有双方的关系。而插件化架构使得更多的“知识边界”的存在。插件之间未必有严格隔离的沙盒,很多非功能性指标都会互相影响。甚至很多功能性的 State 也会彼此修改。这使得“甩锅”变得困难,难以知道是不是一份不是我写的插件引入的问题。

所有以上问题都在于“边界”的划分。如何提供不同粒度的 State / Trace 给处于不同边界内的团队去看。如何提供针对性的 State / Trace 去快速找到该由谁来负责。相比在一份日志中挑拣出我应该关心的,更恰当的应该是直接提供按“边界”裁剪之后,重点突出的 State / Trace。

那么 console.log 还要用多久?

https://www.youtube.com/watch?v=m4AikuoWNFw 是一个很有意思的演讲。很惊讶地发现上古时代的计算机编程并没有那么原始。或者说我们现在的开发方式和当年一样原始。

你看,在 ENIAC 上就有 debugger 了,和你用的这个

是不是差不多

意思就是 1949 年新中国刚建立那会,已经有了自动化的“流量录制”技术。可以根据 Trace 来查问题。类似于这玩意

所以,恐怕 console.log 还要用很久。70年来就没啥新东西了,好想法早就提出来了。人类最终选择了改代码加 console.log 做为“最舒适”地调试方式。console.log 的舒适来自于

  • 避免了存储和索引 Trace 的困难问题,可以选择性输出,没有太大的性能问题
  • 避免了从海量 Trace 中找到信号的问题,因为可以选择性输出
  • 避免了 Trace 和代码关联不上的问题,因为就是在指定的位置加的 Trace
  • 避免了工具不可靠带来的信任问题,没有比 console.log 更可靠的工具了
  • 也很好的实现了甩锅的目的,因为大概率能加 console.log 的地方是你所能掌控的边界之内,避免了阅读陌生 stack trace 的问题

即便是有了一种通用的 Trace 工具,例如流量录制与回放,我能想到的最用户友好的 UI 界面也就是伪装成 console.log 的使用体验。让用户感觉彷佛是修改了代码,加了 console.log 一样。但实际上可能仅仅是用你加的代码过滤了一下 Trace 数据,或者用受控回放的方式重跑了一遍代码。

缺的是什么?

很多问题我们都可以归纳为政治问题,而不是技术问题。debugging 也是如此。表象上来说,就是一个纯粹的缺少工具,缺少经验的技术问题。但是更深层次的,仍然是政治问题。对于 debugging 投入不足,找到 bug 之后也不会深度复盘,让 bug 定位更容易排不进日程表才是根本原因。

The theory of debugging

  • 第一步:比对假设的 state 和实际的 state
  • 第二步:按 id 找到异常 state 的 trace,比对假设的 trace 和实际的 trace
  • 第三步:确定“边界”,找到甩锅给谁
  • 第四步:如果是自己的问题,自己修了
  • 第五步:复盘 debugging 的过程,找到可改进的地方
  • 第六步:针对复盘结果,落实改进方案

前边列了大量具体的技术问题。我相信这些技术问题都可以被解决,只要解决好投入的问题。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK