跳转至

让 spec 与代码保持同步

只有当 spec 仍然描述着它底下的代码时,它才有用。SpexCode 通过每个节点的 code: 列表把节点与它所治理的文件绑在一起,spex lint 则沿着这张图去找两者已经分道扬镳的地方。它不判断文字是否仍与代码的含义相符——那需要人来读——但它能捕捉到 spec 已经陈旧的结构性迹象,且代价足够低,可以在每次提交时运行。

spex lint 检查什么

第一项检查是断链:如果一个节点的 code: 列表指向一个不再存在的文件,那就是错误,会阻断提交。指向空处的 spec 比没有 spec 更糟。

第二项是覆盖度。SpexCode 会枚举项目中受 git 跟踪的源文件,并对任何没有被节点认领的文件发出警告。由于文件清单取自 git 索引,构建产物和依赖永远不会计入其中,因此把工具指向整个项目是安全的。覆盖度是警告而非错误——它是已经漂出 spec 视野的那部分代码的清单,需要随着时间逐步消化。

第三项是 drift(漂移):某个被治理的文件有比其 spec 最后一次修订更新的提交。这是代码已经移动、而 spec 可能没跟上的信号。drift 默认是警告,不过一个已经落后好几个版本的文件,会阻断任何触及它的提交。

lint 还会给出更柔性的建议性警告——当一个节点的正文膨胀成对实现的复述,当一个节点分叉出过多子节点,或当一个文件被过多节点认领时。这些是可读性信号而非同步信号,在同一趟检查中一并浮现。

drift 是如何计算的

drift 是从 git 实时推导出来的。这里没有存储的哈希,也没有对“上一次已知良好状态”的快照:SpexCode 比较的是某个被治理文件的最后一次提交,与修订其 spec 的最后一次提交。既然 git 已经记录了这两者,就没有额外的东西需要维护。

一个文件在其 spec 之后发生变化,并不总意味着 spec 错了——一次重构可以搬动代码,而意图仍然完全成立。对于这种情况,你可以确认(ack)这次变化:spex ack 会记录某个节点的 spec 依然成立,并要求你说明理由。而当意图本身已经改变时,没有捷径可走;你重写 spec 去描述新的行为,新版本自己就会清除 drift。

这些检查在哪里运行

每一份克隆都可以安装一个 git 钩子,在每次提交前运行同样的 lint,这样断链或严重 drift 的节点在落地之前就会浮现。这很有用,但它是本地的、可绕过的:钩子位于仓库之外,一份跳过了安装步骤的新检出根本没有它,任何提交都能用一个环境变量标志跳过它。

谁也无法跳过的强制手段,是在持续集成(CI)中于每次推送和拉取请求时运行 spex lint,无论开发者的机器上发生了什么,构建都会因 lint 错误而失败。把钩子当作快速反馈,把 CI 当作真正的关卡。由于 lint 是从历史中推导版本时间线和 drift 的,CI 需要完整的 git 历史,而不是浅克隆。