12k
All articles

使用 Difftastic 审查 AI 生成的代码

使用 Difftastic 通过结构化 diff 审查 AI 生成代码,接入 git,并从格式化噪音中分离出行为变化。

OpenReplay Team
OpenReplay Team
使用 Difftastic 审查 AI 生成的代码

AI 编码工具经常在同一次提交中将真正的逻辑变更与大规模重新格式化捆绑在一起,而基于行的 git diff 会将那一行真正改变行为的代码淹没在满屏的红绿色中。Difftastic 是一款结构化差异比较工具,它将代码解析为语法树并对树进行比较,而非对文本进行比较,因此纯粹的重新格式化、重新换行和空白字符变动都会被识别为无变更——只留下真正改变行为的编辑内容。本文将介绍 difftastic 是什么、如何将其集成到 git 中,以及一套用于审查 AI 代理所提交代码的具体工作流程。

核心要点

  • Difftastic 通过 tree-sitter 对抽象语法树进行差异比较,因此重新格式化和换行不会显示为变更,而新增的单个参数仍会被标记为变更。
  • Difftastic 支持超过 30 种编程语言,对于无法识别扩展名或存在解析错误的文件,会自动降级为基于行的、带词语高亮的差异比较。
  • ~/.gitconfig 中配置 [diff] external = difft 即可完成集成,然后使用 git show <commit> --ext-diff 查看 AI 代理的提交内容。推荐的 git 配置在 difftastic 0.61(2024 年 10 月)中有所变更,请务必从官方手册中获取最新配置。
  • Difftastic 是一个外部差异比较工具,而非 git 分页器,因此建议将其与 delta 搭配使用——日常查看使用 delta,处理格式化变动较多的 AI 差异时使用 difftastic。
  • difft --check-only --exit-code 可作为提交的门控检查,回答”此次变更是改变了行为,还是仅仅重新格式化了代码?“这一问题。

问题所在:AI 生成的差异将关键行淹没其中

Cursor、GitHub Copilot 和 Claude Code 等 AI 代理和助手经常在同一次提交中既修改一个条件,又对整个函数进行重新格式化——或者将一个较长的调用拆分到多行。基于行的差异比较工具无法区分这两种情况:它会将每一行被触及的内容都标记为已变更。你会看到满屏的红绿色,而那一行真正改变运行时行为的编辑就隐藏在其中某处。审查 AI 生成的代码恰恰是信噪比最为关键的场景——重新格式化是机器产生的噪音,而行为变更才是你需要为合并负责的内容。

Difftastic 是什么?

Difftastic 是一款结构化差异比较工具,它基于代码的语法而非文本来比较文件。它使用 tree-sitter 解析每个版本,构建抽象语法树,然后对这些树进行差异比较。其实际效果是:如果你将代码重新格式化为多行,difftastic 只会显示真正发生变化的内容;移动或重命名的代码块会被报告为移动操作,而非删除加新增。

Difftastic 由 Wilfred Hughes 使用 Rust 编写,以 MIT 许可证开源。它通过 tree-sitter 解析器支持超过 30 种编程语言——而非某些评测文章所称的”50+“。当文件扩展名无法识别时,它会优雅降级为带词语高亮的基于行的差异比较,因此对于它无法解析的语言,也不会出现任何异常。

安装 Difftastic 并集成到 git

通过包管理器或 cargo 安装 difft 二进制文件,然后将其注册为 git 的外部差异比较工具:

# macOS
brew install difftastic

# Rust 工具链(0.69 版本起需要 Rust 1.85+)
cargo install difftastic

如需临时使用,可通过内联方式传入配置,避免永久性修改:

git -c diff.external=difft diff
git -c diff.external=difft show --ext-diff

若要将 difftastic 设为默认工具,请将以下内容添加到 ~/.gitconfig直接参考官方手册

[diff]
    external = difft

这会将 git diff 改为使用 difftastic;其他子命令只需添加 --ext-diff 标志,例如 git show --ext-diffgit log -p --ext-diff推荐配置在 difftastic 0.61(2024 年 10 月)中有所变更,如果你是从旧版博客文章中复制的配置,请将其替换为当前手册中的版本。使用 --display inline 可将默认的双栏显示改为内联显示:

difft old.js new.js --display inline

为什么 Difftastic 更适合审查 AI 代码?

结构化差异比较专注于已变更的语法节点,消除了非实质性编辑带来的噪音。最直观的示例是”重新格式化加新增一个参数”的场景。假设 AI 代理将一个单行函数签名拆分为多行(因为它超出了你的行长限制),同时新增了一个参数:

// 修改前
function createUser(name, email) {}

// 修改后
function createUser(
  name,
  email,
  role,
) {}

基于行的差异比较会高亮整个代码块,因为每一行在文本层面都发生了变化。而 difftastic 只会高亮新增的 role 参数——那唯一改变行为的编辑。这正是 AI 代理可能在审查者浏览嘈杂差异时悄悄混入的那类细微改动。同样的逻辑也适用于代码提取:当 AI 代理将一个代码块提取为新的辅助函数时,difftastic 会将新函数名标记为新增,而不是将包装层和函数体显示为一团混乱的删除加新增。

审查 AI 代理输出的提交工作流程

若要只查看 AI 代理产生的语义变更,请针对其具体提交进行操作:

git show <agent-commit> --ext-diff
# 或审查整个分支
git log -p --ext-diff main..agent-branch

重点关注三类内容:difftastic 实际标记为变更的新增和删除内容;被报告为移动或重命名而非重写的代码块;以及任何回退为普通基于行差异比较的代码块——这是 difftastic 遇到无法识别的扩展名或解析错误的信号。默认情况下,difftastic 在遇到解析错误时会回退到基于行的差异比较,这是一种保守的设计选择,确保它不会将两个语法上不同的文件误判为相同。这对于处于重构中途、可能无法完整解析的 AI 输出尤为重要;如果某个接近有效的文件不必要地触发了回退,可通过 export DFT_PARSE_ERROR_LIMIT=20 提高容错阈值。

在自动化场景中,difftastic 可以在不渲染差异的情况下检查两个文件是否具有相同的 AST:

difft --check-only --exit-code before.js after.js

当没有语法变更时,退出码为 0;当存在语法变更时,退出码为 1——这是一个快速的门控检查,适用于预合并钩子或 CI 步骤,用于回答”AI 代理是改变了行为,还是只是重新格式化了代码?“

局限性,以及与 Delta 的搭配使用

需要客观认识其权衡取舍。Difftastic 在处理变更量较大的文件时扩展性相对较差,且可能占用大量内存,因此应将其作为针对特定提交的审查工具,而非 git diff 的全面替代品。其并排显示效果通常较好,但偶尔可能造成混淆。它是一个外部差异比较工具,而非 git 分页器,无法在 GitHub PR 的”Files”标签页或大多数 IDE 差异面板中渲染——它是一个本地命令行工具。

实际的解决方案是两者并用。日常查看时使用快速的文本分页器,处理格式化变动繁多的复杂差异时切换到 difftastic。

工具差异类型可用作分页器?最适用场景
difftastic结构化(AST)否(仅限外部差异工具)格式化变动较多的 AI 差异
delta文本,带语法高亮日常差异查看
diff-so-fancy文本,美化显示轻量级界面优化

Delta 和 diff-so-fancy 均基于文本;两者都不支持 AST 差异比较。将 delta 作为默认分页器,在差异被格式化噪音淹没时切换到 difftastic。

清晰的结构化差异比较是一道预合并过滤器:它能在你批准之前捕获那行改变行为的代码。那些仍然漏网的变更——隐藏在嘈杂 AI 差异中的细微逻辑修改——往往是在上线后才需要费力还原的,而回放出问题的会话录像是发现此类回归问题最直接的方式之一。立即配置 [diff] external = difft,然后使用 git show <commit> --ext-diff 审查你的下一个 AI 代理提交,只读取真正发生变化的内容。

常见问题解答

Difftastic 和 Delta 有什么区别?

Difftastic 是一款结构化差异比较工具,使用 tree-sitter 解析代码并比较抽象语法树,因此重新格式化和换行不会被识别为变更。Delta 是一款基于文本的、带语法高亮的分页器,仍然以行为单位进行差异比较。Difftastic 无法充当 git 分页器,只能作为外部差异工具使用;而 delta 可作为分页器用于日常查看。建议默认使用 delta,在处理格式化变动较多的 AI 差异时使用 difftastic。

Difftastic 遇到无法解析的文件时会怎样?

Difftastic 在两种情况下会回退到带词语高亮的基于行的差异比较:文件扩展名无法识别时,以及遇到解析错误时。解析错误回退是一种有意为之的保守设计,确保 difftastic 不会将两个语法上不同的文件误判为相同。这对于处于重构中途、可能无法完整解析的 AI 输出尤为重要。当某个接近有效的文件不必要地触发回退时,可通过设置 DFT_PARSE_ERROR_LIMIT 环境变量来提高容错阈值。

Difftastic 能在 GitHub PR 的 Files 标签页中显示差异吗?

不能。Difftastic 是一个本地命令行工具,作为 git 的外部差异工具运行,无法在 GitHub PR 的 Files 标签页或大多数 IDE 差异面板中渲染。你需要通过 git show 加 --ext-diff 标志或 git log -p --ext-diff 等命令来使用它。虽然部分工具(如 magit)存在第三方集成,但 PR 标签页和 IDE 的支持十分有限,因此 difftastic 最适合作为基于终端的审查步骤来使用。

如何检查 AI 提交是改变了行为还是只是重新格式化了代码?

对文件的两个版本使用 --check-only 和 --exit-code 标志运行 difft。Difftastic 会检查两个文件是否具有相同的抽象语法树,而无需渲染差异,当没有语法变更时退出码为 0,当存在语法变更时退出码为 1。这比完整的差异比较快得多,可作为预合并钩子或 CI 步骤的门控检查,用于区分真正的行为变更和纯粹的格式化调整。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.