Files
hoelee-blog/src/content/posts/zh/why-500000-lines-of-commit-churn-only-left-230000-in-production.md
T

8.5 KiB
Raw Blame History

title, description, pubDate, category, blueprint, blueprintLayer, tags, ogImage, banner
title description pubDate category blueprint blueprintLayer tags ogImage banner
30 天提交 51.8 万行,生产环境只留下 23.5 万行 过去 30 天,自主编程 Agent 在 5 个仓库中累计新增了 51.8 万行代码,但最终留存仅 23.5 万行。为什么在 AI 时代,验证闸门与代码剪枝比生成速度重要十倍? 2026-10-18 engineering ai-agent-automation L2-llm-gateway-agents
ai
git
devops
testing
refactoring
/og/zh-why-500000-lines-of-commit-churn-only-left-230000-in-production.jpg /banners/why-500000-lines-of-commit-churn-only-left-230000-in-production.jpg

上周我做了一件平时几乎不会做的事:数了数自己的提交。不是数我记得写过的那些,而是数仓库里真实记录下来的那些。自建 Gitea 上 5 个生产仓库、三十天、一个人:865 次 Commit、新增 518,233 行、删除 130,892 行。

然后我数了数活下来的部分。而对照最终留在生产分支上的有效代码:主生产项目收敛在 234,976 行(期间新增 33.1 万行、淘汰 9.9 万行);同一时间窗口里累计写入 58,240 行的视频转码器,今天整个仓库只有 43,050 行。5 个仓库里被淘汰掉的那 13 万多行(以及未进入主干的十多万行草稿)进哪儿去了?没有丢。它们被写出来、跑过测试、被判为不对,然后删掉——或者被一个更小、做同一件事的东西替换掉。我想把这条循环记下来,因为它这一年对我工作方式的影响,比任何一次模型升级都大。

一开始我不信的这几个数字

以下是 2026 年 9 月 17 日至 10 月 17 日,名下 5 个活跃仓库的精确 Git 统计:

仓库名称 30 天 Commit 累计新增 (+Add) 累计删除 (-Del) 净变动 (Net) 当前存量有效代码/文本 核心技术栈
numerology-report 432 +331,672 -98,937 +232,735 234,976 PHP, Markdown, JSON
hevc-transcode 187 +58,240 -15,508 +42,732 43,050 Python, HTML, Shell
dsm-resource-management 132 +58,234 -14,076 +44,158 368,793 Python, TypeScript, Astro
hoelee-blog 88 +23,603 -709 +22,894 55,933 Astro, TypeScript, Markdown
hoelee-agent-dist 26 +46,484 -1,662 +44,822 (Dist) Python, Shell, Markdown
合计 865 +518,233 -130,892 +387,341 — —

其中两行你可以自己复算,这就是完整的量法:

git -C numerology-report log --since=2026-09-17 --until=2026-10-17 \
  --numstat --pretty=format: | awk '{add+=$1; del+=$2} END {print add, del}'

在人类团队里,这样的更替量通常意味着出事了:需求在代码底下不停移动、有人偷偷在重写、架构反复塌陷。这几种情况我一条都不占——这 5 个仓库都是我每天在跑、离不开的东西,每一次提交都发生在能读到 diff 的会话里。

生成成本变低之后,这种更替量就是重构的样子:Agent 起草一段改动,我跑闸门,闸门不同意,这段就被丢掉或者改小重写。凡是闸门写对的那几天,代码总量往下掉得比功能上来的速度还快。

Agent 实际接手的是什么

大多数人第一次让 AI 写代码,是在一个浏览器标签页里:把函数粘进对话框,再把答案拷回编辑器。这条路的瓶颈是剪贴板,而且模型离真正重要的两样东西都很远——仓库自己的约定,以及运行它刚写出来的东西的能力。

所以我的是跑在终端里、跑在这台机器上的,手里有真工具:

  1. 文件与语法工具:直接读已有模块,用模糊匹配打补丁而不是整文件覆盖,保存前先过语法与 AST 校验。
  2. 测试与执行器:PHPUnit 单元测试、pytest 套件、无头 Chrome 探针,在前台跑起来并把输出读回来。
  3. 版本控制上下文:读 git diff、显式暂存指定路径、翻一个文件的历史看上一个决定为什么这么做。

这套组合很快,比我亲手打第一版快一个数量级;但它本身也是让代码库腐烂得更快的方法——这一半通常没人提醒。

臃肿是从哪里长出来的

不受约束的模型天生爱写代码。给它一个细小的边界情况,它的第一反应是新建一个私有辅助类、把一个已有工具函数复制一份改个参数,或者给一个库套上三层抽象——那三层只有在这段 diff 里才讲得通。放它自己跑一周,就是几千行语气笃定、看起来合理、其实没必要的代码。

我这么讲是因为我自己的历史里就有那一周。每天读这些 diff,最后落成两条规矩:

  • 新建一个类,要跟一个我本来就有的标准库调用竞争位置。
  • 生成出来的东西默认有罪,直到某个确定性检查说它没问题。模型说「看起来对」不算证据,红的测试才算。

真正把代码删掉的是三道闸门

在最近一次主生产系统的 V2 架构重构中,三十天里删掉了 98,000 多行。这些几乎不是我坐下来专门去删的,而是三道闸门不断把回来的东西打回,最后留下的就是更小的版本:

1. 用确定性脚手架,而不是问「这段看着对不对」

让模型审查自己写的代码,多数时候产出的是自信,不是正确。有用的做法是把它接到一个没法被说服成绿灯的东西上:

  • PHP 应用里,所有公开路由参数必须带显式默认值,由专用的反射探针(ControllerAutoRouteSafetyTest.php)守着——这个测试存在,是因为一条裸路由在生产上真的 500 过。
  • 报告生成管线里,无头 PDF 解析器先数一遍渲染出的汉字数量,数不对就不许构建通过;这样字体或排版改动如果悄悄漏字,就是构建失败,而不是发出一份坏 PDF。
  • 转码队列里,台账比的是精确字节数而不是 MB 估算,因为圆整过的数字把一次体积回退藏了好几个星期。

闸门红了,Agent 要么改到过,要么把这段撤掉。没有任何东西能豁免。

2. 把剪枝当成日常动作,而不是一次大扫除

每次会话都会分出一段时间专门删东西:这个 200 行的辅助类,能不能换成一行已有的原生调用?这个新的映射层,是不是在重复 app/Libraries/ 里放了一年的某个函数?上面那 13 万行删除不是重写的副产品,而是活过头了的脚手架、只有一个调用方的迁移层,以及换了名字出现两遍的同一段逻辑。

3. 确定性的活留给确定性代码

我在 AI 项目里反复看到的坑,是让模型去做它根本不该碰的计算。如果一条 Shell 管道、一个 SQL 约束、一个幂等脚本或者一个 Cron 任务就能给出答案,那 LLM 就不该出现在那里。

模型擅长起草初始实现、生成三语词库、摸清三方 API 的脾气;但状态流转、钱、队列调度,我不会交给它——一个确定性问题上概率性的答案,是一颗带延时引信的 bug。

我的工作方式变了什么

我职业生涯的大部分时间里,物理瓶颈是打字速度和记语法。产出就是指标,一个高效的一周看起来就是新建了很多文件。

现在语法是免费的。我可以一边读上一次改动的 diff,一边让 Agent 起草一份带单元测试的 500 行迁移。于是瓶颈转移到了模型替我看不了的三件事上:

  • 边界设计:模块从哪里分开,哪些状态允许跨过去。
  • 验证体系:那个能抓住类型检查看不见的语义漂移的脚手架。
  • 剪枝的决断力:愿不愿意在 51.8 万行提交中砍掉 13.1 万行已落盘的冗余代码与无数中间草稿,让主生产系统精简收敛在 23.5 万行。

所以我现在不再把提交曲线当生产率图表看。一个月新增 50 万行,只能说明工具在转;真正说明这一个月值不值的是那删掉的 13 万行、底下的重构,和一套仍然全绿的测试。

需要一套能长期不倒的系统?

我运营 Hoelee Enterprise,为马来西亚中小企业做网站设计与系统开发。上面这套 Agent 工作流、测试脚手架和自托管基础设施,就是我给客户交付并长期维护系统时用的同一套东西。

如果你需要全栈系统研发、业务流程自动化,或者能扛住自己维护周期的自建基础设施: