接手那个历史包袱很重的项目时,我第一天就崩溃了——同一个业务模块,三个人写了三种命名风格;新增一个接口,新人要花半小时去翻老代码照葫芦画瓢;每周的代码评审会有一大半时间在讨论缩进和变量命名。那时候我意识到,团队缺的不是更高的要求,而是一套把“按规范写代码”这件事自动化的基础设施。这就是 t3code 这个项目出现的背景。它不是一个公开下载的开源框架,而是我从实际项目中提炼、并在我们团队真实运行了三个多月的三层编码治理方案:T1 规则层、T2 模板层、T3 智能辅助层。
这篇文章会用完整篇幅把这套东西拆开讲清楚:每一层的设计逻辑、落地时用的具体配置、我踩过的坑,以及把一个老模块迁过来的全过程。如果你是团队里的规范推动者,或者正在被“AI 生成的代码看着很对但实际很水”这个问题困扰,这套思路可以直接拿去参考。我不会只给结论,每一步都会解释为什么这么做。
1. 先说清楚 T3code 到底治什么病:三层的分工与边界
1.1 为什么叫 T3:三个 Tier,不是三个 Level
有人看到 T3 会以为是“第三级代码”,好像第一级不行、第三级最行。其实我的本意是从治理链路上把代码相关工作分成三层:T1 是“规定动作”,告诉所有人什么能写、什么不能写;T2 是“标准动作”,把高频代码的骨架直接生成出来;T3 是“复核动作”,在人和 CI 都看不过来的时候,用大模型做第一道预审。
这三层不是等级关系,而是递进关系。用做饭类比可能更好理解:T1 是菜谱上的硬性标准,什么肉必须焯水、什么菜不能混炒;T2 是切好的净菜包,拿到就能下锅;T3 是出锅前替你试菜的师傅。三层用同一套味道体系约束,最后端上桌的菜品才是稳定的。
代码也是一样:团队规范写得再详细,如果执行要依赖每个人的自觉,那基本等于没有。T3code 的核心思路,就是把规范变成机器可执行的东西,让“合规”成为默认路径,而不是额外负担。
1.2 三层之间的依赖顺序
T1 产出的是规则集,既可以是 ESLint 配置、tsconfig 的严格选项,也可以是一份自定义的 JSON 规则清单。T2 的模板生成器消费这些规则:它生成的文件必然满足命名、目录、依赖等硬性约定。T3 的智能预审也消费这些规则:在代码提交前,把 diff 和规则一起发给大模型,让它先过一遍。三层本质上是同一份规则的三个不同执行入口。
| 层 | 核心产出 | 面向对象 | 典型工具 | 主要成本 |
|---|---|---|---|---|
| T1 | 可执行的规则集 | 代码本身 | ESLint / Prettier / 自研扫描 | 规则维护成本 |
| T2 | 合规章架代码 | 开发者 | Plop / Hygen / 脚本 | 模板维护成本 |
| T3 | 自动预审意见 | Pull Request | 大模型 API / CI 集成 | Token 与调试成本 |
这三层的实施顺序基本是递进的:没有 T1,T2 生成的文件无法验证是否合规,T3 也没有判断依据。不过不是说小团队必须三层全上,先看规模再决定切到哪一层。
1.3 适合什么规模的团队
小于 5 人的团队,其实 T1 就够了,甚至 T1 做成 report-only 都行,因为大家低头就能对齐。5 到 15 人,强烈建议补上 T2,这个阶段往往是业务扩张期,重复模块大量出现,模板节省的时间最明显。15 人以上或者多业务线并行,T3 的价值才会真正显现——因为跨业务线评审时,reviewer 根本不了解上下文,AI 预审能先把低级问题全部滤掉,让人类评审只关注业务逻辑。
我见过很多团队一上来就想搞大而全的 AI 严格校验,结果误报一堆,成员怨声载道。T3code 的落地顺序请务必自下而上,先把地基打好,再谈上层建筑。
2. Tier 1 规则层的实际落地:让检查先跑起来
2.1 规则层管的事比 lint 更宽泛
很多团队把规范理解为“用 ESLint 跑一遍”,其实不够。ESLint 管的是语法和代码风格,而真正拖垮维护效率的是这四类问题:命名混乱、模块文件膨胀、依赖方向失控、提交信息不可读。这些都应该放进 T1 规则集。
我实际用自定义规则做了三件事:扫描仓库里有没有超过 400 行的“超级文件”;扫描 import 路径有没有跨业务域边界;用 git log 检查提交信息有没有包含需求编号。这些规则 ESLint 原生不提供,但都是真实维护痛点,做成脚本挂在 CI 里成本很低。
2.2 一份可维护的 rules 配置长什么样
直接放一份我们实际使用的规则格式,字段含义我会逐个说明。规则 id 用来关联 CI 警告信息里的可追溯编号,pattern 是正则或 AST 规则,message 是给开发者看的可读解释。
{ "layer": "T1", "version": "1.1.0", "rules": [ { "id": "R001", "category": "naming", "pattern": "\\b(let|var)\\s+(temp|tmp|foo|bar)\\b", "severity": "error", "message": "变量名 temp/tmp 无业务含义,请改为描述实际用途的名称" }, { "id": "R002", "category": "size", "maxLines": 400, "severity": "warn", "message": "文件超过 400 行,建议拆分模块" }, { "id": "R003", "category": "async", "pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}", "severity": "error", "message": "空 catch 会静默吞掉异常,至少记录日志" } ] }命名类规则用正则处理,简单够用。如果团队水平允许,文件大小限制这种更适合通过 AST 工具实现,不一定非要怼进 ESLint。关键是规则要“少而硬”,不要追求几百条规则,维护成本会反噬。
2.3 从 report-only 到强制检查
我不建议第一天就把 pre-commit 钩子全开,那会导致 git commit 失败太多次,团队人人骂街。实测最顺的路径是三步:
- 先以 report-only 方式接入 CI,跑一周,输出报告但不阻断。
- 收集这一周的真实违规数据,把误报、不合理规则全部修订掉。
- 再把最常见的 5 到 10 条规则设为 error 级别,接入 husky + lint-staged。
husky + lint-staged 的配置逻辑非常简单,在 package.json 里声明钩子,然后让 lint-staged 只处理暂存区的文件:
{ "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"] } }这里有一个很实用的经验:--fix自动修复一定要放在 pre-commit 里,但不要接太多规则,否则 commit 会变得非常慢。lint-staged 只扫暂存区,一般要在 6 秒内跑完。跑不完,大家就会想办法绕过钩子,绕过之后规则就形同虚设。
2.4 落地中我踩过的两个坑
坑一:规则膨胀。我一开始参考网上大厂的配置,直接上了 200 多条规则,结果团队成员每写三行代码就报一个 lint error,抵触情绪很大,甚至有人私下把 lint-staged 从本地删了。后来我把规则砍到 20 条左右,核心只保留会导致 bug 的规则,比如未捕获的 promise 错误、any 类型显式泛滥,风格类全部交给 prettier。代码规范要能解释清楚“为什么这条规则值得阻断一次提交”,解释不清的砍掉。
坑二:prettier 和 eslint 冲突。典型情况是 prettier 想强制加尾逗号,eslint 配置里却写 no-comma-dangle,两个工具互相打架。解决方法是 eslint 配置文件里禁用所有 formatting 类规则,格式统一交给 prettier。eslint 管逻辑正确性,prettier 管长得好看,职责分开后问题立刻消失。
3. Tier 2 模板层:把重复劳动变成一键生成的基建
3.1 什么样的代码适合做模板
我的标准是三条:出现频率高、结构套路化、不涉及核心业务逻辑。比如新增一个 REST API handler、一个新前端页面骨架、一个测试文件骨架、一个数据库表的 CRUD 模块。
这些代码每个团队每天都在写,但写出来的质量参差不齐。把它们固化成模板,等于把团队里写得最好的那个人的习惯复制给所有人。模板里甚至可以内嵌一段 TODO 注释,提示新人哪些位置必须补充自定义逻辑,否则无法通过 T1 检查——这比口头要求有效得多。
3.2 模板不只是复制粘贴,要自带合规性
很多团队的“代码模板”其实就是 wiki 上贴一段代码,大家复制后改改用。这种模板最大的问题没人维护,也不强制,新人复制了一份五年前的旧代码,把已经废弃的 API 当宝贝接着用。
T2 的模板生成器是跑一段命令,输出一个当前版本的合规章架。它会从 T1 的规则集读取当前生效的约束,比如当前禁用的 API 名单,生成时直接绕开它们。这样模板和规则天然同步,不会出现“模板还在教大家写违规代码”的尴尬。
3.3 选型对比:plop、hygen、自研脚本
这是个非常实际的决策,我当时对比了三个方案:
| 工具 | 上手成本 | 可编程性 | 适合场景 |
|---|---|---|---|
| Plop | 低 | 中 | 小团队快速搭建,prompts 简单直观 |
| Hygen | 中 | 中 | 模板文件组织清晰,支持自定义逻辑 |
| 自研 Node 脚本 | 高 | 高 | 需要深度集成 T1 规则和内部平台 |
我的选择是先用 Plop,因为它的生成逻辑写在 plopfile.js 里,工程师看一眼就懂。后来因为需要读取内部规则配置并动态注入模板变量,我迁移到了自研 Node 脚本。这是非常典型的演进路径:先用简单工具跑通,再根据真实需求扩展,不要一开始就造轮子。
下面是一个简化的 plopfile.js 示例,它按规则生成 API handler 骨架:
module.exports = function (plop) { plop.setGenerator('api-handler', { description: '生成一个合规章架 API handler', prompts: [ { type: 'input', name: 'moduleName', message: '模块名(小驼峰)' }, { type: 'input', name: 'actionName', message: '动作名(如 fetch/create)' } ], actions: [ { type: 'add', path: 'src/api/{{moduleName}}/{{actionName}}.ts', templateFile: 'templates/api-handler.hbs' } ] }); };模板生成的文件天然满足命名规范,因为路径和文件名都来自生成规则,而不是靠人记忆。
3.4 模板凭什么是 Tier 2 而不是重复轮子
这里想说明一个容易混淆的点:T2 和普通脚手架(比如 create-vite 那种项目初始化工具)的区别在于,它是团队规范的一部分,生成的文件必须能通过 Tier 1 的全部 error 级规则。
我做了一个很小的自动化验证:模板生成的代码在 CI 里会和手写代码走同一套检查,模板本身也是仓库里的一个模块,如果模板跑不过 lint,发布流程直接失败。这样模板就不会腐烂。
另外模板库要打版本,生成时在文件头记录模板版本号,方便出现问题后追查是从哪个版本生成的。这个细节在出过一次“某同学用旧模板生成了一整个模块”的事故后,变成了强制项。
4. Tier 3 智能辅助层:AI 生成代码如何过团队规范这一关
4.1 团队开始用 AI 写代码后的新问题
代码规范推进到第三个月,我发现一个新的效率漏洞:团队成员都在悄悄用大模型生成代码片段。模型生成的代码看起来结构完整、注释清晰,但总有一些“风格很 AI、不合规很隐蔽”的问题:错误处理只写了 if 没写 else;类型用了 any;没有遵循团队的错误码规范。
与其禁止,不如把这个环节纳入治理。这就是 Tier 3 的由来:在代码进 code review 之前,先用大模型做一遍自动预审,让人工 reviewer 把精力留给真正值得讨论的业务设计。
4.2 把团队规范变成 AI 读得懂的 instructions
关键一步是把 T1 的规则转换成自然语言指令,让模型能理解。直接丢一份 JSON 给模型效果很差,因为规则编号和正则模式对模型毫无意义。我把这些规则转成有上下文的 Markdown,作为 prompt 前缀,风格如下:
你是团队的资深代码评审员。请按以下规范审查代码: 1. 变量名必须表达业务含义,禁止 temp/tmp/foo 这类无意义命名。 2. 所有异步函数必须处理 reject 分支,不得静默吞错。 3. 新增文件不得超过 400 行。 4. 错误信息必须带错误码,格式为 ERR_[模块名]_[编号]。 请针对 diff 逐条输出"是否违规 + 问题行号 + 修改建议",如果没有违规则输出 PASS。这段 prompt 不需要复杂技巧,关键是和 T1 的规则一一对应。模型输出的预审意见会贴到 PR 评论里,作者自己先修一轮再请人评审。实测下来,因为低级问题被打回的 PR 数量明显下降,很多问题在预审阶段就改掉了,人工评审的负担轻了一大块。
4.3 CI 里的“伪合规”拦截
还有一个更要命的场景:开发者让 AI 生成了一段代码,为了过 lint,AI 会“照着规则写”,但也只做到表面合规。比如团队要求所有异步错误必须处理,AI 可能生成一行console.log(error)就算完事——这确实是处理了,但毫无意义。这种“合法但不合格”的代码,静态工具查不出来,必须靠语义判断。
我的做法是在 CI 里加一道轻量测试守卫:AI 生成的代码所涉及的核心函数,必须关联至少一个最小单测用例,单测里要覆盖正常路径和异常路径。这个 gate 不要求 100% 覆盖率,只要求覆盖关键分支,成本可控,但效果很好。它让 AI 生成的代码不能只做到“能跑不报错”,还得做到“契约可验证”。
4.4 一个典型例子:AI 生成的错误处理为什么总缺 else
拿一个真实例子说。之前让 AI 补全一个接口调用的错误处理,它生成的是:
const resp = await fetchData(id); if (resp.success) { setData(resp.data); }看起来没问题,但 resp.success 为 false 时什么都没做,用户界面会一直转圈。AI 没有恶意,它只是“语法正确、风格得体”地完成了任务。改进 prompt 后我加了一条:
对于每个 if 条件,必须显式写出 else 或提前 return,不得留空分支。同时把这条加进 T1 规则,通过扫描没有 else 且没有 return 的 if 块来静态拦截。这样 Tier 1 的静态规则和 Tier 3 的 AI 指令同步更新,三条链路就真正统一了。这个案例说明 T3 不是替代 T1,而是 T1 的语义补充——一个管“可观测的合规”,一个管“语义上的健全”。
5. 把一个千行级老模块迁移到 T3code 的完整路径
5.1 迁移前摸底
实践是检验这套方案的唯一标准。我找了一个 1200 行的老模块作为试点,迁移前先摸底,数据很扎心:lint error 137 个,超过 400 行的文件 9 个,无意义命名出现 33 次,类型为 any 的有 47 处。
不摸底直接动手,很容易在“改哪些”上扯皮,甚至改到一半发现规则本身有问题。摸底报告发出来后,和模块负责人对齐优先级就顺畅多了,因为大家讨论的是数据,不是感觉。
5.2 三步法:先规则、再模板、后切检查
具体操作顺序是三条,顺序不能乱:
- 先把 T1 规则跑在目标模块上,把报告发给模块负责人,逐条确认哪些要立即改、哪些可以豁免。
- 为这个模块最常见的三种代码形态(API handler、数据模型、测试)补上 T2 模板,让后续新代码一律走模板生成。
- 最后再打开 pre-commit 强制检查,设置一个 grace period:存量问题不阻断提交,但新增违规立即报错。
很多人上来就反着来,先开强制检查导致 CI 全红,再回头改存量,实际上很难推进。先规则后模板再检查的顺序,是为了让存量问题不成为新代码合规的绊脚石。
5.3 迁移结果:一组我们自己跑出来的数据
迁移后跑了一个月,核心指标对比是这样的:
| 指标 | 迁移前 | 迁移后(一个月) |
|---|---|---|
| lint error | 137 | 31 |
| 无意义命名 | 33 | 6 |
| 文件超 400 行数量 | 9 | 3 |
| 单次 CR 平均耗时 | 40 分钟 | 22 分钟 |
| 新人完成首个需求耗时 | 2.5 天 | 1.5 天 |
数据只能代表我们这一个项目的情况,但趋势是放之四海而皆准的:规范自动化砍掉的不是代码量,而是不必要的沟通成本。代码评审从“教做人”变成“看业务”,这对团队氛围的改善也很明显。
5.4 迁移中我最后悔的一个决定
回头看,最大的问题是存量代码处理过于激进。迁移第二周我就把“any 类型”从 warn 提升到了 error,结果大量历史代码开始报错,和业务需求并行开发时产生了很大的摩擦。最后花了整整一天写了一批豁免注释才平息。
正确做法应该是:存量问题列一个明确的清理计划表,按模块排期逐步处理,而不是靠提升严格度来逼迫大家立即还债。规则严格度提升应该跟着容量走,不是跟着心情走。这是我在这套方案上付出最贵的学费。
6. 跑起来之后:三个月的效果与后续调整
6.1 数据和团队反馈
三个月后,全仓库的 lint error 从最初的几千个降到了可维护水平,PR 评审时长稳定在收敛区间。团队反馈里最有意义的一句是:“我现在不用想格式了,模板给什么就是什么。”这其实就是 T3code 想要的效果——让规范进入肌肉记忆,而不是每天靠意志力维持。
6.2 最大的坑:T2 模板变成了“新的复制粘贴”
模板在运行两个月后开始暴露问题:有些同学直接把模板生成的 80 行骨架文件又复制了几份,在一个业务模块里放了三四个几乎一样的文件,只改文件名。模板降低了创建新文件的成本,也降低了思考成本——因为创建成本太低,大家懒得重构。
我的调整是:模板生成器在文件头部写入业务上下文字段,如果字段为空,T1 检查就报错;同时每个模板提供--context必填选项,让生成过程必须回答“这个文件要做什么”。这一下把盲目复制打回去了不少。工具永远会被人找到偷懒的口子,所以要不停地给“懒”加约束。
6.3 第二个坑:AI 预审的误报与盲区
T3 预审刚开始使用时,误报率大概在 15%。最常见的误报是把刻意为之的宽松命名——比如泛型中的 T、类型占位符——当成不良命名。盲区则是真正需要业务上下文的问题,比如一个删除接口到底该不该加二次确认,AI 无法判断。
所以我把 T3 定位成“预审员”而不是“终审员”:它只负责把客观规范问题捞出来,业务问题交给人工评审。人机各管一段,反而比一开始设想的“AI 全自动评审”更容易落地。如果你打算给自己的团队接 AI 评审,我强烈建议先从“预审”这个角色起步。
6.4 如果重做一遍,我会改什么
重新来一遍的话,我会在第一天就把规则写成文档而不是直接写进 JSON:先让团队读一份《为什么要有这些规则》的说明,再上机器执行。机器可以强制执行,但解释权必须在人手里。任何一条规则,如果写不出来龙去脉,它就只是一个让人反感的限制,而不是一种帮助。
另外,我会给 AI 提供正反例,而不是只给规范条文。模型对“符合规范的样子”缺乏直觉,给两三个合规和不合规的 diff 对比,效果比十条抽象规则都好。这在 T3 的 prompt 设计里特别值得花时间。
现在这套三层链路已经跑成了我们团队的默认开发姿势。我个人最大的感受是:代码规范不是用来限制人的,它是用来降低整个团队沟通成本的。T3code 对我而言也不仅仅是一个项目代号,更是一个习惯——任何反复出现、需要靠自觉才能做对的事,都应该想办法把它变成默认路径。如果你正准备在团队里推规范,我的建议很简单:先别想着三层全上,挑你最痛的那一层开刀。跑通一层的价值,你自然会知道要不要继续往上加。