动态分析:运行起来之后才能知道什么
静态分析关注代码可能如何运行,动态分析关注代码实际如何运行。真实系统里,很多事实只有运行起来之后才能观察到:请求经过哪些服务,哪些路径真正高频,哪个函数消耗 CPU,哪些测试覆盖了目标代码,哪些异常在生产环境出现。
动态分析不是静态分析的替代品,而是对静态分析的校准。静态分析给出全局可能性,动态分析给出运行时证据。本章讨论几类常见动态数据:日志、Trace、Profile、Runtime Call Graph 和 Coverage。
为什么需要动态证据
静态图谱经常会比真实运行路径更“宽”。一个接口可能有多个实现,一个方法可能有很多调用方,一个分支可能永远不会被生产流量触发。如果只看静态关系,影响面可能被放大。
动态数据能回答另一类问题:
这条调用路径是否真的发生过。
哪个入口触发了目标方法。
哪些代码处于核心流量路径。
哪些测试执行过目标代码。
哪些函数或服务是性能热点。
哪些异常和日志与目标代码相关。
在代码可视化中,动态数据可以把“可能影响”进一步分层为“真实发生过”“高频发生”“测试覆盖过”“线上热点”。
日志:业务语义和异常上下文
日志是最常见的动态数据。它记录业务事件、状态变化、异常和关键参数。
日志的优势是语义强。例如一条日志可能直接说明订单创建失败、库存扣减异常、支付回调重复、用户权限不足。它能补充静态代码看不出来的业务上下文。
日志的局限也明显:
埋点不系统。
格式不统一。
日志级别混乱。
关键字段缺失。
过多日志造成噪声。
如果要把日志纳入代码图谱,需要建立日志语句与源码位置、请求 Trace、业务事件之间的关系。这样排查问题时,才能从一条异常日志跳回相关代码和 owner。
Trace 与 Span:请求路径
Trace 用来描述一次请求或任务在系统中的传播路径。一个 Trace 通常包含多个 Span,每个 Span 表示一次操作,例如 HTTP 调用、数据库查询、缓存访问、消息处理或内部函数执行。
Trace 可以回答:
一个请求经过哪些服务。
每个阶段耗时多少。
哪个下游调用失败。
一个接口实际依赖哪些资源。
入口和底层代码之间的真实路径是什么。
对代码可视化来说,Trace 是连接“运行时路径”和“静态代码图谱”的关键证据。静态调用图可以告诉我们可能路径,Trace 可以告诉我们真实路径和耗时。
在微服务系统中,这一点尤其重要。源码级调用图只能覆盖单个仓库或进程内调用,Trace 可以跨服务、跨语言、跨资源展示请求传播。
Profile 与火焰图:性能热点
Profile 关注资源消耗,例如 CPU 时间、内存分配、锁等待、I/O 等。火焰图是 Profile 数据的常见可视化方式,它把调用栈聚合成宽度代表耗时或采样次数的图形。
性能分析不能只看代码复杂度。一个复杂函数如果很少执行,未必是性能瓶颈;一个简单函数如果出现在高频路径上,可能占用大量资源。
Profile 可以回答:
CPU 时间主要消耗在哪些函数。
哪些调用栈最常出现。
哪些方法分配了大量内存。
优化后热点是否下降。
当 Profile 和代码图谱结合时,可以把性能热点映射回文件、方法、模块和 owner。这样性能问题不再只是运行时图表,而能进入代码治理流程。
Runtime Call Graph:运行时调用关系
静态调用图来自代码分析,Runtime Call Graph 来自运行时观察。它可能来自 Trace、采样、插桩或 APM 数据。
运行时调用图更接近真实执行,但通常覆盖不完整。它只记录发生过的路径,未被流量或测试触达的路径不会出现。
因此,两种调用图应该结合:
静态调用图提供全量候选路径。
运行时调用图标记真实发生路径。
高频路径可以提高风险等级。
从未运行的路径需要结合测试和业务判断。
Coverage:测试触达证据
Coverage 描述测试执行时触达了哪些代码。常见粒度包括行覆盖、分支覆盖、方法覆盖和路径覆盖。
Coverage 可以帮助回答:
哪些测试触达了目标方法。
修改代码后应该优先运行哪些测试。
哪些影响路径缺少覆盖。
新增测试是否覆盖了关键分支。
但 Coverage 不等于正确性。代码被执行过,不代表断言充分;覆盖率高,不代表边界条件完整;覆盖率低,也不一定说明代码没有任何测试价值。
在影响面分析中,Coverage 更适合作为“相关性信号”,而不是质量的唯一证明。
动态数据的采样和偏差
动态分析也有误差:
Trace 可能采样,不是每个请求都记录。
测试 Coverage 只代表测试环境。
Profile 受采样频率和运行负载影响。
日志质量依赖开发者埋点。
线上流量可能没有覆盖低频但关键路径。
因此,动态数据也需要标注来源、时间范围、环境和采样策略。一个来自生产环境过去 7 天的 Trace,和一次本地测试产生的 Trace,证据强度不同。
动态数据如何进入代码图谱
动态数据可以转化为代码图谱的属性和边:
方法节点增加执行频率、平均耗时、错误率。
调用边增加运行时观察次数和耗时分布。
测试节点通过 Coverage 连接到代码节点。
Trace 路径连接服务、接口、方法和资源。
日志事件连接源码位置和异常类型。
这样,图谱就不只是静态结构图,而是带有运行时证据的软件事实库。
和 AI Review 的关系
AI 修改代码后,动态证据可以回答:
受影响路径是否有测试覆盖。
这段代码是否处于高频生产路径。
相关 Trace 是否显示下游依赖复杂。
修改前后性能指标是否变化。
是否有新异常日志出现。
这些证据可以进入 AI 生成代码的 Review 报告。人类 Reviewer 不应该只看 AI 的解释,而应该看到与代码路径相关的运行时证据。
小结
动态分析让我们看到代码真实运行时的路径、频率、耗时、异常和测试触达情况。它弥补了静态分析的抽象和不确定性。
一个可靠的代码理解系统需要同时使用静态和动态数据:静态分析负责全局结构,动态分析负责真实证据。下一章会引入第三类数据:变更历史。它回答系统是如何演进的,以及风险如何随时间积累。
Last updated