1. 写代码变快了,但项目不一定更稳
这是一小型创业公司的真实故事。
公司有一个三人的技术团队:一个 CTO,两个工程师。他们做的是一款 B 端 SaaS,核心是一套“自动化合同审查”功能——客户上传合同 PDF,系统拆解条款、识别风险、生成审查意见。
三个月前,他们决定全面拥抱 AI 编码。三个人都装上了 Cursor,CTO 还在自己的环境里加了 Claude Code 和 Codex 做交叉验证。前两个月,他们的产出速度让人咋舌——以前两周才能做完的功能模块,现在三天就能产出可演示的版本。
然后,从第二个月末开始,团队的站立会议出现了一个固定的环节:有人会说“我刚发现 XXX 文件里其实已经有一个做这件事的函数了”。
第一次出现时大家笑笑就过去了。
第三次出现的时候 CTO 开始认真听——这周 Cursor 写的合同分类逻辑,和上个月 Claude Code 写的某个 helper 函数功能基本重合,只是命名风格完全不同。一个叫 classifyContractType,一个叫 detect_doc_category。
第五次出现的时候,CTO 决定花一天时间扫描整个代码库。结果他发现:
三个不同的地方都实现了“从合同 PDF 里提取金额”——一个用正则,一个调了某个 OCR 库,一个写了个 LLM 提示词; 错误处理风格在不同模块里完全不同——有的吞掉异常返回 None,有的抛RuntimeError,有的返回错误码;数据库连接的获取方式有两套并存的模式:一种用全局连接池,一种每次创建新连接; 早期定下的“所有外部 API 调用都要带 timeout”的约定,在最近两周新写的 12 处 API 调用里只被遵守了 4 次。
每一段代码,孤立地看,都是合理的。每一段代码,加上对应的测试,都能跑通。每一段代码,如果你单独问 Cursor“这段写得对不对”,它都会告诉你“看起来没问题”。
但合在一起,这个项目内部正在以每天可见的速度变成一座迷宫。
2. AI 提升局部效率,但不等于整体架构正确
CTO 把这个现象在团队内部讲了一遍。两个工程师听完都点头——他们也感觉到了——但接下来的反馈则是:
“可能是我们 prompt 还不够细。我下次让 Cursor 先读一下整个项目结构再写。”“我们应该写一个更好的 CLAUDE.md,把所有约定都列进去。”“也许换 Plan Mode 会好一些?或者上 AGENTS.md?”
每一个建议都在试图通过更多上下文、更好提示、更智能的工具来解决问题。
CTO 没有当场反驳,他心里清楚一件事:这不是上下文不够的问题。如果上下文是问题,那么把整个 100 万字的 codebase 塞进一个 200K 上下文窗口就该解决问题——但他直觉知道这不会解决。
他甚至怀疑,把上下文塞得越满,问题反而越糟糕。因为那时候 LLM 只会被更大量的“看起来合理”淹没,更难从海量信息里挑出真正应该作为约束的少数几条决定。
他需要的不是更多上下文。他需要的是一种新的方式,把这个项目“该长什么样”显性地、不可绕过地、可被验证地放在系统里。让每一次代码生成、每一次 review、每一次重构,都不得不与之对照。
那天晚上,他在白板上写下一个问题:
如果上下文不是答案,什么是?
我并不认为 AI Coding 的价值被高估了。恰恰相反,它对局部开发效率的提升非常真实。
它可以更快地生成 CRUD 代码,更快地补测试,更快地修复报错,更快地改 UI,更快地根据已有模式完成相似模块。换句话说,AI Coding 非常擅长在一个既定结构里继续向前推进。
但问题也出在这里:它默认接受“既定结构”。
如果这个结构本身是错的,AI 很难会主动停下来问:这个系统是不是应该换一种建模方式?这个对象是不是不应该属于这个模块?这个流程是不是根本不应该被设计成流程?
3. 局部效率不等于整体架构正确
在AI Coding 的时代,一个很容易被忽略的事实是:局部代码生产效率提高,并不等于整体架构质量提高。
一个函数可以被 AI 改得更短,一个组件可以被 AI 拆得更清楚,一个接口可以被 AI 补上类型和测试。但如果系统的核心对象识别错了,模块边界切错了,业务关系被错误地压缩成流程,那么这些局部优化只会让问题变得更隐蔽。
最危险的情况不是代码看起来很乱,而是代码看起来越来越整洁,但系统越来越不像业务本身。
这也是为什么那个合同审查 SaaS 项目会让 CTO 感到不安:他看到的不是单点 bug,而是系统的形状开始分裂。
一处合同分类逻辑叫 classifyContractType,另一处类似逻辑叫 detect_doc_category;三处金额提取逻辑分别用正则、OCR 和 LLM 提示词;外部 API timeout 约定明明存在,却在新代码里被反复遗漏。这些问题单独看都不致命,但它们共同指向同一件事:项目已经没有一个稳定的、可被所有协作者共同遵守的结构中心。
更要命的是,这些局部问题并不会像语法错误一样立刻报错。它们不会触发测试失败,也不会被 lint 工具捕获。它们只是让每一次后续修改都变得更慢、更绕、更依赖某个“还记得当初为什么这样做”的人。
这正是 AI Coding 时代最隐蔽的风险:AI 可以让每一个局部修改都看起来合理,但它不会自动保证这些局部合理能组成一个整体合理的系统。
4. 代码债 vs 架构债
过去我们熟悉的是“代码债”。代码债通常指的是:代码可读性差、重复逻辑多、命名混乱、测试不足、边界不清晰、异常处理粗糙。
这些问题当然重要,但它们多数发生在局部代码层面。传统技术债的常见信号——长函数、深嵌套、重复代码——可以被静态分析工具识别。SonarQube、ESLint 这类工具能告诉你:“这里有个 50 行的函数。”
而我最近更关注的是另一种债:架构债。
架构债不是代码不整洁,也不是测试覆盖不足,而是代码结构与领域结构之间的长期错位。当一个系统每次局部重构都变得更“干净”,但整体却越来越难承载真实业务变化时,通常意味着架构债正在累积。
这类债不一定表现为“代码差”。恰恰相反,它经常表现为“每一段代码单独看都还不错”。真正的问题在于:它们合在一起不像一个系统,而像若干个不同上下文里生成出来的局部答案。
例如,项目早期约定“金额计算统一用 Decimal”。三个月后,某个统计模块里出现了这样的写法:
python# 三月之前的代码from decimal import Decimaltotal = Decimal("0.00")for item in order.items:total += item.unit_price * item.quantity # unit_price, quantity 都是 Decimal# 三月之后某次 Cursor 生成的代码total = 0.0 # ← 这里for item in order.items:total += float(item.unit_price) * item.quantity
第二段代码各项独立看都合理,测试也能通过——但它违反了项目早期定下的约定。开发者让 Cursor 写代码时,没把这条约束塞进上下文。
再比如,项目早期约定“所有外部 API 调用必须带 timeout”。CLAUDE.md 里那条规则并没有消失,但 LLM 不是每次都“看见”它——它的关注度对长 markdown 文件是非线性衰减的。
python# 三月之前response = requests.get(api_url, timeout=10)# 三月之后response = requests.get(api_url) # ← 这里
这类问题的特征很明确:违反约束的代码单独看都合理,你不会说“这写得糟糕”,你只会说“这跟我们之前定的不一样”。每一次违反都有局部理由——这个 endpoint 是内部的、这个统计模块只跑一次、这个超时设置不重要。违反不会触发任何报错、任何测试失败、任何 lint 警告,它只能被对原始约束有记忆的人识别。
所以,违反的检出依赖人。而人会忘、会换、会休假。
另一个典型表现是风格漂移。同一项目里的代码、文档、产出物呈现出明显的风格不一致——好像由若干个不同审美的工程师并行写成,但实际上可能是同一个 AI 协作者在不同会话、不同上下文下产出的结果。
比如,同样是“找一个对象”,三个模块里可能同时存在三种错误处理风格:
python# auth_service.pydef get_user(user_id):try:return db.find_one({"_id": user_id})except Exception:return None # 吞掉异常返回 None# order_service.pydef get_order(order_id):order = db.find_one({"_id": order_id})if not order:raise OrderNotFoundError(f"order {order_id} not found") # 抛异常# payment_service.pydef get_payment(payment_id):payment = db.find_one({"_id": payment_id})return (payment, None) if payment else (None, "not found") # 返回元组
三种“找一个对象”的写法在同一项目里并存,任何调用方都得先看签名才知道怎么处理。
函数命名也可能混乱:
textgetUserData ← camelCasefetch_user_info ← snake_case + 名词-名词user_loader ← 名词-erload_user ← 动词-名词
这些问题不是某一处代码差,而是“放在一起看让人不舒服”。新人入职理解代码库的速度明显下降,reviewer 在看一份新 PR 时,经常要先“切换到这块代码的风格上”才能开始读。
风格漂移的根因不是“AI 不懂得保持一致”,而是“什么是一致”这件事在项目里从来没有被显式定义过。前 AI 时代,一致性靠团队成员之间的默契、靠 senior 对 junior 的传帮带、靠 code review 的隐性纠正。这些都是隐性的、口口相传的、缓慢形成的。
AI 协作打破了这种缓慢机制——它的速度让“默契”来不及形成,它的隐身让“传帮带”无对象。
5. AI 为什么会放大架构债
AI 为什么会放大架构债?
不是因为 AI 写代码一定差,而是因为 AI 太擅长在已有结构中继续工作。
当你给它一个已经存在的项目,它通常会先理解当前文件结构、当前命名、当前接口、当前调用关系,然后在这个基础上完成任务。这对小修改非常有效,但对架构判断很危险。
第一,AI 往往会尊重现有结构,而不是质疑现有结构。
第二,AI 更容易解决“眼前报错”,而不是追问“为什么这个对象会出现在这里”。
第三,AI 会让局部修改变得太便宜,导致团队更愿意继续打补丁,而不是停下来重建模型。
过去,一个错误架构之所以还没扩张太快,是因为人写代码慢,修改成本高。现在 AI 把局部修改成本降下来了,错误架构反而可能扩张得更快。
传统技术债的产生路径通常是:开发者知道最佳做法,但出于赶进度选择了次佳做法,并且记下来“这里以后要改”。
AI 时代的债不一样。开发者通常不知道漂移正在发生——AI 协作者写出的代码看起来都合理,测试都通过。等到漂移积累到可见时,已经过了几个月,而源头却早已无从追溯。
这意味着:传统技术债的偿至少还有“偿还计划”——像TODO 注释、ticket、某个工程师的口头承诺;而AI 时代的债则没有,因为没有人记下来“这里缺了什么”。
在“古法编程”时代,代码是人写的。人写代码的速度有限,这意味着每写一段代码,人都会无意识地调用自己的项目记忆——“这个项目我们以前是怎么处理 X 的?”这种记忆调用是免费的、自动的、几乎无成本的。
AI 协作改变了这件事。LLM 没有“项目记忆”——它有的只是“上下文里现在塞了什么”。当上下文不包括某条约束,该约束就不存在于 LLM 的判断中。这意味着:决策漂移在“古法编程”时代主要发生在人换岗、新人加入这种时刻;在 AI 时代,每次 LLM 的调用它都有可能发生。
这样的频率也从月级变成了秒级。
更大的上下文窗口也不是根本性解法。上下文窗口扩大,信噪比会同步劣化。把整个 100 万字的 codebase 塞进 1M 上下文,LLM 看到的是 100 万字的“原料”,不是 100 万字的“约束”。它仍然不知道哪些是必须遵守的,哪些是历史遗留可以打破的,哪些是反例不该照抄的。
更好的提示词与规则文件也有边界。即使你把“所有 API 调用必须带 timeout”写进 CLAUDE.md 的第一行,LLM 仍然可能忘记。提示词只能管“做什么”,管不了“为什么这样做”。CLAUDE.md 可以告诉 AI“用 Decimal”,但不能让 AI 真正理解“为什么这个项目对精度敏感”。
更严格的 Code Review 也不足以兜底。AI 的产出量已经远超人类的 review 容量。过去工程师一周写 500 行代码,你能仔细 review;现在他用 Cursor 一周提 20000 行代码,你甚至做不到粗看。即使是粗看,这种程度的review也是抓不到结构性问题的。
而且,AI 产出的代码“看起来合理”程度高,这才是 review 的陷阱。它的命名规范、注释完整、逻辑自洽,但它的“形状”可能与你的项目的“形状”不一致。这种不一致需要架构层的判断才能识别,而这种判断比“逐行看代码是否正确”要难得多。
Code review 的核心是“对比标准与产出”——但如果项目从来没有显性的标准,review 就会退化为“看代码顺不顺眼”。标准飘忽,review 之间也无法对齐。
6. 一个项目为什么越改越乱
再看一个更具体的例子:沉默重构。
某 SaaS 公司用 Cursor + Claude Code 做日常开发。某周一早晨,某个工程师让 Cursor 帮她修复一个 bug——bug 报告是“日历组件在某种时区下日期显示偏一天”。
Cursor 仔细分析后,重构了一个看起来重复的辅助函数:项目里有三个名字类似 formatDate 的函数,分别在三个模块里。Cursor 把它们提取到一个公共模块,删除了原来的三处。它的修改包括了 bug 修复,也就是时区处理,并把所有 formatDate 调用替换为新公共函数。
测试通过,bug 修了;code review 通过了,看起来更简洁了;merge 上线了。
三周后,生产环境报警:某个用户的“订阅到期日”显示错了——他买的是月度订阅,显示成了 yyyy-mm-dd 00:00,而原本应该是 Mar 15。
排查发现,那三个被合并的 formatDate 函数虽然名字相似,格式细节完全不同:
一个是 formatDate(date, "long")→March 15, 2025;一个是 formatDate(d)→2025-03-15;一个是 formatDate(d, locale)→ 按 locale 格式化。
合并到公共函数时,Cursor 选了一种统一格式:yyyy-mm-dd。所有调用方都用了这个。bug 修复确实做对了,但它顺手做的“改进”缺破坏了三处调用方的实际格式需求。
复盘时大家想找“为什么这三个函数当初被分开”——但没有任何文档说明。当初写代码的工程师两年前已经离职。原 PR 的 review 评论里也没有讨论这一点。这种隐性知识彻底消失了。
Cursor 没有恶意,它做了它该做的事——重构看起来重复的代码。问题在于:项目里“为什么这三个函数分开”的隐性知识从未被显性化。
这种隐性知识在前 AI 时代有几种存活方式:团队里有几个老员工记得“上次合并这个出了 bug”;代码注释里写着 DO NOT MERGE: locale needs different format;测试用例锁定每种格式的输出。
当任何一种存活方式都没有时,这种知识在下一次 AI 协作时就会被默认抹除——AI 没有理由认为这三个函数“不该被合并”。
所以你会看到一种非常反直觉的现象:短期看,代码变干净了;长期看,系统偏离了业务本身的形状,架构债累积了。
真正的问题不是“AI 不会重构”。恰恰相反,问题是 AI 太会重构了。它会主动消除看起来重复的代码、统一看起来分裂的实现、压平看起来不必要的差异。但有些差异并不是历史垃圾,而是业务规则留下的痕迹。
如果这些规则没有被显性记录,AI 就会把它们当成噪声清理掉。
7. 关注业务的“领域形状”
所以,AI Coding 时代真正值得警惕的,不只是代码债,而是架构债。
代码债让一个函数难读、一个模块难改、一个接口难测。
架构债则会让整个系统越来越难以贴近业务,越来越难以承载真实变化。
AI Coding 的真正挑战不是会不会写代码,而是系统应该长成什么样。
所谓领域形状,指的是一个业务天然存在的核心对象、关系、流程、判断点和演化方式。好的架构,不是把代码拆得更漂亮,而是让代码结构更接近领域本身的形状。
如果领域中真正的核心对象是“任务”,但代码里所有东西都围绕“订单”打补丁,架构债就会累积。
如果领域中真正的关键动作是“审核与确认”,但代码里只是把它当成状态字段,架构债也会累积。
如果领域本身是多角色、多轮反馈、多次收敛,但代码里强行把它压成一条线性流程,架构债同样会累积。
AI Coding 让我们更快地实现代码,但也让我们更快地暴露架构判断的缺失。
未来的软件开发里,真正稀缺的能力不再是“能不能写出代码”,而是“判断代码应该长成什么形状”。
这也是我想讨论“架构债”的原因。
下一篇文章,我会继续讨论《Agent 系统不是 Workflow:它更像一个组织》。如果说架构债的本质是代码形状与领域形状错位,那么 Agent 系统里最常见的一种错位,就是把一个本质上需要多角色判断、多轮反馈和持续收敛的协作系统,误建模成一条线性的 Workflow。
夜雨聆风