一、全文给出去之后你需要改一个很小的行为shop项目的订单取消只有在订单存在时才允许取消——订单不存在时现在的代码抛了一个难以理解的异常。你要把这个行为改成明确的 404。为了让它看清楚你把src/orders/services/order_service.py全文交给了 Agent。这个文件八百多行里面有订单取消、订单创建、退款计算、库存回滚、通知发送还有三段历史遗留的兼容代码。它给出的改动里有三个问题。第一它把取消流程里的一个重复校验删了理由是这段逻辑和上面重复——实际上那段校验是为了兼容一个旧版本的调用方删掉会破坏那个路径。第二它顺手把退款计算里的一个魔法数字提取成了常量改动本身没错但那行代码属于另一个功能和本次任务无关。第三它没有发现取消成功之后还有一条外部通知发到消息队列因为在八百行里这条通知藏在一个名叫_finalize的辅助方法里名字看不出来和取消有关。三个问题有一个共同来源你要它改的是契约而它看到的是实现。当八百行实现同时摆在面前它无法区分哪些是这次任务相关的约定、哪些只是碰巧住在一起的代码同时真正重要的信息有一条外部通知被埋在细节里没有获得足够的分量。这篇讲的是怎么把代码折起来给先给模块结构和公开接口让它知道有哪些约定实现只在需要的时候展开。关键不在这套交付形式叫什么名字而在于一个底线——折叠不能把改变行为的细节藏起来。换个角度看这件事的难点不在于少给代码而在于把该说的说清楚。把八百行删成二十行树状图谁都会把散落在八百行里的三条关键约定外部通知、事务边界、旧调用方兼容提到第一层才是真正需要判断力的部分。这篇文章的篇幅大部分会花在这件事上。二、先把几个词讲明白接口一个模块对外承诺的能力。比如cancel_order(order_id)会取消订单调用者只需要知道这句话不需要知道内部怎么实现。生活里的类比是餐厅菜单菜单告诉你有什么菜、多少钱不告诉你后厨怎么炒。契约接口附带的条件与保证。包括前置条件调用前必须满足什么、后置条件调用后保证什么、以及失败时的行为抛什么异常、返回什么错误。菜单上这道菜要等二十分钟也是契约的一部分。实现接口背后真正的代码。它可以随时重构、替换而契约保持不变。后厨今天换了个灶台菜单不用改。代码折叠把给模型看的材料分成两层——第一层是模块结构、文件职责、公开接口和契约第二层是实现细节。默认只给第一层需要时再展开第二层。它和编辑器的折叠代码是同一个想法先看骨架再决定展开哪里。副作用一次调用除了返回值之外对外部世界造成的影响写数据库、发消息、调外部接口、改缓存、写日志。它是契约里最容易被漏掉、也最容易在折叠时被藏掉的部分。按需展开不是永远不给实现而是由任务决定展开哪一部分。展开的触发条件通常有两个要修改某个函数的行为或者要确认某个边界行为重试、并发、事务到底怎么做的。阅读包把上面这些东西整理成一份文件——包含哪些、排除了哪些、为什么排除、需要时从哪里展开。没有这份说明折叠就会变成藏。三、为什么先接口后实现更可靠3.1 接口是这次任务真正要讨论的对象大多数改动任务本质是在讨论契约这个接口在什么条件下做什么、失败时返回什么。实现是达成契约的手段。当你把全文给出去讨论的焦点就被拉到了实现层——模型会开始评论写法、提取常量、合并分支这些话题看起来专业但和你要的订单不存在返回 404没有关系。先给接口讨论就容易停在正确的层面上契约里有没有写订单不存在的行为没有的话这次任务就是要补上它有的话就要检查实现是否符合。这里还有一层实际收益契约层面的讨论更容易被验收。订单不存在返回 404是一条能写成测试的要求把重复校验合并掉不是——它只能被评价不能被验证。你把讨论固定在契约层验收条件也就自然浮出来了。3.2 折叠同时在做减法减少噪音八百行实现里和本次判断直接相关的可能只有二十行。其余的行数提供的是可能性可能被顺手优化、可能被误判为重复、可能引入风格模仿。折叠的作用是把这些可能性先收起来只在需要时打开一个窗口。这和我们上一节讲的按任务选材料是同一件事只是对象换成了代码。区别在于日志和文档可以按主题挑代码只能按结构挑——所以需要一个折叠方案而不是全靠手工挑选。3.3 折叠的三条底线折叠不是把代码藏起来那么简单。有三类信息一旦藏起来折叠就从减负变成了埋雷第一副作用。任何写数据库、发消息、调外部服务的行为都必须在第一层可见。藏起来的副作用会让模型和你做出错误的判断——以为改的是纯计算实际上触发了一串外部动作。第二异常语义。函数失败时抛什么异常、什么情况下返回空值、什么情况下不会返回。这些属于契约的一部分不能藏在第二层。第三事务、重试、并发和功能开关。它们不改变接口长什么样但会改变行为的时间与次数同一段代码在事务内还是事务外执行、失败后会不会自动重试、有没有被开关关掉。这些细节在评审时是必须看到的。底线可以总结成一句话折叠删掉的是怎么写不能删掉会发生什么。3.4 接口声明本身也需要维护折叠方案的可靠性取决于第一层的准确性。这里有一个循环接口清单是从代码和测试提炼的代码改了而清单没改清单就会误导——而且它比没有清单更危险因为它带着权威感。所以折叠方案要绑定一个维护动作任何改变契约的改动都要同步更新接口清单。判断标准是这次改动会不会让调用者的预期失效改了返回值、改了异常、加了副作用、改了事务范围都要更新纯粹的内部重构不用。这个动作和写代码注释不同它不属于顺手美化而属于交付的一部分——把契约变化写进声明文件和改测试一样重要。有一个轻量的检查可以放进评审改动里出现了新的副作用发消息、写缓存、调外部接口时问一句接口清单更新了吗。一次问、一次改习惯就建立起来了。3.5 三段式流程概览、方案、展开把折叠落到工作流上可以固定成三段每一段都有明确的产出避免给完材料就等它改代码这种一锤子买卖。第一段是概览。给它第一层材料和任务描述要求它产出两样东西对任务的理解一句话、以及它认为需要展开的位置清单。这一段不改代码。为什么先要这个因为要展开哪里本身就是对任务理解的一次检验——如果它要求展开的文件和你预期完全不同通常意味着任务描述或者结构说明有歧义。第二段是方案。基于第一层材料和少量已展开内容让它给出改动方案改哪些文件、每个文件改什么、怎么验证。这一段仍然不改代码。方案阶段的价值在于把改什么和怎么写分开评审——前者更重要的是你的判断后者可以慢慢打磨。第三段是执行。按批准的方案改代码遇到需要更多展开的情况就停下来要材料。这一段里如果它需要展开新的部分应该说明为什么需要这个说明既是对你的交代也会进入阅读包的记录。三段式的成本比直接改多花一轮对话换来的是一次对齐和一份可复用的记录。对于小改动可以合并成两段概览 执行对于涉及多个文件或者有兼容风险的改动三段都值得保留。四、完整例子给一次修改做两层阅读包4.1 第一层结构、职责与接口任务还是那个POST /orders/{id}/cancel在订单不存在时返回 404。第一层材料包含三部分模块结构、每个文件的职责、公开接口与契约。模块结构部分只列这次任务相关的路径和一句话职责src/orders/ api/routes.py # 把 HTTP 请求映射到用例调用负责状态码与响应结构 domain/order.py # 订单实体与状态规则不含 IO services/order_service.py # 用例编排取订单、改状态、写审计、发通知 repositories/order_repo.py # 持久化接口get / save / transaction tests/orders/ # 订单相关测试含取消用例接口与契约部分写成一份清单每一条都要包含前置条件、后置条件、失败行为和副作用classOrderRepo(Protocol):defget(self,order_id:str)-Order|None:...# 前置无# 后置返回订单对象不存在时返回 None# 副作用无defsave(self,order:Order)-None:...# 前置order 必须带有效 id# 后置订单状态与版本号被覆盖写入# 副作用写数据库defcancel_order(repo:OrderRepo,audit:Audit,notify:Notifier,order_id:str)-Order:# 前置order_id 非空# 后置状态变为 CANCELLED审计事件恰好一条通知恰好一条# 失败订单不存在 - OrderNotFound状态不允许 - OrderStateError# 副作用写数据库、写审计、发通知全部在同一事务内...注意最后那行注释里全部在同一事务内这句。它不属于接口签名但属于契约因为删掉它行为就变了。这类信息正是折叠文档存在的意义——它把散落在实现里的重要约定提炼到第一层。4.2 让它在第一层给出方案把这两份材料和任务描述一起交给 Agent要求它先不要改代码只回答三件事这次要改哪些文件、每个文件改什么、有没有它认为需要进一步查看的实现。预期它给出的回答大概是这样示例1. 需要改 api/routes.py把 OrderNotFound 映射为 404目前可能没有这个分支。 2. 需要改 services/order_service.py 中读取订单之后的分支把找不到订单的情况 转成 OrderNotFound。 3. domain/order.py 的规则不需要改状态不允许的语义已经是 OrderStateError。 4. 需要查看cancel_order 里异常从哪里抛出、是否被吞掉routes 里现有的 异常映射表。第 4 条是这个流程最有价值的部分。它明确说出了自己需要展开什么而不是在全文里随便翻。你把cancel_order的实现展开一段给它问题就闭环了——这就是按需展开。4.3 第二层只展开需要的那一段如果它要确认的是订单不存在时异常在哪里产生展开的范围大约是二十行defcancel_order(repo,audit,notify,order_id):orderrepo.get(order_id)iforderisNone:raiseOrderNotFound(order_id)# ← 这里已经有明确异常ensure_cancellable(order.status)withrepo.transaction():order.statusOrderStatus.CANCELLED repo.save(order)audit.append(order.cancelled,order_id)notify.send(order.cancelled,order_id)returnorder看完这段问题的性质就清楚了领域和用例层的行为已经是对的缺的是路由层把OrderNotFound映射成 404。改动的范围从可能涉及多个文件缩小到一个文件里加一个映射分支。把它和直接给八百行全文对比差别在两点一是需要判断的信息只有二十行注意力集中二是那二十行是因为它明确要求才展开的展开范围有依据不是你猜的。4.4 第一层材料怎么来读完例子之后一个现实问题浮出水面这份接口清单从哪来多数项目并没有现成的文档。三个来源按成本从低到高排第一个来源是代码本身。接口清单可以从类型注解、函数签名和 docstring 里抽出来缺少的契约信息副作用、事务、失败行为需要你补几行注释。这项工作的成本不高收益是长期的。第二个来源是测试。测试里常常写着契约什么情况下抛什么异常、失败时数据应该是什么状态。把测试名和断言提炼成契约描述比重新推导快得多。第三个来源是本次任务。如果接口清单只有前两份材料契约部分会很稀薄可以在做任务的过程中把新确认的约定补进去。这个动作有一个副作用是好的它让文档随任务生长而不是靠一次性的文档日。无论从哪里来接口清单都遵守同样的格式签名、前置、后置、失败、副作用。五行写清一个函数比写一段散文有效得多。4.5 阅读包文件长什么样把上面的做法固化成一份文件放在docs/reading-pack/cancel-order.md结构如下## 本次任务 POST /orders/{id}/cancel 在订单不存在时返回 404。 ## 第一层结构与接口 - 模块结构路径与职责清单 - 公开接口与契约签名 前置/后置/失败/副作用 ## 第二层按需展开 - 展开规则模型明确提出需要时提供每次注明展开范围与理由 - 已展开记录services/order_service.py: cancel_order第 41–58 行 ## 排除项与理由 - services/order_service.py 其余部分与本次判断无关 - domains 的退款计算属于另一个用例 - 历史兼容分支本次不改动避免破坏旧调用方 ## 折叠底线检查 [ ] 所有副作用已在第一层说明 [ ] 异常语义已在第一层说明 [ ] 事务/重试/开关已在第一层说明这份文件有两个作用。对本次任务它是材料包对下一次改动它是可复用的起点——把任务、接口清单和排除项换成新的结构照旧。4.6 一次对照记录同一任务、同一仓库版本用两种给法各跑一遍记录结果示例记录用于演示流程观察项给全文给两层阅读包是否需要额外探索不需要但找出无关改动二处一次按需展开20 行改动范围接口映射、退款计算常量、删除旧校验接口异常映射一处是否发现外部通知副作用没有藏在辅助方法里在第一层契约中已标注评审必须说明的问题三处兼容分支、无关改动、漏掉的副作用一处确认通知在事务内往返轮次两轮回滚无关改动、修复兼容分支一轮表格里最值得注意的是第三行和第四行。给全文的那次漏掉副作用不是因为它看不见代码而是因为副作用所在的位置在结构上离取消很远折叠方案把这条信息从实现里提到了契约里它就一定会被看到。这就是第一层材料的核心价值它不是减少信息而是把重要的信息从细节中提升出来。另外别把这张表当成折叠一定更好的证据。它的意义是演示怎么记录、记录什么。你的项目结构、任务大小、模型的读取方式不同结论可能不同——重要的是有记录而不是有立场。4.7 什么时候不该折叠有三种情况折叠会让事情变慢第一种修改的目标就是一个很短的实现。比如你要改的是一段十行的校验逻辑读它的时间和读它的接口清单差不多这时候直接给实现更直接。折叠是为了处理相关信息占比很低的场景不是所有场景。第二种任务本身就是理解实现。比如你怀疑某处有并发问题需要逐行看它怎么加锁、怎么处理重试又比如你要重构一段代码必须看清它所有的分支。这类任务的产出就是理解实现必须完整展开。第三种第一层材料还没准备好。硬要折叠你得到的只是一份目录树反而增加了一轮沟通。这种情况下先给实现跑完这次任务然后把这次获得的契约理解回填成第一层材料——阅读包从任务里长出来比从零开始设计容易得多。4.8 用一条命令把第一层材料生成一半第一层材料里最机械的那部分——有哪些公开函数、签名是什么、首行说明写了什么——不需要手抄。用 Python 标准库里的ast模块解析一遍源文件就能列出来不用装任何东西。把这个脚本存成tools/iface.py和代码一起走版本# tools/iface.py把模块的公开接口打印成一页清单供第一层材料使用importastimportsysdeffirst_line(node):doc(ast.get_docstring(node)or).strip().splitlines()returndoc[0]ifdocelse无说明defshow(node,indent):ifisinstance(node,ast.ClassDef):print(f{indent}class{node.name}--{first_line(node)})forsubinnode.body:ifisinstance(sub,ast.FunctionDef)andnotsub.name.startswith(_):show(sub,indent )else:args[a.argforainnode.args.args]ifargsandargs[0]in(self,cls):argsargs[1:]print(f{indent}def{node.name}({, .join(args)}) --{first_line(node)})forpathinsys.argv[1:]:treeast.parse(open(path,encodingutf-8).read())print(f##{path})fornodeintree.body:ifisinstance(node,(ast.FunctionDef,ast.ClassDef))andnotnode.name.startswith(_):show(node)运行方式和输出示例$ python tools/iface.py src/orders/services/order_service.py src/orders/domain/order.py ## src/orders/services/order_service.py class OrderService -- 订单用例编排取消、支付、查询 def cancel(order_id) -- 取消待处理订单并写一条审计事件 def pay(order_id) -- 把待处理订单标记为已支付 ## src/orders/domain/order.py class Order -- 订单聚合根 def can_cancel() -- 只有待处理状态允许取消 def load_order(raw) -- 从持久化数据还原订单这张清单解决了有哪些接口的问题但它只覆盖第一层材料的一半。签名和首行说明来自代码本身剩下那部分——前置条件、副作用、失败语义、事务范围——没有任何工具能自动生成只能由你或模型从测试、调用点和实际运行行为里提炼。所以脚本的定位是去掉抄写工作不是去掉判断工作清单生成之后你仍然要逐行回答这一行的事实够不够支撑这次改动。怎么检查生成的清单能不能用挑其中一个函数只看这一行问自己两个问题——调用它之后系统里会多出什么、什么情况下它会失败。两个都答得上来说明这一行的说明写够了答不上来就在清单里补一行副作用或者失败语义而这一行恰好是第一层材料里最值钱的部分。五、反例与代价四种折错的姿势下面四种做法都有一个共同特征它们都借用了折叠的名义却只完成了形式上的折叠。判断折叠是否有效的标准只有一条——看第一层材料是否仍然承载了会发生什么。5.1 反例一只折叠出文件列表做法第一层只给目录树和文件名不给接口和契约理由是让它先了解结构。它为什么看起来能行文件树确实提供了结构信息而且生成成本几乎为零tree命令一条就够。最后的代价是结构有了约定没有。文件名很少能说明行为order_service.py告诉你这里有用例编排但不会告诉你取消流程里有外部通知、事务边界在哪里、异常怎么命名。于是它要么向你索要多一轮往返要么按命名猜风险更高。这里的判别标准很简单第一层材料能不能让一个不熟悉项目的人说出这个功能的契约是什么不能就说明折叠折掉了关键部分只是折法看起来很整齐。5.2 反例二折叠时藏掉了副作用和失败行为做法第一层给了函数签名和一句简单描述比如取消订单返回订单对象。它为什么看起来能行签名和一句话描述是很多人对接口文档的全部理解很多文档站也确实在这个粒度上。最后的代价是判断依据缺失。签名里写不出会发一条通知“失败时抛特定异常”“整个过程在事务里”。而这三条恰恰是评审和修改的核心一个改动如果没有考虑到通知和事务它在测试里可能是绿的在上线后却会引发一串问题。第一层材料的作用是把这些信息从实现里提出来如果提不出来折叠的价值就丢失了。5.3 反例三折叠之后又一次性全量展开做法先给接口然后既然它要改这个文件干脆把整个文件都展开。它为什么看起来能行逻辑上说得通——要改的文件当然要给它看全文省得来回要。最后的代价是折叠的收益被抵消。全量展开之后注意力问题、噪音问题、顺手改动问题全部回来了而且这一次连按需都没有了——你没有给展开范围设边界。更合理的做法是把展开和修改范围绑定改哪个函数展开哪个函数及其直接依赖的那一段需要上下文时再补一点。展开的记录要留在阅读包里哪个文件、哪几行、为什么这既方便下一次复用也让评审人知道模型看到了什么。5.4 反例四折叠材料过期做法第一层材料做得很漂亮但此后再没更新接口改了、事务边界变了清单还是旧的。它为什么看起来能行第一版通常很准确而且更新文档看起来像额外工作没有任何测试会因为文档过期而变红。最后的代价是它开始系统性地误导人。旧的契约会被当成现行约定模型据此做的判断全部偏离——而且偏离得很合理因为它是照着材料做的。这类问题最难被发现的时刻恰恰是它最危险的时候材料看起来专业、格式整齐没有人会怀疑它。解决办法是把更新绑定到契约变化这个事件上前面 3.4 节的标准并在评审清单里加一条检查。六、落地步骤为一个模块做阅读包七步做完大约需要半小时到一小时取决于模块大小。第一次会觉得慢因为它要求你把以为知道的东西写下来第二次开始材料的复用会把这部分时间直接省回来。下面每一步都写了做什么、为什么、怎么检查。第一步选定模块边界。以这次任务可能触碰的范围为界而不是以整个仓库为界。为什么阅读包的价值来自集中范围一大它就退化成目录树。怎么检查模块清单能不能在一屏内看完。第二步写结构清单。每个路径一行路径 一句话职责。职责要写对谁负责不要写实现了什么算法。为什么职责是这个文件在系统里的位置对判断改动范围最有用。怎么检查随机挑一个文件问这次改动要不要碰它凭这行职责能不能答上来。第三步写接口与契约清单。每个公开函数五行签名、前置、后置、失败、副作用。为什么五行比一段话好因为它逼你分别回答会发生什么和什么时候不会发生。怎么检查五条里有没有空着的空着的那条是真的没有还是你不知道。第四步写排除项和理由。列出这次不看的部分并写一句为什么。为什么排除也要写理由因为下一次任务很可能需要它理由能帮下一个人判断我这次的情况是不是类似。怎么检查排除项能不能对应到改动范围之外这个结论。第五步约定展开规则。明确两件事展开由谁发起模型提出要求或你判断需要、展开记录怎么留文件、行号、理由。为什么因为按需必须有触发条件否则执行起来会退化成全量。怎么检查阅读包里有没有一段已展开记录哪怕这次是空的。第六步跑一次折叠底线检查。三条底线逐条过副作用、异常语义、事务/重试/开关。为什么单独设这一步因为这三类信息最容易在写第一层时被漏掉而漏掉它们的代价最大。怎么检查把第一层材料给一个不了解这个模块的同事问他这个功能失败时会怎样、会影响哪些外部系统。第七步把阅读包和任务绑定。阅读包放在任务单旁边命名带任务名任务结束后回填已展开记录和实际改动范围。为什么回填因为下一次做同类任务时上一次的展开记录就是最好的起点。怎么检查两份相邻任务的材料能否互相参照。可复制的检查清单[ ] 模块清单在一屏内每个文件一句话职责 [ ] 接口清单含签名、前置、后置、失败、副作用 [ ] 排除项写了理由 [ ] 展开规则明确谁发起、如何记录 [ ] 三条底线检查通过副作用 / 异常 / 事务 [ ] 已展开记录已回填含未展开也可以写无 [ ] 契约若发生变化清单同步更新阅读包和前面几篇的产物是一套任务单定义要做什么、怎么验收材料包定义这次给哪些材料阅读包定义代码材料怎么分层呈现。三者可以放在同一个目录下命名规则一致比如docs/tasks/2026-09-29-cancel-404/下面放task.md、materials.md、reading-pack.md。任务结束时三份文件一起归档下次同类任务整体复用。第一次做阅读包时建议挑一个改动经常出意外的模块因为反馈会最明显。做完之后对比两组数字这次的往返轮次、以及上次同类任务在给全文方式下的往返轮次。如果数字没有变化说明折叠的问题可能不在形式上而在第一层内容里——回去检查三条底线通常是副作用或者事务边界没写清楚。七、常见问题问接口清单和普通的 API 文档有什么区别关注点不同。对外的 API 文档通常描述协议路径、参数、状态码、示例读者是外部调用者接口清单描述内部模块的契约前置条件、后置条件、失败语义、副作用和事务范围读者是本次任务的模型和你自己。两者可以共享一部分内容但接口清单必须包含副作用和失败语义——这两项在对外文档里经常可以省略因为外部调用者只关心协议层面但在修改内部代码时它们决定了行为是否正确。问折叠之后模型看不到实现会不会写出不兼容的代码这个风险真实存在处理方式是两条。第一在任务里明确写出展开义务如果需要查看某个函数的实现来判断行为必须提出请求并说明要看什么而不是凭签名推断。第二用验收条件兜住涉及兼容性的行为异常类型、返回值、副作用次数都写进验收条件模型即使推断错了测试也会拦下来。两条合起来的效果是它不需要靠猜也不被允许靠猜。问遗留代码没有类型注解、没有文档怎么提炼接口清单按三个来源依次找测试里对行为的断言、调用点上对返回值的使用方式、以及运行时观察跑一遍看它实际做了什么。这三样拼起来通常能还原出大部分契约剩下的部分需要你在改动时逐步确认。一个务实的策略是先为这次任务会碰到的函数补清单不必一次覆盖整个模块。清单随任务生长比起憋一份大而全的文档更可持续。问维护阅读包的成本高吗谁来维护初次成本在于把契约写清楚之后每次任务增量很小。维护者是任务的执行者——无论是人还是 Agent——因为契约变了就更新清单是交付的一部分。有一个轻量做法可以显著降低成本把清单放在代码旁边的注释或者独立的 Markdown 文件里改代码的同一个提交就改清单避免出现文档在另一个仓库、另一个流程的割裂。问小项目、单文件也需要折叠吗不需要。折叠的价值随着文件长度和与任务无关内容的占比上升。经验判断如果这次要改的内容集中在一个函数内、文件不超过一两百行、其余内容与任务无关的程度不高直接把文件给出去更快。相反如果文件很长、历史遗留多、或者同一个文件里住着好几个不相干的用例折叠就值得做——哪怕只是把这次要改的函数和其余部分分开给。问怎么判断该展开哪一段用问题驱动而不是用文件驱动。具体方式是先写下我需要确认什么再据此决定展开范围。常见的三类问题各有对应的展开范围——失败时会怎样看抛出异常前后几行会不会影响外部系统看副作用调用链“重复执行会不会有问题看事务边界与幂等处理。展开永远是为了回答一个具体问题而不是为了看完整一点”。问折叠材料和让模型自己检索代码应该怎么配合可以配合顺序是先给第一层、再允许检索。第一层结构 契约 排除项定义了它该去哪找、不该去哪找检索动作则补上第一层没有覆盖的细节。配合时加一个要求让它把检索到的关键内容带来源文件、行号、版本写进输出方便你核对它看到的是不是当前版本——这一点和检索结果需要时间和版本元数据是同一个道理。如果没有第一层直接让它检索你就会回到它到底看了什么这个不可控的状态。问第一层材料里要不要放测试要放至少放测试名和关键断言。测试是契约最直接的证据test_cancel_missing_order_returns_404这个名字就把一条契约说清楚了断言里的状态码、状态变化、审计条数都是书面约定。把测试放进第一层还有一个额外好处它天然和验收条件对齐——如果验收条件里有某条测试覆盖不到这个缺口会在对照时暴露出来。至于测试的实现细节夹具怎么搭、数据怎么造留在第二层按需展开。问怎么避免折叠最后变成只给文档、不给代码用三条底线检查加一个习惯来兜。三条底线副作用、异常语义、事务与重试在第一层必须写明习惯是凡是要改动的地方实现必须展开到能读懂行为为止——只要涉及修改就不是看看签名就能改的事。还有一个信号值得警惕如果模型在没有看到任何实现的情况下就给出了完整的代码改动方案而任务描述里又没有明确说先给方案不改代码那说明它开始凭推断改代码了应当要求它先说明需要展开什么。问同一个模块如果一天内要改两次阅读包要重做吗不用重做只要改任务部分。阅读包的结构模块清单、接口与契约、排除项、展开规则是稳定的随任务变的只有本次任务和已展开记录两节。做法是把阅读包当成一个模板加实例的组合模板部分是长期的实例部分是单次任务的。重复使用同一个模板还有额外收益——你能观察到这个模块的任务总是要求展开同一段代码那说明那段代码可能就是下一个值得重构或者补文档的对象。问如果接口清单和实现不一致以哪个为准以当前分支的实现和测试为准并且把这次不一致当成一个必须处理的发现。处理方式有两种如果实现是正确的、清单过期了就更新清单如果实现偏离了原本的设计意图那这次任务要讨论的是要不要把实现改回来而不是悄悄按实现走。无论哪种情况都不要把不一致留在原地——你此刻看到了它下一个读到这份材料的人和模型看不到这种不一致他们只会照着材料理解。把发现写进任务记录是让这次判断沉淀下来的最便宜的方式。问给模型看的接口清单和给我自己看的应该是同一份吗可以同一份也可以分层。因为对模型有用和对人有用的信息偏好不完全一样模型需要明确的事实签名、副作用、失败行为、允许修改的范围人还需要背景和取舍为什么当初这样设计、哪些地方在计划中要改。一个实用的做法是同一份文件里分层写上面是事实清单双方都读下面是背景说明人读模型也会读到但不影响事实部分。最忌讳的是同一份文件里既有事实又有推测而且没有区分——一旦推测被当作事实使用这份材料就从帮助变成了误导。问模块被重命名或者拆分了阅读包怎么办把重命名和拆分本身当成一次契约变更来处理在同一个改动里更新阅读包而不是等下次任务顺手改。具体动作有两件更新结构清单和路径引用检查接口清单里的函数有没有换位置或者换了名字——位置信息哪个文件、多少行在折叠材料里是最容易过期的部分所以它应该只在按需展开记录里出现而不是写死在结构说明里。一个实用习惯是把结构清单写成路径 职责而不是路径 行号行号只在展开记录里出现并且带上日期提醒读者它是某个时刻的快照。问如果团队里有人不喜欢写这些材料怎么推进从收益最容易看见的地方开始而不是从规范开始。选一个改动经常出意外的模块做一次阅读包用它跑一次真实任务记录两次的往返轮次和改动范围。把对比放到评审会上比讲道理有效。另外把动作降到最低第一版不需要覆盖整个模块只覆盖这次要碰的函数和它的直接依赖排除项可以只写三行契约段落允许先粗糙遇到缺口再补。材料是长出来的一开始就要求完整往往会导致它永远停留在第一版。问模型输出里的展开请求应该怎么验收把它当成方案的一部分来验收而不是当成聊天内容。具体做法是要求它把展开请求写成固定格式要看哪个文件、哪几行、想确认什么行为。你收到之后回答两件事给不给给的话附上片段、以及它要确认的行为的正确答案是什么如果这一行为已经有明确约定。这样做有两个好处一是展开范围有记录下次可以复用二是它的疑问会被翻译成契约层面的结论这些结论正好可以回填到接口清单里——一次任务下来第一层材料反而更完整了。问接口清单要写到什么程度才算够用标准是够这次判断。具体检验方法是拿清单去回答三个问题——这次改动会影响什么行为失败时会怎样有没有外部影响三个都能答清单够用有一个答不上来就补对应的一行。反过来如果清单里写了很多这次用不到的细节比如完整的数据结构定义、历史版本对比它就该精简——第一层材料的目标是支撑判断不是穷举事实。不同的任务对够用的要求不同所以接口清单也需要随任务生长而不是一次写死。问自动生成的接口清单能替代人工整理吗不能但能把人工整理的范围砍掉一半。自动生成的部分是结构事实有哪些公开函数、参数叫什么、顺序如何、有没有一行说明。这些内容从代码里读出来比手抄准确也不会漏。人工要补的是行为约定前置条件、副作用、失败时会抛什么、事务范围到哪里结束、可重试还是不可重试。这些内容代码里通常没有测试里只有一部分只能靠提炼。判断一份清单是否被人工作业覆盖过方法很简单如果它只能回答有哪些函数说明它还是自动生成的半成品如果它能回答调用之后系统里会多出什么才算完成。八、动手练习与小结练习为一个真实模块做两层阅读包选一个你最近改动过的模块最好是那种文件很长、改动常常出意外的模块按下面五步做一份阅读包。第一步写结构清单。列出这次的模块边界内所有文件每个文件一句话职责。写完自检这些职责里有没有写成实现了某某算法的有的话改成对谁负责什么。第二步写接口与契约清单。挑出会被本次任务触碰的公开函数每个写五行签名、前置、后置、失败、副作用。写不出来的那几行就是你需要通过读测试或者读实现补齐的部分。第三步写排除项与理由。把这次不打算看的部分列出来每项写一句理由。这一步常常会发现其实我还不知道它有没有关系——那就把它从排除项挪进待确认项不确认完不开工。第四步做一次真实任务。用这份阅读包完成一个小改动记录三件事模型主动要求展开了哪些内容、实际改动落在哪些文件、有没有出现绕过契约的行为。第五步回填和归档。把展开记录、改动范围、遇到的问题写回阅读包并检查契约部分是否需要更新。这份文件现在就变成了下次任务的起点。做完的产出是一份两层阅读包第一层 展开规则与记录以及一份本次任务的对照记录。当你有三四个模块的阅读包之后你会发现一个变化新任务开始时不再需要重新介绍项目材料从文件里复制即可。如果练习过程中发现某个函数的契约怎么写都不对先停下来检查一件事是不是把它想得太大。一个函数如果同时承担取数据、判断规则、写库和发通知它的契约就会长到写不下。这种情况下的正确动作不是再写详细一点而是在契约里把职责拆开——取数据的部分失败时返回空规则部分失败时抛业务异常写库和通知属于同一个事务。契约写清楚了实现该怎么拆也就自然清楚了。练习里最难的一步通常是第二步——写契约。写不出来的那些行恰好就是这次任务最不确定的地方可能是你不知道副作用有哪些也可能是团队没有约定失败行为。把写不出来当成一个信号先把它变成一个问题比如这个接口失败时抛什么去代码或者测试里找答案找不到就问人。阅读包的价值有一半就来自这个逼你把模糊变清楚的过程。小结这一篇讲的是给代码折出一个合适的呈现方式核心有四点。第一接口承载契约实现只是达成契约的手段讨论改动应该从契约层开始。第二折叠不能藏掉会改变行为的信息副作用、异常语义、事务与重试、功能开关四条都必须在第一层可见。第三按需展开要有触发条件和记录由具体问题驱动展开范围与理由写进阅读包避免退化成全量。第四第一层材料需要维护契约变化时同步更新否则它会从帮助变成误导。把它和前后篇连起来看上一篇处理给哪些材料这一篇处理同一个模块的材料怎么分层给——前者决定材料的集合后者决定材料的呈现顺序。下一篇继续沿着这条线走当一次修改可能影响多个模块时怎么找到真正受影响的代码——也就是从入口到副作用的完整链路而不是靠搜索关键词碰运气。最后补一个容易忽略的收益第一层材料写下来之后“改动范围这件事第一次变得可评审。以前评审靠印象——“这个改动看起来只碰了一个文件吧”现在对照的是书面清单接口契约里写着哪些副作用、排除项里写着哪些不碰。评审的对话从我觉得变成清单上写着”这类变化看起来不起眼但它决定了同样的流程能不能被交给别人执行。
阅读完成 · 觉得有帮助?