作者:Pablo Kuschniroff,由 Claude Code 辅助。第 1 版,2026 年 10 月 18 日。这一页是方法的后半部分:主文档描述的是一个新项目的路,而在这里我描述的是,当系统已经存在、是别人做的(或者是我自己"凭感觉写"、没有证据做出来的),现在要纠正它或者让它成长时,我会怎么做。陌生的词在主文档的术语表里;真实案例在参考资料里。公开仓库: github.com/pkuschnirof/urazuke。
我写梯子的时候,是看着一个空文件夹写的:第 0 级台阶决定项目要不要接,第 4 级部署没有功能的骨架,直到第 5 级才出现用户将会看到的第一样东西。那是干净的情形,也是最适合教学的情形。但它不是最常见的情形。在实践中,大多数有人来找我的时候,都已经有一个在跑的系统,有依赖它的用户,做它的人已经不在了,或者是一个没人向它要过证据的模型做的,而请求是"把这个修一下"或者"给它加上那个"。早在梯子写出来之前,我自己的工具就已经以诊断模式进入过别人的系统 Ref. 11;而方法第一次从一个生产中的系统里吸取养分,正是在看一个已经存在的系统的时候 Ref. 12。
这里有一个陷阱,我想在往下讲之前先把它说出来:面对一个遗留系统,诱惑是在两个极端里选一个,而这两个都是错的。一个是"我用方法从零重写它":听起来干净,而且几乎总是以第二个永远替代不了第一个的系统收场。另一个是"我直接对它套用循环,第 5 到 12 级台阶不就是干这个的吗":听起来务实,其实是在沙子上盖房,因为循环预设了存在一份契约、一张地图、一个测试环境、一套能跑的测试套件和一条可复现的部署链。在一个不是用方法诞生的系统里,这些一样都不存在。这条路走的是中间:先规范化,不重写,然后再登梯子。
方法有三个使用场景,在打开代理之前最好知道自己在哪一个里,因为入口不一样。
| 场景 | 起点 | 从哪里进 | 第一个功能之前有什么 | 写在哪里 |
|---|---|---|---|---|
| 新项目 | 空文件夹,加一个带着请求的所有者。 | 第 0 级台阶,然后二十四级按顺序登。 | 第一个功能出现之前,整个启动阶段。 | 主文档、二十四份契约和课程手册。 |
| 对一个用方法做出来的系统做演进-纠正 | 系统已经登过梯子:有契约、地图、带证据关闭的待办项、测试套件、部署链和手册。 | 第 5 级台阶:开一个功能块,然后跟着循环走;第 13 级定期;第 23 级在阶段收尾。 | 没有入口:它就是第 23 级台阶回到第 0 级,开始下一个阶段,这已经在主文档的 B 部分里了。 | 主文档(阶段 4,运维)。 |
| 对一个不是用方法做出来的系统做演进-纠正 | 一个在跑的、有用户的系统,上面那些产出物一样没有,或者有几样做了一半。 | 这一页的规范化阶段(N0 到 N7),之后才进入循环,以纠正模式或演进模式。 | 整个规范化阶段,然后才碰第一行功能代码。 | 这一页,以及课程手册第 2 部分。 |
有一样东西落在外面,我把它点名出来免得混淆:技术迁移(换平台、换语言、换数据库)不是方法的使用场景;那是另一个项目,而且一般会落进第 0 级台阶的排他性准则 Ref. 46。如果你继承的系统没有可访问的仓库,或者它的界面既不能由程序操作、也没法改造成可操作,那么该做的不是规范化它,而是做一个替代它的新项目,走完整的第 0 级台阶。
还有一点说明对第三个场景有效:遗留系统是别的团队的还是你自己的,都一样。一个你三个月前用模型、没有证据构建出来的系统,和一个承包商三年前做的系统,是同等程度的遗留。变的是读懂它要花多少力气,而不是路。
规范化,就是把系统带到方法在开启循环时所预设的那个状态。我用三条不讲价的规则来做。
第一条:不重写。 规范化不碰功能。它在系统周围补上它缺的东西(契约、地图、测试环境、测试套件、可复现的部署链、对那些已经作用于现实世界的东西加的锁),而让系统继续做它一直在做的事。一个遗留系统被"全面整理一下"的热情弄坏的次数,比被那个本来要修的 bug 弄坏的次数要多。
第二条:两种速度。 债务里有一部分要在任何改动之前就还掉,我把它叫做安全带:能在一个有近似真实数据的测试环境里跑这个系统,能以可复现的方式部署它,密钥在代码之外,以及有一套关键流程的表征测试,只要上面任何一样变了它就变红。安全带是一道硬闸门:没有它,什么都不许改,只有一个为生产紧急情况写下来的例外,下面我会解释。其余的债务触及时偿还:当一个功能块进入某个模块时,那个模块的规范化待办项跟着它一起进。永远不要在开工之前"做一次大扫除"。
第三条:前面各个阶段的输入物都必须存在,哪怕要靠自动发现把它们恢复出来。 这条规则是这条路和"直接套用循环"的分界线。启动阶段的每一级台阶都产出一个产出物,后面的台阶要读它:初始调研、适格性卡片、协作契约、项目文件、语义树、容量调研、容量估算、环境调研、闸门。在新项目里,这些输入物是所有者和代理一起、从需求摄入里写出来的。在遗留系统里没人写过它们,而循环没有它们就启动不了。解法不是跳过它们,而是让代理从系统本身把它们恢复出来:读代码、读数据库结构、读各环境的配置、读日志、读现有的任何文档,并且让这些文档里的每一条断言都带上一个"它从哪里来"的标记。我用的标记有五个:[代码]、[数据库]、[配置]、[文档]、[推断],再加上 [业务],给所有者确认过或纠正过的东西。一份带标记恢复出来的文档,是一份不读代码就能验证的文档:所有者去看标着 [推断] 的断言,确认或纠正它们;再去看标着 [代码] 的,看看系统做的是不是他以为的那回事。对不上的地方就是一条不一致,而这些不一致正是规范化的金矿:它们是没人报过的 bug,和没人决定过的规则。
为了不停留在抽象层面,下面列出启动阶段的每一个输入物、它在新项目里从哪里诞生,以及在演进-纠正之路上怎么拿到它。
| 输入物 | 新项目里由谁产出 | 演进-纠正之路上怎么来 | 谁确认 |
|---|---|---|---|
docs/INITIAL_SURVEY.md | 所有者,手工写,在需求摄入里(第 0 和第 2 级台阶)。 | 代理在 N2 从代码、数据库和配置里起草,带来源标记;所有者补上只有业务才知道的东西。 | 所有者,对 [推断] 的部分逐条确认。 |
docs/ELIGIBILITY_CRITERIA.md 和 docs/ELIGIBILITY.md | 第 0 级台阶。 | N0,外加四条遗留系统专属的准则。 | 所有者决定。 |
docs/WORKING_AGREEMENT.md | 第 1 级台阶。 | N1,外加五条遗留系统专属的规则。 | 所有者。 |
PROJECT.md、docs/SEMANTIC_TREE.md、docs/STATUS.md | 第 2 级台阶,从调研出发。 | N2,从系统出发,带标记;外加不一致清单。 | 所有者走一遍树,并对每条不一致做决定。 |
docs/AUDIT_[日期].md | 第 13 级台阶,在最后。 | N3,在最开始:它是债务的清单。 | 所有者读它,并验证其中两条发现。 |
docs/ENVIRONMENTS_SURVEY.md、测试环境、持续集成、可复现的部署链、docs/GATES.md | 第 4 和第 15 级台阶。 | N4,安全带,附带表征测试套件。 | 所有者看截图和部署链的第二次运行。 |
docs/CONSEQUENTIAL_ACTIONS_SURVEY.md、docs/PRODUCER_CONSUMER_MAP.md | 第 14 和第 11 级台阶。 | N5,靠盘点代码和数据库。 | 所有者一把锁一把锁地确认。 |
docs/VOLUME_SURVEY.md、docs/SIZING.md | 第 3 级台阶,数字由业务给出。 | N6,数字在数据库和日志里测出来(第 7 级台阶用在容量估算上)。 | 所有者只提供测不出来的那部分。 |
docs/PENDING.md | 第 5 级台阶,从客户的请求出发。 | N7,从审计、不一致和客户的请求出发,带上"阻塞/触及时偿还/可以等待"的标记。 | 所有者排序。 |
docs/DECISIONS.md、docs/GOTCHAS.md、docs/LOCKS.md、docs/DEPLOYMENT.md | 第 6、8、12、13 级台阶。 | 在 N2、N4 和 N5 里建成空的或补全,然后像新项目一样在循环中生长。 | 所有者。 |
如果 N7 结束时这些文件缺了任何一个,这个阶段就没有登上去。系统"能跑"并不要紧:循环在第一次会话就会需要它。
我把它们叫做站点而不是台阶,并且编号 N0 到 N7,是为了不和梯子的二十四级混淆。它们不是新台阶:它们就是启动阶段的那几级,加上运维阶段的三级(第 11、13 和 14 级),换了顺序,换了入口。在新项目里入口是调研;在这里入口是系统本身,而阅读由代理来做。
| 站点 | 来源台阶 | 相对新项目变了什么 |
|---|---|---|
| N0 — 既有系统的适格性 | 0 | 七条由代理做出来验证的前提条件;一条红的不是"不可行",而是一个前置项目。 |
| N1 — 遗留系统的协作契约 | 1 | 多五条规则:不重构没被要求的东西,动之前先读,顺手发现的怪事只记不修,当前行为就是规格,表征测试不绿就什么都不许进。 |
| N2 — 阅读系统 | 2 | 调研、项目文件和语义树都从代码里出来,带来源标记;出现不一致清单。 |
| N3 — 作为清单的审计 | 13 | 放在最前面而不是最后面,而且每条发现都要说明它是阻塞、触及时偿还,还是可以等待。 |
| N4 — 安全带 | 4、8、15 | 带数据的测试环境、可复现的部署链、密钥挪到外面、持续集成,以及表征测试套件。硬闸门。 |
| N5 — 从代码里找出锁和地图 | 14、11 | 在改动任何东西之前,盘点所有作用于现实世界的动作,以及各部分之间的全部契约。 |
| N6 — 用真实数字做容量估算 | 3、7 | 原始数据是测出来的,不是问出来的;所有者只提供数据库里没有的那部分。 |
| N7 — 规范化待办队列 | 5 | 债务和客户的请求进同一条队列,带速度标记,并按模块配对。 |
顺序很重要。N3 放在 N4 前面,是因为审计会告诉你安全带里必须包含什么(它找到的密钥、那些不能由程序操作的界面、那条没法重复的部署链)。N5 放在安全带之后,是因为盘点有后果的动作需要能在一个"弄错了也不要紧"的地方把系统跑起来。N6 放在最后,是因为它要在带数据的测试环境上测量,而那个环境从 N4 起才存在。N7 收尾,是因为它把前面所有东西汇进一条队列。
没有期限:每个阶段要花多久由系统来定,唯一固定的是顺序。
| 阶段 | 站点 | 还会发生什么 |
|---|---|---|
| 1 | N0、N1、N2、N3 | 和所有者一起阅读系统,确认节点、决定不一致。什么都不改。阶段收尾:恢复出来的地图和债务清单。 |
| 2 | N4、N5 | 安全带:带数据的测试环境、部署链跑两遍、密钥轮换、关键流程的表征测试套件。对已经作用于现实世界的东西确认锁。周末:系统在测试环境里跑起来,测试套件是绿的。 |
| 3 | N6、N7,以及第一个功能块 | 测量出来的容量估算。排好序的待办队列。用第 5 级台阶开第一个功能块,纠正型或演进型,带上配对的规范化待办项。从这里开始就是一贯的循环了。 |
如果系统很大,长大的是第 1 阶段(要读的更多)和第 2 阶段的表征测试套件(流程更多)。不会长大的是 N7:一份没法一口气写完的规范化待办队列,是一份没人会去维护的待办队列。
docs/ELIGIBILITY.md,含决定,以及遗留系统的七条排他性前提条件,每一条都由代理带着证据验证过,而不是由一个人声明:发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
docs/INITIAL_SURVEY.md,第一天就能写出来的那个简短版本:按业务的说法,系统做什么、给谁用;今天哪里疼(引出这次请求的那个 bug、缺的东西);谁做的,他还在不在;它在哪里运行;谁有什么访问权;有没有备份、谁在做。半页,故意不完整:到了 N2,代理会从系统里把它补全。docs/ELIGIBILITY_CRITERIA.md:第 0 级台阶的卡片(X1、X2 和那些加权准则),再加上面那七条遗留系统的前提条件,每一条带三列空白:绿或红、证据(代理做了什么来验证它),以及如果是红的,那个前置项目。初始:
We are going to assess whether this existing system can be taken over with the Urazuke method, with prerequisites that you verify by doing, not by asking. Read the initial survey in
docs/INITIAL_SURVEY.mdand the criteria sheet indocs/ELIGIBILITY_CRITERIA.md. Then, changing nothing in the system, verify each prerequisite with evidence: (1) try to set up a complete environment of the system from the repository and a copy of the data, with its sources, data model and persistence, and tell me exactly what you could and could not bring up; (2) read the whole code and tell me whether you can understand and work with it, module by module, naming what you cannot understand; (3) from the code alone, writedocs/FUNCTIONALITY_RECOVERED.md: the complete functionality of the system described for an end user, screen by screen and process by process, marking what you inferred; (4) list every screen and say whether it can be driven by a test tool with each element accessible and the backend effect checkable, and prove it by writing and running one end-to-end test of one flow; (5) try to build and run the system from the sources with today's tools and report every dependency that does not resolve; (6) check whether the production data can be copied and anonymized into a test environment and whether a backup exists and has ever been restored; (7) list every external integration and say whether it has a test mode or can be isolated at the edge. For each prerequisite: green or red, the evidence (what you did and what happened), and, if red, what project would make it green. Do not decide: present me the sheet, and I decide. Reply in my language.
我们要评估这个既有系统能不能用 Urazuke 方法接下来,判断用的是那些你靠做来验证、而不是靠问来验证的前提条件。读 docs/INITIAL_SURVEY.md 里的初始调研和 docs/ELIGIBILITY_CRITERIA.md 里的准则卡片。然后,在不改动系统任何东西的前提下,逐条带着证据验证每条前提条件:(1) 尝试从仓库和一份数据副本搭起系统的一套完整环境,带它的源码、数据模型和持久化,并准确告诉我你搭起了什么、没搭起什么;(2) 把整份代码读完,逐个模块告诉我你能不能理解它、能不能在它上面工作,并点名你看不懂的部分;(3) 只凭代码,写出 docs/FUNCTIONALITY_RECOVERED.md:系统的完整功能,写给一个最终用户看,一个界面一个界面、一个流程一个流程,并标出你推断的部分;(4) 列出每一个界面,说明它能不能被一个测试工具操作、每个元素是否可访问、后端效果是否可验证,并通过编写并运行一条流程的端到端测试来证明它;(5) 尝试用今天的工具从源码构建并运行系统,并报告每一个解析不了的依赖;(6) 核实生产数据能不能被复制并匿名化到一个测试环境,以及是否存在一份曾经恢复过的备份;(7) 列出每一个外部集成,说明它有没有测试模式、能不能在边界上被隔离。对每条前提条件:绿或红、证据(你做了什么、发生了什么),以及如果是红的,什么样的项目能把它变绿。不要做决定:把卡片呈给我,由我来决定。用我的语言回复。
我在要求的东西:那些前提条件要靠做来验证(搭起来、读、写出那份文档、跑一个测试),因为一个前沿模型这四件事都能做,于是适格性就不再是一种看法;功能恢复文档,它是"代码可上手"最有力的证明,而且它还是 N2 将要变成地图的那份东西的第一稿;以及每一条红的都要带上能把它变绿的那个项目,因为一条缺失的前提条件不是一次拒绝,而是一件有它自己范围的前置工作。
发送提示词之前(收尾提示词):你的决定,一行话,在读完带着每条前提条件证据的整张卡片之后做出;以及,对每一条红的,你决定那个前置项目是现在做、以后做,还是永远不做(那样这个系统就不适格)。
收尾:
Decision: [go / go after these prerequisite projects: … / no-go]. Write the final sheet to
docs/ELIGIBILITY.mdwith the date, who decided, each prerequisite with its colour and its evidence, and, for every red one, the prerequisite project as a pending item indocs/PENDING.mdmarked as blocking, with its own acceptance criterion: the prerequisite verified green. Include the questions you asked and my answers.
决定:[可行 / 在这些前置项目之后可行:…… / 不可行]。把最终卡片写到 docs/ELIGIBILITY.md,附上日期、谁做的决定、每条前提条件及其颜色和证据,并且对每一条红的,在 docs/PENDING.md 里把那个前置项目作为待办项写下来、标为阻塞,带上它自己的验收标准:该前提条件被验证为绿。把你问过的问题和我的回答包含进去。
完整性检验:每条前提条件都有代理做了某件事的证据,而不是你给出的一个回答吗? docs/FUNCTIONALITY_RECOVERED.md 存在吗,而且你以所有者的身份读过它:它描述的是他以为自己拥有的那个系统吗?它跑过一条流程的真实端到端测试吗?每一条红的都有带出口标准的前置项目吗?警报信号:对一个没人看得懂的系统,一个下午就全绿了。代理是在回答,而不是在做。
docs/WORKING_AGREEMENT.md,含方法的六条规则和遗留系统的五条规则。发送提示词之前,你得把这些准备在手边:
docs/ELIGIBILITY.md(N0),已关闭。初始:
Before we touch anything, we are going to set the working agreement between you and me for a system that already exists and has users. Write
docs/WORKING_AGREEMENT.mdwith the six rules of the method (my time is sacred; zero technical debt; exact scope; documentation in the same commit; never report as done what you have not verified; speak my language), each with one line explaining what it prevents, and these five rules for an inherited system: (7) Nothing is refactored that was not asked for, and no module is rewritten. (8) Code you do not understand is read before it is touched, and what you learn goes into the system map. (9) Anything strange you find (a secret in the code, a dead path, a duplicated rule, a query without an index) is logged as a pending item with its evidence, never fixed in passing. (10) Current behaviour is the specification until the owner says otherwise: a difference between what the code does and what the business says is a discrepancy for me to decide, not a bug for you to fix silently. (11) No change reaches the test environment without the characterization suite green. Add any rules you think this system needs, marked as proposals. If you need to know something about how I work in order to write this, ask me.
在我们动任何东西之前,要为一个已经存在、而且有用户的系统,定下你和我之间的协作契约。写 docs/WORKING_AGREEMENT.md,包含方法的六条规则(我的时间是神圣的;零技术债务;精确的范围;文档在同一次提交里;绝不把没验证过的东西报告为已完成;说我的语言),每条附一行说明它避免了什么,再加上这五条针对继承来的系统的规则:(8)不重构任何没被要求的东西,不重写任何模块。(11)你不理解的代码,先读再动,而你学到的东西要进系统地图。(7)你发现的任何怪事(代码里的一个密钥、一条死路径、一条重复的规则、一个没有索引的查询)都作为带证据的待办项记下来,绝不顺手修掉。(12)当前行为就是规格,直到所有者另有说法为止:代码做的事和业务说的事之间的差异,是一条由我来决定的不一致,不是一个由你悄悄修掉的 bug。(13)表征测试套件不绿,任何改动都不许进测试环境。你认为这个系统还需要什么规则,就加上,标为提案。如果你需要知道我是怎么工作的才能写出这份契约,就问我。
我在要求的东西:一贯的六条规则,再加五条,它们之所以存在,是因为一个模型面对别人的代码会条件反射地做三件事:重排它,在它觉得不对的地方"修"它,以及当代码的行为和它预期的不一样时就认定那是个错误。规则 7、9 和 10 恰好挡住这三个反射。规则 8 把阅读变成地图,这样代理从某个模块里学到的东西不会在会话关闭时丢掉。规则 11 是把安全带写成规则,好让它在测试套件存在之前就先存在。
收尾: 第 1 级台阶的收尾提示词,不变:你带着理由接受或拒绝那些提案,契约留下日期和版本。
警报信号:代理提出一条规则叫"随着触及逐步把代码现代化"。那条规则就是伪装起来的重写;带着写下来的理由拒绝它。
docs/INITIAL_SURVEY.md;PROJECT.md;每个节点都带标记的 docs/SEMANTIC_TREE.md;docs/STATUS.md;以及不一致清单,每一条都由所有者决定并写进 docs/DECISIONS.md。[推断] 节点缺少所有者的裁决;没有任何不一致缺少决定或缺少待办项;第 2 级台阶的阅读测试通过。发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
docs/INITIAL_SURVEY.md,加一个新小节,由你和所有者一起写:业务认为系统做了什么,用他们自己的话,一个用例一个用例地写,以及他们记得的业务规则("一个预约在不到两小时前不能取消")。不要去对着系统核实:这个比对恰恰是代理要做的事,而差异就是那些不一致。docs/WORKING_AGREEMENT.md(N1),已关闭。初始:
We are going to recover the map of this system from what exists. Read the whole repository (every branch), the database schema (read-only), the configuration of each environment, the recent logs and whatever documentation there is; if some part cannot be read from where you are, tell me and mark it as a gap; do not fill it by guessing. Then write three things. First, complete
docs/INITIAL_SURVEY.mdin the form the method expects (what the system does and for whom; what it does NOT do; who uses it; the technology; the environments; what an error costs), with every statement tagged by its source: [code], [database], [configuration], [documentation], [business] for what the survey already says, or [inferred]. Second,PROJECT.md, three hundred lines at most, anddocs/SEMANTIC_TREE.md: the domains and, under each one, the actors, the processes (each with trigger, steps, outcome and exceptions as the code actually implements them), the entities with the attributes, states and transitions the database and the code enforce, the screens, the external integrations and the scheduled jobs; every node tagged the same way. Third,docs/DISCREPANCIES.md: everything where the code, the database, the documentation and what the business says in the survey do not agree, with the evidence on each side. Do not resolve any discrepancy and do not fix anything: list them. Createdocs/STATUS.mdfor the living state. Reply in my language.
我们要从现有的东西里把这个系统的地图恢复出来。读整个仓库(所有分支)、数据库结构(只读)、每个环境的配置、最近的日志,以及现有的任何文档;如果某部分从你所在的位置读不到,告诉我并把它标为缺口;不要靠猜来填它。然后写三样东西。第一,按方法预期的形式补全 docs/INITIAL_SURVEY.md (系统做什么、给谁用;它不做什么;谁在用;技术;环境;一个错误的代价),每条断言都带上来源标记:[代码]、[数据库]、[配置]、[文档]、对调研里已经写了的东西用 [业务],或者 [ 推断]。第二,PROJECT.md,最多三百行,以及 docs/SEMANTIC_TREE.md:各个领域,以及每个领域下面的参与者、流程(每个带触发条件、步骤、结果和异常,按代码实际实现的样子写)、实体连同数据库和代码真正强制的属性、状态和状态转移、界面、外部集成和定时任务;每个节点都用同样的方式打标记。第三,docs/DISCREPANCIES.md:代码、数据库、文档,以及业务在调研里说的话,这四者对不上的一切,附上各方的证据。不要解决任何一条不一致,也不要修任何东西:把它们列出来。创建 docs/STATUS.md 来记录活状态。用我的语言回复。
我在要求的东西:要它读完一切并声明读不到什么(完整性条款在遗留系统里的形态:缺口要标出来,不要填上);要调研和树里的每条断言都带来源标记,因为那个标记正是让你不读代码也能验证的东西;要流程和状态是代码实际实现的那些,而不是本该是的那些,因为当前行为就是规格(契约的规则 10);要它把定时任务包含进来,因为那是没人记得、却伤害最大的东西;还要那些不一致附着各方的证据列出来而不解决,因为解决它们就是做决定,而做决定是你的事。
发送提示词之前(收尾提示词):树已经和所有者一个节点一个节点地走过,带着确认了的节点清单和纠正了的节点清单;还有不一致清单,每条附上所有者一行话的决定和日期(系统是对的,业务忘了;系统是错的,要改;或者挂着不决定但有日期,而且别太多)。
收尾:
I confirm these nodes: [list]. Correct these: [list]. The discrepancies are decided as follows: [one line each: discrepancy, my decision, today's date]. Finalize
docs/INITIAL_SURVEY.md,PROJECT.mdanddocs/SEMANTIC_TREE.mdwith today's date, keeping the source tags. Write each discrepancy decision indocs/DECISIONS.mdwith my exact words, and open a pending item indocs/PENDING.mdfor every discrepancy where I decided the system must change; for the ones where I decided the current behaviour stays, write it next to the node as a confirmed rule. Add toPROJECT.mdthe instruction to read this file, the tree and the working agreement at the start of every session.
我确认这些节点:[清单]。纠正这些:[清单]。那些不一致这样决定:[每条一行:不一致、我的决定、今天的日期]。用今天的日期关闭 docs/INITIAL_SURVEY.md、PROJECT.md 和 docs/SEMANTIC_TREE.md,保留来源标记。把每一条关于不一致的决定用我的原话写进 docs/DECISIONS.md,并且对每一条我决定要改系统的不一致,在 docs/PENDING.md 里开一条待办项;对那些我决定保留当前行为的,把它作为一条已确认的规则写在对应节点旁边。在 PROJECT.md 里加上一条指令:每次会话开始时读这个文件、语义树和协作契约。
我在要求的东西:每条不一致的决定都落在一贯的三个地方(决策记录、要改的话就落在待办项、确认的话就落在树上),而且来源标记在关闭时不要被抹掉:一棵丢了标记的树,是一棵再也无法验证的树。
完整性检验:像所有者那样走一遍树:有没有某个实体状态是业务从来没用过的?如果它标着 [代码],那系统里就有它,哪怕没人用,而这就是一条不一致。树里有没有定时任务?如果一个都没有,去问代理它在哪里找过。每条不一致都有带日期的决定吗?第 2 级台阶的阅读测试过了吗(一个新会话知道系统 不做什么)?警报信号:树里没有任何东西标着 [推断](代理没有在区分它读到的和它猜到的);不一致清单是空的(在遗留系统里它从来不是空的:代理比错了,或者根本没比);项目文件因为"要记录的东西太多"而超过三百行(那些东西该进树,而发生过的事该进活状态 Ref. 41)。
docs/AUDIT_[日期].md,每条发现都标着"阻塞/触及时偿还/可以等待"。发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
PROJECT.md、docs/SEMANTIC_TREE.md)和 docs/DISCREPANCIES.md,好让审计者不要再去发现一遍已经写下来的东西。初始:
Audit this system for integrity, security, performance, code quality and deployability, as the inventory of what must be normalized before and during its evolution. Read-only: change nothing except writing your report to
docs/AUDIT_[date].md. If auditing some part requires access you lack (the database, the logs, the server), tell me and mark that part as not audited; do not pass it as fine. Go through all of the code, not a sample, and readdocs/SEMANTIC_TREE.mdanddocs/DISCREPANCIES.mdfirst so that you do not report what is already known. For every finding: where it is (file and line), the risk, the evidence that proves it (the command, the query, the number), its priority (P0 must be fixed now, P1 soon, P2 when possible), and one more field: whether it BLOCKS (must be fixed before any change, by my criteria: [criteria]), whether it is PAID WHEN TOUCHED (fixed together with the first change that enters its module), or whether it CAN WAIT. Grade every dimension you audited. End with every finding turned into a proposed pending item, blocking ones first. Reply in my language.
从完整性、安全、性能、代码质量和可部署性五个方面审计这个系统,作为"在演进之前和演进期间必须规范化什么"的清单。只读:除了把报告写到 docs/AUDIT_[日期].md,什么都不要改。如果审计某部分需要你没有的访问权(数据库、日志、服务器),告诉我并把那部分标为未审计;不要把它当成没问题。走遍全部代码,不是抽样,并且先读 docs/SEMANTIC_TREE.md 和 docs/DISCREPANCIES.md,免得报告已经知道的东西。对每条发现:它在哪里(文件和行)、风险、证明它的证据(命令、查询、数字)、它的优先级(P0 必须马上修,P1 尽快,P2 有条件时),再加一个字段:它是阻塞(任何改动之前必须修掉,按我的标准:[标准])、是触及时偿还(和进入它所在模块的第一个改动一起修掉),还是可以等待。给你审计过的每个维度打一个评级。最后把每条发现都变成一条提议的待办项,阻塞的排在前面。用我的语言回复。
我在要求的东西:第 13 级台阶的审计,加上"可部署性"这个维度(因为在遗留系统里部署往往是第一个问题),再加上速度字段,而这正是把一份报告变成一份计划的东西:没有那个字段,一个审计过的遗留系统有四十条发现,没人知道从哪条开始。什么算阻塞由我来给标准,不是审计者,因为那取决于业务的后果。
收尾: 第 13 级台阶的收尾提示词,发给构建者,加一句:它开出的待办项要带上对应发现的速度标记。
检验:报告有没有说它没能审计什么?有没有至少一条标着"阻塞"的发现?(在遗留系统里总会有;如果没有,审计者就没看密钥也没看部署。)随便挑两条发现,它们有文件、行和可复现的证据吗?警报信号:全都在"可以等待"里。那是一个不想打扰人的审计者。
docs/DEPLOYMENT.md,记录着向测试环境重复跑过两次的部署;挪到服务器配置里并且轮换过的密钥;绿色的持续集成;docs/GATES.md;以及绿色的表征测试套件,每个流程都演示过它的红。发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
docs/ENVIRONMENTS_SURVEY.md,代理在 N2 带标记起草、由你补全的那份:今天它在哪里运行、今天怎么部署(哪怕只是"我用 FTP 把文件复制上去")、密钥将来住在服务器的什么地方、谁有访问权,以及测试环境将在哪里运行(另一台服务器、一个容器、一个云账号;如果你什么都没有,就说出来,代理会提出最简单的方案)。提示词里管它叫 "the environments survey";告诉它它在哪儿。docs/AUDIT_[日期].md(N3),好让代理知道它找到了哪些密钥、哪些界面不能操作。环境、部署与密钥:
We are going to build the safety belt around this system before changing anything in it. Read the environments survey,
docs/AUDIT_[date].mdanddocs/SEMANTIC_TREE.md. Evaluate whether you have what is needed (read access to the server where it runs and to its database, write access to where the test environment will run, the way it is deployed today); if anything is missing, ask me before starting. Then set up, without touching any functionality: (1) a test environment that runs the system exactly as production does, with a copy of the data where the personal fields I listed are anonymized; (2) a reproducible deployment to that environment, run twice, showing me that the second run changed nothing; (3) every secret found in the repository or in the code moved to the server configuration (environment file or vault), with the list of what you moved so that I rotate each one; (4) continuous integration running whatever test suite exists today, even if it is a single smoke test, on every push; (5)docs/GATES.mdwith the gates of this project, including: nothing reaches the test environment without the characterization suite green, and nothing reaches production without the full suite green with the exact version. Document all of it indocs/DEPLOYMENT.mdso that a person can do it by hand without you. Take a screenshot of the system running in the test environment and show it to me. Reply in my language.
在改动这个系统里的任何东西之前,我们要在它周围造一条安全带。读环境调研、docs/AUDIT_[日期 ].md 和 docs/SEMANTIC_TREE.md。评估你是不是具备所需的东西(对它运行所在的服务器和它的数据库的读访问权、对测试环境将要运行的位置的写访问权、它今天的部署方式);如果缺什么,在开始之前向我索要。然后搭建以下东西,而且不碰任何功能:(1)一个测试环境,它运行这个系统的方式和生产完全一样,带一份数据副本,其中我列出来的那些个人字段已经匿名化;(2)一条到那个环境的可复现部署链,跑两遍,并演示给我看第二遍什么都没有改变;(3)仓库里或代码里找到的每一个密钥都挪到服务器配置里(环境文件或保险库),并给我一份你挪动过的清单,好让我把每一个都轮换掉;(4)持续集成,在每次 push 时运行今天现有的任何测试套件,哪怕只有一个冒烟测试;(5)docs/GATES.md,写上这个项目的闸门,包括:表征测试套件不绿,任何东西都不许到测试环境;完整测试套件不以确切版本变绿,任何东西都不许到生产。把这一切记录到 docs/DEPLOYMENT.md,让一个人不靠你也能手工完成。给我截一张系统在测试环境里运行的图并拿给我看。用我的语言回复。
我在要求的东西:第 4 级台阶的骨架,围着一个已经存在的东西来造,并且把第 15 级台阶的一半提前(密钥挪到外面、部署有记录),因为在遗留系统里代码里有密钥是常态而不是例外 Ref. 42;匿名化作为环境的一部分,而不是一个细节;还有在测试套件存在之前就先写好闸门,好让测试套件一出生就已经是闸门。
发送提示词之前(表征测试套件的提示词):测试环境由你亲手打开过,里面是匿名化数据;清单上的密钥已经轮换,今天就轮换;关键流程清单,用来填提示词的空;而且如果 N0 留下了红色的界面,那条让它们变得可操作的阻塞待办项要在这套测试之前或者作为它的一部分关闭。
表征测试套件:
We are going to write the characterization tests of these critical flows: [list]. A characterization test captures what the system does today, exactly as it does it, defects included; it is not a test of what the system should do. For each flow: drive it from the screen as a user would and check the effect in the backend (the row, the state, the message queued). Every test must pass against the current code. Then prove that each test actually watches its flow: break the flow deliberately with a temporary change, show me the red output, revert the change, show me the green. If a flow cannot be driven from the screen, do not simulate it: say so and open a pending item marked as blocking. If while writing these tests you find behaviour that looks wrong, do not fix it: add it to
docs/DISCREPANCIES.mdwith the evidence. Record the command to run the suite indocs/STATUS.md, and make the suite the gate for the test environment asdocs/GATES.mdsays. Reply in my language.
我们要为这些关键流程写表征测试:[清单]。一个表征测试捕捉的是系统今天做了什么、而且完全按它今天的做法,缺陷也照收不误;它不是一个检验系统"本该做什么"的测试。对每个流程:像用户那样从界面操作它,并在后端验证效果(那一行、那个状态、那条进了队列的消息)。所有测试都必须对着当前代码通过。然后证明每个测试真的在看守它那个流程:用一处临时改动故意把流程弄坏,给我看红色输出,再把改动撤回,给我看绿色。如果某个流程没法从界面操作,不要模拟它:说出来,并开一条标为阻塞的待办项。如果在写这些测试的过程中你发现某个行为看起来不对,不要修它:把它连同证据加到 docs/DISCREPANCIES.md 里。把运行测试套件的命令记录到 docs/STATUS.md,并按 docs/GATES.md 所说,让这套测试成为测试环境的闸门。用我的语言回复。
我在要求的东西:反过来的第 8 级台阶。在新项目里,测试诞生时是红的,改动让它变绿;在这里,测试诞生时是绿的,因为它捕捉的是现有的东西,而它有用的证据是当你故意弄坏流程时它会变红。没有那次演示过的变异,一个表征测试就是一个因为什么都没看着所以才通过的测试,这和那种藏着 bug 的绿色是同一个问题 Ref. 44。还有两条纪律条款:操作不了的东西要声明出来并且阻塞,看起来不对的东西记为不一致、不去修,因为在所有者做决定之前,当前行为就是规格。
完整性检验:测试环境的网址是你自己打开的、而且你看到系统带着数据吗?你在那些数据里搜过一个真名、而且搜不到吗?部署跑了两遍,第二遍什么都没变吗?你要求过在仓库里搜密钥、而且搜出来是空的吗?清单上的每个密钥你都轮换了吗?每个关键流程都有自己的测试,而且每个测试的变异红都摆在眼前吗?警报信号:表征测试套件"一次就"全通过了,而且从来没展示过一次红色;测试环境跑"在代理的机器上",而不是跑在一个你能打开的地方;测试数据是"几条示例记录",而不是一份匿名化副本(遗留系统的 bug 住在真实数据里,不住在示例数据里)。
docs/CONSEQUENTIAL_ACTIONS_SURVEY.md、 docs/PRODUCER_CONSUMER_MAP.md,以及收尾之后的 docs/LOCKS.md,里面写着哪些锁现在就上、哪些以后再上。发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
docs/INITIAL_SURVEY.md 的一个小节。代理会在代码里找到比业务记得的更多的东西;两份清单的比对就是结果。初始:
Go through the code, the database triggers and the scheduled jobs of the server looking for everything that charges, sends, approves, deletes, notifies outwards or runs on a schedule, and write
docs/CONSEQUENTIAL_ACTIONS_SURVEY.mdwith every statement tagged by its source. Compare it with the list the business wrote indocs/INITIAL_SURVEY.md: whatever the code does that the business did not list, or the other way round, ask me about before going on. For every action: what triggers it, whether it can run twice, whether it is reversible, what lock exists today if any, and what the way back is. In the same pass, builddocs/PRODUCER_CONSUMER_MAP.md: for every contract in the system (a service, a table, a file format, a list of values), who produces it and who consumes it, including the things outside the repository I told you about. If there are parts you cannot see, say so instead of leaving them out. Present me the actions table with a proposed lock for each (explicit confirmation, double safety, dry-run mode, daily limit, a human in the loop) and a proposal of which locks go NOW, before any change, and which can go when the module is touched. Do not implement anything yet: I confirm lock by lock. Reply in my language.
走遍代码、数据库触发器和服务器上的定时任务,找出一切会收钱、发送、批准、删除、对外通知或者按时间表自己运行的东西,并写 docs/CONSEQUENTIAL_ACTIONS_SURVEY.md,每条断言都带上来源标记。把它和业务写在 docs/INITIAL_SURVEY.md 里的那份清单做比对:代码做了而业务没列出来的,或者反过来,在继续之前都来问我。对每个动作:什么触发它、它会不会跑两次、它可不可逆、它今天有没有锁(如果有是什么锁),以及回头路是什么。在同一遍里,构建 docs/PRODUCER_CONSUMER_MAP.md:对系统里的每一份契约(一个服务、一张表、一种文件格式、一份取值清单),谁生产它、谁消费它,包括我告诉过你的那些仓库之外的东西。如果有些部分你看不到,说出来,不要把它们略掉。把动作表呈给我,每个动作附一把提议的锁(显式确认、双重保险、演练模式、每日上限、回路中的人),以及一份提案:哪些锁现在就上,在任何改动之前,哪些可以等到触及那个模块时再上。先不要实现任何东西:我一把锁一把锁地确认。用我的语言回复。
我在要求的东西:把第 14 和第 11 级台阶的盘点放在一遍里做,因为它们走的是同一份代码;业务记得的和代码做的之间的比对,而那正是"没人记得的那些发送"冒出来的地方;还有每把锁的现在还是触及时提案,那就是两种速度的规则应用在保护上:一把管着收钱动作的锁现在就上;一把管着月度导出的锁,可以等到触及那个模块的功能块。
收尾: 第 14 级台阶的收尾提示词,带上标为"现在"的那些锁,每一把都带着它对着没有锁的版本的红色测试;其余的留在待办队列里,带着它们的标记。
警报信号:代理找到的动作和业务列出来的一模一样。在遗留系统里这从来不会发生;它漏看了定时任务或者数据库触发器。
docs/VOLUME_SURVEY.md,每个数字都标着 [数据库] 或 [日志] 并附上它的命令;仓库里带测试的量具;以及 docs/SIZING.md,写着"乘以十"和演进之前值得做的那些不对称决策。发送提示词之前,你得把这些准备在手边:
初始:
Before changing anything we are going to size this system with its real numbers. With read-only access to the production database (or the complete test copy) and to the logs, measure instead of asking: rows per table and growth per month from the timestamps, events per entity, peak concurrency from the access logs, data size today and projected at five years, and the slowest queries. Write a meter for each number (a script that lives in the repository, with a test against a known case I will give you) and show me every number together with the command that produces it; audit each meter in both directions (is everything that should be counted, counted? does everything counted exist?). Write
docs/VOLUME_SURVEY.mdwith every figure tagged [database] or [log], and ask me only for what cannot be measured: the growth the business expects and whether the system will serve more than one site. Then writedocs/SIZING.md: what breaks first if everything is multiplied by ten, which of the current architecture choices hold and which do not, and which asymmetric decisions we take now, before evolving, because they are cheap today and expensive later. Do not change any code. Reply in my language.
在改动任何东西之前,我们要用这个系统的真实数字给它做容量估算。用对生产数据库(或者完整的测试副本)和日志的只读访问权,去测量而不是去问:每张表的行数和按时间戳算出的每月增长、每个实体的事件数、从访问日志算出的峰值并发、今天的数据体量和五年后的预测,以及最慢的那些查询。给每个数字写一把量具(一个住在仓库里的脚本,带一个对着我会给你的已知案例的测试),并把每个数字连同产出它的命令展示给我;对每把量具做双向审计(该被数到的都数到了吗?数到的都真实存在吗?)。写 docs/VOLUME_SURVEY.md,每个数字标上 [数据库] 或 [日志],并且只向我索要测不出来的东西:业务预期的增长,以及这个系统会不会服务于不止一个站点。然后写 docs/SIZING.md:如果一切都乘以十,最先坏掉的是什么;当前架构的哪些选择撑得住、哪些撑不住;以及我们现在、在演进之前要做哪些不对称决策,因为它们今天便宜、以后昂贵。不要改任何代码。用我的语言回复。
我在要求的东西:测量而不是提问,这是遗留系统给得起而新项目给不起的;第 7 级台阶的带测试量具和双向审计,因为量具会撒谎 Ref. 35、 Ref. 36;还有把容量估算当成演进之前的闸门 Ref. 14:如果当前架构撑不住"乘以十",而客户的请求恰恰就是增长,这件事现在就要知道,而不是等到第三个功能块。
收尾: 第 3 级台阶的收尾提示词,在它为撑不住的部分提出的那些方案里做选择,或者确认现状撑得住。
警报信号:一个"大约"的数字,却没有标明它是大约。如果它在数据库里,就去测;如果不在,就去问;没有第三个选项。
docs/AUDIT_[日期].md、带决定的 docs/DISCREPANCIES.md、docs/LOCKS.md、 docs/SIZING.md、语义树,以及客户对这一阶段的请求。docs/PENDING.md,所有待办项都有规格,标着"阻塞/触及时偿还/可以等待"和"纠正型/演进型/规范化",并带按模块的配对。发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:
docs/blocks/STAGE_01_REQUEST.md 或者你喜欢的地方;提示词里说的是 "the client's request for this stage",所以要告诉它它在哪儿。如果所有者给了优先级,也一起写上。初始:
We are going to turn the normalization and the client's request for this stage into one queue of numbered pending items in
docs/PENDING.md, each with its full specification as the method asks (number, title, node of the semantic tree, origin, description, business rules with decision and date or marked as missing, data in and out, screens, edge cases, acceptance criteria as given, when, then, how it will be verified, status). Sources for the normalization items:docs/AUDIT_[date].md, the discrepancies I decided to change, the screens that cannot be driven by a program, the secrets to rotate, the locks that are not implemented yet, and the decisions indocs/SIZING.md; each one keeps its speed mark: BLOCKS, PAID WHEN TOUCHED, or CAN WAIT. Sources for the client's items: the client's request for this stage; each one marked CORRECTIVE (something that fails today, with the way to reproduce it in the client's words) or EVOLUTIVE (something new). For every client item, list the normalization items that touch the same modules and should be paid together with it. If the request is ambiguous on any point such that you cannot write a verifiable item, ask me; do not resolve it by assuming. Order: blocking items first, then the client's items in the priority I gave you, each with its paired normalization. Reply in my language.
我们要把规范化和客户对这一阶段的请求,变成 docs/PENDING.md 里一条编号待办项的单一队列,每一条都带着方法要求的完整规格(编号、标题、语义树节点、来源、描述、带决定和日期的业务规则或标为缺失、进出的数据、界面、边界情况、以"给定、当、那么"形式写的验收标准、将如何验证、状态)。规范化待办项的来源:docs/AUDIT_[日期].md、我决定要改的那些不一致、不能由程序操作的那些界面、要轮换的密钥、还没实现的那些锁,以及 docs/SIZING.md 里的决策;每一条都保留它的速度标记: 阻塞、触及时偿还,或者可以等待。客户待办项的来源:客户对这一阶段的请求;每一条标为 纠正型(今天出错的东西,附上用客户的话写的复现方式)或演进型(新东西)。对每个客户待办项,列出那些触及同样模块、应该和它一起偿还的规范化待办项。如果请求在某一点上含糊到你写不出一条可验证的待办项,就问我;不要靠假设去解决它。顺序:先是阻塞的那些,然后是客户的那些,按我给你的优先级,每一条带着它配对的规范化待办项。用我的语言回复。
我在要求的东西:完整的第 5 级台阶(详细规格住在每一个待办项里,规范化的那些也一样),加上让这条路之所以成立的两个标记:速度,它说每一笔债务什么时候偿还;类型,它说每一项怎么关闭(一个纠正型用复现故障的测试关闭;一个演进型用第 8 级台阶的测试关闭)。还有按模块配对,那是把 "触及时偿还"这条规则做成可操作的:当我开"定金"这个功能块时,支付模块的规范化待办项跟着它一起进,不早也不晚。
收尾: 第 5 级台阶里每次会话的例行,不变,写进协作契约。
完整性检验:还有"阻塞"开着吗?那第一个功能块就不能开。每个纠正型都说明了怎么复现、而且用的是客户的话吗?每个客户待办项都有自己的配对清单吗,哪怕是空的?警报信号:待办队列里有超过五十条 "可以等待"的规范化待办项。那不是登记在册的债务:那是伪装成清单的重写。把永远不会偿还的那些归档掉,并且把这件事说出来。
N7 结束之后,这条路和梯子汇合:第 5 到 10 级台阶随每条待办项登上去,和新项目一样,同样的契约、同样的提示词。但有两个细微之处值得单开一节,因为它们正是"有方法地动遗留系统"和"小心地动遗留系统"之间的区别。
一条纠正型待办项是一个 bug:今天出错的东西,而客户知道。在新项目的第 8 级台阶里,红是制造出来的(写测试、对着旧代码跑、看它失败)。在纠正模式里,红就是那个 bug 本身:那个复现故障的测试,像报告它的那个用户一样从界面操作流程,必须对着当前代码失败。如果它不失败,你复现的就不是别人报给你的那个 bug;你复现的是另一个。那个红是你在修对东西的证据,而且比代理关于"应该是在发生什么"的任何解释都更有价值。那个走了四轮才找到根因的案例 Ref. 22,就是它教会我:没有写下来的复现,每一轮修的都是另一个症状。
发送提示词之前,你得把这些准备在手边:
docs/PENDING.md(N7)里那条纠正型待办项,带着用客户的话写的复现方式:他做了什么、发生了什么、他期待什么。如果客户不知道怎么复现,先去拿到一个案例(一条记录、一个日期、一张截图);没有复现就没有测试,没有测试就没有关闭。docs/STATUS.md 里。关闭一条纠正型待办项:
This pending item is a correction: [what fails, in the client's words, and how to reproduce it]. Before changing anything: (1) Reproduce the failure with a test that drives the flow from the screen and checks the effect in the backend, and show me that it fails against the current code; that red is the proof that you reproduced the right defect. If you cannot reproduce it, tell me what you tried and what you need; do not fix what you cannot see. (2) Find the root cause and tell me in prose, with the evidence, before fixing. If the root cause is a business rule nobody wrote down, stop and ask me. (3) Fix at the root, not where it hurts, and nothing else: anything else you find goes to a pending item. (4) Show me the test green, the characterization suite still green, and the regression of the neighbouring items. (5) If the fix changes a current behaviour that someone may depend on, say so explicitly: that is a decision for me, not a side effect. Close the paired normalization items of this module in the same block, each with its own evidence. The commit message says "proved red against [version]" and the test counts. Reply in my language.
这条待办项是一次纠正:[用客户的话写:什么出错了,以及怎么复现它]。在改动任何东西之前:(1)用一个从界面操作流程、并在后端验证效果的测试复现这次故障,并给我看它对着当前代码失败;那个红是你复现对了缺陷的证据。如果你复现不出来,告诉我你试过什么、你需要什么;不要去修你看不见的东西。(2)找出根因,并在修之前用散文连同证据告诉我。如果根因是一条没人写下来的业务规则,停下来问我。(3)在根上修,不要在疼的地方修,而且只修它:你发现的任何别的东西都进一条待办项。(4)给我看测试变绿、表征测试套件仍然是绿的,以及相邻待办项的回归。(5)如果这次修复改变了某个当前行为、而可能有人依赖它,明确说出来:那是我的一个决定,不是一个副作用。把这个模块里和它配对的规范化待办项在同一个功能块里关闭,每一条都带自己的证据。提交信息里写"对着[版本]先证红"以及测试的数字。用我的语言回复。
我在要求的东西:复现在诊断之前,因为一个模型会非常笃定地诊断它没有复现过的东西;根因在动手之前用散文说出来,好让你能叫停一次指错方向的修复;在根上修而且只修它,因为在遗留系统里"反正我都来了"是弄坏本来能跑的东西最常见的方式;表征测试套件作为整个系统的回归;还有第 5 条,它把行为的改变变成一个决定。还有配对:这个模块的债务现在就偿还,带着它的证据,不是"以后"。
一条演进型待办项是在已有的东西上加新东西。它用第 5 级台阶的提示词打开,用第 6 和第 8 级原样构建,唯一的区别是"相邻的回归"意味着什么:在这里相邻的就是整个系统,而表征测试套件就是那个说 "本来能跑的还在跑"的东西。还有两条规则。第一:当功能块进入某个模块时,那个模块里标着"触及时偿还"的规范化待办项跟着它一起进,带着各自的证据;这就是两种速度的规则。第二:新东西一出生就带着完整的方法,也就是说,它在待办项里有规格,有红色测试,有端到端测试,如果它作用于现实世界还有锁。不要对新模块做遗留系统当年对旧模块做的事。
安全带是一道硬闸门,而一道硬闸门需要它写下来的例外,因为如果我不写,那么在某天晚上十一点有东西塌了的时候,例外就会被当场发明出来。例外是这样的:如果生产环境已经宕了或者正在损坏数据,而安全带还没造完,那就做能止住损害的最小改动,带着手工的回退路径,并且告知所有者;而且当天 就开出那条纠正型待办项,用完整的例行来关闭它:复现它的红色测试、根因、回归。一次没有再走一遍循环的紧急修复,是一笔要连本带利收回来的债务。
发送提示词之前,你得把这些准备在手边:确认这真的是一次紧急情况(生产宕了或者正在损坏数据,而不是"客户很着急")、所有者已被告知,以及回头路(N0 的那份备份或者上一个版本)就在手边。
紧急情况:
This is an urgent fix on production: [what is happening]. Do the minimum that stops the damage, on a branch, with the rollback ready before you deploy, and tell me exactly what you changed and what you did not verify. Do not touch anything else. Then, today, open the corrective pending item in
docs/PENDING.mdwith what happened, and we close it with the full routine: the test that reproduces it in red, the root cause, the fix at the root, the characterization suite and the regression green. Reply in my language.
这是一次生产环境的紧急修复:[正在发生什么]。做能止住损害的最小改动,在一个分支上做,在部署之前把回退准备好,并且准确告诉我你改了什么、以及什么是你没有验证的。别的什么都不要碰。然后,就在今天,在 docs/PENDING.md 里开出那条纠正型待办项,写上发生了什么,我们用完整的例行来关闭它:复现它的红色测试、根因、在根上的修复、表征测试套件和回归都变绿。用我的语言回复。
我在要求的东西:最小改动,回退在前不在后,还有对未验证内容的诚实报告(第 12 级台阶);以及当天就必须重新走一遍循环的义务,那正是把一次例外和一个习惯区分开来的东西。
主文档里的第 16 到 19、21 和 22 级台阶(可观测性与可追溯性、持久化、漏洞、性能,以及两个监控)对两个使用场景都有效,而在遗留系统里它们有一个精确的位置,因为其中有几级的工作已经被规范化阶段做掉了一半:
在实践中,部署之前那四级是规范化的又一个阶段,在第一个演进功能块之前,或者和它一起,看请求有多急;而两个监控随部署搭起来。
永远不会完全规范化,而这样也挺好:标着"触及时偿还"的债务会在几个月里一个模块一个模块地偿还,标着"可以等待"的有时候永远不会偿还,而这件事要说出来。真正有一个精确时刻的,是这个阶段的收尾,也就是第一个功能块打开的时候。清单是这样的:
[推断 ] 节点(N2)。如果这八条都是"是",那么系统已经规范化到这条路所要求的程度,接下来就是一贯的梯子。主文档的阶段 3 和 4(上线生产和运维)原样适用:定期审计、手册(在遗留系统里往往不存在,用第 20 级台阶根据系统现状生成)、驻留代理,以及带数字的复盘,而复盘在遗留系统里多了一个它自己的问题:规范化花了多少成本,以及我们标为"可以等待"的债务里,有哪些最后还是照样付了代价。
假设