能“从零开发”,却不会“接盘维护”
——国产大模型开发能力亟待突破的十二个瓶颈之一
从零开发和接盘维护,是两种完全不同的体验。
如果你要看模型是否有精致妆容,就给它一个空目录;如果你想考验模型是否有深厚的内力,就给它一个历史项目。
在从零开发上,国产模型已经可以做得相当不错。做一个 React 或 Vue 页面,写一个 Python 小工具,实现一组常规后端接口,很多时候不仅速度很快,视觉和功能效果也会让人眼前一亮。
Kimi K3 刚发布的那几天,我曾经用 AVL Code 配合 Kimi K3,生成了一个老式计算机操作演示站 `cs.avlcode.cn`。整个过程的 API 计费消耗约为 200 元人民币,最后呈现出来的效果总体令人满意。这就是老张在blog中,说我向他“致敬”的页面,其实这个页面不是给老张的,真正“献给老张”的页面遇到了一个值得总结的坑,我会在另一偏瓶颈点讲。
。Moonshot 对 Kimi K3 的官方定位也已经明确包括长程编码、大型仓库导航和终端工具编排,说明国产头部模型的目标早已不再局限于代码补全。
从实际应用看,前端页面、脚本工具和相对标准的业务接口,已经足以覆盖相当一部分机构的日常数字化需求。即便进入 C++、Rust 等技术栈,只要需求边界清晰,把国产模型作为 IDE 的增强器,用来补齐代码、解释接口、查找调用关系和生成局部实现,也有很大价值。问题在于,未来衡量编程模型和 Agent 挽具的,不是传统软件工程的场景。真正的软件工程变化,是编程正在从少数人的专业技能,逐渐变成一种按需生产能力。越来越多非专业开发者会从零起手,通过自然语言生成软件,再根据反馈持续迭代。传统意义上的“程序员”和“用户”之间,正在出现一个人数更大、边界更模糊的新群体。
这个新群体与国产模型结合中,共同面对一个大坑和一座大山。
一、需要填的大坑:历史项目不是一张白纸
所谓大坑,就是历史项目的接盘。国产模型现在通常已经能够读懂相当复杂的代码。把一个仓库交给它,让它解释模块关系、梳理调用链、说明某个函数在做什么,结果未必比国际主流模型差很多。但“看懂”和“动手”之间,隔着一道很深的沟。我曾经把一款历史跨平台工具的代码和文档,分别交给国产模型和国际主流模型做理解与迁移测试。单看代码解释,两者差距并不明显。可当第一个实际任务变成“恢复技术栈和编译环境,并成功完成构建”时,差别立刻出现了。国际模型很快沿着原有技术栈完成了编译。国产模型则判断这个项目技术栈复杂、陈旧、维护价值不高,建议直接换一套新技术栈重构。这类建议从文字上看甚至很有道理。老技术栈难维护,文档残缺,构建工具过时,代码风格混乱,重构似乎比修修补补更“先进”。但接下来真正让模型执行长程重构时,它又往往驾驭不了这个过程:旧系统没有完全理解,新系统没有真正建立,接口兼容和异常路径被逐渐遗漏,最后留下一个既不能替代旧版本、也无法独立交付的新工程。
这不是个别项目的偶然。国内软件工程的发展极不均衡。一些大厂和大型企业积累了成熟的工程体系,但作坊式开发也长期大量存在。很多历史项目文档不全,代码与文档不一致,构建环境依赖某一台旧电脑,关键逻辑只存在于原作者的记忆里。还有一些代码,经过十几年人员更替和需求叠加,早已形成了只有“考古”才能解释的结构。
这些项目本身就是一个巨大的历史遗留层。
模型进入这类仓库后,很容易出现几种典型行为:
- 不理解某段“看起来很丑”的代码为什么不能动;
- 不知道现有抽象背后承担着哪些历史兼容责任;
- 放着已有工具类不用,重新实现一套;
- 绕过原有错误处理、日志、配置和生命周期框架;
- 为完成一个局部任务,破坏公共接口或模块边界。
其中最危险的,不是模型写不出代码,而是它能够写出一段**局部合理、全局错误,而且可以顺利通过编译的代码**。
案例:一个小任务,怎样变成六个死循环
这是我们协助客户进行AVL Code+某国产模型移植的一个典型案例,项目前期约 60% 的代码由 Opus 协助完成,随后转交给 某国产模型继续开发。事故涉及的文件是一个约 1600行的 `Manifest.cpp`,接盘的首个任务看起来极小:为十个 `List*` 查询函数补上 `sqlite3_reset`。
模型的理解是:这就是一个十行修改。找到十个位置,各插入一行 `sqlite3_reset`,编译,测试,结束。
但这项任务真正的形状并不是“十行插入”,而是“确认十个调用点是否符合语句缓存的所有权契约”。
这个契约其实并不隐蔽,就写在文件最前面的 120 行里:
- 缓存命中时,`StmtCache::Get` 会统一执行 `sqlite3_reset` 和 `sqlite3_clear_bindings`;
- 缓存析构时,会统一 `finalize` 所有语句;
- 数据库关闭前,`Manifest::Close` 会先释放语句缓存,再关闭数据库连接。
也就是说,调用方原则上不拥有这些 SQLite 语句的最终释放权;下一次复用时,缓存层本身也已经负责 reset。那十个调用点并不存在必须补 reset 的正确性缺口。所谓“十个函数缺少 reset”,本身就是建立在错误诊断上的一张假工单。
模型没有读这段契约。它拿到交接摘要后,直接把一个所有权问题降维成了行号编辑问题。最终十个预定落点中,只有两个位置正确;六个 reset 被插进了查询循环体;另外两个甚至落进了任务清单之外的相邻函数。只要数据库中存在一行查询结果,循环体内的 `sqlite3_reset` 就会让下一次 `sqlite3_step` 从头重新执行查询,再次返回第一行,最终形成死循环,并让结果容器持续增长。
结果是:
> 十处修改,二处正确,六处埋下死循环,两处改错函数。
更危险的是,这份代码编译通过了。
模型还运行了一个样例测试。恰好,这个测试只经过十个函数中位置正确的那个函数,没有触及六个已经受损的查询路径。于是测试转绿,模型宣布“核心功能修复完成”。
这次事故最值得注意的地方,不是模型不知道 `sqlite3_reset` 怎么用。
恰恰相反,它知道这个 API,也知道如何插入代码,能够处理编译错误,甚至能够根据链接错误临时补写一个缺失函数。
它真正没有理解的是:
> reset 的责任究竟属于调用方,还是属于语句缓存?
这才是整个任务的逻辑关窍。
如果模型先读懂了缓存所有权契约,就会发现十处修改一处都不应该做。真正需要完成的任务,只有补上另一个链接所需的缺失函数,并为它增加行为测试。
它把不需要做的十件事做了一遍,还做错了其中八件。
事故中还有一个很有象征意味的细节。
模型接盘时,工作区里残留着上一轮修改失败形成的畸形代码:重复的 `return true`、多余的右花括号,以及位置错误的 reset。模型认为这些是垃圾,直接执行 `git checkout` 将文件恢复,而且没有先 stash。
事后复核才发现,那些代码虽然编译不过,但其中保留了前任正确的修改意图——reset 原本准备放在查询循环闭合之后,只是在复制代码时把 return 和右花括号一并带了进去。
换句话说:
> 前任留下的是“语法错误但意图基本正确”的代码;接盘模型把它清掉之后,写出了“语法正确但语义危险”的代码。
前一种错误会被编译器拦住,后一种错误却可能进入版本。
这就是接盘维护最典型的风险:模型把代码库看成了一块等待自己重新书写的画布,而不是一个已经运行多年、充满历史约束和所有权关系的系统。
二、需要越过的大山:如何让更多中国使用者摆脱国外模型与工具的依赖
如果说历史代码是大坑,那么更大的那座山,是当前国内产业领域中,不得不最终摆脱的国外模型与工具供应依赖。
问题并不只是把 API 地址从一家换成另一家。
编程智能体已经深度介入:
- 项目理解;
- 架构设计;
- 代码生成;
- 调试;
- 测试;
- 文档;
- 发布;
- 版本交接;
- 团队知识沉淀。
当一个项目长期使用 Claude Code 和 Opus 开发后,模型的行为方式实际上已经进入了项目的生产过程。切换到国产模型,不只是“换一个更便宜的推理接口”,而是在更换一名熟悉项目历史、工具习惯和工作节奏的虚拟工程师。
支撑这个迁移过程比当年的 WPS 对决 Office 更困难,更低的价格、做好百分之二十的常见功能,这些打法有用,但效用不是太灵,办公软件迁移的主要问题是文件格式、功能习惯和生态兼容;编程模型迁移还多了一层:**新模型必须理解并延续旧模型参与塑造的软件系统。**
案例:模型如何以编译环境为最后的借口
我们指导客户进行工程迁移时,一家 AVL Code 客户此前也是基于 Claude Code 和 Opus开发,完成了一个万行级的客户端工具。后来,希望把后续开发迁移到某国产模型 API。
客户配置AVL Code 完成对接后,首先要求模型根据工程代码和文档检查编译环境。模型判断环境已经完备,同时发现了主机路径差异,也指出原编译脚本中的路径不一致,并成功编译出了第一个版本。
到这里,一切很顺利。但在实施一个很小的功能改进时,模型开始长时间陷入各种错误。几小时后,它给出的结论竟然是:
> “终于定位到问题,编译环境并不完整。”
但这个过程中,它莫名其妙地修改了部分头文件。
客户指出,同一个项目此前也曾在 Codex 和 Claude Code 之间迁移,主机技术栈环境同样存在差异,却没有发生这种反复推翻结论、又把环境问题转化成源码修改的问题。在之前的移植过程中,并不需要单独让Claude Code去理解环境差异,其会自然的重构编译脚本。
淡然这里是一个个案,并不是说 Claude Code 或 Codex 不会犯错,Codex的“我删”模式已经是多人都需要过的严重事故。
但我们需要看到的是而是它们在接管既有项目时,通常更能把以下几件事区分开:
- 环境问题;
- 构建脚本问题;
- 代码问题;
- 本次修改引入的问题;
- 仓库原有问题。
而能力不足或工程纪律较弱的模型,容易在这些问题之间来回漂移。一开始说环境完整,后来又说环境缺失;一开始说路径有误,后来直接修改头文件;编译失败时不断缝补,最终已经说不清自己是在修功能,还是在重造构建环境。
从 AVL Code 开发者的角度,我们当然要不断通过技术栈标准化、环境指纹、编译探针、Plugin、Skill、任务状态机和验证门禁来改善这些问题。但对于已经重度依赖 Claude Code、Opus、Codex 和 GPT 系列模型的用户来说,他们不会用“某个国产模型已经比过去进步很多”作为评价标准。
他们真正比较的是: 能否给与一套国产模型+挽具同样的心智信任。这就形成了一个双重痛苦期。一方面,国产模型需要接续此前由国际模型生成和维护的版本;另一方面,已经习惯了 Codex 与 Claude Code“双枪并用”的开发者,也必须重新调整自己的能力预期、任务拆分方式和使用习惯。
它不仅要接代码,还要接前任模型的设计决策、临时方案、命名习惯、测试空白,甚至接前任留下的错误。
当交接文档与代码不一致时,模型必须知道应该相信谁;当旧代码很丑时,必须先判断这是技术债、兼容层,还是不能动的业务规则;当构建失败时,必须区分环境异常和代码回归;当局部测试通过时,还必须知道验证面是否覆盖了修改面。
这些能力,远远超过“根据需求生成代码”。
三、 “尊重代码”不是态度,而是一套方法
回到这个系列第一篇想讨论的瓶颈,我的核心感受是:
> 国产主流模型已经越来越能看懂“代码做了什么”,但不一定能推断出“为什么当初必须这么做”。
而在历史项目中,后者往往比前者更重要。
所以,从体验上说,我很想给当前的国产编程大模型——特别是我们自己的模型——一个不太客气的评价:
会写代码,不会尊重代码。
这里的“尊重”不是拟人化的道德要求,也不是要求模型迷信旧代码,我始终反对对大模型人格化想象————这是一种人的“认知幻觉”。
尊重代码,是模型+挽具的工程方法,我们总结本文中的案例,可以看到:
1. 契约先于工单。 涉及缓存、句柄、连接、线程和对象生命周期时,先找到所有权中枢,再修改调用点。
2. 代码先于交接叙事。 上一轮说“已经完成”,不等于仓库里真的存在;接盘第一步应当是逐项对账。
3. 疤痕先于清理。畸形工作区不一定只是垃圾,也可能保存着前任尚未表达完整的修改意图;回滚前必须保全。
4. 模式验证先于编译通过。 批量修改十个位置,就要逐点检查十个位置;编译器只能证明语法成立,不能证明语义正确。
5. 验证面不能小于改动面。 修改十个函数,只运行一条恰好不触及错误路径的测试,那个绿色没有多少意义。
6. 完成声明必须建立在证据上。 编译命令、退出码、测试覆盖范围和未验证项,应当比模型的总结更可信。
这次事故复盘给出的五步防线——读契约、做疤痕考古、复核工单、验证批量落点、让测试覆盖全部改动面——其中任何一步,都足以独立终止事故链。
因此,我非常反对把国产模型仍然存在的不足简单归结为“国产模型参数不够大”。模型当然需要继续提高软件演化、因果调试和长程工程能力;但编程智能体和 Agent 挽具同样必须承担责任。模型容易忘记的约束,就不应该只写在提示词里;模型容易夸大的完成状态,就应该由证据系统裁决;模型容易随意扩大的修改范围,就应该被 Diff 预算和接口锁限制。我们不能指望靠一句“请充分理解项目后再修改”解决工程纪律问题。
四、土壤不够肥沃,但空间足够宽广
写到这里,我要再次明确这个系列不是一份国产模型的败绩清单。
中国的软件开发者从宽口径计算约千万,即使对照美国BLS的全职程序员标准统计,也可能有400万之多,已经式美国的两倍。中国拥有毋庸置疑的巨大的软件需求、开发者生态和数字化市场。过去几十年,这片土地上产生了大量优秀的软件,也留下了数量更多、质量参差不齐的历史项目。它们可能没有完整文档,没有稳定测试,没有统一架构,甚至连编译环境都无法轻易恢复。这些田地谈不上肥沃,更谈不上精耕细作。但它足够广阔。
对于国产编程大模型来说,有价值的下一步,不是再生成一个更漂亮的 Todo List,不是在空目录里更快地搭出一个网页,甚至不是去刷新一些旧的和新的榜单记录,而是走进这些真实、混乱、带着历史伤痕的项目里,把它们接住。先理解它为什么会变成今天这样,再决定什么应该保留,什么可以修改,什么必须重构。
一个模型只有在动手之前开始追问:
> 这段代码为什么还存在?
> 谁在依赖它?
> 它背后有什么没有写出来的契约?
> 我真的有必要动它吗?
它才算真正从“会写代码”,走向了“会做软件工程”。
而当我们的国产模型开始学会“尊重代码”,它就将接住中国软件产业真正庞大、真正复杂,也真正有价值的历史,也能更好、更完全的承载住它的使命。 |