资讯动态

用t3code统一团队代码规范:从配置到CI的自动化实践

发布时间:2026/10/13 12:58:02 来源:尧图企业网站定制
第一次意识到代码规范必须靠工具来管是在一次 code review 上。同事提了个三百行的改动里面 tab 和空格混用、两个未使用变量、还有一处明显应该拆分的嵌套 if。我一条条指出来他改了一下午第二天又提了一版结果还是漏了一处。这不能全怪他记性差——规则散落在 wiki 里IDE 设置因人而异git hook 压根没配。后来我花了两周把团队项目的规范工具链全部打包成一个叫 t3code 的命令行工具一条命令初始化npx 一下跑起来问题从源头少了一大半。t3code 是什么简单说它负责把代码规则、格式工具、提交检查、CI 校验串成一条流水线。适合正在从两三个人长到十几人、开始被代码风格折磨的小团队也适合接手一个没有 lint、没有 prettier 的老项目先垫一层底。这篇文章基于我自己做这个工具的真实过程把设计思路、接入步骤、常见坑位都写清楚你可以直接拿去参考。1. 为什么要做 t3code规范不是靠文档是靠工具1.1 团队协作里最耗电的不是写代码是“纠偏”很多项目在一开始只有一两个人的时候代码风格是天然统一的因为都是同一个人写的。等第二个人进来风格就开始分叉有人习惯单引号有人习惯双引号有人用四个空格有人用 tab有人喜欢把工具函数塞进 utils.ts有人喜欢就近定义。这些事本身没有对错但它们会消耗真实的协作成本。Reviewer 在评审时被格式问题分散注意力真正该看的逻辑反而看不过来新人改到旧文件时不小心碰了不该碰的格式PR diff 变得巨大谁都不愿意看再严重点脚本里漏了分号压缩后可能没问题但回归排查时多一个不确定因素。我见过最离谱的规范文档写了一万多字从缩进到变量命名到组件边界都有规定但推行一个月后依旧没什么人看。问题不在于文档写得不好而在于人不可能每次都记得住几十条规则。代码规范这件事最终必须交给机器去裁决而不是靠人的自觉。t3code 最开始就是想解决这个“自觉失效”的问题。1.2 现有工具链为什么还不够ESLint、Prettier、Husky、lint-staged、commitlint这些单独拿出来都是好工具社区也足够成熟。但组合到一起时接入成本就变成了团队里没人愿意认领的杂活。新项目还能靠脚手架带出来老项目就麻烦了。你得先装 eslint 和 prettier再选一套规则集配 ignore 文件处理 prettier 和 eslint 在个别规则上的冲突然后装 husky 和 lint-staged把 pre-commit 钩子写好最后还要在 CI 里跑一遍。听起来不复杂但每一步都有版本选择问题ESLint 8 和 9 的配置格式不一样Prettier 3 修了一些默认行为husky 初始化时需要拉一遍 git 配置。我记得第一次给同事的项目手动加规则时一个下午都在对付依赖版本。装完 eslint 后发现项目里已经有一个旧的 .eslintrc规则互相覆盖prettier 格式化完 TS 泛型结果和 eslint 的 indent 规则打架。最后我打开编辑器满屏红色波浪线新人在旁边一脸茫然。t3code 做的事很直接把这些工具的安装、配置、钩子注册、CI 脚本全部封装成固定的初始化流程减少团队里每个人各自摸索的成本。它不是替代 ESLint而是让 ESLint 维护成本更低。1.3 t3code 怎么定义它自己名字里的 t3 我取的是 three-tier 的意思代表规则分三个等级error、warning、info。code 就是代码本身。合在一起就是“用三级规则去管代码”的意思。这个设计其实来自一次很痛的教训。之前我们把所有规范都设为 error导致老项目一跑 lint 就是上千个错误开发直接红温。后来我把规则分级error 只保留语法错误、未使用变量、明显 bug 高发点warning 留给代码风格层级info 是 suggestion。这样既保证了底线也留出了缓冲空间。t3code 的定位是一个工程化入口层它不自己发明一套全新的 lint 规则而是负责组合和分发规则默认内置一份规则预设生成配置文件同时留出本地覆盖的口子。团队的公共底座用它来统一特殊场景再单独调。2. 核心设计拆解一条命令背后的四层逻辑2.1 命令面init、check、fix、upgradet3code 对外暴露的命令不多我刻意控制了命令数量。工具一旦命令太多别人就不想用了。目前核心命令是这四个命令作用典型场景t3code init生成配置文件、安装依赖、初始化 git hooks新项目接入、老项目第一次接入t3code check运行代码规则检查不修改文件CI、本地提交前自查t3code fix自动修复可修复的问题并报告不可修复项存量代码清理、提交前修复t3code upgrade升级内置规则集给出规则变更报告定期维护规则、跟随社区更新每条命令都要回答一个“为什么”。init 不需要多说check 和 fix 分开是为了可控性。我不喜欢那种一条命令又检查又修改又提交的设计出了问题很难回溯。fix 执行完毕后会打印一个表格列出修复了多少个 error、多少个 warning以及哪些是它改不了的。upgrade 这个命令是我后来加上的。某次我们直接升级了 eslint-config-airbnb 的版本结果全项目冒出来一堆新 error。后来我把规则集升级做成可对比模式先拉取新规则集和当前项目规则 diff生成“会新增 / 会减少 / 会变更”三类清单再决定要不要真正切换。这个流程用于规则变更非常合适。2.2 配置生成逻辑把全家桶揉成一个入口init 的核心逻辑是分析项目类型然后输出对应配置。判断维度包括是否存在tsconfig.json是否使用 React/Vue是否包含测试文件包管理器是 npm、yarn 还是 pnpm。根据这些信息t3code 会组装一套默认配置。生成的文件大致是这样{ preset: ts-react, eslint: { extends: [ t3code:recommended, t3code:typescript, t3code:react ], rules: { no-console: [error, { allow: [warn, error] }] } }, prettier: { singleQuote: true, semi: true, trailingComma: all }, commitlint: { extends: [t3code:commit] }, hooks: { pre-commit: [lint-staged], commit-msg: [commitlint] } }如果项目里已经有 eslint 配置我不会直接覆盖而是先走到冲突检测分支。这里有一个很重要的原则工具不是来砸场子的。老项目可能有自己的特殊规则强制覆盖会引发反弹。所以默认行为是把 t3code 的规则集作为extends中的基底已存在的规则文件作为上层覆盖。技术上实现也不复杂通过 AST 或者 JSON 解析读取已有配置合并时遵循“具体项目规则优先于预设规则”。预设规则只做兜底不抢控制权。2.3 分级规则体系把规则分成“必须改”和“建议改”默认预设里error 级别的规则大概只保留了二三十条。比如no-unused-vars、no-undef、react-hooks/rules-of-hooks这些都是容易被忽略又容易引发 bug 的。warning 级别留给代码风格比如prefer-const、no-var。info 级别更多是 suggestion比如建议使用可选链?.、建议拆解过长的函数参数。有人会觉得这样太松但我一直认为规则集合的约束力应该像“交通灯”红灯必须停黄灯注意观察绿灯随意。如果所有的灯都是红色司机反而会麻木最终连最严重的红灯也一起闯了。老项目接入时尤其如此一上来全是 error大家只会想办法把检测关掉。分级还有一个实际好处CI 里默认只让 error 级别阻断合并warning 只展示不阻断。这样团队可以先达到“没有 error”再逐步消化 warning。一步一步推进比一次到位要现实得多。2.4 性能与兼容性只在要提交的时候才跑全量lint 最大的抱怨永远是“慢”。全量 lint 一个几百个文件的项目跑下来几十秒很正常。把全量扫描放进 pre-commit基本等于让每个开发者每次提交前抽根烟等它跑完。t3code 的性能策略很简单借助 lint-staged让 pre-commit 只检查 git 暂存区里改动的文件同时所有检查都并行执行ESLint 和 Prettier 基于 worker 进程并发。CI 里可以开全量但本地默认只跑增量。实测一个中等规模项目本地提交钩子的增量检查稳定在 2 到 4 秒内完成这个数据是可以接受的。兼容性方面我踩过 Node 版本的坑。工具自身用 Node 18 开发但团队里有人还在用 Node 16直接 import 某些新语法就崩。后来我把源码编译产物 target 设成 Node 16并且在init时检查当前 Node 版本低于 16.14 的给出明确提示而不是让用户看一堆堆栈。理论上npm 包管理器被判别出来后脚本会用对应的命令执行依赖安装避免 npm 和 pnpm 混用导致的锁文件错乱。3. 实操全记录把一套遗留项目接入 t3code3.1 接入前的体检先看差距再动手接入老项目之前我强烈建议先跑一次体检。这个步骤看似多余实际可以省掉很多返工。体检要回答三个问题代码量有多少当前的 lint/prettier 配置是什么状态CI 流程里有没有现成的质量检查环节。我在给一个内部后台管理系统接入时先统计了目录311 个 TS 文件零 lint 配置零格式化统一PR 合并全靠人工 review。这时候如果直接把所有规则全部开成 error不可能落地。所以我把体检的结果量化了先跑一遍默认的 check生成一个“存量问题基线”。这个基线不用于追责只用于衡量后续改进幅度。体检之后根据结果决定接入方式。存量问题非常多的项目直接上修复命令会有风险需要在干净分支上做并且要有一次格式化的专用提交。3.2 安装与初始化一条命令做完接入时我通常先创建一个新的分支避免直接在主干上折腾。假设项目使用 pnpm命令如下pnpm add -D t3code npx t3code initinit 过程会问几个问题? 请选择项目类型: TypeScript React / TypeScript Vue / JavaScript ? 现有代码是否已有 lint 配置: 是 / 否 ? 是否自动初始化 git hooks: 是 / 否 ? 包管理器: pnpm / npm / yarn选择完成之后工具会生成上面那套配置自动安装 eslint、prettier、lint-staged、commitlint 等依赖并注册pre-commit和commit-msg钩子。这个过程中我遇到过一个问题公司内网源不一定有最新依赖包所以需要让 init 支持自定义 registry。后来我在配置文件里增加了registry字段默认读 npm 配置不额外造轮子。初始化完我习惯先用一条npx t3code check验证工具链本身是否跑得通。如果 check 能正常结束并输出报告说明前面的安装过程没问题接下来才是处理存量代码的重头戏。3.3 处理存量代码先格式化再修 error存量代码最怕的就是全量 fix 之后diff 乱七八糟代码逻辑被格式调整掩盖。我现在推荐的顺序是先跑 fix 把格式问题修复生成一次独立的“chore: format”提交然后处理 error 级别问题最后再看 warning。具体操作npx t3code fix --targetformat git add . git commit -m chore: apply prettier formattingt3code 内部会把 Prettier 格式化和 ESLint 修复拆成两个阶段。第一阶段只做格式化不改语义第二阶段才尝试修 error。这样的好处是代码改动分层复盘时有据可查。格式化这一步在大型项目里往往会带出几百个文件变更。这时候不要慌可以在 PR 描述里写清楚“本次提交只包含格式化建议开启忽略空白 diff 审查”。GitHub 和 GitLab 都支持?w1或忽略空白对比reviewer 可以看到真正的逻辑变化。格式化之后再处理 errornpx t3code fix --targeterror如果还有剩余 errort3code 会给出精确到文件和规则编号的报告。对于无法自动修复的比如某个接口返回类型不对就需要人工介入。我通常把这个阶段的任务按模块分给对应负责人定一个两周的缓冲期而不是要求当天全部清完。3.4 把检查接进 CI机器不给红灯人就容易妥协本地 hook 可以拦大部分人但管不住“跳过 hook”的人。所以 CI 里必须有一层强校验。t3code 接入 CI 的方式比较简单就是一个命令行加一个上传报告的动作。在 GitHub Actions 里大概是这个意思name: code-quality on: pull_request: types: [opened, synchronize] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: pnpm/action-setupv2 - uses: actions/setup-nodev4 with: node-version: 18 cache: pnpm - run: pnpm install --frozen-lockfile - run: npx t3code check --ci--ci参数会禁用颜色输出把结果改造成适合日志查看的纯文本并且只以 error 级别作为退出码判断。warning 在 CI 里显示但不阻断合并。这也是分级规则的实战价值CI 的通过标准一开始就设置在“不出现 error”而不是“零 warning”。我还踩过一个坑CI 和本地 Node 版本不一致导致本地产物正常CI 上报语法错误。后来在 CI 配置里固定 Node 版本并加了.nvmrc作为项目级的 Node 版本声明。这个是老生常谈但真有很多项目忽略。3.5 团队推广先不要一刀切用“白名单增量”模式工具接好了直接让所有人强制用通常会被抵触。我的经验是先跑两周“奶妈模式”只加检查不改代码。也就是在 CI 里允许既有 error 存在但新增代码不能新增 error。t3code 有--baseline参数可以用来指定存量基线文件。CI 判断逻辑变成“当前 error 数量必须小于或等于基线”。这样新代码一旦引入新错误构建立刻挂但存量错误不会瞬间阻塞所有人的工作。这么做之后我自己观察到的效果是第一周大家还是会偶尔抱怨“哎呀这个我改不就行了”但没过多久新代码的质量明显上来了。因为钩子已经替他们把问题拦在提交之前CI 再用基线兜底团队对工具的信任度慢慢建立起来了。4. 常见问题与排查技巧实录4.1 ESLint 配置冲突老配置盖掉了新规则最常遇到的情况是项目根目录存在旧的.eslintrc里面的内容没有使用extends而是直接在rules里写满了自定义配置。初始化后t3code 的预设规则被覆盖很多 error 没被检查出来。排查技巧很简单先运行npx eslint --print-config 某个文件.ts看最终生效的规则是什么。如果发现某些规则没生效大概率是配置层级里的覆盖顺序问题。处理方式是调整.eslintrc里extends的顺序把 t3code 的预设放在最前面项目自定义规则放最后。预设是基底自定义是增强。4.2 格式化引发的 diff 爆炸格式化后整个项目的 diff 都变了这是接入 Prettier 后最容易遇到的投诉。尤其是老项目之前完全没有统一格式几百个文件同时变动reviewer 根本看不过来。我现在的做法是格式化提交必须独立于功能提交且建议在 PR 标题上写“chore: format only”。同时让工具在格式化前生成一份“本次格式化修改了哪些文件”的清单确认清单里没有非格式化文件。如果团队实在不接受大范围 diff也可以采用“增量格式化”先只对正在修改的文件执行格式化其他文件不动。t3code 支持--staged参数来只格式化暂存区文件配合.prettierignore把存量目录先排除掉。等代码被后续改动逐步覆盖再慢慢收窄排除范围。4.3 Husky 不生效Windows 环境最让人头大Husky 不生效的原因通常有几种Node 包没有重新安装导致.husky目录缺失文件权限不够或者开发者直接在终端运行了带--no-verify的命令。我在 Windows 上踩到最典型的一次是 git 没有把.husky/pre-commit当作可执行文件。解决办法是在 init 时检测到 Windows 环境主动写入一个 shell 包装层并确保core.hooksPath指向正确。如果是已有的项目可以用命令重置npx husky init npx husky set .husky/pre-commit npx lint-staged npx husky set .husky/commit-msg npx commitlint --edit $1这个操作不复杂但很多文章没讲清楚 Windows 上需要额外配置core.hooksPath的问题。建议接完钩子后先改一个文件尝试提交确认钩子真的在跑。4.4 有一些文件就是不想被检查任何规范工具都必须给“例外”留出口否则就是过度约束。常见需要排除的有生成文件、第三方 SDK 的 d.ts 文件、自动拉取的配置文件。我建议按优先级使用排除方式全局排除在.eslintignore或ignorePatterns里写目录适合构建产物、node_modules、自动生成的 mock 数据。局部排除对于个别当前还不规范但暂时不能动的文件用文件顶部的/* eslint-disable */。提交排除通过lint-staged配置里的 glob 表达式让某个目录不参与 pre-commit 检查。需要注意排除越少越好。排除了一个目录就意味着这个目录里未来可能出现新的 errorCI 基线也覆盖不到。每排除一处都要在 README 里留下理由和清理时间。4.5 规则升级引发团队争议升级内置规则集时最容易出现的场面是有些人觉得新规则更好有些人觉得“改动太大没必要”。我处理这个问题的办法是让 upgrade 命令输出一份“变化报告”而不是直接改完配置就完事。报告内容包括新增了哪些规则分别落在哪个等级哪些规则被移除如果切换后当前项目预期会增加多少个 error有了这份报告团队就能基于事实讨论而不是原地打转。我之前升级过一次 react 规则集报告显示会新增 40 个 error其中有 12 个是 hooks 依赖数组相关。因为这些确实是容易出 bug 的点团队很快就接受了。4.6 常见问题速查表问题现象排查方向规则没生效明显违反规范但仍通过 checkeslint --print-config查看实际生效配置钩子没跑提交时无任何检查输出检查 core.hooksPath 与 .husky 文件权限格式化把逻辑改了diff 中包含代码逻辑变化确认是否只跑了 format建议采用独立格式化提交CI 与本地结果不一致CI 报错但本地通过检查 Node 版本、依赖锁文件、环境变量依赖安装失败init 中途报错检查 registry、包管理器是否与锁文件匹配这个表基本能覆盖接入过程中 80% 的疑问。剩下 20% 需要结合具体项目结构去排查但方向不会偏。5. 从 t3code 里得到的一些工程化经验5.1 规范不是越严越好分级比一刀切更有生命力做 t3code 之前我以为规则越全越好。实际做完之后发现规则集的维护是持续性的工作冗余规则只会增加噪音。现在我会定期清理规则原则有三个只保留能自动修复的规则、只保留历史上真实引发过 bug 的规则、只保留团队投票通过的必要规则。分级制度让这些原则落地了。error 是底线warning 是提醒info 是引导。新人在收到 info 级别提示的时候不会产生“是不是我做错了”的焦虑反而会去查建议背后的原因。这比直接给一个错误红叉更容易培养习惯。5.2 小团队的接入顺序先本地再 CI最后定团队规范如果是十人以下的团队我不建议同时推进所有环节。先让开发者本地用起来体验一把“提交前自动修正”的爽感再谈 CI 基线。顺序错了很容易陷入“工具是来管我的”这种对抗情绪。我推荐的三步走是第一步本地 init 好能跑 fix第二步在 CI 里只针对新增代码做阻断第三步团队规范文档只写工具自动覆盖不到的部分比如目录结构规范、状态管理规范、命名习惯。把工具能管的事交给工具文档才有人愿意读。5.3 后续还能扩展什么t3code 目前解决的是代码质量和提交规范但这个模式可以复制到更多场景。比如生成 CHANGELOG、自动添加 license 头、检查环境变量是否遗漏、提交信息联动需求单号这些都可以做成插件。我在后续规划里会把规则集拆成 preset 包让不同团队选择自己的组合而不是所有项目都用同一套配置。5.4 给想自己写工具的人一点建议做这类工程化工具最大的门槛不是写代码而是想清楚“什么时候该替用户做决定什么时候让用户自己选”。t3code 里默认值我替用户定了但覆盖出口每种都留了。如果一开始就把配置项做得极其开放等于把复杂度重新抛给用户如果完全不开放又没法适应真实世界的脏乱差。我个人的体会是工具维护者要经常去一线用自己写的工具。只有真正在一个让人崩溃的老项目里跑过才会知道“规则全开”只是一个纸面上的方案。最后再分享一个小技巧给工具加的每一个命令都要先问一句“如果新人第一次用会不会被它的输出吓到”。把报错信息写得像人话比增加十个功能更有价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑