For the complete documentation index, see llms.txt. This page is also available as Markdown.

静态分析:不运行代码时能知道什么

静态分析是在不运行程序的情况下,从源码、配置、依赖和构建信息中提取事实。它是代码可视化的数据基础之一,也是 IDE、质量平台、安全扫描、架构治理和影响面分析的核心能力。

静态分析的优势是覆盖面广。只要代码在仓库里,就可以被扫描、解析和建模。它不依赖线上流量,也不依赖测试是否执行到某条路径。但它的局限也很明显:它看到的是“代码可能如何运行”,而不一定是“代码实际如何运行”。

本章重点回答:不运行代码时,我们能知道什么,不能知道什么。

静态分析的输入

静态分析的输入不只是源码文件。一个真实项目通常还需要以下信息:

  • 源码:类、函数、变量、注解、装饰器、配置读取。

  • 构建文件:Maven、Gradle、npm、Go module、Cargo 等依赖声明。

  • 配置文件:路由、数据库、消息队列、功能开关、框架配置。

  • 生成代码:IDL、ORM、API client、protobuf、OpenAPI 生成物。

  • 测试代码:测试类、测试用例、测试夹具。

  • 静态规则:架构分层、禁止依赖、命名约定、安全规则。

如果只分析源码而忽略配置,很多关系会丢失。比如 Spring 的 Bean 绑定、路由配置、数据库连接、消息 topic,经常不只存在于普通方法调用里。

文件、模块与依赖关系

最基础的静态事实是文件和模块结构。工具可以从目录、包名、构建配置和导入语句中提取依赖关系。

依赖图可以回答:

  • 模块之间如何依赖。

  • 是否存在循环依赖。

  • 上层模块是否直接依赖底层实现。

  • 哪些模块被大量模块引用。

  • 哪些模块处于边界位置。

依赖图通常比调用图粒度更粗。它适合做架构治理、模块拆分和遗留系统理解。比如一个后台报表模块直接依赖核心交易模块,可能说明分层不清;多个业务模块共同依赖一个工具包,可能说明工具包处于高风险公共路径。

符号、引用与调用关系

在更细粒度上,静态分析可以提取符号定义、引用和调用关系。

符号定义告诉我们类、方法、字段在哪里声明。引用关系告诉我们这些符号在哪里被使用。调用关系进一步描述一个函数或方法调用了哪些目标方法。

调用图可以回答:

  • 谁调用了目标方法。

  • 目标方法会调用哪些下游方法。

  • 某个业务入口可能经过哪些代码。

  • 修改底层方法可能影响哪些上游入口。

调用图是影响面分析的核心输入。但调用图很少是完全准确的。多态、反射、动态代理、依赖注入和运行时配置都会让静态调用解析变得困难。因此,调用图中的边最好带上来源和置信度:确定解析、候选解析、框架规则推断、运行时确认。

继承、实现与类型层次

面向对象项目中,类型层次本身就是一种重要结构。继承图和接口实现图可以帮助理解扩展点、插件机制和多态分派。

类型关系可以回答:

  • 一个接口有哪些实现类。

  • 一个抽象类有哪些子类。

  • 某个方法重写了哪个父类或接口方法。

  • 修改接口签名会影响哪些实现。

这类关系在 AI 时代尤其关键。Agent 修改一个接口方法时,不能只看接口文件,还要检查实现类、调用方和测试。如果缺少类型层次,AI 很容易做出局部修改,却遗漏完整语义链路。

复杂度和代码气味

静态分析还可以生成质量指标,例如:

  • 圈复杂度。

  • 函数长度。

  • 嵌套深度。

  • 重复代码。

  • 参数数量。

  • 依赖数量。

  • 不合理异常处理。

  • 潜在空指针或资源泄漏。

这些指标不应该被机械理解。高复杂度不一定立刻有问题,低复杂度也不代表没有风险。它们更适合作为排序和提醒信号。

当复杂度与变更频率、测试覆盖、线上错误结合时,价值会更高。一个高复杂度、频繁变更、缺少测试、又在核心链路上的文件,才是真正应该优先关注的风险点。

架构规则检查

架构治理可以被表达为一组静态规则:

  • 表现层不能直接访问数据库。

  • 业务域之间只能通过公开接口通信。

  • 基础设施模块不能依赖业务模块。

  • 某些包不能跨层调用。

  • 核心模块不允许依赖实验性组件。

静态分析可以持续检查这些规则,并把违规依赖转化为报告或图形。相比人工 Review,规则化检查更稳定,也更适合进入 CI。

在 AI 生成代码后,架构规则检查会更重要。Agent 可能为了完成任务快速引入一个依赖,但它不一定知道这个依赖违反长期架构约束。静态分析可以作为边界守卫。

安全规则和数据流入口

很多安全扫描也从静态分析开始。工具会识别危险 API、敏感函数、输入入口、权限校验和数据流路径。

例如:

  • 用户输入是否进入 SQL 拼接。

  • 文件路径是否来自未校验参数。

  • 敏感字段是否被记录到日志。

  • 权限判断是否覆盖关键操作。

  • 不安全反序列化是否可达。

完整安全分析通常需要数据流支持,但静态层至少可以识别 Source、Sink 和候选路径,为后续 DFG 或污点分析提供基础。

静态分析的误差来源

静态分析并不等于真相。常见误差包括:

  • 反射和动态加载。

  • 依赖注入和运行时绑定。

  • AOP、代理和拦截器。

  • 动态语言特性。

  • 生成代码缺失。

  • 构建 Profile 或配置环境不同。

  • 测试代码和生产代码路径差异。

因此,静态分析结果应该表达不确定性。不要把候选调用说成确定调用,不要把语法匹配说成真实漏洞,不要把模块依赖误解为业务依赖。

和动态分析的边界

静态分析适合全量覆盖,动态分析适合确认真实执行。二者应该互补:

  • 静态分析发现所有可能路径。

  • 动态分析确认真实路径和频率。

  • 静态分析发现候选风险。

  • 动态分析提供运行时证据。

  • 静态分析支持修改前评估。

  • 动态分析支持修改后验证。

后续代码图谱应该同时容纳两类事实。比如一条调用边可以来自静态解析,也可以被 Trace 证实;一个方法可以静态上可达,但运行时很少被访问;一个接口可以有多个实现,但生产环境只启用了其中一个。

小结

静态分析让我们在不运行代码的情况下获得大量软件事实:结构、依赖、调用、类型、质量、安全和架构规则。它是代码可视化的基础层,也是 AI Agent 查询代码库的重要入口。

但静态分析必须承认自己的边界。它描述的是可能性,不总是现实。下一章会进入动态分析,讨论运行起来之后才能看到的事实。

Last updated