Maintainability sensors for coding agents

TL;DR · AI 摘要
Martin Fowler提出通过多阶段传感器(编码、集成、持续监控)提升AI生成代码的可维护性,涵盖类型检查、依赖分析、安全扫描等工具组合。
核心要点
- 使用类型检查、ESLint等实时传感器减少AI代码中的结构问题
- 定期运行依赖新鲜度报告和耦合分析可预防长期技术债务
- 结合Claude Sonnet和Cursor的Composer-2模型实现高效AI编码协作
结构提纲
按章节快速跳转。
- §引言
定义可维护性为代码长期变更的易操作性和低风险,并指出AI生成代码在维护性方面的问题表现。
通过社区管理仪表盘案例说明传感器在TypeScript/NextJS/React技术栈中的应用。
分阶段介绍编码期、集成后、持续监控三个阶段的传感器类型及具体工具组合。
说明使用Cursor、Claude Code等工具组合,以及Claude Sonnet等模型的分工策略。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 可维护性传感器
- 编码期传感器
- 集成后传感器
- 持续监控
金句 / Highlights
值得收藏与分享的关键句。
内部质量问题影响AI与人类开发者同样显著,可能导致代码位置错误、重复实现或上下文过载
传感器组合包括类型检查、ESLint、Semgrep安全扫描、依赖关系分析和测试覆盖率监测
依赖新鲜度报告通过脚本分析库版本年龄,结合AI生成升级建议
在代码库中,我们通常希望实现并监控多个维度的目标:功能性正确性(按预期工作)、架构健康度(足够快速/安全/易用),以及可维护性。我在这里将可维护性定义为让代码库随时间推移能够轻松且低风险地进行变更——也被称为“内部质量”。因此,我不仅希望今天能快速做出变更,未来也能如此。每次进行变更时(无论是人类还是AI进行的),都不想担心会引入错误或导致架构健康度下降。当AI生成的代码库需要修改大量文件才能完成小调整时,或者当变更开始破坏原有功能时,我通常会发现可维护性出现裂痕的最初迹象。
内部质量问题对AI代理的影响方式与对人类开发者的影响类似。在混乱的代码库中工作的代理可能会在错误的位置寻找现有实现,因未发现重复内容而产生不一致,或被迫加载超出任务所需范围的上下文。
在这篇文章中,我将介绍自己在实验各种帮助人类和AI评估代码库可维护性的传感器时的实践,并分享从中获得的见解。
应用场景
我正在为社区管理者开发一个内部分析仪表盘,该仪表盘通过组合多个API读取聊天空间活动、互动和人口统计数据,并在Web前端展示数据。

图1:示例应用:Web UI、服务层和外部API。
技术栈采用TypeScript、NextJS和React。后端负责读取并整合API数据。该应用已存在一段时间,但为了实验需要,我用AI从头重新构建了它。
目前几乎没有关于代码质量和可维护性的AI指南(例如Markdown文档),我想测试仅依赖传感器反馈时AI的表现。
使用的所有传感器概览

图2:传感器的运行阶段:编码阶段、流水线集成后、定期运行以及生产环境。
这是我在生产路径中设置的传感器概览。
编码阶段传感器
与代理持续协同运行以提供即时反馈的计算型传感器:
- 类型检查器
- ESLint
- 由内部应用安全团队指定的Semgrep静态应用安全测试工具
- dependency-cruiser(通过结构规则检查内部模块依赖)
- 测试套件结果(包括测试覆盖率)——尽管测试套件由AI生成,但属于计算型传感器
- 递增式变异测试
- GitLeaks(作为预提交钩子运行,当代理尝试提交时提供反馈)
集成后阶段
与编码阶段传感器相同,但在CI环境中验证干净基础设施下的集成结果。
定期运行传感器
以较慢频率运行以检测随时间积累的漂移问题:
- 安全审查(基于内部应用安全检查清单生成提示)
- 数据处理审查(提示包含"绝不能将用户名发送到Web前端"等规则)
- 依赖新鲜度报告(脚本获取库依赖的年龄和活跃度,AI生成升级建议报告)
- 模块化与耦合审查(结合计算和推理分析)
接下来我们将深入探讨第一类传感器。
基础框架与模型
在构建应用过程中,我主要使用Cursor、Claude Code和OpenCode(按使用频率排序)。默认模型通常是Claude Sonnet,规划分析任务使用Claude Opus,实现任务则频繁使用Cursor的composer-2模型。
静态代码分析:基础代码检查
我们先从ESLint在本项目中的使用经验谈起。ESLint等基础代码检查工具主要针对单个文件和函数级别的可维护性风险。
针对AI常见缺陷的规则
根据我的经验,最易通过静态分析检测的AI失败模式包括:
- 函数参数最大数量
- 文件长度
- 函数长度
- 圆周复杂度
但这些规则在ESLint默认预设中并未启用,需要手动配置阈值。希望未来静态分析工具能提供更多适配AI的预设配置。目前已有团队开始发布专门针对已知代理缺陷的ESLint插件,例如Factory的插件,包含要求测试文件或结构化日志等规则。
自我修正指南
传感器的作用是向代理提供反馈,使其能够自我修正。理想情况下,我们希望为代理提供额外的上下文来进行这种自我修正——一种有益的提示注入。为此,我借助AI的帮助,构建了一个自定义ESLint格式化程序来覆盖部分默认消息。
以下是我为 no-explicit-any 警告制定的指导示例:
我们需要通过类型化代码来避免错误,尤其是对关键概念。但也要避免在代码库中添加不必要的类型。请自行判断。如果你选择不引入类型,请用以下方式抑制警告: // eslint-disable-next-line @typescript-eslint/no-explicit-any -- (说明原因)`
管理警告——现在更可行了吗?
静态代码分析存在已久,但团队往往未能持续使用它,即使已经配置好了相关工具。其中一个原因是伴随而来的管理开销。有效利用这类分析需要团队保持“干净的代码库”,否则指标会沦为噪音。特别是像上述 no-explicit-any 这样的警告尤其棘手,因为并非总是需要修复——这取决于具体情况,而逐一抑制它们总显得繁琐且会污染代码。
借助编码代理,我们或许能实现这种干净的基准线。在上述指导文本中,代理被要求自行判断,并允许其在代码中抑制警告。这使得抑制项保持可控、可见且可审查。
对于阈值规则(如最大行数或允许的环形复杂度),我在lint消息中告知代理,如果认为某个特定情况下重构没有必要或不可能,它可以略微提高阈值。这不会永久抑制阈值警告,而是让规则在情况进一步恶化时再次触发。这样既保留了约束,又避免了非此即彼的“抑制或遵守”选择。
观察要点
- 查看AI创建的例外情况(被抑制的警告、提高的阈值)是开始代码审查的良好切入点。
- AI经常选择提高环形复杂度阈值,但在进一步推动时会建议合理的重构方案。这是唯一出现这种情况的类别,后来我发现是因为该规则缺少明确的自我修正指导,未规定提高阈值应作为绝对例外。这表明自定义lint消息确实能产生显著影响。
- 有时我希望在代码的不同部分对规则进行差异化处理。例如
no-console规则:在后端,要求AI使用日志组件代替console.log;在前端可能完全禁止直接日志输出,或至少使用不同的日志组件。这再次体现了自我修正指导的力量,以及AI在语义判断和警告管理中的作用。 - 我在关注规则之间的权衡案例。目前唯一观察到的例子是
max-lines和max-lines-per-function规则的相互作用。AI确实通过传感器反馈进行了大量有益的重构,将代码拆分为更小的函数和组件。但在React前端,我注意到组件属性数量因通过不断缩小的组件链传递值而急剧增加,这可能引发新问题。目前尚未观察到AI在处理这类权衡时能否保持一致性。
核心结论
总体而言,我惊讶于静态分析能覆盖如此多的内容。我多次反思过去为何它被低估,以及现状为何改变:成本效益的平衡发生了变化。成本降低是因为借助AI可以更轻松地创建自定义脚本和规则。收益也增加了:分析结果帮助我快速识别许多卫生因素(这些在自行编码时可能被忽略),从而能提前排除AI的常见错误。
但我不禁担心这可能导致虚假的安全感和质量错觉。毕竟过去这类lint工具使用率低的另一个原因是它们存在局限性,我们曾谨慎将其作为质量的简化指标。静态分析无法捕捉质量的许多语义层面,尚待观察AI能否与这些工具结合填补这一空白。每次启用新规则集时,我都会发现代码中新的疑似问题——总是夹杂着无关紧要和真正重要的内容。我担心代理会因反馈过载陷入过度工程化重构的漩涡。
静态代码分析:依赖规则
基础linting主要关注单个文件或函数内的质量和复杂度。接下来,我开始探索能为代理和我提供跨文件及模块边界可维护性反馈的传感器。这类分析工具的历史使用率甚至低于基础linting。
为了解这类传感器帮助维持代码库模块化的能力,我重点研究了三个方面:
- 依赖规则(确定性)
- 耦合分析(确定性和推断性)
- 模块化审查(推断性)
Let's start with dependency rules. I worked with the agent to come up with a layered module structure for my application, about half way through implementing it. I asked it to help me write `dependency-cruiser` rules to enforce these layers.

Figure 3: 分层模块结构与依赖规则
For example, one of the rules enforces that code in the clients folder never imports anything from the services folder:
{
name: "clients-no-services",
comment:
"API clients must not depend on the orchestration layer above them. " + LAYERS,
severity: "error",
from: { path: "^server/clients/", pathNot: "/__tests__/" },
to: { path: "^server/services/" },
}Similar to ESLint messages, I expanded the error messages to provide self-correction guidance, summarizing the overall layering concept:
ERROR clients-no-services
API客户端不应依赖其上层的编排层。
[分层结构:routes -> services -> clients + domain;服务层负责编排:通过客户端获取数据,借助领域层进行计算——无I/O操作、无SDK依赖、不涉及数据获取机制。]Observations
- 没有AI,我无法如此快速地制定这些规则。工具的配置语法学习成本很高,而AI几乎承担了全部学习成本。
- 引入规则后,代理偶尔违反规则,但会根据
dependency-cruiser的反馈自我修正,确实帮助维护了我的文件夹概念。 - 我还采用相同方法制定了前端React钩子结构的规范。
- 需要解决AI创建超出结构的新文件夹时的检测问题,为此添加了强制所有新文件必须位于预定义文件夹结构中的规则。
Main takeaways
在引入这些规则时,代码文件夹的组织已略显杂乱。我观察到规则帮助代理整理了现有结构,并持续维护分层规范。因此,我认为这些规则是替代Markdown文档描述代码结构的有效工具。不过这类工具的局限性在于仅能通过导入关系、文件名和文件夹结构表达约束。
静态代码分析:耦合数据
接下来,我尝试从代码库中提取典型的耦合指标,即每个文件的入站/出站导入和调用数量。
我没有使用现成工具,而是让编码代理编写了一个基于TypeScript编译器的应用程序,以便在实验阶段灵活调整指标生成。该应用提供了两个接口:用于人类查看的Web界面(包含多种指标可视化),以及供代理调用的CLI工具。

Figure 4:耦合指标——人类可视界面与代理CLI接口
人类使用场景
这些可视化大多属于成熟概念,例如依赖结构矩阵(DSM)。我发现它们解读起来较为繁琐,尽管代码可以优化,但更多受限于数据本身的特性。这类数据需要大量上下文和经验才能关联到高层次的设计原则,因此我认为这类工具在减轻人类审查AI修改代码库的认知负荷方面仍有限。
代理使用场景
我让代理通过自定义CLI工具coupling-analyser访问指标数据,并基于数据生成包含改进建议的报告。以下是提示词片段(主要展示我未提供具体好坏标准,完全依赖模型解读):
生成针对目标TypeScript代码库的模块化和耦合质量的Markdown报告,需基于npx coupling-analyser的实际CLI输出,而非单纯静态浏览猜测。
收集证据(执行CLI)
运行CLI并捕获标准输出。根据需求组合report子命令...
生成Markdown报告
使用清晰标题。优先引用或转述CLI输出中的具体模块ID/路径和数值。
建议章节:
- 背景 —— 分析范围
- 执行摘要 —— 2-5条要点:整体模块化态势,前1-3项系统性问题
- 工具发现 —— 总结CLI报告中的热点、高风险、循环依赖等
- 模块化视角解读 —— 将指标与软件设计关联:内聚性 vs 变更扩散、稳定性 vs 依赖方向、扇入扇出直觉、循环影响
- 高/关键问题深度分析
- 问题描述 —— 涉及模块、系统角色、依赖邻居(结合CLI数据+必要代码片段)
- 当前职责...
- 为何有害...
- 设计选项(2+方案)
- 新设计优势 —— 减少循环、清晰依赖方向、缩小影响面、测试切面、契合变更趋势
- 未来风险降低 —— 各方案如何降低回归风险并降低演进成本(具体场景:"添加X"、"替换Y"、"独立发布Z")...
...
This LLM-led analysis actually pointed me to the same coupling hot spots that I would have found by looking through the visual diagrams, just in a format that was more digestible. And asking the LLM to ground its analysis in the results from the deterministic tool gave me a higher level of confidence, and probably also used less time and tokens than if the agent had scanned the codebase itself to find coupling problems.
Observations
What the LLM found based on this data was quite lackluster (I used Claude Opus 4.7 for this):
- It said one of the biggest issues was a factory that initialises all the necessary components, but I had introduced that factory on purpose as a component that acts like a lightweight dependency injection framework.
- Another issue it had was with a shared (
zod) schema between frontend and backend, declared a “god module” by the LLM. This is a common pattern though to create an explicit contract between backend and frontend, and is not as much of an issue when backend and frontend evolve together anyway, or even live together in the same repo, like in my case. - When legitimate patterns appear as high-coupling hubs, there would have to be a way to suppress those in future analyses, otherwise they create even more noise.
- The one kind of interesting finding it had: An
index.tsfile in the domain folder indiscriminately exposed all files in./domain, and is imported by lots of places. While that is also a common pattern to create explicit contracts for a layer, it does have its pros and cons, and is at least worth an investigation to see if it is appropriate for this codebase.
Main takeaways
The examples above show that even more so than with the basic linting, _good_ and _bad_ does not have a clear definition, instead it is all about what is _appropriate_. And what coupling is appropriate depends on a lot of context, not just the raw call and import graph of a codebase. So based on this small experiment, I don't have the impression that this type of coupling data is useful to AI on its own.
A more practical use I can imagine for this data is during risk triage for code review. When I review a code change made by AI, it seems useful to know what the impact radius of the changed files is, so that I can pay more attention when e.g. a file with 10+ callers is changed. Or an AI review agent could use the data to prioritise where it spends its tokens.
Static code analysis: AI modularity review
The lackluster results from the coupling data experiment could have multiple reasons:
- My prompt about what to analyse was not very specific
- The coupling data is not useful to AI
- The coupling data only is too shallow and lacks context of the full code
So the final thing I did was to go fully down the inferential route and use Vlad Khononov's “Modularity Skills” to analyse the codebase design and find modularity issues. This proved to be very fruitful! It gave me lots of interesting pointers for refactorings that would obviously reduce the risk of future changes. I ran the skills a second time and gave them access to my coupling analysis CLI. The AI mostly found confirmation in the data, but not any additional findings. On the contrary, it pointed out lots of things that the CLI was missing. It's also worth noting that the second run of the analysis (without context of the first one) surfaced yet another issue that the first run did not find. A useful reminder that when it matters, it's often worth running an LLM-based analysis multiple times, to get a fuller picture.
Observations
Here are some highlights from the results (model used was Claude Opus 4.7, same as for the coupling analysis):
- 重复的路由代码 - 我的三个后端端点各自拥有独立的路由文件,且每个路由实现几乎完全相同。因此,每当我要对后端API的通用原则进行修改(例如引入请求ID,或调整错误处理或日志记录方式时),就需要在多个文件中重复操作。由于当时刚新增第三个端点,我认为尚未抽象这部分代码是可以接受的。但根据我的经验,AI代理通常在第三次或第四次重复代码时,不会主动进行重构,而是更倾向于复制粘贴。
- 调用后端的不一致性 - 或者换个说法,这是另一种形式的语义重复。应用程序中有三个页面需要使用相同的参数(选定的聊天空间和分析的时间范围)调用后端。其中两个页面使用了相同的钩子和通用方法,但AI在实现第三个页面时偏离了这一模式,自行重新实现了类似行为。这可能导致错误处理不一致,或在后端API原则变更时需要修改多个文件。
- 核心参数处理低效 - 如前所述,所有页面都会将聊天空间ID和时间范围传递给后端。我之前在修改用户指定时间范围的方式时已注意到,AI为此修改了大量文件——超过40个!因此我已意识到问题所在,分析结果也证实了这一点:“问题:请求参数在每一层重复出现”。建议引入一个封装所有参数的对象。虽然AI已部分实现该对象,但未彻底使用,导致混乱不堪。
- 职责归属不当 - 代码审查发现,本应仅负责模块组装的工厂(factory)中,混入了认证代码。它在用户未认证时会回退到模拟数据。这种意外的位置安排可能在新增路由时被忽略,带来风险。
- 对高引用计数“中心节点”的更合理解读 - 记得之前耦合分析发现的“上帝类”(god classes)吗?模块化分析同样注意到这些,但更贴心地指出它们在本应用上下文中有其合理性。这可能是由于技能提示更优化,或是该分析实际读取了代码(而我之前要求另一工具仅依赖耦合数据)。
主要收获
- 依赖项解析器(如dependency-cruiser)可有效作为实时传感器,强制执行基础文件结构和依赖方向,但其作用有限。
- AI模块化审查是“垃圾回收”的绝佳案例,当赋予强大提示时效果显著。将其与实际耦合数据结合似乎并无显著提升。若能将其应用于提交变更的文件,提前在流程中实施会很有帮助,但目前尚未探索这一方向。
- 我在未自行进行此类审查的情况下构建了大部分代码库,模块化审查的结果显示出一些令人担忧且切实存在的问题,未来将增加风险。这表明:若缺少人工评审和耦合专业知识,且没有这些额外的AI审查,代理确实会无意中积累技术债务(参考Martin Fowler的技术债务象限)。
总体而言,代码库设计和模块化问题仅靠计算传感器无法有效解决,需要AI进行语义解读并权衡取舍。
本文下一部分将探讨回归测试作为传感器的作用,以及我在AI生成测试套件上使用覆盖率和变异测试的体验。
如需获取下一期内容,请订阅本站的RSS订阅源,或关注Martin的Mastodon、Bluesky、LinkedIn和X账号。
- * *