ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AGENTS.md、测试和 CI 都写同一条规则,会不会重复?

AGENTS.md、测试和 CI 都写同一条规则,会不会重复? 一、一次评审里的三句话shop订单服务的评审会上三个人几乎同时说出三句话。第一个人说取消成功后审计事件恰好一条’这条我已经写进 AGENTS.md 了为什么还要再写测试第二个人说测试里已经有断言了AGENTS.md 里再抄一遍改的时候忘一边怎么办第三个人说“CI 每次都跑整仓库测试本地跑一遍不就行了何必再配一条流水线”三个人说的都是同一种担心重复。这个担心是合理的——重复的规则确实会带来维护成本而且三处措辞不一致时最坏的情况是它们互相矛盾。但把这条规则放到实际发生过的几次问题里看会发现另一种情况只写文档它会被忘只写测试别人不知道要做什么只接 CI红了以后没人知道是为什么。所以问题不是要不要重复而是三层各自负责什么、怎么让重复不变成负担。这一篇用一条具体规则走完三层文档层、测试层、CI 层并给出减少重复的三个技巧。二、先把几个词讲明白文档层写在 AGENTS.md 或者任务单里的规则回答应该做什么。它面向执行者的意图是动作与约束的来源。测试层写在代码里的断言回答做成了没有。它面向行为是证据的来源。CI 层在持续集成流水线里运行的检查回答允不允许合入。它面向流程是门禁门禁的意思是不通过就不让合并不依赖任何人记得。门禁一个自动的、强制性的检查点。生活里的类比是地铁闸机票不对门不开它不需要工作人员记住你是谁。重复与冗余重复指同样的信息在多处出现冗余指多余的备份。两者不完全等价——在三层结构里同一条规则出现在三层是分层带来的冗余只要三层各自表达的信息不同意图、证据、门禁它就不是多余的。单一事实源同一件事只在一处定义其他位置引用它。在本篇的场景里命令和测试名最适合做单一事实源命令只在脚本里定义一份文档和 CI 都引用脚本名。失败可见性检查失败时人能不能快速知道哪条规则被违反了、违反了会怎样。失败信息越具体修复越快。漂移同一件约束在两处的表述逐渐不同最终互相矛盾。漂移通常不是一次造成的而是多次顺手改一处累积出来的。失败信息检查不通过时输出的内容。好的失败信息包含三样哪条规则、哪次运行、期望与实际。它是三层之间最后一段黏合剂——因为无论哪一层拦住你最后被读到的是这几行字。三、三层为什么都要有3.1 三层回答三个不同的问题把它们写成三句话就清楚了文档层应该做什么给方向 测试层做到了没有给证据 CI 层能不能合入给门禁三个问题没有互相替代关系。只有方向没有证据你无法判断做没做到只有证据没有方向执行者不知道要做什么测试也可能压根没覆盖到那条规则有方向有证据但没有门禁在忙的时候可以被绕过——而忙的时候恰好是最容易出问题的时候。3.2 少一层的三种典型失败缺文档层测试写了但没人知道背后的约定。新同事看到assert len(events) 1时能读懂代码却不知道为什么必须恰好一条代理想改动时也容易把它当作可以放宽的细节。测试是证据不是说明书。缺测试层文档写了但没有证据。代理在交付时只能说按约定做了你只能选择相信或者逐行读代码。更糟的是当实现出现偏差时没有任何东西会变红——规则变成了希望。缺 CI 层文档和测试都有但依赖人记得运行。本地跑的时候忙起来跳过、评审时看到测试通过的口头说明就放行几次之后规则的分量就被自然削弱了。CI 的价值不在于再跑一遍而在于它不依赖任何人的记性。3.3 为什么重复在这里是好事回到最开始那个担心三层都写是不是浪费把它和一份只写在一处的规则对比一下就清楚了。只写在一处的规则有一个致命弱点它的可被绕过性取决于那一处的性质。只写在文档里可以不被执行只写在测试里可以不被理解只写在 CI 里可以不被知道。三层各写一份等于把绕过成本提高到了需要同时绕过三个地方——而这三处恰好覆盖了意图、证据和流程。真正的浪费不是同一条规则出现在三处而是三处说的不是同一件事。所以这一篇的重点不是讨论要不要写三层而是怎么让三层保持同一个意思。3.4 三层的写入顺序三层都写那么按什么顺序写实践中最省事的顺序是先测试再 CI最后文档。这个顺序的理由是每一步都能验证前一步。先写测试的原因在前面提过写不出断言的动作说明它还不够具体。测试写完之后接到 CI 是一次机械动作——如果发现接不上比如测试依赖本地环境、需要人工准备数据说明这条规则的验证方式还不稳定要回去调整。最后写文档时你已经有了一组可运行的证据和一条确定会执行的流程文档只需要描述做什么、去哪里看证据写起来最快。反过来先文档后测试也可以但有一个常见的陷阱文档写完会带来已经完成的心理感受测试和 CI 的部分容易被推后。而测试和 CI 恰好是三层里唯一能自动执行的部分——推迟它们等于让规则回到只靠人记的状态。3.5 三层与三次法则还有一个常见疑问是不是每条规则都要三层齐全答案是看它违反的代价。可以用一个简单法则第一次靠口头提醒第二次写成文档第三次补测试与门禁。第一次出现问题时口头说明就够了——此时你甚至不确定它会不会重复发生。第二次出现同类问题时把约定写进文档让执行者有据可依。第三次出现时说明这条约定反复被违反靠人记不住了这时候补测试、接 CI 才是划算的。这个法则能避免两个极端把所有问题都变成流程成本高、执行不下去以及所有问题都靠口头反复踩同一个坑。四、完整例子一条规则的三层落点4.1 规则本身规则示例取消订单成功时审计事件恰好一条重复取消不新增事件。这句话在两处场景里被违反过一次是重复请求写了两条审计事件一次是状态变更成功但审计写入失败留下了一条没有对应事件的取消订单。规则要同时防住这两种情况三层落点也要覆盖两者。4.2 文档层怎么写文档层的目标是让人和代理在动手前知道要做什么。写法沿用上一篇的三要素### 取消流程的数据要求 - 触发任何修改订单状态的操作 - 动作 - 状态变更与审计事件在同一事务中提交 - 同一订单重复请求不新增审计事件 - 失败时状态与审计事件都不保留 - 验证tests/orders/test_cancel.py 中的三条断言 - 检查时机与责任人CI 运行该测试文件评审人确认三条断言都在文档层里最值得注意的是验证那一行它指向具体测试文件而不是笼统地说有测试。指向文件名的好处有两个一是执行者知道去哪里看证据二是文档和测试之间形成了可核对的对应关系——改名或者删掉测试时这句话会立刻显得不对劲。4.3 测试层怎么写测试层负责把三条约定翻译成可以运行的断言示例deftest_cancel_writes_exactly_one_event(repo,audit):repo.add(Order(ido-1,statusOrderStatus.PENDING))cancel_order(repo,audit,o-1)assertlen(audit.events_of(o-1,order.cancelled))1deftest_repeat_cancel_does_not_add_event(repo,audit):repo.add(Order(ido-2,statusOrderStatus.PENDING))cancel_order(repo,audit,o-2)cancel_order(repo,audit,o-2)# 第二次调用assertlen(audit.events_of(o-2,order.cancelled))1deftest_cancel_rollback_leaves_no_event(repo,audit,failing_save):repo.add(Order(ido-3,statusOrderStatus.PENDING))withpytest.raises(Exception):cancel_order(repo,audit,o-3)assertaudit.events_of(o-3,order.cancelled)[]三个断言分别对应三条约定命名也保持一致恰好一条、重复不新增、回滚不留痕。这种一一对应是文档层与测试层之间最重要的关系文档里的每条动作在测试里都有一条名字相近的断言反过来如果某个断言找不到对应的文档条目说明文档漏写了或者测试多测了。第三个测试依赖failing_save这个夹具它让保存订单的动作抛异常用来验证回滚行为。这个夹具的存在也提示了一件事——回滚测试需要主动构造失败不会自然发生。写这类测试时夹具体现的正是我们关心失败路径这个约定。4.4 CI 层怎么写CI 层的目标是让检查不依赖人的记性。最小版本只有一件事运行验证命令不通过则阻止合入示例思路触发提交到任意分支以及合并请求更新时 步骤 1. 安装依赖 2. 运行 pytest -q tests/orders 3. 运行 ruff check . 4. 任一步骤退出码非 0流水线失败禁止合并这里有三个设计点值得说明。第一CI 里跑的命令和文档里写的命令完全一致——不一致会造成本地过了 CI 不过的困惑也会让文档失去权威。第二CI 触发条件写的是提交与合并请求更新而不是仅合并时——早失败早修复成本更低。第三失败时保留产物测试输出、失败用例名让排查不用重跑。4.5 减少重复的三个技巧三层都写之后剩下的问题就是怎么让维护成本可控。三个技巧按投入从低到高第一个用引用代替复制。文档里不重复写命令细节而是写运行 scripts/check.sh或者指向测试文件名CI 也调用同一个脚本。这样命令变更时只改一处。文档里保留的是动作与意图不是命令的完整实现。第二个让三层的措辞保持一致。文档里的动作名、测试名、失败信息里的描述尽量用同一批词恰好一条、重复不新增、回滚不留痕。措辞一致带来的好处在失败时最明显CI 报出的失败信息能直接对应到文档条目不用再翻译一遍。第三个定期做一次一致性检查。方法很朴素把文档里每条规则的验证一栏抄出来去测试文件里找对应的断言去 CI 配置里确认这些测试真的会被运行。三处都对得上这条规则就是健康的有一处对不上就修那处。4.6 三层的对照表层回答什么写什么谁维护失败时表现文档层应该做什么触发、动作、验证指向规则责任人被忽略或争议无自动信号测试层做到了没有断言与夹具开发者测试变红给出具体断言CI 层能不能合入运行哪些命令、何时运行团队/维护者流水线失败阻止合并表格可以当检查表用一条规则上线时三行是否都落实了如果某一行为空就意味着这条规则在对应环节上有缺口。4.7 只写两层的对照记录把这条规则在三种缺一层的配置下各跑一次示例记录看看差别出在哪里配置第一个月的结果出问题的时刻只有文档执行者凭理解做事多数任务正确一次紧急改动里没人想起这条规则审计事件写了两条文档 测试本地可以验证评审有依据有人忘记跑测试合并后发现断言失败返工重来文档 测试 CI三层一致红灯阻止合并没有出现越过规则的情况出现的是断言本身需要调整示例对比。三行的差别不在第一个月而在出问题的时刻。前两种配置在顺利的时候看不出毛病问题只在压力出现时暴露第三种配置也会出问题但问题变成了规则本身该怎么调整而不是规则有没有被执行。这正是三层结构想要达到的状态把争论从有没有做移到规则是否合适。记录这张表还有一个附加用途当你需要说服别人为什么还要接 CI时直接展示第二行和第三行的差别比讲道理有效——因为它们描述的是同一条规则、同一批人唯一变量是那一层自动检查。4.8 一次演练故意破坏一层看会发生什么前面的对照表是推理这一节做一次演练。三层都接好之后故意在代码里制造一次违规观察每一层的表现。做法是把重复取消不新增审计事件这条约定破坏掉——去掉审计写入前的去重判断。演练步骤与观察示例记录1. 改坏实现去掉重复取消时的去重判断 2. 本地运行pytest -q tests/orders 结果test_repeat_cancel_does_not_add_event 变红 观察测试层给出具体断言失败信息里有订单编号和实际条数 3. 推送CI 运行同一条命令 结果流水线失败禁止合并 观察这一层不依赖任何人记得红灯自己出现 4. 读文档AGENTS.md 的验证指向仍然指向同一个测试文件 观察文档层不会变红它负责的是去看哪个证据第四步是这次演练最值得留意的地方。文档层不会因为实现被改坏而报警这是分工的正常状态文档指向证据测试给出判定CI 负责阻断。文档层的健康要靠另一种检查维护——定期确认它指向的测试仍然存在也就是前面第五步讲的做法。演练还有一个附带收获它顺手检查了失败信息够不够用。若失败信息只写数量不符你还得回去读代码若写清订单 o-2 期望 1 条、实际 2 条修复路径就短得多。演练时把失败信息也当作检查对象一举两得。4.9 三层记录放在哪里三层材料各有归属文档条目在 AGENTS.md断言在测试文件运行方式在 CI 配置。规则条数多起来之后可以再维护一份索引表把哪条规则、落在哪一层、当前状态压成一页编号 一句话规则 文档位置 测试文件 CI 任务 状态 最近核对 R-04 取消成功审计事件恰好一条 AGENTS.md 第4节 tests/orders/test_cancel.py 订单测试 生效 2026-09-29 R-05 领域层不依赖基础设施 AGENTS.md 第5节 tests/architecture/test_domain_isolated.py 架构检查 生效 2026-09-29这张表的用法是体检时按行推进一行对应一条规则核对三处是否都还在、是否都还准确。它的代价是自己也会过期所以每行都要带最近核对日期日期过久的行就是下一轮体检的优先项。规则在十条以内时可以先不建这张表——文档里的验证指向已经能充当索引。等条数上去、开始出现这条规则到底有没有测试的问题时再补避免一上来就维护两份材料。五、反例与代价五种让三层互相打架的做法5.1 反例一三处各写各的措辞不一致做法文档写审计事件不能重复测试断言事件数量小于等于二CI 只跑一个无关的检查。三处看起来都在讲这件事实际说的不是同一件事。它为什么看起来能行措辞模糊时三处很难被当场发现矛盾评审只看其中一处也会觉得没问题。最后的代价在出问题时集中爆发。有人按文档理解绝对不能重复有人按测试理解可以有一两条争执时没有裁决依据——因为三处都是官方文件。更麻烦的是修复你不知道该改哪一处才算对。避免方式很简单动作级别保持一致恰好一条就是恰好一条测试断言与文档动作一一对应CI 跑的命令覆盖这些测试。5.2 反例二命令散落在多处做法文档里写一遍测试命令CI 配置里写一遍部署脚本里再写一遍。它为什么看起来能行三处都能单独运行各自看起来都对。最后的代价是漂移。测试目录调整后只改了其中一处于是出现本地通过、CI 失败或者CI 通过、部署后失败的分歧而分歧的排查成本通常比修这条命令高得多。修法是建立单一事实源命令只在脚本或者项目配置里定义一次文档与 CI 都引用它。判断标准很直接全仓库搜索这条命令如果出现次数大于一处就存在漂移风险。5.3 反例三文档里写死实现细节做法文档写必须使用UPDATE ... WHERE statusPENDING完成状态变更测试也按这条 SQL 断言。它为什么看起来能行越具体越像有约束评审时显得严谨。最后的代价是规则绑死了实现。合理的技术方案比如改成带乐观锁的更新会因为违反文档而被拒测试也因为绑定了具体写法而变得难以维护。改法是把行为作为规则状态必须从 PENDING 变到 CANCELLED且并发下只有一次成功把实现自由度还给开发者。三层结构里文档层和测试层都应该面向行为而不是面向写法。5.4 反例四测试和 CI 都做了文档却没写做法团队习惯是改完就补测试、CI 自动跑从来不写文档因为代码就是文档。它为什么看起来能行对熟悉代码的人确实如此测试名和断言往往能说明大部分意图。最后的代价集中在两类人身上新成员和代理。新成员能读懂断言但读不出为什么恰好一条遇到边界情况时不知道是否可以放宽代理在动手前需要的是动作清单而测试文件是结果清单——它得先反推意图再决定怎么写。文档层的成本其实很低三行缺失带来的沟通成本却会反复发生。5.5 反例五CI 里堆满检查但没人知道失败原因做法CI 把仓库里所有命令都跑一遍失败时输出一大段日志不区分是哪条规则没通过。它为什么看起来能行覆盖越全越安全失败日志越长信息越多。最后的代价是失败不再提供方向。人看到长长一段红色输出第一反应是重跑一次而不是去修问题几次之后“CI 红了变成常态门禁的威慑力随之消失。修法是把检查分组、给每组一个清晰的名称比如订单测试”“静态检查”“契约校验”失败时能直接定位到对应的规则条目。CI 的价值一半来自阻止合入另一半来自明确告诉你是谁被拦下了。5.6 五种反例的共同点五种做法的共同特征是它们都让三层之间失去了对应关系。措辞不一致、命令散落、写死实现、缺一层、失败不可读本质上是同一件事的不同表现——某一层的存在没有和另外两层对上。反过来说判断三层是否健康不需要复杂检查只要问一句从文档里的这条动作能不能一路走到 CI 的一次失败能走到三层就是连着的走不到中间就有一层在自说自话。这句话也可以当成设计顺序先想清楚失败时会怎样报出来再决定文档怎么写。以终为始的写法会让三层天然对齐因为它们共享同一个终点。5.7 换个词看这件事不是重复是分工如果把三层都写重命名为三层分工很多争论会自然消失。分工的含义是同一件事不同角色承担不同的部分。文档是写作角色测试是验证角色CI 是守门角色——三者说的是同一件事的不同侧面而不是同一句话的三份副本。用分工视角检查现状会得到三个具体问题文档有没有说清触发与动作测试有没有覆盖这些动作的边界CI 有没有真的在跑并阻止失败三个问题都回答有分工是完整的某个问题回答不上来那一层就是缺失的。相比我们是不是重复了这种抽象疑问这三个问题都指向具体动作也更容易在评审里被回答。分工视角还有一个附加收益它让三层的责任人自然分开。文档由规则责任人维护测试由开发者维护CI 由团队或平台维护——不同的人有不同的关注点改动时也容易判断这次该改哪一层。重复的焦虑往往来自所有内容都堆给一个人分工之后焦虑变成清单上的三条待办。六、落地步骤给一条规则设计三层落点第一步选出这条规则用一句话写清楚。比如取消成功时审计事件恰好一条。为什么强调一句话因为一句写不出来的规则通常混合了两三条约束分层时会互相纠缠。怎么检查这句话里有没有和并且之类的连接词有就拆开。第二步写出动作与验证的对应关系。动作几条验证条目就有几条一一对应。为什么在写文档之前先想验证因为验证方式会暴露动作里含糊的地方——比如保证一致性会因为找不到验证方式而被改写成更具体的动作。怎么检查每条动作后面能不能跟一个可以执行的检查。第三步先写测试。按动作逐条写断言命名与动作保持一致。为什么先写测试因为它能立刻验证动作描述得够不够具体写不出测试的动作说明它还需要再拆。怎么检查每条测试都能对应到文档里的某一条动作。第四步把测试接入 CI。CI 里运行同一个脚本或者同一条命令失败即阻止合并。为什么要阻止合并而不是只做提醒因为提醒在忙的时候一律被跳过。怎么检查故意提交一次会失败的改动确认它无法合入。第五步回填文档。文档里写清触发、动作、以及验证指向哪个测试文件不复制命令细节。为什么用引用而不是复制因为命令的单一事实源在脚本里文档复制一份就会产生漂移。怎么检查文档里的动作名称与测试名称是否一一对应。第六步做一次三层一致性检查。从文档抄出验证条目去测试文件找断言去 CI 配置确认它会被运行。为什么这三处都必须走一遍因为测试存在和测试会被跑是两件事。怎么检查三处结果放在一张表里每一行都有结论。第七步定期复查。命令、目录、测试名都可能变化三层里任何一层过期都会造成错觉。为什么定期而不是随时因为随时检查无法执行定期才有落点。怎么检查找一个季度以上的规则看它的验证指向是否还准确。三层落点模板规则一句话 文档层AGENTS.md / 任务单 - 触发条件 - 动作1..n 条 - 验证指向测试文件与用例名 测试层 - 用例 1断言 - 用例 2断言 CI 层 - 触发提交 / 合并请求更新 - 命令脚本名或命令 - 失败处理阻止合并 保留输出6.1 三条容易忽略的顺序纪律在三层落地的过程中有三条顺序纪律能让整个过程顺畅很多。第一条先复现再上新规则。如果这条规则是为了解决某个已发生的问题先把那个问题复现成一个失败的测试。这样做的收益是规则从第一天起就有证据——它的测试不是为了覆盖而写而是因为真的出过问题。另外复现过程通常会修正你对规则的理解你以为是并发问题复现出来发现是事务边界问题。第二条先接门禁再写说明。很多人习惯先把文档写完整再去接 CI结果文档和 CI 之间长期没有验证过。换个顺序先让检查跑起来并且能拦住失败再写文档描述它——此时你描述的是一件已经成立的事实而不是一个计划。写出来的文档也因此不会有描述了一个没落地的流程这类问题。第三条先小范围再全量。一条新规则先只在新代码上强制观察一段时间再考虑覆盖历史代码。历史代码里往往存在大量合理的例外一次性全量强制会把例外也判成违规进而逼着人们去关掉检查。小范围试点的另一个好处是给规则留出调整期前两周发现措辞不合适、断言太严改起来成本很低。三条纪律的共同逻辑是让每一步都有可验证的产物。复现给出失败证据门禁给出强制点试点给出适用范围——三者都是具体的东西而不是已经安排上了这种状态描述。顺带说一个和顺序有关的细节给规则编号。三层材料里同一条规则在文档、测试、CI 中出现时带上同一个编号比如 R-04。编号让跨文件的对应关系一眼可见也让沟通有共同语言——R-04 现在卡在 CI比那条审计事件的规则精确得多。编号不需要复杂体系按加入顺序递增即可退役的编号保留空缺不重复使用这样历史记录不会错位。编号还有一个隐含好处它让三层是否齐全变成一次可以快速完成的盘点——把编号列出来逐个检查三处是否存在缺哪层一目了然。规则条目少的项目十分钟能盘完一遍盘完之后你会对自己的规则体系到底长什么样有一个具体印象而不是模糊的应该都写了吧。最后如果你的项目目前只有一两层也不要觉得落后。三层是一个目标状态不是准入门槛从文档 现有测试开始让每条新规则至少落在两层上等积累了几条之后再考虑接 CI同样能收到大部分收益。关键是让每一层都真实存在而不是在文档里描述一个还没有发生的流程。用一句话收束这一篇同一条规则出现在三层是为了让它在三种情况下都不被绕过——想不起它的时候、想偷懒的时候、以及忙不过来的时候。把这句话贴在文件的末尾比任何口号都实用它解释了为什么值得多写两处也提醒你三层缺一不可。七、常见问题问三层都写规则改动时要改三处维护成本是不是很高成本确实存在但和它换来的一致性相比通常值得。降低成本的三个做法第一把命令做成单一事实源一个脚本文档和 CI 都引用它这样最常见的变更命令调整只需要改一处第二文档只写动作和验证指向不复制断言细节断言变更时文档通常不用动第三把三层写在一份规则条目文件里文档条目 测试名 CI 任务名改动时按条目走不容易漏。三层里最常变的是实现细节不动文档最不常变的是动作一旦确定能撑很久所以实际维护量往往比想象中低。问CI 里应该跑整个测试套件还是只跑相关的部分分阶段跑。提交阶段先跑受影响范围的测试快反馈及时合入前再跑完整套件慢但覆盖全。原因是反馈速度和覆盖率很难同时满足只跑一切会让人等太久只跑一部分又可能漏掉跨模块影响。具体怎么划范围取决于你的项目但有一个通用建议把必须通过才能合入的检查写得少而硬比如完整测试 静态检查把建议看的情况做成提示比如覆盖率变化避免门禁里混入不确定的检查——一旦门禁出现不稳定判断它很快就会失去权威。问文档里写测试文件名测试改名之后文档就过期了怎么办这是引用式写法必须接受的代价但可以做两件事减轻。第一改名时把文档更新列入同一个改动测试改名的提交顺带更新引用并在评审清单里加一条引用是否仍然有效。第二给一致性检查留一个自动化的可能写一个小脚本扫描文档中的测试文件引用检查文件是否存在——这类检查成本极低却能挡住大部分过期引用。如果连这个也不想做那就退一步文档里只写由订单测试覆盖不写具体文件名代价是准确度下降收益是永远不会指错。问我们团队很小只有两三个人需要三层吗需要但可以简化。小团队的分工通常是文档 CI两层为主测试层依赖已有的用例不专门为这条规则补测试。判断是否需要补齐测试层的标准是这条规则被违反过吗如果被违反过哪怕一次就值得一条专门断言如果从来没被违反可以先靠 CI 跑现有测试兜住等出现问题时再补。CI 在小团队里更重要因为没有人专门做评审门禁就是最稳定的第三个评审人。问能不能只用测试不写文档能但要接受两个代价。第一执行者新人、代理需要从测试里反推意图这个过程会出错——测试覆盖不到的边界反推也反推不出来。第二测试本身会演化断言可能因为夹具调整而变化此时代码即文档会把噪声当成约定。实践里的折中是文档写三行触发、动作、验证指向剩下的理解交给测试。三行的成本很低却能把为什么固定下来。当这条规则只对一个人有效时不写文档也可以当它需要被第二个人执行时就该补上。问CI 慢团队总是抱怨怎么平衡把 CI 分成两条路径。快速路径只跑与改动相关的测试和静态检查用于每次提交几分钟内出结果。完整路径跑全套测试与契约校验在合并前触发允许更长的时间。划分的关键是把反馈速度当成设计目标之一如果一次提交要等半小时才有结果人会开始一次提交多个改动、或者绕过检查门禁就名存实亡。另外把所有检查都塞进一条流水线的做法通常会同时牺牲速度和可读性——失败时也看不出是哪一层的问题。问三个地方都写了规则评审时应该看哪个按顺序看先看文档层这次要做什么、边界在哪再看测试层证据是否覆盖这些边界最后看 CI 层门禁是否真的会拦住。这个顺序的原因和三层的关系一致先对齐意图再检查证据最后确认强制。只抓 CI 结果会让评审变成绿灯放行只读文档会让评审变成看描述对不对先文档后证据的顺序能让评审在一次对话里完成意图对齐 证据核对。问历史遗留项目里三层都不完整从哪里开始补从最近发生过问题的那条规则开始而不是从最重要的那条开始。理由是出过问题的规则有现成的场景和证据改造它时你不需要说服任何人而最重要的那条往往争论最多容易停在讨论阶段。补齐的顺序建议是先测试有可运行的断言再接 CI让它自动跑最后回填文档把触发和动作写清楚。顺序反过来的风险是先写文档写完之后大家以为已经完成了测试和 CI 反而被拖着不做。问文档层和任务单的关系是什么两者都是文档层但作用范围不同。AGENTS.md 是仓库级的长期约定任何任务都会读到任务单是这次任务的约定只在本次任务里有效。分界线的用法和前面几篇一致有效期超过一个季度的写进 AGENTS.md只对本次有效的写进任务单。把两者混为一谈会带来两类麻烦一次性要求长期化上次为了不改前端写进 AGENTS.md这次要改前端时被卡住或者长期约定临时化每次任务都要重新交代一遍测试命令。分清楚之后两处都变短了。问CI 失败了但是规则本身有问题怎么区分是实现错了还是规则错了看失败信息的内容。如果断言失败指向一个明确的、违反约定的行为多半是实现错了如果断言失败指向这条约定本身太严比如要求恰好一条事件但业务上确实允许补发那是规则需要调整。区分的动作是先把失败复现出来然后对着文档问一句这条动作在当前业务下还成立吗答案是不成立就改规则同时改文档、测试、CI 三处的表述成立就修实现。最怕的处理方式是直接放宽断言让 CI 变绿——那会让三层同时失真。问怎么让新成员理解这三层的关系用一条真实规则做例子讲一遍最快。步骤是带他看文档里的这条规则指出动作和验证指向打开对应的测试文件看断言和命名怎么对应打开 CI 配置指出哪一步会运行它最后故意改坏一处本地实验让他看红灯出现的位置和内容。整个过程十分钟但能把三层关系讲清楚——比抽象地讲我们有文档、测试和流水线有效得多。讲完之后给他一个任务为另一条规则做同样的三层检查。问如果 CI 暂时不可用故障流程怎么走把门禁降级成人工替代 记录。具体做法本地运行同一条命令并把输出贴进 PR评审人核对命令与输出合并记录里注明CI 不可用已完成人工验证。这个降级方案的关键是同一条命令——它保证恢复之后不会有行为差异。同时保留一条纪律CI 恢复后把降级期间合并的提交补跑一遍。补跑不是为了追责而是因为人工验证的覆盖率通常低于自动检查补跑能发现遗漏。把降级方案写进团队文档比临时决定要可靠。问一条规则只在文档里、还没有测试怎么标记才不会造成错觉给它加一个状态字段写清它现在落在哪一层。例如状态仅文档待补测试“。这样读者一眼能看出这条规则的强度不会把写下来了当成已经被检查”。状态字段还有一个用途它让补齐工作变成可见的待办。每季度体检时把状态仍是仅文档的条目列出来按违反代价排序决定先补哪一条比凭感觉挑要靠谱。问测试层该写单元测试还是集成测试看这条规则对应的行为边界。只涉及对象内部判断的例如只有 PENDING 可以取消单元测试足够跑得快、失败原因单一涉及事务、数据库或外部调用的例如失败时状态与事件都不留痕必须用集成测试因为这类行为只在真实的事务和存储里才成立。两者都写不算重复它们覆盖的是不同的失败模式单元测试挡住逻辑写错集成测试挡住提交时机和边界写错。省掉后者的典型后果是逻辑全对、事务拆错测试却始终是绿的。问CI 全绿评审还是放行了一个有问题的改动三层结构能挡住吗挡不住也不该指望它。三层防的是已知规则被违反防不住规则没有覆盖到的情况。三次结构能保证的是凡是写在规则里的约束不依赖人的记性没写在规则里的事情仍然需要人的判断。遇到这类问题时正确的收尾动作是复盘并把结论转成新规则如果这个错误以后还会出现就写进文档、补一条断言、接进同一条流水线。这样一次遗漏会变成一条长期有效的检查而不是只换来一句下次注意。问三层里哪一层最值得先建如果只能先建一层选测试层。理由是它的产物最硬一条能跑的断言既是可以复现的证据也是将来接 CI 的现成材料还能反过来校正文档里含糊的动作。如果只能先建两层加 CI。文档层可以稍后补因为它的成本最低三条信息而缺了 CI 的测试会退回到靠人记得跑。最不推荐的顺序是先写文档、把测试和 CI 一起推后——那时的进度看起来最快风险却最高。八、动手练习与小结练习给一条规则找到三层落点选一条你项目中大家都同意、但偶尔会被违反的规则按下面四步处理产出一份三层落点记录。第一步把它写成一句话。如果写不出一句话先拆成两条。写完之后自检这句话里的每个词执行者能不能理解成同一个意思——有歧义的词一致性、合理、适当换成具体动作。第二步写测试。按动作逐条写断言命名与动作对齐。如果发现某条动作写不出断言把这条动作再拆细或者把它降级成建议不写进规则。这一步的产出是可运行的测试文件或测试函数名。第三步接 CI。把第二步的测试接入流水线并确认失败会阻止合并。如果还没有 CI至少把命令写进提交前检查清单和任务交付要求并把这个临时方案记在文档里等有 CI 时替换。第四步回填文档并做一致性检查。文档写触发、动作、验证指向然后从文档出发逐一验证测试里有断言“CI 会运行它”。三处一一对上记录完成。做完的产出是一份包含三层的规则记录以及一次一致性检查的结果。下次再有类似规则把这份记录当模板。小结同一条规则出现在文档、测试和 CI 三层不是重复劳动而是三种不同功能的组合文档给方向测试给证据CI 给门禁。三层回答的问题不同——应该做什么、做到了没有、能不能合入——所以任何一层都不能替代另一层。少一层的典型后果是缺文档规则没人知道缺测试规则没有证据缺 CI规则依赖记性。让三层不变成负担的关键是减少同一信息的多处副本命令只在一处定义脚本文档引用测试名而不是复制断言CI 调用同一个命令。三层真正需要保持一致的是意思——动作名称、断言、失败信息用同一批词不一致的措辞会在出事时变成争论。三个可以直接带走的判断标准从文档里的动作能不能一路走到 CI 的一次失败全仓库搜索验证命令出现次数是否只有一处失败时能不能一眼看出是哪条规则被违反。三项都满足这条规则的三层就是健康的。和前后篇的关系上一篇讲怎么把坏规则改成可执行的条目这一篇讲这些条目应该在哪些层落地、怎么配合。下一篇处理规则的另一半——知识除了怎么做还有为什么这样做这部分内容适合跟代码一起评审而不是留在聊天记录里。补充一份可以直接套用的三层检查表文档层 [ ] 触发明确什么时候适用 [ ] 动作具体动词开头1..n 条 [ ] 验证指向具体测试文件或用例名 测试层 [ ] 每条动作都有对应断言 [ ] 断言覆盖失败路径回滚、重复、并发 [ ] 命名与动作一致失败信息可读 CI 层 [ ] 运行的是同一条命令单一事实源 [ ] 失败会阻止合并并保留输出 [ ] 触发时机覆盖提交与合并请求更新 一致性 [ ] 从文档动作可以一路走到 CI 的一次失败 [ ] 全仓库搜索命令只有一处定义这张表可以在评审一条新规则时使用也可以作为季度复查的底稿把仓库里最重要的三到五条规则拿出来逐条过一遍缺口会非常具体地显示出来。复查结束后把缺哪一层记进任务记录——下一次改动时这些缺口就是优先要补的部分。
返回列表