ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用 pre-commit hook 自动修复代理代码格式,告别代码评审手动画红线

用 pre-commit hook 自动修复代理代码格式,告别代码评审手动画红线 用 pre-commit hook 修复代理相关代码的格式问题听起来像是一个很小的工程细节实际跑起来之后会发现它比在代码评审时手动画红线靠谱得多。这里的“代理”指的是开发里常见的反向代理配置、代理类、代理中间件不是网络代理工具。AI 辅助编程现在写代码很快但生成的代理代码格式经常不稳定缩进、引号、import 顺序各写各的提交前不做自动修复后面看 diff 会非常痛苦。下面按真实项目的落地顺序拆一遍先搞清楚哪些代码算代理代码再把 pre-commit 装进项目然后按语言配置格式化钩子最后跑一遍完整流程。中间会穿插一些我在实际项目里踩过的坑以及批量、团队、CI 场景下的注意事项。1. 先搞清楚 pre-commit 要修的“代理代码”到底是什么1.1 软件开发里的代理代码不是网络代理工具一提到“代理”很多人的第一反应是网络代理、代理服务器、浏览器代理设置。这类内容不在本文讨论范围。这里说的代理代码是开发环节里经常出现的几类东西反向代理配置例如 nginx 的 server 块、location 块、upstream 转发规则。代理设计模式的实现例如 Python 里的 ServiceProxy、CacheProxyJava 里的静态代理和动态代理。请求代理中间件例如 Node.js 的 Express 或 Koa 中把/api请求转发到后端服务的 middleware。这些代码的共同点是它们负责转发、拦截、包装请求或服务调用结构重复度高改动频繁。正因为重复度高AI 编码工具生成起来特别快也正因为重复度高一旦格式乱了整个文件的可读性会立刻下降。实际项目里最常见的情况是一个网关仓库里同时有 nginx 配置、Python 脚本和前端代理文件三个人维护三种风格。没有统一格式化规则之前每次提交的 diff 里都混着大量空格和缩进改动真正的逻辑改动被淹没在里面。这是 pre-commit hook 要解决的第一个问题让格式统一变成一个自动化动作。1.2 AI 生成的代理代码格式问题为什么更明显AI Coding 工具的优势是生成速度快但风格一致性并不稳定。同一个项目里你可能会遇到三种不同风格的代理类一种把所有语句压成一行一种喜欢多级缩进一种 import 顺序完全随机。这些差异不影响功能但会影响后续维护。这里要补一个点AI 生成代理配置时尤其容易在 nginx 配置这类“非通用编程语言”上出格式问题。nginx 配置没有强制的缩进规范AI 生成的结果可能这次缩进两格下次缩进四格前后括号不齐。手动改可以但每次生成都手改效率就下来了。把格式修复交给 pre-commit hook等于把“风格统一”变成机器行为。只要规则定下来所有 AI 生成的代码、所有手写的代码进入仓库前都会经过同一道格式化工序。这样既保住了 AI 写代码的速度又不让格式问题成为后续维护的成本。1.3 pre-commit 解决的是“提交前一刻”的格式问题pre-commit 是 Git 的钩子机制。你在项目里安装并注册之后每次执行git commit它会在提交动作真正完成之前运行一系列检查。检查通过提交继续检查不通过提交被中断。它和 CI 里跑格式检查的差异在于时机。CI 通常发生在代码推送到远端之后发现问题已经晚一步pre-commit 发生在本地提交之前能直接把坏格式拦在仓库门外。对代理代码这类结构固定、规则清晰的代码来说pre-commit 是最合适的落点。我的习惯是先用一个新分支验证配置确认格式化逻辑符合团队预期再应用到主干分支。不要在主干上直接开全量格式化否则冲突会很多。2. 把 pre-commit 装进项目环境准备和最小配置2.1 前置条件Git、Python、一个已有 Git 仓库pre-commit 本身是 Python 写的工具安装它需要 Python 3.9 以上环境以及一个已经初始化好的 Git 仓库。如果你的项目还没纳入 Git 管理先执行git init再进入下面的步骤。已经有 Git 仓库的项目安装前先确认当前分支状态最好在干净的工作区操作。第一次运行 pre-commit 时可能会触发全量格式化工作区有未提交改动时容易混在一起增加排查难度。2.2 安装 pre-commit 并初始化钩子安装方式不复杂核心命令如下# 全局安装推荐用 pipx 隔离环境 pipx install pre-commit # 或者直接用 pip 安装 pip install pre-commit # 在项目根目录创建配置文件后执行一次初始化 pre-commit installpre-commit install的作用是把钩子注册到当前项目的.git/hooks/目录下。注册成功后后续的git commit命令才会触发检查。这里有个容易忽略的点如果项目由多个开发者在不同机器上 clone每个人的钩子都是本地注册的。也就是说一个人注册了钩子另一个人不一定注册。所以配置文件要入库install命令要写进 README 或开发文档CI 也要跑同一套检查。2.3 最小配置文件长什么样在项目根目录创建.pre-commit-config.yaml先放一个最小配置# .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml这个配置做了三件事去掉行尾多余空白、确保文件末尾有换行、校验 YAML 格式。它不针对任何代理代码但作为基础钩子几乎每个项目都建议先加上。配置中的rev是版本号。第一次运行时会根据rev去拉取对应仓库的代码这个过程需要网络。如果网络比较慢可以先从最小配置跑通再逐步加其他钩子。3. 按代理代码类型配置格式化钩子3.1 Python 代理类用 ruff 代替 black如果你的代理代码是 Python 类比如 ServiceProxy、CacheProxy、RPCClientProxy格式问题集中在缩进、引号、import 顺序、单行多语句。这类代码用 ruff 处理很合适。ruff 的特点是速度快能同时做 lint 和 format。配合 pre-commit 时一般配置两个入口ruff负责修复可自动修复的问题ruff-format负责格式化。- repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.9 hooks: - id: ruff args: [--fix] - id: ruff-format注意ruff-format 和 black 不要同时启用。它们的格式化风格在某些边界场景下有差异同时启用会出现互相修改、提交反复中断的情况。选一个作为主力格式化工具就够了。版本号以官方仓库最新稳定版为准。配置里的rev一旦锁定团队所有成员的格式化结果才会一致。如果 A 用 v0.6.9B 用 v0.8.0可能格式化出不同结果。3.2 nginx 反向代理配置用专门或通用的 formatternginx 反向代理配置是另一个高发区。常见问题包括server 块缩进不统一、分号缺失或空格错乱、proxy_set_header行挤在一起。格式化这类文件可以用专门的 nginx formatter也可以借助 prettier 插件。- repo: https://github.com/pre-commit/mirrors-prettier rev: v3.3.3 hooks: - id: prettier types_or: [nginx, json, yaml, markdown]如果你不想引入太多节点依赖可以先用基础钩子里的 check 能力做最低限度校验再在 CI 阶段用nginx -t验证配置语法。注意pre-commit 做的是格式修复语法正确性还是需要 nginx 本身来验证。格式正确不等于配置可用。3.3 TypeScript / JavaScript 代理中间件prettier 为主Node.js 项目里的代理中间件比如 Express 或 Koa 中转发/api请求的 middleware格式问题主要是引号混用、语句分号不一致、对象属性缩进乱。这类代码交给 prettier 处理就行。- repo: https://github.com/pre-commit/mirrors-prettier rev: v3.3.3 hooks: - id: prettier types_or: [ts, tsx, js, jsx, json, yaml]prettier 的优势是语言覆盖面广一个工具能处理前端代理代码和配置文件。缺点是高版本对某些自定义语法支持有限需要根据项目实际使用范围调整types_or。3.4 其他语言Java 静态代理、动态代理的格式化思路Java 代理常见的是静态代理类、JDK 动态代理和 CGLIB 代理。格式上主要关注缩进、空行、包名导入顺序。pre-commit 生态里有 google-java-format 的钩子也可以直接用 maven 插件或 IDE 格式化。老项目要谨慎全量格式化可能造成大规模 diff建议先跑一次全量再进入增量维护。不同语言可以选不同的格式化工具体系但同一门语言里不要混用两套功能重叠的工具。表格整理一下常见场景代理代码类型主力格式化工具常见格式问题Python 代理类ruff / ruff-format缩进混乱、单行多语句、import 顺序不定nginx 反向代理配置nginxfmt 或 prettier 插件缩进不统一、分号缺失、proxy_set_header 行混乱TypeScript/JavaScript 代理中间件prettier引号混用、分号不一致、对象属性缩进乱Java 静态代理/动态代理google-java-format空行、导入顺序、缩进不统一这一节的要点是先根据项目语言选择主格式化工具然后把选择写进.pre-commit-config.yaml。不要同时上多个功能重叠的工具。4. 实际操作从手动格式化到提交前自动修复4.1 第一次运行先全量格式化建立基线配置写好之后第一次建议不要直接git commit而是先跑一次全量检查pre-commit run --all-files这个命令会针对仓库所有文件执行所有钩子。第一次运行会拉取依赖耗时较长。跑完后会出现两种情况如果所有钩子通过说明仓库已经是干净状态如果有文件被修改说明存量代码里有格式问题pre-commit 已经帮你改好了。此时用git diff看一下改动确认没有误伤再把改动提交一次。建立基线非常重要。如果仓库存在很久、文件很多不要幻想第一次就能全绿。先把全量格式化结果单独提交一次后面新增代码才不会被存量问题干扰。4.2 正常提交时钩子的工作流程之后正常开发流程就是git add文件然后git commit。pre-commit 会在提交前执行。如果发现格式问题它会自动修复并中断提交。此时工作区里的文件已经被改写你需要重新git add被修改的文件再次执行git commit。重复这个过程直到全部钩子通过。这个流程不是 bug是设计。先把坏格式修好再提交保证进入版本历史的代码是干净的。如果连续几次提交都因为同一个文件中断不要急着--no-verify。先看是不是这个文件本身有特殊格式需要排除或者某个钩子的规则和项目实际情况冲突。此时应该调整配置而不是绕开检查。4.3 一个代理类格式修复的完整示例用一个简单的 Python 缓存代理类来演示。提交前如果代码长这样class UserServiceProxy: def __init__(self,service):self._serviceservice;self._cache{} def get_user(self,user_id): if user_id in self._cache: return self._cache[user_id] userself._service.get_user(user_id);self._cache[user_id]user;return userpre-commit 里的 ruff-format 会把这段改成class UserServiceProxy: def __init__(self, service): self._service service self._cache {} def get_user(self, user_id): if user_id in self._cache: return self._cache[user_id] user self._service.get_user(user_id) self._cache[user_id] user return user改动本身不复杂但如果你是手工处理每个文件都要过一遍很容易漏。交给钩子之后提交时的格式问题被自动吸收不用再花时间在代码评审阶段纠正缩进。4.4 什么时候可以跳过钩子pre-commit 支持临时跳过git commit --no-verify我不建议把这条路作为常规路径。临时跳过只适合两种情况一是有紧急 hotfix 需要立刻提交且改动内容与钩子的检查范围完全无关二是钩子本身配置错误需要尽快绕过它让阻塞解除同时马上修配置。如果每两次提交都要跳过钩子问题不在钩子而在配置——钩子覆盖范围太广、格式化规则和团队习惯冲突、或者钩子跑得太慢应该优先调整配置。5. 团队协作和 CI让代理代码格式不再靠人盯5.1 配置文件入库统一并锁定工具版本pre-commit 要发挥团队作用第一原则是.pre-commit-config.yaml必须入库。其次所有钩子的rev都要锁定。锁定的意义在于团队成员在不同时间安装格式化结果一致CI 和本地的检查结果一致升级某个钩子版本时能单独看到 diff。不要依赖“最新版”这种隐式约定。如果团队里有人用 Windows有人在 macOS还有人用 Linux更要依赖配置锁定。同一个工具的格式化结果在不同操作系统上偶尔会有差异锁版本能减少一部分问题但换行符和路径问题还需要配合.gitattributes处理后面会说。5.2 CI 流水线里跑同一套钩子本地钩子属于用户环境可以被用户主动跳过或删除。为了不把坏格式放进主分支CI 里建议加一步pip install pre-commit pre-commit run --all-files这一步的作用是强制检查。本地漏掉、跳过、忘记注册钩子的情况在 CI 都会被兜住。CI 流水线里跑的是同一个配置文件所以结果和本地一致。如果钩子会在 CI 里修改文件MR 中能看到独立提交由开发者确认后合入。对于代理代码这种规则明确的文件CI 强制检查的效果很好。最常见的问题是 nginx 配置或 Python 代理类在本地提交时已经被修复但 CI 用的是旧缓存或不同版本导致结果不一致。这时先清缓存、锁版本再重新跑。5.3 多语言项目里如何限定范围和避免误伤大型项目通常会混用多语言。比如前端是 TypeScript网关配置是 nginx后端工具脚本是 Python。这时可以在钩子配置里加files或exclude字段让特定钩子只处理特定目录- repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.9 hooks: - id: ruff args: [--fix] files: ^backend/.*\.py$这样 Python 代理代码只由 ruff 处理不会误伤前端文件。这种限制对存量代码尤其重要。全量格式化一个几百个文件的老仓库会产生巨大的 diff合入时容易和其他分支冲突。正确做法是先把每个子目录的格式统一再逐步放开扫描范围。5.4 存量代码与新代码的切换策略存量代码已经很久没有格式化所有文件都不可能一次通过钩子。先不要急着在 CI 里开启全量检查否则 MR 会变成垃圾堆。推荐的切换顺序第一次全量格式化后单独提交一次。后续所有新改动都走 pre-commit。CI 开启强制检查时先设置白名单或目录范围覆盖一个小模块。稳定运行一两周后再扩大范围。这个顺序能避免把所有时间花在格式清理上同时保证新增代码不会继续积累格式债。对代理类代码来说可以先选一个代理模块试点验证钩子效果再覆盖到全部代理相关文件。6. 常见问题排查和参数边界6.1 pre-commit 不生效最常见的原因是钩子没有注册。检查项目根目录ls -la .git/hooks/pre-commit如果文件不存在执行pre-commit install。如果文件存在但不运行先确认pre-commit install是在项目根目录执行的再确认 entry 路径是否正确。另一个隐蔽原因是终端工作目录不在项目根目录。pre-commit 依赖.git目录和.pre-commit-config.yaml的相对关系在子目录里直接执行git commit没问题但pre-commit install要在项目根目录执行。6.2 多个格式化工具互相打架典型场景同时配了 black 和 ruff-format。两个工具都会按自己的规则格式化 Python可能存在边界差异导致提交时一个改完、另一个又改回去循环无法结束。同一类语言只保留一个主力格式化工具。如果你想保留 lint 和 format 的分工ruff 可以同时承担两者不需要再叠 black。前端项目里 prettier 和 eslint 的冲突也类似先关掉 eslint 里的样式规则再让 prettier 负责格式eslint 负责逻辑问题。6.3 Windows 换行符和路径问题Windows 上最常见的坑是换行符。git 默认可能把 CRLF 转成 LF而部分钩子期望固定行尾。建议在仓库根目录加.gitattributes明确文本文件的行尾* textauto eollf统一为 LF 能避免很多配置类文件的格式误报。路径问题出现在脚本类钩子中如果钩子用 shell 脚本写死在 Unix 路径Windows 原生环境可能直接失败。遇到这种问题优先看钩子日志不要只看pre-commit failed这一行。6.4 钩子执行太慢怎么处理第一次运行慢是正常的因为要下载钩子仓库并创建环境。之后一般会走缓存。如果每次提交都慢先看是哪个钩子耗时长。用 SKIP 环境变量临时跳过某个钩子定位瓶颈SKIPruff-format git commit -m test这种办法适合排查不适合长期使用。长期变慢的常见原因是钩子处理了过多无关文件比如前端格式化工具扫描了整个node_modules。这时通过files、exclude限制范围效果比换工具明显。6.5 排查顺序先现象再配置再环境最后才是工具本身最后给一个通用排查顺序代理代码格式化问题也适用现象优先排查点钩子没触发.git/hooks/pre-commit是否存在pre-commit install是否执行过钩子触发但没格式化目标文件files/exclude是否覆盖目标文件文件被频繁改写多个格式化工具是否冲突Windows 上反复失败换行符、路径、shell 脚本兼容性每次提交都很慢钩子扫描范围是否过大缓存是否失效第一步看现象是报错、卡住、文件被改乱、还是格式根本没变。第二步看输入配置文件里的路径、files/exclude是否覆盖了目标文件目标文件编码和行尾是否正常。第三步看环境Git 版本、Python 版本、pre-commit 版本、依赖缓存是否损坏。第四步看参数钩子参数、rev、args是否有误。第五步才是怀疑工具本身只有在前面都确认没问题时再去查工具是否支持你的文件类型或者去找已知限制。很多看似诡异的 pre-commit 问题最后都是路径、换行符、版本不一致这类前置因素导致的。回到开头的问题用 pre-commit hook 修复代理代码格式说到底不是为格式化而格式化。AI 编码工具把写代码的速度提起来了但代码质量和可维护性还是要靠工程机制兜底。把格式修复放到提交前把规则写进配置让所有人和所有机器都跑同一套检查代理代码的格式问题就不再需要靠人工盯着。我个人更建议从最小配置开始先跑通trailing-whitespace再逐步加上语言相关的 formatter。一次不要引入太多规则先让钩子稳定跑一两周再考虑批量、团队和 CI 场景。
返回列表