解决方案
在 Ramp,一个由机器学习工作流组成的 monorepo 已从一个方便的共享空间,演变成团队口中的“有点像沼泽”:一个 requirements.txt 里全是未锁定版本的传递依赖,在不同机器之间漂移不定;格式不一致;没有自动化测试;类型注解要么缺失,要么存在细微错误。工程师们越来越不敢碰这些代码,生怕弄坏什么,而最初的目标——开发速度——已经慢到爬行。
团队没有重写这个仓库,而是在原地引入了四个工具:poetry 用于锁定、可复现的依赖;ruff 用于格式化和 lint;pytest 用于测试高杠杆的共享工具,而不是追求完整覆盖率;mypy 用于渐进式类型检查。让这套方案能在一个活跃代码库上落地的诀窍,并不是先把所有问题都修好。他们在整个仓库上跑了一次自动格式化,作为一个单独的提交,然后把该提交的哈希加入 .git-blame-ignore-revs,这样它就不会污染 blame 历史。
对于 lint 和类型检查,他们没有在开启 CI 之前先修复数百个既有错误,而是为每一处现有违规机器生成“# noqa”和“# type: ignore”注释,然后对任何新代码启用硬性强制执行。Ruff 的“RUF100”规则和 mypy 的“warn_unused_ignores”会标记出那些在底层代码实际修好后已经过时的抑制注释,因此忽略列表只会缩小,绝不会悄悄腐烂。
结果是一个从未靠命令变得“干净”、但从第一天起就对新代码强制执行的代码库,而随着开发者自然地改动旧文件,债务也在不断缩小。
取得了什么成果
豁免旧有违规,只对新代码强制执行
为什么有效
全面重写或大爆炸式清理会耗费数周,之后才能开始任何强制执行,因此团队选择向前强制执行,而不是追溯性地清理。
机器生成初始的抑制注释,把无界的手工清理变成了一次性、可自动化的步骤。
标记未使用的抑制注释意味着债务只会减少,因为修复一处旧违规就会暴露出一条如今毫无意义的忽略注释,而 CI 会抓住它。
把重新格式化压缩成一个提交并将其排除在 git blame 之外,保留了查看某一行最后一次因真实原因被谁改动的能力。
可以借鉴什么
你不需要先有一个干净的代码库才能对其强制执行标准。一次性让所有现有违规静默,只对新代码严格强制执行,而随着人们本来就会改动旧文件,积压的问题会逐渐缩小。
后续
团队报告称,代码库达到了这样一种状态:质量从此向前保持:poetry 的锁文件在开发者机器和 CI 之间提供了一致的环境;ruff 在合并前抓住了重复字典键之类的真实 bug;mypy 抓住了单元测试未覆盖的调用点上签名变更导致的回归。文章把目标定位为恢复了进行大规模改动的信心,而不是工具本身,并指出团队仍在考虑把更多这类负担从贡献者转移到工具上。
从这里,追溯依据
参考来源
- Increasing velocity by modernizing a Python codebase Ramp Engineering · builders.ramp.com ·