第 20 章 代码质量与协作
副题:轨道、把关、账本——1 个人写得动,8 个人改得动
第 19 章结尾的问题:"每道防御都要有人维护,谁来维护?"答案来了:团队从 1 人变成 8 人(2 前端/3 后端/1 测试/1 运维/1 产品,五阶段故事线的阶段 4)。但人来了,问题也来了:合并冲突、互相踩代码、没人敢重构——一个人写得动,三个人改不动。本章回答:协作的质量怎么守住——Git 工作流(轨道)、Code Review(把关)、技术债管理(账本)。
第四部分地图(最后一站):16 性能管"快"、17 交易管"对"、18 测试管"稳"、19 安全管"防"、本章(20)管"久"——前面四件事(快/对/稳/防)都是"一次做对",本章管"长期不改坏":质量不是某一天的事,是每一天的协作(第 19 章说"安全是持续过程",质量也是)。
本章要建立的认知
- 协作需要基础设施:轨道(Git 工作流)、把关(Code Review)、账本(技术债)——三件套让多人协作"有轨可循";
- 流程是团队的函数:1 人不需要 CR,8 人必须——流程的严与松跟着团队规模走;
- 技术债是快交付的利息:积累是必然,偿还靠机制(重构+测试兜底)——第 18 章的测试,在这里成为重构的胆量来源。
20.1 三个人改不动
阶段 4 的第一周,三起协作事故。
先交代团队扩张的背景(第 19 章钩子的兑现):第 19 章结尾问"每道防线都要有人维护,谁来维护?"——答案是招人:2 前端/3 后端/1 测试/1 运维/1 产品(五阶段故事线的阶段 4 编制)。人来了,防线确实有人维护了(安全审查有人专门过表、测试有人专职写)——但人带来的新问题比解决的问题还先到:新人要熟悉代码(第 12 章的分层是他们认识代码的地图)、大家要同时改同一份代码、发布要协调——从 1 人到 8 人,代码质量的问题从"写得好不好"变成"协作得好不好"。
**事故一:合并冲突,互相踩。**小明和后端同事同时改 contentService.js——小明加"内容举报"功能,同事修"列表排序"的 bug。两人各自改完,合并时冲突了:git 提示"同一段代码两边都改了"。小明解决冲突时,把自己记得的改动留下了,把同事那段改动覆盖了——同事的 bug 修复上线后"又回来了",线上列表排序又乱了。
事故一的细节也补完(它是 Git 工作流的入门课):冲突发生时,git 把两边的内容都标出来(<<<<<<< 和 >>>>>>> 之间的区域)让人选择——小明看到两段代码,一段是自己加的举报逻辑(他认识),一段是同事的排序改动(他不认识)——他"保留自己认识的",把不认识的那段删了——覆盖不是恶意,是不认识——这给解决冲突立了一条规则:不认识对方改动时,先找到对方问清楚,而不是按"我认识的留下"猜(猜错的代价是第 18 章测试都不一定拦得住:功能测试没覆盖那个排序场景)。
**事故二:没人敢重构。**代码库里有一段"能跑但很乱"的代码(第 12 章一坨代码的残骸)——新同事提议重构,被大家否决:"万一改坏了怎么办?"——没有测试兜底(第 18 章说过:没有测试的重构是重写),谁都不敢动——于是这段代码继续乱着,每次改功能都在它上面打补丁,越来越乱。第 4 章说过"技术债堆积(返工和蔓延的代码没人愿意重构)"——现在它成了现实。
事故二还有一个"人"的维度(它是 1→8 人特有的):代码的主人变了——1 人时"这段代码是我写的,我知道怎么改";8 人时"这段代码不知道谁写的,没人敢碰"——代码从"我的"变成"我们的",责任也从"个人记得"变成"机制保证"(第 12 章说分层是团队协作的地图——机制的第一块,就是让代码"有主":模块有负责人、改动有记录)。
**事故三:发布互相踩。**团队 8 人,发布还靠"谁改完谁部署"——两个同事同一天先后上线,后上线的把先上线的覆盖了:先上线的功能"消失"了,用户报障才知道。发布没有轨道(第 21-23 章会建起来),今天的协作问题里先记它一笔。
事故三的后续也交代一句(它是第 21-23 章的开场预告):这次事故后,小明在周会上说了一句"我们得有个发布流程"——这句话就是第五部分(构建/CI/CD/部署)的起点——第 20 章解决"代码怎么协作",第 21-23 章解决"代码怎么上线"——两个问题挨着发生,两套流程挨着建(第 2 章演进循环:事故逼出流程,流程解决事故)。
发布流程建起来之前,团队先用了两个临时手段(它们是第 23 章的过渡形态):发布清单(第 15 章联调清单的发布版:这次上线改了什么、影响哪些接口、回滚方案是什么);发布窗口(约定"每天下午 4 点前合入,4 点后不再发布"——把"随时踩"变成"集中发")——临时手段的定位:撑到正式流程建好(第 4 章复杂度预算:过渡方案要便宜,不必投资过多)。
三起事故的共同点:不是代码质量问题,是协作质量问题——代码各自写都没问题,合起来就出问题(第 15 章联调说过的"各自能跑连起来不通",在团队层面重演)。协作的问题,靠协作的基础设施解决。
三起事故的优先级也排一下(它是本章结构的来源):事故一(互相踩)最痛——代码直接错;事故二(没人敢改)最隐蔽——质量慢慢烂;事故三(发布踩)最急——用户直接受害——本章先解决一和二(Git 工作流+评审+技术债),三交给第 21-23 章(发布是第五部分的主场)——问题排优先级,和需求排优先级是同一个动作(第 4 章)。
三件套与前面章节的收束关系也先点一句(它是本章与全书的地图关系):轨道(Git)管的是第 12 章之后的代码流动;把关(评审)用第 12/18/19 章的三张单;账本(技术债)记的是第 4/16/17/19 章欠下的债——协作章不是新知识的章,是把前面章节的"质量约定"变成团队制度的章(第 18 章说"测试是承诺的代码形态",本章说"协作制度是承诺的团队形态")。
协作的第四块基石:知识传递(三件套之外,一句点明):轨道管代码、把关管质量、账本管债——还有"知识"要管:新人怎么知道"为什么这么设计"(第 5 章的决策记录)、"这段代码为什么这样写"(commit message + PR 描述 + 注释)——知识是协作的隐性基础设施:代码会流动,知识要跟着流动(评审教学/文档/决策记录——第 27 章迭代时,知识在不在决定"能不能改")。
20.2 协作的基础设施:轨道、把关、账本
"大家小心一点"不是协作的解法(第 17 章说过:仔细不是解决方案,机制才是——协作也是)。协作的基础设施三件套:
**轨道:Git 工作流。**代码的"路"——分支怎么开、怎么合、谁有合入权。没有轨道,大家在同一份代码上乱改(事故一);有轨道,每个人在自己的分支上干活,合入走统一入口——轨道解决"同时改"的问题。
**把关:Code Review。**代码合入前的检查——另一个人读你的代码,确认"没改坏、没漏测、没埋雷"(第 18 章说过的评审检查单在这里上岗)。没有把关,问题合入后靠线上发现(第 19 章"敌人先发现"的教训);有关把,问题在合入前被拦下——把关解决"改坏了没人发现"的问题。
**账本:技术债管理。**代码质量不是"一次到位"的——为了赶发布,有些代码写得急(事故二的残骸),这些"欠的账"要有记录、有偿还计划。没有账本,债越积越多(第 4 章说过"需求分析欠的债,最后都在代码质量上还");有账本,债是可见的、可还的——账本解决"质量怎么下滑的没人知道"的问题。
三件套的名字也呼应一下本书的地图(第 1 章的三条线):轨道管"技术线"(代码怎么流动)、把关管"质量线"(第 18/19 章的质量约定怎么执行)、账本管"业务线"(需求变化留下的债怎么记)——协作三件套,是三条线在团队层面的交汇。
三件套的关系一句话:轨道管"代码怎么流动",把关管"流动时怎么检查",账本管"检查不过关的债怎么还"——协作的质量,是这三件事的日常。
"日常"两个字展开一下(它是协作章最容易忽略的部分):三件套不是"建完就完"的——轨道要维护(分支命名约定、合流节奏)、把关要执行(每次 PR 都看,不是"重要 PR 才看")、账本要更新(每次妥协都记,不是"想起来才记")——协作基础设施的价值在"持续使用",不在"建设完成"(第 16 章说"缓存上线不是终点,命中率是每天的体检"——协作流程也是:建完是起点,日常是全部)。
20.3 Git 工作流:代码的轨道
Git 是版本管理工具,但协作要的不是"能存版本",是**"怎么流动"的约定**——这就是工作流。先看 Git 的机制(它是工作流的地基):
Git 的本质(一句话):分布式版本管理——每个开发者本地都有一份完整的历史,改动先提交到本地,再推送到共享仓库(远程)。协作的入口是共享仓库的分支:每个人在自己分支上改,改完合并回主分支。
Git 的心智模型(不教命令,教"它是什么",命令跟着心智走):提交(commit)= 一份快照 + 一段说明——每次提交记录"这一刻代码长什么样",说明(commit message)告诉未来的人"这一刻为什么长这样";分支 = 一条独立的时间线——从主时间线的某个点分出去,各走各的,最后合并回来——分支是 Git 让"同时改"成为可能的关键(没有分支,8 个人只能排队改);合并 = 把两条时间线接回一条——大多数合并是自动的(两边改的地方不重叠),只有重叠处需要人决定(冲突)。
分支命名约定(它是"轨道"的标识系统,一句话):feature/举报功能、fix/列表排序——分支名说出意图(和 commit message 一个纪律):分支名是 PR 的标题、是评审的第一行、是将来 git 历史里找"这个功能哪来的"的索引(第 6 章命名纪律的分支版)。
合并冲突的机制(事故一的根源,看懂它才能防它):git 合并两个分支时,如果同一段代码被两边改了,git 不知道该留哪边,就停下来让人解决——冲突不是 bug,是"两个人都改了同一处"的信号——解决冲突时的规则(它是事故一的教训):不确定对方改了什么,先问,别猜——覆盖对方改动是冲突事故最常见的形态(第 19 章说"按攻击面修不按事故现场修",这里说"解决冲突先问不猜")。
工作流解决"同时改"的三个手段:
- 分支隔离:每个人在独立分支上干活,互不干扰——事故一的"同时改同一个文件"变成"各自分支各自改",冲突只在合并那一刻发生(集中解决,而不是散落在代码里);
- 小步合入:分支不要活太久(一天到几天),改完就合并——分支活得越久,冲突越大(两边都改了很多,合并时一堆冲突);小步合入把"大冲突"变成"小冲突";
- 统一入口:合并不是"谁想合就合",走PR(Pull Request)——推送分支 + 发起合并请求 + 有人评审 + 通过才合——入口处把关,就是 20.5 的 Code Review。
冲突的预防(比"会解决冲突"更值钱):改之前先同步(拉最新主干再开分支——第 15 章"先同步再干活"的 Git 版);改的时候大声说(在团队频道里说"我在改 contentService,你们别动"——协调是同步的,Git 是异步的,先同步后异步);小步提交(改一点提交一点,冲突时好定位)——冲突的预防靠的是"少重叠",不是"会解决"(重叠少=冲突少=踩代码少)。
合并的两种方式(一句定位,够用):merge(保留两条时间线的合并记录——历史完整但会有"合并节点",适合记录"这条功能从哪来的");rebase(把分支的提交"重新接到"主分支顶端——历史线性干净,但改写了提交历史,多人共用的分支不要 rebase)——团队约定一种即可(本章的 trunk-based 用 merge,简单),两种方式吵起来的团队不少,其实选哪种都行,不选才是问题(第 5 章说过:"不上"也是决策——不选工作流,等于默认接受混乱)。
提交信息的纪律(它决定 Git 历史有没有用):commit message 是写给未来的人看的(第 25 章 git blame 排障时,第一个看的就是它)——"修复列表排序"比 "fix" 有用一百倍——写不清 message 的提交,等于给未来的人留了一个谜(第 6 章命名纪律的提交版)。PR 描述同理:PR 是"这次改动为什么存在"的文档——改了什么、为什么改、怎么测的——评审人(和三个月后的自己)靠它理解这次改动(第 20.5 评审的"教学心态",描述是教学的第一页)。
回滚的代码侧(第 23 章发布回滚的伏笔):Git 让"回到上一个版本"成为一条命令(git revert——生成一个"撤销这次改动"的新提交,而不是抹掉历史)——第 18 章事故处置的"先回滚",在代码层就是这条命令(第 23 章部署会把回滚变成发布流程的一部分)。
20.4 工作流选型:git flow vs trunk-based
工作流不是只有一种——两个主流模型,选型走一遍九步(第 5 章模型的第 13 次应用):
候选:git flow(多分支:master + develop + feature + release/hotfix——发布节奏慢的项目常用,适合"版本发布制":固定版本号、发版窗口);trunk-based(主干开发:所有人在主干或短命分支上干活,频繁合并——发布节奏快的项目常用,适合"持续发布制":随时可发布)。
git flow 的分支矩阵先看懂(它是"版本发布制"的工程形态):master(正式发布的历史)、develop(开发主线)、feature(功能分支,从 develop 分出)、release(发版分支——版本号定稿、只修 bug 不添功能)、hotfix(线上紧急修复,从 master 分出)——五类分支对应"固定版本号 + 发版窗口"的产品节奏:每个版本有一个 release 窗口,hotfix 走紧急通道——分支模型不是审美,是发布节奏的映射(发布节奏变了,分支模型要跟着变)。
① 业务目标:多人协作不踩代码、发布不互相覆盖、质量有把关;② 业务约束:阶段 4 团队 8 人、产品迭代快(第 4 章三周 MVP 的节奏延续——周更/双周更)、发布流程还没建(第 21-23 章才建);③ 技术问题:用哪种工作流?④ 候选:A git flow(多分支严谨);B trunk-based(主干快速);⑤ 评价维度:冲突成本(分支多=冲突多?)、发布节奏匹配(版本制 vs 持续制)、学习成本(8 人团队要人人会);⑥ 打分:git flow 的分支矩阵(master/develop/feature/release/hotfix 五类)对 8 人团队是过度设计——release 分支和 hotfix 分支是为"版本发布制"服务的,案例是持续迭代(没有"发版窗口");trunk-based 的分支模型简单(主分支+短命特性分支),和发布节奏匹配,冲突靠"小步合入"控制(20.3)——打分的过程就是"先想清楚自己的发布节奏"的过程:没想清楚节奏就选工作流,等于没看路就开车;⑦ 选择:B(trunk-based 简化版)——主分支 + 特性分支 + PR 合入,分支短命(1-2 天);⑧ 收益:冲突可控(短命分支)、发布随时可做(主干总是可发布状态——第 21 章构建的前提)、8 人学习成本低;⑨ 代价与演进:主干必须"绿"(测试永远通过——测试是工作流的保障),主干红了是第一优先级的修复(红着的主干上继续开分支,等于在坏地基上盖楼);演进条件:如果将来做"版本发布制"(固定版本号的对外产品),再评估 git flow 的 release 分支。
"主干绿"是第 22 章 CI 的前提:trunk-based 能成立,靠的是"主干随时可发布"——怎么保证?先靠纪律("主干红了先修"是团队的第一条纪律,⑧ 说过)→ 再靠机器(第 22 章 CI:推送自动跑测试,红了不能合——"主干绿"从人保证升级到机器保证,就是 CI 的登场理由)——这也是本书"人保证→机制保证"规律的又一次实例(第 18 章测试从手工到自动化,第 20 章主干绿从纪律到 CI:人总会忘,机制不会)。
决策记录又添一行:工作流=trunk-based 简化版,演进条件=版本发布制出现。第 13 次填写——从第 12 章的七次到本章的十三次,决策模型覆盖了协作领域。
工作流与发布的关系(它把 20.3 和 21-23 章接起来):trunk-based 的"主干总是可发布"是第五部分(构建/CI/CD/部署)的前提——构建(第 21 章)从主干出制品,CI(第 22 章)保证主干绿,部署(第 23 章)把制品送到生产——工作流选型不是孤立的技术决策,它决定了发布体系长什么样(第 5 章模型的"约束→方案"链,在协作+发布两个领域串起来了)。
20.5 Code Review:质量的第一道闸
第 18 章说过"评审是质量的第一道闸,测试覆盖是它的第一项"——现在它上岗。Code Review 的流程(它把"检查"变成制度):
写代码 → 自测(本地跑测试,第 18 章)→ 推分支 → 发 PR → 评审人看 → 提意见/通过 → 合并
评审看什么(三张单,前面章节全部埋过):
- 第 12 章的单:分层有没有被破坏(表现层有没有直连数据库——第 12 章说"跨层是评审最先检查的")、中间件顺序对不对(第 12 章说"排列顺序是评审要看的第一行"——鉴权在路由前吗?);
- 第 18 章的单:测试覆盖了吗(新功能有测试吗?改 bug 补测试了吗?——第 18 章说"测试是评审的检查单",第一项);
- 第 19 章的单:安全表过一遍(新输入点校验了吗?SQL 参数化了吗?挂鉴权了吗?——第 19 章的四问)。
三张单之外,评审还有两块"软检查"(它们决定代码好不好改):可读性——名字起得清不清楚(第 6 章命名讨论的评审形态:变量名/函数名说出意图了吗)、一段代码 30 秒能不能看懂(看不懂=该拆函数了,第 12 章分层的最小形态);边界处理——输入为空/超长/异常时怎么办(第 4 章验收标准的反向:正常路径写了吗,边界路径呢?)——评审的"软检查"是代码质量的日常感知(硬检查拦事故,软检查保可维护)。
评审的两种心态(它决定评审是"有用"还是"走过场"):找茬心态(把评审当考试,专挑毛病——作者防御、评审对抗,最后评审变成形式);教学心态(把评审当"两个人一起把代码改好"——作者说明意图、评审提建议,新人从评审里学——评审是团队知识传递的主通道(第 12 章说"分层是团队协作的地图",评审就是这张地图的日常使用;第 12 章说新人培训靠"命名清楚+评审把关"——评审就是新人培训的第一课)——评审的价值不在"抓 bug",在"让代码被第二个人理解"(能讲清楚的代码,才有人敢改——事故二"没人敢重构"的解法之一)。
评审的节奏(它是流程成本的来源,要诚实):PR 要小(一个 PR 一个功能/一个修复——大 PR 评审人看不进去,走过场);评审要快(当天评审完——拖几天的评审等于没有评审,作者等不及就自己合了);意见要对事不对人(说"这里缺测试"不说"你总是忘测试")——评审的节奏和氛围,是流程能不能持续的关键(流程死在"太慢"或"太凶"上,都是常见死法)。
评审人怎么选(它是评审质量的一个隐藏变量):至少一个"懂这块代码的人"(熟悉相关模块——第 12 章分层让"这块代码属于哪块"可指认)+ 至少一个"不懂的人"(新手视角——不懂的人会问"这为什么这样",逼作者把代码讲清楚——讲的过程就是梳理:能对着一个外行讲明白的代码,结构一定不坏)——评审人的组合:一个懂行的把关,一个不懂的提问——两个都是评审的必要视角(懂行的防漏,不懂的防糊)。
评审与测试的关系(第 18 章的收束):评审发现的问题,能写成测试的写成测试("这里没处理空值"→ 补一条空值测试——"评审问不出问题=测试覆盖不全"的反向:评审提出的每个问题,都是测试的一个候选)——评审和测试是同一件事的两只手:测试问"行为对吗",评审问"还有没有没测的行为"(测试是评审的检查单,评审反过来喂测试新用例——双向的)。
评审的度量(一个就够了,它防止评审走过场):评审中发现的问题数——长期为零,说明要么代码质量极高(不太可能),要么评审没在看(更可能)——评审的问题数是评审质量的体温计(第 16 章说过的"命中率是缓存的体温计",同一个用法);另一个软指标:合入后线上 bug 的密度——评审拦住的 bug 不会到线上,线上的 bug 就是评审漏的——评审的合格标准:线上的问题在减少(第 20.7 的"流程的合格标准"在这里有了度量)。
第 15 章的伏笔也在这里兑现:第 15 章说"先看代码后联调能提前发现一半的契约问题"——现在它是 PR 的默认动作:后端 PR 里,前端会看(接口返回符合契约吗);前端 PR 里,后端会看(调用符合契约吗)——评审让"契约问题"在合入前被发现,而不是联调时爆炸(第 15 章的联调清单,往前移到了评审)。
lint 工具也在这里收账(第 8 章的伏笔):第 8 章说过"HTML 容错让前端需要 lint 工具(第 20 章会看到)"——现在它进 PR:lint 是 PR 的第一道自动检查(格式/未使用变量/常见错误模式——机器能查的先让机器查,评审的人力留给机器查不了的:逻辑、边界、设计)——lint 是评审的"前置过滤":把低级问题过滤掉,评审的注意力留给机器查不了的(逻辑、边界、设计)——机器查得了的先让机器查。
TDD 的协作语境(第 18 章预告的兑现):第 18 章说"TDD 是工作流选择,第 20 章协作语境会讨论"——团队怎么选?三个信号:团队对"先测后写"的接受度(有人强烈抵触就别强推)、代码的规则密度(规则多(交易)TDD 收益大,探索多(新页面)TDD 别扭)、现有测试基础(没有测试的代码库先补测试,再谈 TDD)——TDD 不是道德问题,是匹配问题(第 5 章决策模型:按团队和代码的约束选,不按"哪个更高级"选);折中形态也常见:新功能先写核心规则测试(TDD 的精华),UI/集成后补(传统形态)——流程的匹配原则(20.7)在这里又一次适用:选团队能坚持的,不选理论上最优的。
20.6 技术债:快交付的利息
代码质量最诚实的部分:技术债。定义一句话:为了快交付,暂时放弃的"本该做对"的部分——它像贷款:借的时候痛快,利息(后续改动的额外成本)每天在还。
技术债的积累机制(它是必然的,不是失误):每个项目都有"这周先这样,下周再改"——发布压力(第 4 章三周 MVP)、需求变化(第 4 章说"返工和蔓延")、新人加入(对旧代码不熟就动手)——债是常态,关键不是"没有债",是"债有没有被看见"。
技术债的四种形态(前面章节埋的,现在收账):
- 需求债(第 4 章):需求没分析清楚就开发——返工代码、蔓延功能——"需求分析欠的债,最后都在代码质量上还"(第 4 章原话);
- 优化债(第 16 章):为了性能引入的复杂度(缓存层、索引)——"优化是负债管理,还不起的债就是技术债"(第 16 章原话);
- 安全债(第 19 章):为了体验后置的防御(验证码/双因素)——"窗口期"就是安全债的利息;
- 复杂度债(第 17 章):交易系统的机制复杂度——"业务逼出来的复杂度"是良性债(不欠不行),但"没有注释、没有测试的复杂"是恶性债(欠得没道理)。
依赖管理也在这里收账(第 9 章的伏笔):第 9 章说过"框架的维护成本属于团队日常(第 20 章代码质量会看到依赖管理)"——依赖是代码的"外债":升级依赖(安全补丁/版本跟进)是债的利息,弃坑的库是收不回的坏账——依赖管理的基本动作:依赖清单要审(第 19 章说"依赖也是攻击面"——供应链攻击的入口)、升级要测(第 18 章测试是升级的安全带)、锁版本(第 15 章说过的 lockfile——"环境一致性第二道保险",在协作里是"所有人用同一份依赖")——依赖债是技术债里最容易被忽略的一种(它不写在代码里,写在 package.json 里)。
技术债的偿还机制(它把"还债"从口号变成动作):
- 看见:技术债要有记录(TODO 清单/技术债看板——"债没被记录的,等于不存在的债"——第 4 章说过验收标准是清单,技术债也要清单)——记录的形态不用复杂:一行一个债(什么时候欠的/欠在哪/利息多高),比"心里有数"强一百倍(心里的数会忘,清单不会——第 4 章清单思维的第 N 次应用);
- 偿还:重构("重构的定义是行为不变结构变"——测试兜底让重构敢做:先补测试,再重构——测试在这里变成"重构的胆量来源",第 18 章说过);偿还的节奏:每次改到这段代码时顺手还一点("路过就还"——改功能时把旁边的烂代码整理一下),定期做一次专门的还债迭代(第 27 章迭代循环里会有它的位置);
- 止损:恶性债的识别(没有测试的复杂、没有注释的魔法数字——第 20.2 的账本要标出"哪些债在收高利息")——利息最高的债先还(和 18.7 的"优化资源花在常走的路上"同一个排序逻辑)。
债的度量("利息"怎么估——一句话够用):改这段代码需要的时间 vs 它本该需要的时间——改一次要一天(本该一小时)的代码,就是高利息债——度量不需要精确,需要可见:债主(谁/哪个功能欠的)和利息(改动的额外成本)记在账本上,还债时按利息排序(第 4 章的排序逻辑)。
重构的实操步骤(它是"还债"的执行细节,测试兜底的展开):先补测试(重构对象的现有行为先测住——"重构前确认已有测试,没有就先补核心路径的",第 18 章说过)→ 小步重构(一次只做一个小变换:改个名字/拆个函数/换个结构——不做"顺手优化",第 15 章上线前纪律的日常版)→ 每步跑测试(测试绿了才走下一步——测试在这里是重构的安全带)——重构的节奏和写代码相反:写代码追求快,重构追求慢(慢=每步可验证)。
重构的方向(它让"往哪改"有依据):往第 12 章的分层改(一坨的代码拆到该去的层)、往第 6 章的命名改(名字说出意图)、往第 20 章的可读性改(30 秒看不懂的拆函数)——重构不是"随便整理",是"向本书的每个质量约定靠拢"(第 12/6/20 章的标准,就是重构的目标)。
技术债与新人(它是债的隐性成本):新人对旧代码的恐惧是债的利息在收——"这段代码我不敢动,怕改坏"(事故二在 8 人团队的普遍形态)——新人不敢动 → 老代码更没人维护 → 债更重——恶性循环的解法还是那两件事:测试兜底(敢动)+ 评审教学(会动)(第 20.5 的评审是新人熟悉代码的主通道)。
技术债与第 5 章决策记录的关系(它是账本的更完整形态):第 5 章的决策记录记"当时为什么选",技术债记"当时为什么没做对"——一个是选择的历史,一个是妥协的历史——两份记录合起来,就是代码的完整病历(第 27 章迭代时,翻病历做诊断)。
技术债与第 2 章演进循环(它是"原方案无法承受"的另一种形态):第 2 章说"业务复杂度上升 → 原方案无法承受 → 新技术出现"——技术债是这条链的"延迟版":债积累到"原方案无法承受"的那天,就是还债(重构/换方案)的日子(第 26 章架构演进,很大一部分是在还第 4-20 章欠的债)——技术债的积累是演进的前奏,不是失败(关键是"被看见、有计划"——恶性债才是失败)。
技术债的"良性"与"恶性"的再区分(它防止"零容忍"的极端):良性债——明知且记录的妥协("这版先硬编码,下版接配置"——有记录、有偿还计划,是健康的决策记录形态);恶性债——不知且无记录的混乱("这段代码谁写的?不知道"——无主、无记录、无测试)——管理技术债不是消灭债,是消灭恶性债:良性债是决策(第 5 章模型),恶性债是失明(第 20.2 说"债没被记录的,等于不存在的债")——账本的目的是让债从"恶性"变成"良性"(看不见的债才可怕,看见的债可以排队还)。
20.7 流程的边界:流程是团队的函数
三件套讲完,最后立边界——流程不是越多越好,是团队的函数:
1 人团队(阶段 1-3):不需要 PR(自己合自己看?没有意义)、不需要评审(第 19 章说"安全审查是评审流程的一部分"——但 1 人时审查就是自己过表)——1 人团队的正确流程是"最少的流程"(第 4 章复杂度预算:流程也是复杂度)。
2-3 人团队(阶段 3 末):PR 可以开始上了(两个人互相看——"先看代码后联调"(第 15 章)在两人间开始有意义),但评审可以是轻量的(口头+PR 评论,不设专门评审人)——流程的爬坡从"第二个人加入"开始(第 7 章说过"第二个人加入冲突才爆发"——协作流程也是第二个人加入才需要)。
8 人团队的流程清单(阶段 4 的具体落地,按"事故痛什么先建什么"排):第一步:PR+评审(解决事故一/二:互相踩、没人敢改——第一周就上);第二步:测试进 PR(第 18 章:PR 里测试必须过——评审的第二张单上岗);第三步:CI(第 22 章:推送自动跑测试——"主干绿"从人保证到机器保证);第四步:部署流程(第 21-23 章:构建/制品/发布——解决事故三)——流程不是一次建完的,是跟着事故和规模长出来的(第 2 章演进循环的团队版)。
远程协作的额外挑战(阶段 4 的一个现实,一句交代):团队可能不在一个办公室——异步沟通(PR 评审/文档/提交信息)比同步沟通(开会)更依赖"写得清楚"——commit message、PR 描述、评审意见的"可读性"在远程团队里是硬通货(第 20.3 的提交纪律、第 20.5 的评审教学,在远程是必需品不是加分项)。
流程的代价(诚实交代):PR 等待(评审慢=合入慢)、流程仪式(每个改动都要走流程)——流程的成本 vs 事故的成本:事故一(覆盖对方改动)的成本 >> PR 的等待成本——流程的取舍和 19.8 安全投入是同一个决策模型:按风险排,不按热度排。
流程的演化信号(什么时候加流程/减流程):加——事故重复发生(同一个坑踩两次,说明缺流程);减——流程变成纯仪式(PR 没人看就合、评审走过场——仪式化的流程比没有更糟:它给了虚假的安全感)——流程的合格标准:它能拦住事故(拦不住的事故 = 流程的缺口;拦住了但没人知道为什么 = 流程的仪式化)。
流程的"体检"(它把合格标准变成日常):每季度对一遍流程清单——哪些流程还在拦事故(留着)、哪些流程没人用了(删掉或简化)、哪些事故还没流程拦(新增)——流程和代码一样会腐化(人走了、工具换了、规模变了——流程跟不上就变成仪式)——流程的维护是协作的日常一部分(第 20.2 说"三件套的价值在持续使用",体检就是"使用"的检查)。
20.8 本章对应表
| 业务诉求 | 技术选择 | 为什么 | 代价/取舍 |
|---|---|---|---|
| 多人同时改代码互相踩 | Git 工作流(分支+PR 合入) | 分支隔离+统一入口,冲突集中解决 | 分支要短命,否则冲突更大 |
| 工作流选哪种 | 九步决策(trunk-based 简化版) | 发布节奏匹配+8 人学习成本低 | 主干必须"绿的"(测试保障) |
| 改坏了没人发现 | Code Review(三张单) | 合入前把关,问题不流到线上 | 评审节奏(慢=流程死) |
| 没人敢重构 | 测试兜底 + 评审教学 | 第 18 章测试=重构胆量;评审=知识传递 | 补测试要时间 |
| 质量下滑没人知道 | 技术债账本(看见/偿还/止损) | 债被记录才可还,利息高的先还 | 还债要排期 |
| 流程多严 | 团队规模匹配(流程是团队的函数) | 1 人最少流程,8 人跟着事故长 | 流程成本 vs 事故成本 |
每一行都在本章正文里有完整论证:三起事故在 20.1,三件套在 20.2,Git 在 20.3,选型在 20.4,评审在 20.5,技术债在 20.6,边界在 20.7。先看"业务诉求"列——这一章的每一行,都是从"三个人改不动"那一周里长出来的。
本章对两类读者的收益(与前几章同款):前端读者——组件 PR 的评审(第 9 章组件怎么被评审)和 lint 工具(第 8 章的伏笔:HTML 容错让前端需要 lint——第 20 章把它收进"提交前自测":lint 是 PR 的第一道自动检查);后端读者——分层/中间件/参数化的评审单,是后端 PR 的日常;阶段 4 的时间锚点(五阶段故事线的登记):团队 1→8 人、部署还靠手搓——本章是阶段 4 开篇,第五部分(21-23)会在这个规模上建发布体系;两类读者共同的收获:协作第一次有了完整的基础设施(轨道+把关+账本)——"团队如何守住业务质量"有了制度答案。
本章与第 21 章的边界:本章管"代码在团队里怎么流动"(分支/评审/债);第 21 章起管"代码怎么变成可部署的东西"(构建/CI/部署)——协作的终点是"合入主干",发布的起点是"从主干出制品"——两章的交界处,就是 trunk-based 的"主干总是绿且可发布"(20.4 说过)。
本章小结
协作速查(第四部分回指本章时翻回这里):
- 协作三件套:轨道(Git 工作流)/ 把关(Code Review)/ 账本(技术债)——协作的质量是这三件事的日常;
- Git 工作流:分支隔离+小步合入+PR 统一入口——冲突不是 bug,是"都改了同一处"的信号,解决冲突先问不猜;
- 工作流选型:trunk-based 简化版(九步第 13 次应用)——主干必须绿,演进条件=版本发布制;
- Code Review:三张单(第 12 章分层/第 18 章测试/第 19 章安全)——评审是知识传递不是考试;
- 技术债:快交付的利息——四种形态(需求/优化/安全/复杂度债),偿还=看见+重构+止损;
- 流程是团队的函数:1 人最少流程,8 人跟着事故长——流程的合格标准是"它能拦住事故"。 (六条速查对应本章结构:1-2 是"轨道",3 是"选轨",4 是"把关",5 是"账本",6 是"边界"——第 21-23 章发布体系回指本章时重点翻 3(trunk-based 前提)和 6(流程匹配)。)
第四部分(第 16-20 章)收官:性能管"快"(16)、交易管"对"(17)、测试管"稳"(18)、安全管"防"(19)、质量管"久"(20)——业务增长期的五件事全部讲完:从"首页打不开"到"三个人改不动",第四部分回答的是"业务变大之后,系统怎么还能又快又对又稳又安全又能改"——第五部分开始,回答"怎么把这些东西送到用户面前"。
- 协作需要基础设施:轨道(Git)、把关(CR)、账本(技术债)——"大家小心一点"不是解法;
- 流程是团队的函数:跟着事故长、跟着规模调——仪式化的流程比没有更糟;
- 技术债是快交付的利息:看见、偿还、止损——第 18 章的测试是重构的胆量来源;
- 阶段 4 开篇:1 个人写得动,8 个人改得动——下一站,代码从"开发机能跑"到"生产可部署"。
下一章
协作轨道建好了:分支、评审、账本——代码在团队里流动有了规矩。但下一个问题出现了:代码在"开发机"上能跑,不等于在"生产环境"能部署——本地跑得好好的(第 15 章联调的教训),到了服务器上为什么起不来?
第 21 章,构建与制品:开发环境到生产环境的转换——打包、依赖、产物——代码写好了,怎么让它"可部署"。
写到这里,第四部分(第 16-20 章)全部完成——全书进度:第 1-3 章地图、第 4-7 章设计、第 8-15 章走通请求、第 16-20 章业务增长期的快对稳防久——第五部分(21-23)开始:把设计、走通、守住的这一切,送到用户面前。