开发向专区
账漏了两个月,头一件事不是补账
先证底数、再找根因、止血打头,后面的单按依赖排。
一个跑了近两个月的账本系统,被查出一整条录入口自上线那天起就没接过账本——文档写着「已接」,代码从来没有。从证底数、找根因、止血,到后面四张单,前后三天,每一步的位置都有理由。这一页讲的不是账,是顺序:系统在跑、账在漏的时候,头一件事为什么不是补账。
这套顺序我们只在一个系统、一条线上跑过一次;没跑完的那一步,末尾如实写。
一、头一个反应不是修全,也不是补账
顺序错了,每多做一步都在扩大缺口
总纲那一页讲过这件事的一句话版本:一处入口的账本挂钩,文档先于代码写了「已接」,代码一直没接;当天出止血单、当天上线。这一页把那一句展开成方法。
系统在跑、账在漏,人的头一个反应通常有两种。一种是「先把缺的账补上」——看板一直是负的,补上就好看了。另一种是「一次修全」——既然发现了,把入口、字段、口径一口气改到位。
两种都错在同一个地方:漏口还开着。 补账的时候新账还在漏,补完的那一刻缺口又长出一截;一次修全要碰的东西多,签字多、部署链长,等它上线,又是几天的新漏。
所以顺序是固定的:先证底数,再找根因,止血单打头,后面的单按依赖排。 底数不证,你不知道自己在修什么;根因不找,止血会止在错的地方;止血不打头,后面每一步都在跟漏口赛跑。
二、先证底数:感觉不是底数
「看板一直是负的」只是一个感觉
「看板一直是负的」是感觉,不是底数。底数只有一个来源:对生产数据库只读实查一次。
实查之前先过一道闸:连的是哪个库。 本地有同名的开发库,连错了读出来的数字一样整齐、一样可信,只是全错。所以先读库的身份指纹——库名、几张关键表的行数——对上了再读数。
读出来的东西,比「漏了几单」更要紧的是形状:
- 有履约成本、没有对应收入的订阅,从账本上线那天起连成一片,不是零星几单;
- 这批单的收款路径字段全空——不是有的填了有的没填,是一个都没有;
- 成本侧基本齐,收入侧只剩零头。
这个形状指向的不是「几个人忘了填」,是「一个入口没接」。 人忘填会零星、会随人变;一整条从上线起就空,只可能是那条路本身没通。底数证完,问题从「怎么让大家记得填」变成了「哪条路没接」。
三、根因去读代码,不猜习惯
文档说它在,代码说它从来没有
要找哪条路没接,先问拍板人一句:开单走哪条路? 答案是「走新开服务那个弹窗」。
然后去读那条路的代码。读出来的是:那个弹窗提交到的接口,从头到尾没有调用过账本挂钩;而前端表单里,收款路径那两栏是送出去了的,旁边还带着一行注释——「后端会自动记账」。前端以为后端会做,后端从没做过。
这一步把判断改写了。 它不是客服习惯问题,也不是「把字段改成必填」就能解决的——字段改必填,填得再齐,后端照样丢。先补挂钩,别的都在后面。
再往前查一层:设计图和总说明里白纸黑字写着「挂钩点四处,含这个入口」。翻回头一次提交,代码里从来没有这一处——文档先于代码写了,代码没跟上,不是后来被删掉的。 两个月里,所有人按文档相信它在。
由此立一条规矩:文档与代码不符时,以代码为准;订正文档与止血放在同一个提交里。 分开提交,就会出现「代码已修、文档还错」或者反过来的窗口,下一个读文档的人照样被带偏。
四、止血单打头,越小越好
一单一件事,当天立单当天上线
止血单长什么样:一单一件事,改一个文件,约二十行——让那条入口开出的每一单自动进账本,并把收款路径落库。当天立单、当天施工、当天合并、当天傍晚部署生效。
止血生效的那个时刻,从此是一条分界线:之后开出的单都在账上,之前的都归回填。这条线不是为了好看,是后面回填单的范围上界——没有它,回填不知道该补到哪一刻为止。
止血不等回填,也不等口径改。回填要三方签字,口径改要动表结构、走含建表的部署链——两件都对,但等它们,就是多漏几天。止血单小到当天能上线,正是因为它什么都不等。
五、后序列按依赖排,不按想到的顺序排
每一张单都写明「为什么在这个位置」
止血之后还有四张单。它们的顺序不是谁先想到谁先做,是按依赖排的——每一张都要能答一句「为什么在这个位置」。
① 必填闸。 前端两个弹窗把收款路径改成必填,不给缺省值,逼每一单选一次。只改弹窗,不动后端。为什么在这里:止血堵的是「后端丢」,这一单堵的是「人不填」——后端已经接了,人工空值才成为下一个漏口。
② 回填。 把止血之前那一批没进账本的收入补进去。为什么必须在止血之后:回填范围的上界要锚在止血生效时刻,止血没生效,上界不存在,补到一半新账还在漏。做法上四条:先跑一遍「只读不写」的模拟,出一张清单;三方对汇总行(笔数、金额)签一次,不逐单签;脚本可以重复跑,重跑不重写;带基线闸——账本行数不等于预期就停手。不改铁规:账本只增不改,更正走红冲;回填分录落在回填当天的块里,原成交日写进摘要,不往过去的块里插。
③ 第二个活口。 批量续期那条路径同样从没接过账本挂钩——它是做回填单时撞出来的(下一节讲)。同形补上,也是一个文件、二十来行。为什么在这里:它是止血的同类项,晚一天就多漏一天,但它是在做②的过程中才知道的,所以排在②后面而不是①前面。这一单有一个拍点:账本记的是标价还是实收——拍「与订单同源,差异走红冲」,小单不做成大单。
④ 口径改。 可分配基数从「账本口径」改成「资金口径」:账本收入减成本算出来的净利,改成公账余额减掉待交付成本、再减运营底金。为什么排在末尾:它要新建一张表、改接口、改页面、走含建表的部署链,是五张单里分量重的那张;而且在前三张没做完之前,账本本身不可信,改口径改的是一个不可信的数。
六、未知的未知从实读里冒出来,序列是活的
登记进悬账,下一单接,当轮不顺手修
做回填单的时候要实读代码——回填脚本要知道每一单「已经入账多少」,才能算「还缺多少」。读到这里,撞出两处此前没人知道的:
头一处:续过期的单,开单时的那笔收入同样缺。 证底数那次是按「一笔收入都没有」圈的单;而续期路径是接了账本的,续过期的单账上有续期那笔,开单那笔照样没有——它们不在底数那个口径里。于是回填的口径从「补没有收入的单」改成「按缺口算:已付减已入账」,条数比底数略多,判据也跟着改:对底数口径的子计数对不上才停,总数对不上不停。
另一处:批量续期路径零挂钩。 这是上一节的③。
两处的处理方式一样:登记进当轮留痕的「新悬账」,当轮不顺手修,下一单接。 一单一件事,顺手修就是把回填单做成一张谁也审不动的大单。
序列是活的:每张单的实读都可能再冒出一张单。这不是计划没做好,是实读的正常产出——没读代码之前,你不可能知道代码里还有什么。
七、每张单长什么样
没有「代码实况」段的单不许动工
五张单一个模子,读者能照抄的部分是这几段:
- 代码实况:实读的是主干哪个提交、哪几行、现状是什么。这一段是防「文档先于代码」再犯的闸——单里写的每一句「现在是这样」,都要能指到一行代码。
- 拍点表:每个待定项列选项、推荐、代价;默认按推荐推进,拍板人改字即改。
- 解决什么:三列——问题、现状证据、本单之后。
- 范围表:改哪几个文件,明确不改哪些——不改的那一栏和改的一样重要。
- 判据:逐条贴实跑输出,不写「已确认」。
- 部署链:有没有表结构变动、要不要构建、重启哪个进程——写明,不靠执行的人猜。
没有「代码实况」段的单不许动工。 这一段就是那道闸:写单的人先去读,读完写下来,动工的人对着它核。文档先于代码的失实,就是在这一段被拦住的。
八、边界如实
已实跑:一条入口,从证底数到口径改,五张单立在三天之内;其中止血、必填闸、第二个活口、口径改四张已合并上线;上一节说的两处「未知的未知」,都是这三天里真实撞出来的。
在途:存量回填那张单,脚本与合并请求已就位,模拟清单与三方签字排在后面几天——写这一页的时候它还没跑完,所以这里写「待三方签」,不写「已完成」。 本页第七节自己讲的规矩,先用在自己身上。
未实跑:这套顺序只在一个系统、一条线上跑过一次。它能不能迁到别的系统、别的漏法上,我们没有第二次证据。