从提交记录里挖实现模式,再让模型标出哪些是反模式

提交记录比源码更诚实。源码只告诉你“现在是什么样”,提交记录却保留了“当时为什么这么改”。我近几年在几个遗留系统上做代码审计和重构规划时,反复用同一个思路:把 git log 里的提交信息、diff 内容、关联的 issue 编号喂给大模型,让它先提炼实现模式,再逐条标注反模式。效果比直接读代码快得多,而且经常能挖出静态扫描根本发现不了的问题。

这篇文章整理一下我沉淀下来的操作方法和几个关键提示词模板。

先给提交记录做结构化预处理

直接让模型读原始 git log 输出效果很差。提交信息格式不统一、diff 里混着大量噪音、文件路径变更干扰判断,模型很容易被带偏。我的做法是先做一层轻量 ETL,把提交记录转成结构化文本再喂给模型。

具体操作:用 git log 加 format 参数导出固定字段,然后按提交切块。

git log --since="2024-01-01" \
  --pretty=format:"===COMMIT_START===%nHASH:%H%nAUTHOR:%an%nDATE:%ad%nSUBJECT:%s%nBODY:%b" \
  --date=short

再对每个提交追加 diff 信息:

git log -p --since="2024-01-01" --no-color

关键是把每个提交的 subject、body、变更文件列表、关键代码片段拼成一个独立区块,用明确的分隔符隔开。单个提交的 diff 超过 300 行时我会截断,只保留文件路径和增删行数统计,避免上下文窗口被单个大提交占满。

预处理脚本我用 Python 写了一个约 80 行的工具,核心逻辑就是解析 git log 输出并按分隔符切块、过滤掉 merge commit 和纯格式化提交(比如只改了空格的提交)。这一步做完,喂给模型的数据质量直接决定后面提取结果的可信度。

第一轮提示词:提取实现模式

提取实现模式时,我会明确要求模型输出结构化结果,并且每条模式必须附带真实提交哈希作为证据。这样方便我回溯验证,防止模型“发明”不存在的模式。

核心提示词模板:

你是一位资深代码审查专家。下面是从一个代码仓库中提取的提交记录,每条记录包含提交信息、变更文件列表和关键 diff 片段。

请从这些提交记录中提取“实现模式”(Implementation Patterns),要求:

1. 模式定义为:在至少 3 个不同提交中反复出现的相似技术决策、代码结构或改动方式。
2. 每条模式输出格式为:
   - 模式名称:简短、可检索的命名
   - 触发条件:什么场景下开发者会采用这个模式
   - 典型做法:具体怎么实现,给出代码片段或伪代码
   - 证据提交:至少 3 个提交哈希,说明该模式在这些提交中如何体现
   - 涉及文件/模块:模式最常出现的代码区域
3. 排除一次性修改、纯粹 bug 修复、版本号更新等无模式价值的提交。
4. 模式数量控制在 5-15 条,按出现频率从高到低排列。

提交记录如下:
{结构化提交数据}

说几个容易踩的坑。第一,模式定义里“至少 3 个提交”这个阈值很重要,设成 2 个会混入大量巧合,设成 5 个又容易漏掉低频但重要的模式。第二,一定要让模型给提交哈希,我用这个要求筛掉过好几次模型幻觉——它编造的模式听起来合理,但给的哈希在仓库里根本不存在。第三,diff 片段要保留足够上下文,至少让模型能看到改动的函数签名和周边 5-10 行代码,否则它只能基于提交信息瞎猜。

第二轮提示词:标注反模式

拿到实现模式清单后,第二轮再让模型做反模式标注。这一步不能和第一轮合并,因为模型需要先对模式有整体认识,才能判断哪些模式之间存在冲突、哪些模式在特定上下文中是有害的。

反模式标注的提示词模板:

你是一位软件架构评审专家。下面是从一个代码仓库的提交记录中提取出的实现模式清单,以及这些模式出现的原始提交上下文。

请逐条评估每个模式,标注其中构成“反模式”(Anti-Pattern)的部分。判断标准:

1. 该模式是否导致代码可维护性下降(如重复逻辑、隐式耦合、难以测试)?
2. 该模式是否与仓库中其他模式产生冲突,形成不一致的技术方案?
3. 该模式是否在提交历史中引发了后续的修复提交(即该模式实施后,紧接着出现了针对它的 bug 修复或回滚)?
4. 该模式是否掩盖了根本问题,用表面手段绕过而非解决?

每条标注输出格式:
- 模式名称
- 反模式判定:是/否/部分
- 反模式类型:如“复制粘贴式扩展”“上帝对象”“隐式全局状态”“接口过度设计”等
- 具体危害:结合提交证据说明该模式导致了什么问题
- 建议替代方案:在相同场景下更合理的做法

特别注意:如果某个模式在提交历史中引发了后续修复提交(标准3),请明确指出修复提交的哈希和修复内容,这是最有力的反模式证据。

模式清单和提交数据如下:
{第一轮输出的模式清单 + 关键提交上下文}

标准 3 是我觉得最有价值的一条。一个模式如果实施之后紧跟着出现针对它的修复,这本身就是反模式的最强信号。比如某个系统里开发者反复用“在 controller 里直接拼 SQL”的模式,每次上线后都跟着一个“修复 SQL 注入/修复字段映射错误”的提交,这个因果关系模型能从提交时间线和 diff 内容里准确抓出来。

实测案例:一个支付模块暴露出的三个反模式

拿我最近处理过的一个订单支付模块举例。仓库 14 个月、约 600 个提交,预处理后筛出 420 个有效提交,喂给模型后第一轮提取出 9 个实现模式,第二轮标注出 3 个明确反模式。

第一个反模式是“条件分支内联扩展”。开发者处理支付渠道时,不在策略层做分发,而是在订单服务里写 if channel == 'alipay' 的散落分支。模型找到了 17 个相关提交,每次新增支付渠道都要改 4-6 个文件,而且模型准确指出提交 a3f2c1e 之后连续 3 个提交都在修复因为漏改某个分支导致的渠道参数丢失。

第二个是“事务边界随调用链漂移”。支付回调处理里,事务注解有时加在 service 层、有时加在 controller 层,模型从 12 个提交中识别出这个不一致,并关联到一个生产事故修复提交——因为事务提前提交导致支付状态和库存扣减不同步。

第三个比较隐蔽,是“日志中泄露敏感字段的渐进式蔓延”。早期提交里日志脱敏是完整的,但后续某次性能优化时开发者觉得脱敏耗时,去掉了手机号中间四位掩码,之后新开发者照抄这个写法,模型追踪到 8 个提交中出现了未脱敏的手机号和卡号输出。这种问题静态扫描工具如果规则没配好是发现不了的,但提交历史里的“模式复制”行为非常清晰。

整个分析流程跑下来,从预处理到两轮模型输出,大约花了 40 分钟。如果人工从 600 个提交里梳理出这些内容,至少两到三个工作日。

几个直接影响效果的细节

提交信息质量决定上限。 如果团队的提交信息就是“fix”“update”这种,模型能提取的信息极其有限。这种情况我会在预处理阶段用模型对提交信息做一次补全,根据 diff 内容反向生成规范化的提交描述,再做模式提取。补全这一步用便宜模型就行,量大但任务简单。

按时间窗口切片比一次全量喂更有效。 提交超过 1000 个时,我会按季度或按版本里程碑切分成多个窗口,分别提取模式后再做跨窗口合并。这样既能控制上下文长度,也能观察到模式的演化——有些模式早期是合理的,后期随着系统复杂度上升变成了反模式,这个演化过程本身就是很有价值的发现。

模型选择上,Claude 系列在代码 diff 理解上明显优于同级别的 GPT-4 系列,尤其是长 diff 片段的细节抓取。我用的是 Claude Sonnet 4 处理提取,用 GPT-4o 做交叉验证。交叉验证的做法是:把第一轮提取的模式清单抹掉证据提交哈希,让另一个模型根据原始提交数据判断这些模式是否真实存在,一致率低于 85% 的模式直接丢弃。

反模式标注必须结合业务上下文。 纯技术视角下有些模式看起来是反模式,但在特定业务约束下可能是合理取舍。比如“手动管理数据库连接”在常规后端是反模式,但在一个低延迟的嵌入式网关服务里可能是刻意的性能决策。所以第二轮提示词里我会附上仓库的业务背景描述(一段话),让模型在判断时参考上下文。

常见问题

问:提交记录质量很差怎么办,大部分提交没有规范信息?

先用便宜的模型对提交信息做批量补全。把每个提交的 diff 喂给模型,让它生成一句话的规范化描述,格式统一为“类型:具体改动内容”。这一步成本很低,几千个提交也就几块钱 API 费用,但后续模式提取的准确率能提升至少 30%。

问:模型会不会编造不存在的模式?

会,尤其是当提交数据里噪音多、有效信号少的时候。所以必须要求模型为每条模式给出具体的提交哈希作为证据,然后写个脚本自动验证这些哈希是否存在、diff 内容是否真的体现了该模式。验证脚本很简单,git show <hash> 然后做关键词或代码片段匹配就行。

问:这个方法和直接让模型读源码做架构分析有什么区别?

读源码是静态视角,看到的是“当前状态”。读提交记录是动态视角,能看到“决策过程”和“问题演化”。反模式的定义本身就隐含时间维度——一个做法在什么时间点从合理变成了不合理,只有提交历史能回答。两者结合效果最好,但单看反模式挖掘,提交记录的信号密度远高于源码。

问:小仓库提交太少,提取不出模式怎么办?

提交少于 100 个的仓库,我会把粒度从“提交级”降到“代码块级”。具体做法是:用 git blame 把当前源码的每一行/每个函数关联到引入它的提交,然后按“谁在什么时候因为什么原因写了这段代码”来组织输入数据。这样即使提交数量少,也能从代码归属信息里提取出模式。