@obie:我和 Stephen 在 ZAR 的合作非常紧密,在这篇文章中他深入探讨了我们在学习以呼吸般的节奏使用编码智能体时所发现的基本原理……

X AI KOLs Timeline 工具

摘要

exhale-ruby 是一个面向 Rails 的代码收敛工具箱,它能在拉取请求中检测重复代码,并引导编码智能体将重复副本折叠回已有代码中,其内置的 DRY 检查基于 Prism 和 Herb 构建。

我和 Stephen 在 ZAR 的合作非常紧密,在这篇文章中,他深入探讨了我们在学习以呼吸般的节奏(一吸一呼)使用编码智能体时所发现的基本原理。 如果你是 Ruby 爱好者,强烈建议你读完这篇文章后,再去仔细看看 https://github.com/tools4imps/exhale-ruby……,你会明白为什么这是你今天就能付诸实践的可操作情报。
查看原文
查看缓存全文

缓存时间: 2026/10/03 21:04

— # tools4imps/exhale-ruby 源地址:https://github.com/tools4imps/exhale-ruby # exhale exhale 是 Rails 的收缩工具集,对应 “不耐烦式编程”(Impatient Programming)中“呼气“阶段的检查。它的首个检查是 exhale dry。当代码库中存在 Contract(契约)没有保留的重复代码时,它会让拉取请求失败,并告诉负责清理的智能体每份副本应当合并回哪一个原始实现。 智能体默认会重复代码。它们读取代码库,找到一种可行的模式,然后复制。当拉取请求在没有人工逐个审查 diff 的情况下合并时,这些副本就会进入 main 分支,除非有一道机器防线拦截;而之后的每一次会话又会把它们复制一遍。exhale 就是这台守护呼吸后半程的机器:先扩展学习,然后在同一个拉取请求里把学到的内容收缩回已有的实现中。 exhale dry 结合了 Uncle Bob 的 dryer(https://github.com/unclebob/dryer)和 Ryan Davis 的 flay(https://github.com/seattlerb/flay),基于 Prism(https://github.com/ruby/prism)和 Herb(https://herb-tools.dev)重建,因此它能按照 Rails 编写代码的方式来理解现代 Ruby 和 ERB。这是 0.1 版本中唯一的检查。未来还计划加入 CRAP 分数检查和防护泄露(leaked-guards)检查。 ## 安装 ruby # Gemfile group :development, :test do gem "exhale", require: false end bash bundle install bundle binstubs exhale bin/exhale dry exhale dry 在代码库干净时以 0 退出。当检查门禁失败时以 1 退出:存在未被保留的重复代码,或者某个 Contract 条款已过时、指向了不存在的代码。当它无法运行时以 2 退出,通常是因为某个文件无法解析。 ## 它比较什么 比较的单元是方法、Rails DSL 调用的主体(scope、validate、回调、before_action 块)以及 ERB 模板。在每个单元内部,它还会比较片段:块、条件语句、HTML 元素,以及从方法中间提取出来的连续三条以上语句的序列。像 has_many 和 validates 这样的类体声明从不计入比较范围,db/、config/、vendor/ 或 tmp/ 下的内容同样不计入。除非你传入 --include-tests,测试会被排除在外,因为测试应该是 DAMP(明确且有表达力的)而不是 DRY(不重复)的。 归一化过程保留操作的名称,去掉事物的名称。调用点处的方法名得以保留,运算符、HTML 标签和 Stimulus 的 data-controller 值也保留。局部变量、实例变量、常量、符号和字面量则变成占位标记。下面这两个方法具有相同的形状: ruby def alpha(xs) ys = xs.select(&:odd?) ys.map(&:succ) end def beta(items) kept = items.select(&:even?) kept.map(&:pred) end ## 评分 归一化单元的每个子树都是一个指纹。每个指纹按其稀有程度加权,权重为 ln(1 + 1000 / count),两个单元则通过这些权重上的 Jaccard 相似度来评分。在每个控制器中都出现的模式,其权重接近于零,因此脚手架代码不会淹没真正的重复。默认阈值为 0.80,同时设有 4 行和 20 个归一化节点的最低门槛。 ## 门禁 每次运行都会扫描整个代码库。main 分支也要通过同样的门禁,因此任何拉取请求触发的检查结果都是它自己的“过错“。exhale dry 只在与合并基点(merge base)对比时用于给发现打标签: | 标签 | 含义 | | — | — | | Introduced(新引入) | 拉取请求改动了一侧,而这对匹配在基点时并不匹配 | | Shifted(被推移) | 两侧都没被改动,但拉取请求的变更改变了权重,使这对匹配超过了阈值 | | Already there(原本就有) | 这对匹配在基点时就已经匹配 报告还会列出这次拉取请求收缩了哪些内容:那些在基点时匹配、现在已经不再匹配的重复对。一个已有的应用不会在首次运行时就全部扫清。使用 exhale dry --introduced-only 时,门禁只针对“新引入“和“被推移“的发现,其余的列为警告。等 main 分支变干净后,就可以去掉这个参数了。 ## Contract(契约) 有意为之的重复是设计决策,因此它会被声明在 Contract 中,并附上理由。Contract 按原语(primitive)组织,每种分析在原语内部有各自的文件: text contract/ provider_adapter/ README.md 该原语的说明文档,以及 covers 块 duplication.md 保留的重复代码、原因,以及相关设置 parallel 块声明那些有意保持平行的单元。它的理由就写在它所处的章节中: markdown ## Adapters stay independent Each provider adapter stays independent. Providers change on their own schedules, and a shared base class would couple their releases. ``parallel Payments::*::Adapter `` 原语 README.md 中的 covers 块指明属于它的代码,而 duplication.md 中的 settings 块则为该代码覆盖默认设置: markdown ``covers Payments::*::Adapter views/payments/** `` ``settings threshold: 0.75 min-lines: 6 min-nodes: 30 当条款指向已经不存在的代码时,门禁会失败;当一个条款已经没有需要保留的内容时,门禁同样会失败。Contract 要么保持真实,要么构建保持红色(失败)。 ## 在 CI 中 yaml exhale: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: exhale shell: bash run: bin/exhale dry --base origin/${{ github.base_ref || 'main' }} | tee -a "$GITHUB_STEP_SUMMARY" fetch-depth: 0 让 exhale dry 获得找到合并基点和运行 git blame 所需的完整历史。exhale dry --format json 会以数据形式输出同样的发现结果,--format edn 则以 dryer 写入 .metrics/dry.edn 的格式输出。 ## 确定性 同一个提交在任何机器、任何时间都会得到相同的判定结果。该判定只读取提交的代码树、它的 Contract,以及 Gemfile.lock 中的 gem 版本,除此之外别无其他。摘要值不带随机种子,权重是不依赖平台浮点对数计算的定点整数,所有平局都按照稳定键来打破。 ## 缩小运行范围 exhale dry PATH... 只报告位于这些路径下的发现。缩小范围后的运行仍然会执行门禁检查,但只针对其路径内的发现;而且每个路径都必须存在,所以像 exhale dyr 这样的拼写错误会以 2 退出而不是通过。 exhale 如何用这套标准来约束自己 exhale 在 contract/ 中拥有自己的 Contract:跨越 11 个原语(source、unit、shape、fingerprint、matcher、sweep、gate、clause、report、revision 和 cli)的 64 条编号义务。每条义务至少有一个测试,通过 # Contract: 注释来指明它。rake contract 会发布契约覆盖率,并在任何义务缺少可执行测试时使构建失败。CI 还会用这套 Contract 对 exhale 自身运行 exhale dry。 变异门禁会针对 Mutineer(https://github.com/davidteren/mutineer)对 lib/ 能做的每一个变异体,运行整个测试套件。每个变异体要么被某个测试杀死,要么在 .mutineer.yml 中列出,并注明没有测试能够捕获它的原因:等价变异体、无限循环,或者 Ruby 覆盖率机制无法感知的代码行。bin/mutate 在 Ruby 3.4 下运行它。 ## 0.1 版本的已知局限 - 意图层面的重复——即用不同结构表达同一个想法——是结构化匹配无法触及的领域。 - 当一个方法整体被复制为另一个方法中的嵌套 def 时,如果其主体本身低于尺寸门槛,则不会被报告出来。 - 每次运行都会冷启动扫描整个 head,只缓存合并基点。精确的增量扫描将在 0.2 版本中推出。 - 一个被复制超过 50 次的语句序列会以从其最宽副本辐射的星形结构进行关联,这可能漏掉那些只与另一份副本匹配的近似副本。 ## 许可证 MIT。版权所有 Obie Fernandez。

相似文章

@obie:周五夜特刊,透露下周即将揭晓的重大惊喜线索!

X AI KOLs Timeline

tools4imps 发布了 exhale-ruby,一个专为 Rails 打造的代码收缩工具包。它在 Prism/Herb 之上整合了 dryer 和 flay,用于检测 AI 智能体引入的重复代码,并以 `exhale dry` 作为其首个 PR 检查关卡。它会将代码单元标准化,通过稀有指纹 Jaccard 加权算法计算相似度,并将发现的问题标记为「新引入」「发生位移」或「原本就存在」。

为什么多年来 Ruby 依然让人有家的感觉

Lobsters Hottest

作者回顾了使用 Ruby 的 15 年经历,称赞了其隐藏特性,如 refinements、delegation 以及新的 ZJIT JIT 编译器,并指出 Ruby 搭配 ZJIT 正在缩小与 Go 和 Rust 等更快语言的性能差距。