Urazuke — 演进-纠正之路:把一个不是用方法诞生的系统规范化,然后再登梯子

作者: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 收尾,是因为它把前面所有东西汇进一条队列。

规范化的顺序

没有期限:每个阶段要花多久由系统来定,唯一固定的是顺序。

阶段站点还会发生什么
1N0、N1、N2、N3和所有者一起阅读系统,确认节点、决定不一致。什么都不改。阶段收尾:恢复出来的地图和债务清单。
2N4、N5安全带:带数据的测试环境、部署链跑两遍、密钥轮换、关键流程的表征测试套件。对已经作用于现实世界的东西确认锁。周末:系统在测试环境里跑起来,测试套件是绿的。
3N6、N7,以及第一个功能块测量出来的容量估算。排好序的待办队列。用第 5 级台阶开第一个功能块,纠正型或演进型,带上配对的规范化待办项。从这里开始就是一贯的循环了。

如果系统很大,长大的是第 1 阶段(要读的更多)和第 2 阶段的表征测试套件(流程更多)。不会长大的是 N7:一份没法一口气写完的规范化待办队列,是一份没人会去维护的待办队列。


N0 — 既有系统的适格性:一个模型能够验证的那些前提条件

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • docs/INITIAL_SURVEY.md,第一天就能写出来的那个简短版本:按业务的说法,系统做什么、给谁用;今天哪里疼(引出这次请求的那个 bug、缺的东西);谁做的,他还在不在;它在哪里运行;谁有什么访问权;有没有备份、谁在做。半页,故意不完整:到了 N2,代理会从系统里把它补全。
  • docs/ELIGIBILITY_CRITERIA.md:第 0 级台阶的卡片(X1、X2 和那些加权准则),再加上面那七条遗留系统的前提条件,每一条带三列空白:绿或红、证据(代理做了什么来验证它),以及如果是红的,那个前置项目。
  • 给代理的仓库读访问权、一份数据副本(或者生产环境的只读访问权)的访问权,以及一个让它可以尝试把系统搭起来的地方,凭证不要放在聊天里。没有这些,前提条件 1、5 和 6 没法验证,只能算红。
  • 最低可接受状态:代理能克隆仓库,而且有一个地方可以尝试把系统跑起来。

初始:

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.md and the criteria sheet in docs/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, write docs/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.

我在要求的东西:那些前提条件要靠做来验证(搭起来、读、写出那份文档、跑一个测试),因为一个前沿模型这四件事都能做,于是适格性就不再是一种看法;功能恢复文档,它是"代码可上手"最有力的证明,而且它还是 N2 将要变成地图的那份东西的第一稿;以及每一条红的都要带上能把它变绿的那个项目,因为一条缺失的前提条件不是一次拒绝,而是一件有它自己范围的前置工作。

发送提示词之前(收尾提示词):你的决定,一行话,在读完带着每条前提条件证据的整张卡片之后做出;以及,对每一条红的,你决定那个前置项目是现在做、以后做,还是永远不做(那样这个系统就不适格)。

收尾:

Decision: [go / go after these prerequisite projects: … / no-go]. Write the final sheet to docs/ELIGIBILITY.md with the date, who decided, each prerequisite with its colour and its evidence, and, for every red one, the prerequisite project as a pending item in docs/PENDING.md marked as blocking, with its own acceptance criterion: the prerequisite verified green. Include the questions you asked and my answers.

完整性检验:每条前提条件都有代理做了某件事的证据,而不是你给出的一个回答吗? docs/FUNCTIONALITY_RECOVERED.md 存在吗,而且你以所有者的身份读过它:它描述的是他以为自己拥有的那个系统吗?它跑过一条流程的真实端到端测试吗?每一条红的都有带出口标准的前置项目吗?警报信号:对一个没人看得懂的系统,一个下午就全绿了。代理是在回答,而不是在做。


N1 — 遗留系统的协作契约

发送提示词之前,你得把这些准备在手边:

  • 你的工作偏好,写成一张短清单,和第 1 级台阶一样:语言、你希望它多问还是少问、你给它多大的自主权、你不能容忍什么。
  • 你自己对规范化期间可接受风险水平的一个决定:代理能不能自己部署到测试环境(部署到生产环境永远不行,直到 N4 关闭且第 15 级台阶登上去)。
  • docs/ELIGIBILITY.md(N0),已关闭。
  • 最低可接受状态:你答得上第 1 级台阶的那些问题,再加上风险那一条。

初始:

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.md with 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.

我在要求的东西:一贯的六条规则,再加五条,它们之所以存在,是因为一个模型面对别人的代码会条件反射地做三件事:重排它,在它觉得不对的地方"修"它,以及当代码的行为和它预期的不一样时就认定那是个错误。规则 7、9 和 10 恰好挡住这三个反射。规则 8 把阅读变成地图,这样代理从某个模块里学到的东西不会在会话关闭时丢掉。规则 11 是把安全带写成规则,好让它在测试套件存在之前就先存在。

收尾: 第 1 级台阶的收尾提示词,不变:你带着理由接受或拒绝那些提案,契约留下日期和版本。

警报信号:代理提出一条规则叫"随着触及逐步把代码现代化"。那条规则就是伪装起来的重写;带着写下来的理由拒绝它。


N2 — 阅读系统:恢复出来的地图和那些不一致

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • 给代理一切能读的东西的读访问权:整个仓库(所有分支,不只是主分支)、数据库结构(一个只读凭证,放在聊天之外)、每个环境的配置(把密钥值遮住的环境文件,或者只列变量名不列值的清单)、最近的日志(如果有),以及任何文档,哪怕只是一封旧邮件或一份过时的手册。
  • N0 的 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.md in 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, and docs/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. Create docs/STATUS.md for the living state. Reply in my language.

我在要求的东西:要它读完一切并声明读不到什么(完整性条款在遗留系统里的形态:缺口要标出来,不要填上);要调研和树里的每条断言都带来源标记,因为那个标记正是让你不读代码也能验证的东西;要流程和状态是代码实际实现的那些,而不是本该是的那些,因为当前行为就是规格(契约的规则 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.md and docs/SEMANTIC_TREE.md with today's date, keeping the source tags. Write each discrepancy decision in docs/DECISIONS.md with my exact words, and open a pending item in docs/PENDING.md for 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 to PROJECT.md the instruction to read this file, the tree and the working agreement at the start of every session.

我在要求的东西:每条不一致的决定都落在一贯的三个地方(决策记录、要改的话就落在待办项、确认的话就落在树上),而且来源标记在关闭时不要被抹掉:一棵丢了标记的树,是一棵再也无法验证的树。

完整性检验:像所有者那样走一遍树:有没有某个实体状态是业务从来没用过的?如果它标着 [代码],那系统里就有它,哪怕没人用,而这就是一条不一致。树里有没有定时任务?如果一个都没有,去问代理它在哪里找过。每条不一致都有带日期的决定吗?第 2 级台阶的阅读测试过了吗(一个新会话知道系统 不做什么)?警报信号:树里没有任何东西标着 [推断](代理没有在区分它读到的和它猜到的);不一致清单是空的(在遗留系统里它从来不是空的:代理比错了,或者根本没比);项目文件因为"要记录的东西太多"而超过三百行(那些东西该进树,而发生过的事该进活状态 Ref. 41)。


N3 — 作为债务清单的审计

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • 一个代理的新会话,如果你的工具支持的话,用一个干净的克隆和一份写着"你是审计者;你没读过、也不会去动这个系统"的指令文件,好让它不共享 N2 那次会话的预设。如果做不到,就用同一个会话,但你要心里有数。
  • 对系统运行环境(数据库、日志、配置)的只读访问权,只读凭证放在聊天之外。
  • N2 恢复出来的地图(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 read docs/SEMANTIC_TREE.md and docs/DISCREPANCIES.md first 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.

我在要求的东西:第 13 级台阶的审计,加上"可部署性"这个维度(因为在遗留系统里部署往往是第一个问题),再加上速度字段,而这正是把一份报告变成一份计划的东西:没有那个字段,一个审计过的遗留系统有四十条发现,没人知道从哪条开始。什么算阻塞由我来给标准,不是审计者,因为那取决于业务的后果。

收尾: 第 13 级台阶的收尾提示词,发给构建者,加一句:它开出的待办项要带上对应发现的速度标记。

检验:报告有没有说它没能审计什么?有没有至少一条标着"阻塞"的发现?(在遗留系统里总会有;如果没有,审计者就没看密钥也没看部署。)随便挑两条发现,它们有文件、行和可复现的证据吗?警报信号:全都在"可以等待"里。那是一个不想打扰人的审计者。


N4 — 安全带

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • docs/ENVIRONMENTS_SURVEY.md,代理在 N2 带标记起草、由你补全的那份:今天它在哪里运行、今天怎么部署(哪怕只是"我用 FTP 把文件复制上去")、密钥将来住在服务器的什么地方、谁有访问权,以及测试环境将在哪里运行(另一台服务器、一个容器、一个云账号;如果你什么都没有,就说出来,代理会提出最简单的方案)。提示词里管它叫 "the environments survey";告诉它它在哪儿。
  • 代理对生产服务器的只读访问权(用来复制数据库和配置),以及对测试环境运行位置的写权限,凭证放在聊天之外。
  • 你对测试数据匿名化的决定:哪些字段是个人数据(姓名、电话、邮箱、证件号),以及它们怎么替换。这由你决定,因为这是法律义务,不是技术偏好。
  • 给表征测试用的关键流程清单,由你根据语义树里的流程写出来:那些一旦被无意改掉就会让业务赔钱或失去信任的流程。小系统四到八个。不要"全部":全部等于没有。
  • docs/AUDIT_[日期].md(N3),好让代理知道它找到了哪些密钥、哪些界面不能操作。
  • 一份至少恢复过一次的生产备份,哪怕是在你自己的机器上恢复的,在代理碰服务器之前。这是 N0 那条到期日落在这里的条件。
  • 最低可接受状态:你知道测试环境将在哪里运行,有关键流程清单,决定了匿名化方案,而且恢复过一次备份。

环境、部署与密钥:

We are going to build the safety belt around this system before changing anything in it. Read the environments survey, docs/AUDIT_[date].md and docs/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.md with 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 in docs/DEPLOYMENT.md so 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.

我在要求的东西:第 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.md with the evidence. Record the command to run the suite in docs/STATUS.md, and make the suite the gate for the test environment as docs/GATES.md says. Reply in my language.

我在要求的东西:反过来的第 8 级台阶。在新项目里,测试诞生时是红的,改动让它变绿;在这里,测试诞生时是绿的,因为它捕捉的是现有的东西,而它有用的证据是当你故意弄坏流程时它会变红。没有那次演示过的变异,一个表征测试就是一个因为什么都没看着所以才通过的测试,这和那种藏着 bug 的绿色是同一个问题 Ref. 44。还有两条纪律条款:操作不了的东西要声明出来并且阻塞,看起来不对的东西记为不一致、不去修,因为在所有者做决定之前,当前行为就是规格。

完整性检验:测试环境的网址是你自己打开的、而且你看到系统带着数据吗?你在那些数据里搜过一个真名、而且搜不到吗?部署跑了两遍,第二遍什么都没变吗?你要求过在仓库里搜密钥、而且搜出来是空的吗?清单上的每个密钥你都轮换了吗?每个关键流程都有自己的测试,而且每个测试的变异红都摆在眼前吗?警报信号:表征测试套件"一次就"全通过了,而且从来没展示过一次红色;测试环境跑"在代理的机器上",而不是跑在一个你能打开的地方;测试数据是"几条示例记录",而不是一份匿名化副本(遗留系统的 bug 住在真实数据里,不住在示例数据里)。


N5 — 从代码里找出锁和生产者-消费者地图

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • 和所有者一起,写一份清单,列出业务知道系统对外做了什么:收什么钱、发什么、通知谁、删什么、什么会自己跑。它放进 docs/INITIAL_SURVEY.md 的一个小节。代理会在代码里找到比业务记得的更多的东西;两份清单的比对就是结果。
  • 代理对服务器定时任务(计划任务清单或者你那个平台的等价物)的访问权,因为自动发送往往住在那里,而不在代码里。
  • 住在仓库之外的东西的清单(N2),给地图用。
  • N4 的测试环境跑着,好让它能触发一个动作、看看会发生什么,而不产生真实后果。
  • 最低可接受状态:业务写好了他们那份清单,而且代理能看到定时任务。

初始:

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.md with every statement tagged by its source. Compare it with the list the business wrote in docs/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, build docs/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.

我在要求的东西:把第 14 和第 11 级台阶的盘点放在一遍里做,因为它们走的是同一份代码;业务记得的和代码做的之间的比对,而那正是"没人记得的那些发送"冒出来的地方;还有每把锁的现在还是触及时提案,那就是两种速度的规则应用在保护上:一把管着收钱动作的锁现在就上;一把管着月度导出的锁,可以等到触及那个模块的功能块。

收尾: 第 14 级台阶的收尾提示词,带上标为"现在"的那些锁,每一把都带着它对着没有锁的版本的红色测试;其余的留在待办队列里,带着它们的标记。

警报信号:代理找到的动作和业务列出来的一模一样。在遗留系统里这从来不会发生;它漏看了定时任务或者数据库触发器。


N6 — 用真实数字做容量估算

发送提示词之前,你得把这些准备在手边:

  • 对生产数据库的只读访问权(或者 N4 那份完整副本,如果匿名化没有动过日期和数量的话),以及对访问日志或应用日志的访问权,凭证放在聊天之外。
  • 两个只有业务才有的答案,写下来:他们预期未来两年增长多少,以及这个系统会不会用在不止一个地方("乘以十")。
  • 给每把量具的测试用的一个已知案例:比如某张小表的记录数,你可以手工数出来。
  • 最低可接受状态:代理能查询数据库,而且你有那两个答案。

初始:

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.md with 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 write docs/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.

我在要求的东西:测量而不是提问,这是遗留系统给得起而新项目给不起的;第 7 级台阶的带测试量具和双向审计,因为量具会撒谎 Ref. 35、 Ref. 36;还有把容量估算当成演进之前的闸门 Ref. 14:如果当前架构撑不住"乘以十",而客户的请求恰恰就是增长,这件事现在就要知道,而不是等到第三个功能块。

收尾: 第 3 级台阶的收尾提示词,在它为撑不住的部分提出的那些方案里做选择,或者确认现状撑得住。

警报信号:一个"大约"的数字,却没有标明它是大约。如果它在数据库里,就去测;如果不在,就去问;没有第三个选项。


N7 — 和客户请求配对的规范化待办队列

发送提示词之前,这些东西必须存在,而且是你(或者业务和你一起)来做,不是代理:

  • 客户对这一阶段的请求,用散文、用他自己的话写,分成两份清单:今天出错的(每一条附上怎么复现、会发生什么、按业务的说法本该发生什么)和缺少的(新东西)。它放在 docs/blocks/STAGE_01_REQUEST.md 或者你喜欢的地方;提示词里说的是 "the client's request for this stage",所以要告诉它它在哪儿。如果所有者给了优先级,也一起写上。
  • N2 到 N6 的全部产出物都已关闭:树、审计、决定过的不一致、锁、容量估算。如果缺了任何一个,先别发这条提示词:待办队列会出得不完整,而你不会知道它缺了什么。
  • 已经存在的那些阻塞待办项(来自 N0、N3 和 N4)已关闭,或者在进行中并有日期。
  • 最低可接受状态:客户的请求已经用他的话分成两份清单,而且前面各站点的产出物都存在。

初始:

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 in docs/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.

我在要求的东西:完整的第 5 级台阶(详细规格住在每一个待办项里,规范化的那些也一样),加上让这条路之所以成立的两个标记:速度,它说每一笔债务什么时候偿还;类型,它说每一项怎么关闭(一个纠正型用复现故障的测试关闭;一个演进型用第 8 级台阶的测试关闭)。还有按模块配对,那是把 "触及时偿还"这条规则做成可操作的:当我开"定金"这个功能块时,支付模块的规范化待办项跟着它一起进,不早也不晚。

收尾: 第 5 级台阶里每次会话的例行,不变,写进协作契约。

完整性检验:还有"阻塞"开着吗?那第一个功能块就不能开。每个纠正型都说明了怎么复现、而且用的是客户的话吗?每个客户待办项都有自己的配对清单吗,哪怕是空的?警报信号:待办队列里有超过五十条 "可以等待"的规范化待办项。那不是登记在册的债务:那是伪装成清单的重写。把永远不会偿还的那些归档掉,并且把这件事说出来。


对遗留系统跑循环:纠正模式与演进模式

N7 结束之后,这条路和梯子汇合:第 5 到 10 级台阶随每条待办项登上去,和新项目一样,同样的契约、同样的提示词。但有两个细微之处值得单开一节,因为它们正是"有方法地动遗留系统"和"小心地动遗留系统"之间的区别。

纠正模式:红是自然的

一条纠正型待办项是一个 bug:今天出错的东西,而客户知道。在新项目的第 8 级台阶里,红是制造出来的(写测试、对着旧代码跑、看它失败)。在纠正模式里,红就是那个 bug 本身:那个复现故障的测试,像报告它的那个用户一样从界面操作流程,必须对着当前代码失败。如果它不失败,你复现的就不是别人报给你的那个 bug;你复现的是另一个。那个红是你在修对东西的证据,而且比代理关于"应该是在发生什么"的任何解释都更有价值。那个走了四轮才找到根因的案例 Ref. 22,就是它教会我:没有写下来的复现,每一轮修的都是另一个症状。

发送提示词之前,你得把这些准备在手边:

  • docs/PENDING.md(N7)里那条纠正型待办项,带着用客户的话写的复现方式:他做了什么、发生了什么、他期待什么。如果客户不知道怎么复现,先去拿到一个案例(一条记录、一个日期、一张截图);没有复现就没有测试,没有测试就没有关闭。
  • N4 的表征测试套件在测试环境里是绿的,命令写在 docs/STATUS.md 里。
  • 和这条待办项配对的那些规范化待办项,列在它里面。
  • 你愿意做决定的准备:如果根因原来是一条没人写下来的业务规则(这经常发生),代理会停下来提问,而那天必须有人回答。
  • 最低可接受状态:你知道怎么在测试环境里手工复现这个 bug。

关闭一条纠正型待办项:

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.

我在要求的东西:复现在诊断之前,因为一个模型会非常笃定地诊断它没有复现过的东西;根因在动手之前用散文说出来,好让你能叫停一次指错方向的修复;在根上修而且只修它,因为在遗留系统里"反正我都来了"是弄坏本来能跑的东西最常见的方式;表征测试套件作为整个系统的回归;还有第 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.md with 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.

我在要求的东西:最小改动,回退在前不在后,还有对未验证内容的诚实报告(第 12 级台阶);以及当天就必须重新走一遍循环的义务,那正是把一次例外和一个习惯区分开来的东西。

遗留系统里的运维质量六级台阶(16 到 19、21 和 22)

主文档里的第 16 到 19、21 和 22 级台阶(可观测性与可追溯性、持久化、漏洞、性能,以及两个监控)对两个使用场景都有效,而在遗留系统里它们有一个精确的位置,因为其中有几级的工作已经被规范化阶段做掉了一半:

在实践中,部署之前那四级是规范化的又一个阶段,在第一个演进功能块之前,或者和它一起,看请求有多急;而两个监控随部署搭起来。

系统什么时候算规范化了

永远不会完全规范化,而这样也挺好:标着"触及时偿还"的债务会在几个月里一个模块一个模块地偿还,标着"可以等待"的有时候永远不会偿还,而这件事要说出来。真正有一个精确时刻的,是这个阶段的收尾,也就是第一个功能块打开的时候。清单是这样的:

  1. 恢复出来的输入物一览表里的全部输入物都存在,带来源标记,而且所有者确认了树里每一个 [推断 ] 节点(N2)。
  2. 每条不一致都有带日期的决定,要改系统的那些都有待办项(N2)。
  3. 审计存在,而且每条发现都有速度标记(N3)。
  4. 安全带是绿的:带匿名化数据的测试环境由你亲手打开过;部署跑了两遍;搜密钥搜出来是空的,而且找到的都轮换了;持续集成是绿的;表征测试套件是绿的,而且每个关键流程的变异红都演示过(N4)。
  5. 每个有后果的动作都有裁决,而且标为"现在"的锁都有它们的红色测试(N5)。
  6. 原始数字都用带测试的量具测过,而且"乘以十"写下来了(N6)。
  7. 没有任何标着"阻塞"的待办项还开着,而且每个客户待办项都有它的配对项(N7)。
  8. 协作契约里有遗留系统的那五条规则,而且一个新会话能通过阅读测试(N1、N2)。

如果这八条都是"是",那么系统已经规范化到这条路所要求的程度,接下来就是一贯的梯子。主文档的阶段 3 和 4(上线生产和运维)原样适用:定期审计、手册(在遗留系统里往往不存在,用第 20 级台阶根据系统现状生成)、驻留代理,以及带数字的复盘,而复盘在遗留系统里多了一个它自己的问题:规范化花了多少成本,以及我们标为"可以等待"的债务里,有哪些最后还是照样付了代价。

整条路的警报信号

尚待展开的 假设

  1. N0 的那七条前提条件是靠做来验证的,而这让它们可被校准:每一个走过 N0 的遗留系统都会留下一张带证据的卡片。缺的是案例:有多少遗留系统在每条前提条件上失败,以及把它变绿的那个前置项目要花多少成本。这已经不是一个关于判断准则的假设了;它是一笔我还没有的账。
  2. 表征测试套件的规模:四到八个流程是我在小系统上行之有效的;在大系统上,我不知道标准该是按流程、按模块,还是按涉及的金额。
  3. 规范化在一个大系统里怎么分配:我猜阅读(N2)整体做一次,而安全带(N4)按模块来搭,每个模块在它将要被触及的时候搭,于是那道硬闸门从按系统变成按模块。我没有试过。
  4. 一个由模型用半套方法构建出来的遗留系统(有契约,没有测试)会怎么样:这条路是整条走完,还是有捷径。我的直觉是没有捷径,而捷径恰恰就是那个错误,但这只是直觉。