Skip to content

Latest commit

 

History

History
88 lines (47 loc) · 7.18 KB

File metadata and controls

88 lines (47 loc) · 7.18 KB

工程方法论笔记

这份笔记记录 solution-drafter 在演进中踩过的几个坑,以及对应的工程实践。每条都是"问题 → 为什么是坑 → 怎么解决"的叙事,目的不是炫技,而是把"看起来对、其实有隐患"的地方讲清楚。这些方法不绑定本项目,可迁移到任何有"架构契约 + 多层扩展 + 自动化测试"的工程。


一、架构契约要写成可执行断言,但锁定基线必须配套"推进协议"

问题:三层解耦的核心卖点是"加新文档类型不动框架层(L1)"。这句话很容易写进 README 然后被慢慢侵蚀——某次改动顺手动了 L1,没人发现。

怎么解决:把"L1 不动"写成可执行的测试——锁定 L1 每个文件的内容哈希,测试里逐一比对,偏离即红。架构契约从"文档里的承诺"变成"CI 里的闸门"。

踩的坑:契约测试写成可执行断言是对的,但锁定基线如果没有"推进机制",会变成演进的枷锁。L1 偶尔有合规演进(比如修一个真 bug、补一处注释),一改就触发契约测试红——于是要么不敢改、要么偷偷改哈希。

最终做法:配一套"基线推进三步协议"——① 确认是合规演进而非范围漂移;② 显式推进锁定哈希(而非松开断言);③ 在测试注释里追加推进历史(推进原因 + 哪些文件 + 哪一步)。不允许静默推哈希。这样契约既防误改,又不阻碍正当演进,且每次推进都有留痕。

要点:可执行的架构契约 + 配套的推进协议,缺一不可。只有前者会变成枷锁,只有后者等于没有契约。


二、内容哈希锁定,而不是"按路径去某个历史提交取"

问题:上面的基线锁定,第一版是"去某个基准提交、按路径捞出文件、比对哈希"。

踩的坑:这把"内容不变"和"路径不变"耦合在了一起。一旦做目录改名(路径变、内容不变),按路径去旧提交取会失败——明明内容一个字没改,契约测试却红了。

最终做法:改成内容哈希常量锁定——测试里直接写死 {路径: 期望内容哈希} 字典,比对当前工作树文件的哈希是否等于锁定常量。不再"去历史提交按路径取"。改名时只改字典的 key(路径串),value(内容哈希)不变、自动通过。

要点:当你要锁"内容不变"时,就只锁内容(哈希),不要顺带把"路径不变""提交不变"绑进去——耦合的约束会在无关变更上误报。


三、ID 硬编码是隐藏的跨层耦合

问题:框架层(L1)本该与具体文档类型无关。但早期代码里,有的地方按 section 的 ID 写死了分支逻辑("如果是第 1 节就这样、第 2 节就那样"),还有的提示词里写死了"章节数 = 6"。

踩的坑:这些硬编码当时不报错——因为恰好所有文档类型都是 6 节、ID 排布也一致。它是隐藏的耦合:直到加第二个、第三个文档类型(节数不同、ID 不同),框架层才暴露出"它其实偷偷依赖了具体文档类型"。

最终做法:凡是框架层里出现具体文档类型的 ID / 数量 / 名称,一律改成"运行时从 outline 配置读取"。比如评审环节不再写"章节数应等于 6",而是"章节数应等于本文档类型 outline 列表的长度"。

要点:分层架构里,"恰好能跑"不等于"解耦了"。框架层里任何一个具体业务 ID / 数字,都是一处等着在扩展时爆雷的隐藏耦合。加第二个实例,是检验解耦的唯一可靠手段。


四、测试夹具要自包含,不依赖历史产物

问题:早期有的测试依赖"上一个阶段跑出来的产物文件"作为输入。

踩的坑:测试之间产生了隐式顺序依赖——单独跑某个测试会失败,因为它依赖的产物还没生成。测试不再是独立可重复的。

最终做法:每个测试的夹具现场构造自己需要的输入(在测试内拼出 fixture,或用临时目录),不依赖任何"别处跑出来的"文件。测试可以任意顺序、单独运行。

要点:测试的可信度来自可重复。一个依赖外部产物的"绿"测试,可能只是恰好那个产物还在。


五、测试要验"真正的价值点",而不只是"跑通了"

这是最值得记的一条,因为它咬过两次。

第一次:有一条"素材链"——S2 阶段获取素材产出 assets.json,S4 阶段消费它生成内容。文档里写了这条链,测试也"绿"。但测试只验了 assets.json结构(字段对不对),从没验过 S4 真的消费了它。结果:assets.json 产出后,下游根本没读它——链在可执行路径上是断的,而绿测试完美地掩盖了这一点。

第二次:长文档"逐段落盘"机制(见下一条)。如果测试只验"正常跑完后 docx 里有全部章节",那只证明了"正常情况是对的",没证明这套机制相对"攒齐再写"的唯一优势——中途断了,已写的能不能保住。

最终做法:测试必须打在"这次改动真正解决的问题"上。

  • 素材链:加测试断言 draft 里真的出现了被消费的素材内容 + 待补充处落了【待补充】,不只验结构。
  • 逐段落盘:加测试模拟中途断——写 2 个章节 + save,丢弃内存态,从磁盘重新打开,断言这 2 个章节还在。

要点:写测试前先问一句"这次改动真正的价值是什么",然后让测试打在那个点上。只验"跑通"的绿测试,最容易掩盖"价值其实没实现"。


六、长文档不断流:逐段落盘 + 诚实声明边界

问题:方案文档动辄几万到几十万字。LLM 单次输出有 token 上限,一口气生成整本会被截断。

坑(而且是反向示范):早期的参考实现是"把所有章节攒在内存里,最后一次性写文件"。这恰恰是最容易丢数据的模式——最后那次写之前,磁盘上是空的。生成到一半上下文耗尽 / 进程中断,已经生成的全部丢失。

最终做法

  1. 逐段落盘:每生成完一个章节立即 append(追加语义 / 对同一文档可反复调)+ save,再生成下一个。即使中途断,已写入磁盘的章节保住。
  2. 单章节过长再分批:某些章节(如"方案架构""实施计划")本身可能上万字,按其三级标题分块生成 + 分块落盘,避免单章节撞上限。
  3. 诚实声明边界:逐段落盘保证"已写的不丢",但"断了之后自动从断点续写"依赖调用方是 agentic 的(能重读已写内容、判断写到哪、接着写)。框架明确声明:只保证逐段落盘不丢,不保证自动续写

要点:能力要兜到框架自己(逐段落盘),边界要诚实写清(续写依赖调用方)。把"靠运行环境恰好能扛"的东西说成"框架保证了",是另一种形式的不诚实。


这些笔记的共同主线:"看起来对"和"真的对"之间,隔着一次扩展、一次中断、一次改名、一个被掩盖的断点。工程纪律就是把这些隔阂提前变成可执行的检查。