AI 帮我写了 10 万行代码,然后我花两周删掉了 6 万行

发布时间:2026/7/12 18:34:52
AI 帮我写了 10 万行代码,然后我花两周删掉了 6 万行 去年我接了个外包工期压得紧客户要的东西又碎又杂。我做了个决定这回全部交给 AI 写我只负责把需求拆清楚、把跑起来的结果验收了。三个月下来项目交付了功能也都通了。代码量我数过——10 万行出头。但等客户验收完、我准备交接的那天晚上我想再读一遍核心模块改个边界 bug打开文件夹直接懵了。10 万行我一个字都看不进去。不是看不懂是根本找不到北。每个文件都长得差不多每个函数都似曾相识改一行不知道会扯动哪里。那周我干了一件很蠢但很必要的事我花两周把那 10 万行删到了 4 万行。不是重写是删。删到功能还在、测试还在但人能读了。这篇文章不聊「AI coding 多厉害」聊点反方向的——AI 写出来的代码到底是怎么膨胀成这样的以及我后来怎么跟它相处。膨胀不是 bug是 AI 的出厂设置先说结论AI 生成代码爱膨胀不是因为它笨是因为它的优化目标和你不一样。它优化的是「看起来把这件事做完了」你优化的是「这东西三年后还有人能改」。这两个目标天然冲突。给你看一段我删之前的核心工具函数。这是当时 AI 写的「安全获取数据」def safe_get_data(req): try: if req is None: return None if req.params is None: return None if data not in req.params: return None data req.params[data] if data is None: return None if not isinstance(data, dict): return None if id not in data: return None if data[id] is None: return None result process(data) if result is None: return None return result except Exception as e: log_error(e) return None二十多行全是防御。防御本身没错但这函数把「取值」和「错误处理」揉成了一团浆糊。任何一个上游改动你都得顺着这堆 if 往下读。我把这类函数删完之后版本是这样的def safe_get_data(req): return req and req.params and req.params.get(data) and process(req.params[data])一行。功能一模一样边界行为一模一样因为它本来就没有那么多分支。AI 当时写那二十行是因为它每次生成都在「假设最坏情况」而最坏情况从来没发生过。六种我一眼就删的膨胀模式两周删 6 万行重复的模式就那么几种。说几个最典型的你以后让 AI 写代码写完直接搜这几个词能省一半工期。第一种复制粘贴的错误处理的。十个函数九个长得一样的 try/except 包着 log_error return None唯一的区别是日志里多写个函数名。这种我全删了换成装饰器一行注解解决。第二种过度抽象的「通用工具」。AI 特别爱造抽象——为了一个只用一次的逻辑它先写个基类再写个工厂再写个策略枚举。我有个文件叫BaseHandlerFactoryImpl里面就一个子类在用。这种抽象不是设计是假想敌。直接用具体实现删掉那层壳。第三种重复造轮子的。同一个项目里我能找到三套 JSON 解析、两套日期格式化、四套「深拷贝」。AI 每开一个新文件就当它是一片孤岛不读你已有的代码。统一成一个工具模块之后光这块就砍了两万多行。第四种永远走不到的 else 分支。AI 写 if 喜欢配一个 elseelse 里写一句raise NotImplemented或者返回空字符串。你问它为什么有 else它说「为了健壮性」。健壮性个鬼那就是占地方。第五种注释比代码还长的。一段十行的函数上面飘着三十行中文注释逐行解释这十行在干嘛。代码能自解释的注释纯属噪音真需要注释的它又不写。第六种写了没人调的。我跑了一次覆盖率发现 15% 的文件零调用。AI 在加功能时顺手补的「备用工具」「示例模块」全成了死代码。删的时候毫无心理负担。真正让我后怕的不是多是假删代码最怕的从来不是「多」是「看着对其实错」。那 10 万行里最危险的一类问题是 AI 非常擅长写的「 plausible 错」——逻辑读起来完全通顺跑起来也不报错但语义是反的。举个例子。我有个统计模块要算「过去 7 天的活跃用户」。AI 给的版本是active_users sum(1 for u in users if u.last_login now() - timedelta(days7))看着没问题吧我交接前跑了一次真实数据核对发现数字对不上。问题在哪——last_login是「最后一次登录」但用户可能中间活跃、后来没登。这个口径算出来的是「近 7 天登录过的人」不是「近 7 天活跃的人」。差一个字报表给运营看决策全歪。这类 bug 不会红不会崩会在你最放松的时候悄悄污染下游。所以我后来定了个死规矩AI 写的任何带「业务含义」的数字必须我手写一条对账 SQL 或断言先验证再往上游接。两周删完之后我换了一套跟 AI 协作的方式删完那天我算了笔账那 10 万行里真正承载业务价值的大概就那 4 万行剩下 6 万全是「AI 为了让代码看起来完整」而产生的副产物。但从交期看那 10 万行确实比我自己写 4 万行快——前提是最后有人来收尸。现在我跟 AI 写代码的规则变了不是「它写我验」是「小步快跑 即时收尸」# 旧流程攒一大坨最后统一读 ai_write(whole_feature) - merge - 三个月后看不懂 # 新流程每次只给一个明确的小单元写完立刻 review for unit in break_down(feature): code ai_write(unit, contextrelevant_existing_code) review(code) # 当场删膨胀、补断言 assert passes_test() commit()关键改动有两个。一是把任务拆到「一个函数能讲清楚」的粒度别让它一次产出五千行的文件。二是给它看上下文——把项目里已有的工具模块贴进 prompt明确告诉它「日期格式化用 util/date别自己写」它造轮子的概率直接降一半。还有一条最反直觉但最管用删比写重要。我现在每次让 AI 加功能先问自己「这能不能替代掉已有的某段」而不是「再补一段」。代码库的总量是它的健康度不是成就感。最后说点实在的别把「AI 帮我写了 10 万行」当成战绩。代码量不是资产是负债每一行都要有人养。我那两周不是浪费是把三个月欠的债一次还清。如果你也在用 AI 疯狂产出给你一个今晚就能做的动作挑一个你最看不懂的文件跑一遍覆盖率把零调用的函数列出来删掉。你会吃惊地发现项目不但没坏反而变轻了。我那个外包项目后来交接得很顺对方工程师接手第一天就说「这代码比想象中好读」。我想这大概是我那两周删 6 万行换回来最值钱的一句话。为什么团队比个人更容易炸我那 10 万行还是一个人喂出来的已经够乱了。如果你在团队里几个人各自用 AI 并行写膨胀是平方级增长的。因为没人统一抽象层A 写了 util_aB 写了 helper_b功能一模一样谁也不服谁最后两个都留着。我后来给团队定的规矩特别简单AI 生成的新代码PR 里必须标注「这是 AI 生成的」reviewer 的第一反应不是看它对不对是看「能不能用已有的东西替换掉」。这条规矩推行一个月我们代码库净增长从每周两万行掉到了三千行功能量反而上去了。还有个坑是 AI 特别爱在 PR 里写漂亮的 description把没做的事也写得像做了。我吃过一次亏它说「重构了鉴权模块并加了单测」我扫了眼描述就合了结果单测是空的就一个assert True。从那以后AI 写的东西测试我至少要亲手跑一遍描述一个字都不信。工具上我踩过的一个具体雷顺便说个技术细节省得你重蹈覆辙。AI 特别喜欢在 Python 里用可变默认参数当缓存比如def get_cache(key, _store{}): if key not in _store: _store[key] expensive_compute(key) return _store[key]单测跑着好好的一上并发就串数据——因为那个_store是模块级的所有请求共享。我那个服务线上出过一次串号事故两个用户看到了对方的数据。改成functools.lru_cache或者用显式的缓存对象之后才稳。这类「单测通过、并发翻车」的坑是 AI 代码最高发的一类因为它是按「常见写法」拼的而不是按「你的运行时」想的。所以我现在对 AI 代码的验收除了读还有一个硬动作凡是有状态的东西、有共享默认值的东西、有异步的东西一律单独写个并发小测压一遍。成本是一次多花十分钟省的是一次线上事故。