GitHub Copilot 这几年的热度,恐怕不用我多说。做我们这块业务系统开发的,一开始确实被各种演示视频轰炸过,什么“十分钟重构一个模块”“AI 自动写单元测试”,看着就让人上头。我们组当时也跟风开了团队订阅,四十多人的研发小组,费用倒是小事,关键是想看看这玩意到底能不能把每个月那堆重复劳动给消化掉。
结果一个月下来,组里直接分成两派:写脚本、写 CRUD、写算法题的同事觉得它是神器;做核心业务链路、维护老模块、搞跨系统对账的同事,恨不得把它从 IDE 里卸掉。我当时属于后者,而且越用越觉得不对劲。后来我把组里的反馈整理了一下,又自己在几个典型项目上做了两周的对照实验,得出了一个可能不太讨喜的结论:GitHub Copilot 对我们这行,至少在目前的形态下,是真的没什么大用。
我说的“我们这行”,不是泛指“程序员”这个职业,而是指做企业级业务系统、传统行业软件、长周期项目的这拨人。我们的代码库不是那种清爽得可以当教材的 Demo 项目,而是动辄五到十年的老仓库,夹杂着三四套技术栈、两个时代的架构思想、还有说不清道不白的业务规则。在这个环境下,Copilot 暴露出来的问题不是“偶尔笨一下”,而是从根上就不匹配这个场景。
1. 先别急着反驳:我们这行到底需要什么
我不是在否定 AI 编程工具的价值,我有不少朋友在前端团队、算法团队、工具链团队里,他们用 Copilot 的满意度非常高。但他们的共同特征是:代码边界清晰,上下文闭环,外部依赖少,业务规则要么不存在、要么能用代码直接表达。
可我们这块完全不是这么回事。
1.1 日常工作里 Copilot 真正参与不进来的三个瞬间
我仔细复盘过自己一天的工作,掐掉了开会、扯需求、看消息的时间,剩下真正写代码的部分大概四小时。这四小时里,Copilot 能插上嘴的,基本只有三种场景:写一个独立的工具函数、补一段样板代码、调一个格式固定的接口。真正耗时间的活,它一个都帮不上。
第一个瞬间是跨模块改需求。比如我要把订单状态流转加一个中间态,这个改动涉及数据库表、缓存策略、消息队列里的三个消费者、前端两个页面的展示条件,以及一份别人两年前写的状态机文档。Copilot 在我打开第一个文件的时候,会热情地给出一段看起来完全合理的代码,但它不知道还有后面七个文件等着我改,也没法理解这次改动的边界在哪儿。
第二个瞬间是跟遗留代码搏斗。我们有个老模块是用上古时期的框架写的,连依赖都锁死在旧版本里。这个框架的写法跟现在主流风格完全不一样,Copilot 的补全模型大概率没见过这种“方言”。它给出的代码要么用了一堆当前版本根本不存在的 API,要么就是把它训练数据里的“标准答案”硬套到我们的烂摊子上,结果当然是不能用的。
第三个瞬间是处理隐式业务规则。举个例子,财务对账里有一条规矩:金额在界值附近的时候,舍入方向和银行保持一致,不能按常规四舍五入。这种规则不会写在任何接口注释里,也不会出现在代码变量名上,它只存在于几个核心开发者的脑子里,甚至只存在于一份没人维护的 Excel 规则表里。你要怎么把它提示给 Copilot?你在注释里写一百个字,它也不一定能理解你真正要什么。
这三种瞬间加起来,已经覆盖了我每天八成以上的编码时间。剩下那两成能用的窗口,说实话,我用几个现成的代码片段库就能应付,不一定非要花这个订阅费。
1.2 “ChatGPT 都会写”和“项目里能落地”不是一回事
前段时间有个刚毕业的同事跟我说,他觉得 Copilot 挺有用的,因为他把一道 LeetCode 中等难度的题目贴进去,它写得又快又好。我说你把咱们仓库里那个对账任务的入口类贴进去试试,让它在那套既有结构下把今天的月度汇总逻辑补完。他试了十分钟,回我一句“算了,它连我们自定义的事务注解是什么意思都搞不明白”。
这里面的差别,就是“生成一段可运行的代码”和“在一个真实系统里做一次正确的修改”之间的差别。前者只需要语法正确、逻辑自洽,后者需要你理解一个系统的演进史、模块间的隐性契约、以及好几年前的决策为什么长成今天这样。
Copilot 在这道题上是拿不到分的,因为它的上下文窗口再大,也不可能包含我们仓库里那一万多条 commit 所承载的决策信息。它每次给出的代码,都是基于你当前文件、你刚写的几行、加上它训练语料里的统计规律。这跟我们业务系统真正需要的“全局理解力”之间,隔着一整个仓库的距离。
2. 上下文缺失,是所有问题的总根源
后来我把组里反馈的十几条问题全部汇总到一起,发现它们本质上都是同一个病根:上下文不够。这不是说 Copilot 的模型不行,而是它在我们这种开发形态下,能拿到的上下文天然就是残缺的。
2.1 单函数有效,跨文件失效
我发现一个特别有意思的规律:凡是能在单个函数内部解决的问题,Copilot 的表现都能达到“可用”水平。比如写一个把 JSON 转成内部 DTO 的映射函数、写一段正则提取、写一个状态枚举到文案的翻译表,这些活它完成得又快又好。因为这些任务的信息都在你面前,不需要跨文件追踪。
一旦你把任务边界推到函数外面——比如让 Copilot 帮忙完成一次“从控制器到仓储层的新增接口链路”,它就开始露馅了。倒不是语法错,而是它不知道你们项目里 Controller 层的返回结构统一是Result<T>,也不知道仓储层所有方法都要手动管理事务边界,更不知道审计日志要记到哪个深度。你让它按现有风格补全,它给出的代码大概率能在别的项目里跑,但在你们项目里就是要改。
我用一个周末专门做过对照测试:同一个“用户登录后记录最近一次登录时间”的需求,分别让 Copilot 在一个新开的 Spring Boot Demo 项目里写,和在我们现有的老系统里写。Demo 项目里它一次通过;老系统里它给我补了四个方案,没有一个能跟现有登录拦截器的上下文对上的。这已经不是模型智能程度的问题了,是信息供给和任务需求不匹配的问题。
2.2 对话式助手本地化也救不了
最近我也在关注 IDE 里那些越来越重的对话式 AI 助手,什么聊天侧边栏、代码库问答、本地化索引之类的功能。听着很厉害,好像能解决上下文缺失的问题了。但实测下来,它们在 Demo 仓库里的效果好得惊人,到了真实老项目里还是只能当“高级搜索引擎”用。
原因很简单:对话式助手靠的是把仓库向量化之后做检索,它能告诉你说“你问的这个事务注解定义在某个基类里”,但它没法替你做那一步推理——“既然这个事务注解是自定义的,那新增的写操作应该沿用同一套事务边界,并且要注意这个方法是被异步代理调用的,所以不能直接注入 this”。这种推理依靠的是对系统运行时行为的理解,不是对代码文本的索引。
本地化确实比纯云端补全多了些“看得见仓库”的优势,但距离“理解一个系统”还差得远。就好比你把一本操作手册背得滚瓜烂熟,不代表你就能上手修那台被人改装过二十次的机器。
2.3 一次真实跨模块重构里的失败实录
为了不让自己显得太武断,我说一个具体案例。上个月我需要把一个老模块里的“状态判断逻辑”从三处散落的if/else收敛成一个统一的状态机。这是个典型的跨文件重构,涉及十一个 Java 文件、两个枚举、一张配置表。
我抱着试试看的心态让 Copilot 帮我写重构后的状态机核心类,并且特意在文件头写了一段很长的注释,描述了原有的各种状态、触发条件、流转规则。它生成的代码结构确实漂亮,状态定义、转换表、校验方法都有了,可一旦对照整条调用链去检查,就发现问题一个接一个:它发明的afterTransition()钩子函数我们项目里根本没有类似的扩展点;它默认同步执行的状态流转,忽略了原实现里有些操作要走异步消息队列;最重要的是,它把两个原本不允许同时成立的状态设计成了兼容并存的状态,这在业务上属于事故级别的大坑。
我最后花了三个小时手工重写,Copilot 唯一的作用是帮我生成了一堆状态枚举的样板代码——这部分我本来十分钟就能打完。这件事之后,我对它的定位就从“自动编程助手”降级成了“顺手的补全插件”,心态反而稳定了很多。
3. 代码库熵值面前,Copilot 的“干净假设”注定翻车
AI 编程工具训练用的代码库,是 GitHub 上那些开源项目的优质截面,它们普遍结构清晰、命名规范、模块边界合理。可真实世界里能活过五年的业务系统,几乎没有一个长成那样。
3.1 技术债、历史包袱和多重风格并存的现实
我们仓库里有一套老逻辑用了近十年的命名风格:类名首字母大写没问题,但方法名偶尔会有人用下划线风格,数据库字段是 snake_case,Java 字段却是 camelCase,中间还有一层不规范的 DTO 做手工映射。这种代码长什么样,大家心里都有数。
Copilot 面对这种代码时特别矛盾。它一方面会根据你的既有代码,仿出一段“看起来很像”的新代码,让你觉得它懂你的风格;另一方面,它仿的只是表层的命名习惯,仿不了那些隐藏在代码深处的债。比如说,某个老方法里有个明显是 bug 的写法,但是因为下游系统已经依赖了它的异常行为,没人敢动。这种“约定俗成的错误”是系统最重要的隐性知识之一,Copilot 不可能知道,于是它会在你重构时贴心地“修正”这个 bug,带来一次完美的线上事故。
我管这个叫“干净假设翻车”:Copilot 默认代码库是健康的、合理的、应该被规整的。但我们的老系统不是,它是一层层补丁堆出来的活物,有很多看似不合理实则动不得的东西。你需要的工具是能让你“在不惊动这些妖怪的前提下完成修改”,而不是一个看到妖怪就想把它打死的好心人。
3.2 领域词汇与内部框架,Copilot 基本学不进去
另一个很要命的地方是行业“方言”。我们内部有个框架,里面全是自定义的概念,比如“账期”“冲正”“轧差”“头寸”这类业务词,以及配套的一套内部 API。这些词在通用代码里几乎不出现,Copilot 的训练语料里当然也没有。
写代码的时候,我需要调用内部框架的PositionService.adjustByCurrency()来调整某个币种下的头寸,Copilot 经常给我换成CurrencyService.adjust()或者干脆发明一个PositionManager.updatePosition()。从代码阅读者的角度看,它写的那个方法名可能更“自然”,但在我们系统里根本不存在,编译都过不去。
有人可能会说,你可以把这些内部库的文档喂给它,或者用注释把 API 说明写清楚。可我们在一个业务模块里经常要涉及十几个内部组件,每个组件的调用规则、边界条件、异常处理策略都不同,你不可能在每个文件开头都写一本使用手册。更要命的是,内部框架版本经常变,今天这个方法还叫adjustByCurrency,下个月可能就重构改名了,你再喂一次文档?这种维护成本已经比手写代码还高了。
4. 领域知识缺失:不是“不够智能”,而是根本不进场
如果说上下文缺失是技术层面的硬伤,那领域知识缺失就是业务层面的一堵墙。这堵墙不管模型参数怎么涨,都很难被推倒。
4.1 行业合规、业务规则、隐式约束,提示词根本写不清楚
我们这类系统有一个共同特点:业务规则里掺杂着大量法律法规、行业规范、审计要求和内部风控策略。这些东西不像编程语言有清晰的语法,它们是一大堆“如果……但是……除非……”的条款。
我随便举一个例子。我们在做系统的资金划拨功能时,有一条规则是:超过一定金额的交易,必须触发双人复核,并且复核人不能是同一个团队的人;同时,如果这笔交易关联到特定的业务类型,还需要自动加送一条预警消息到合规部门。这条规则横跨权限体系、工单流、消息服务三个子系统,而且它的判定条件本身还会随着监管要求变化。
你想想,这个需求要怎么“提示”给 Copilot?把它写成一长串自然语言,它倒是能给你生成一大段代码,但这段代码你敢直接用吗?你真正需要的是在一个特定的审批服务里做改动,让它在满足条件时跳到特定的处理器。这里的难点不是“怎么写这段逻辑”,而是“怎么把它嵌入现有代码的流程里,并且保证不破坏其他判断分支”。领域知识不是写在注释里的,它存在于一个从业者几年的经验里。
4.2 幻觉比错误更可怕:看起来全对,实际全错
Copilot 这类工具最危险的地方在于,它的输出看起来太专业了。当它生成一段带注释、带异常处理、带单元测试的代码时,很容易让人放下戒备。
我遇到过一件印象很深的事。我们有个模块需要对接一个第三方支付的退款接口,这个接口有个坑:退款金额必须传分为单位的整数,而且不能带小数位。Copilot 生成的示例代码里,把金额处理写得特别漂亮——用BigDecimal、设置精度、还做了类型转换,一切都非常标准。唯一的问题是,它调用了支付 SDK 里一个已经废弃的退款方法,那个方法在最新版本里会直接把小数位截断而不是四舍五入。这要是真上线,每一笔退款都会少几分钱,日积月累,光对账就够我们喝一壶的。
这还不是最严重的。更可怕的是它在生成完整功能时,可能会自己“发明”一些不存在的接口或配置项。比如在一个配置类里,它自动补了一段config.setRetryCount(3),但这个配置项在老版本框架里根本没有。编译的时候不会报错,因为它是反射读取的;运行的时候也不会立刻崩溃,只是重试逻辑永远不生效。这种问题,你排查起来比没有代码还痛苦。
所以我的判断是:Copilot 在那种“对与错可以立刻验证”的场景里很有价值,但在“对与错由业务规则和运行时行为决定的场景”里,它产出的幻觉代码反而是负资产。它会让你多花几倍的时间去验证那些本不该存在疑问的部分,而且验证的难度往往比直接写更难。
5. 到底什么场景能给 Copilot 留一席之地
说了这么多坏话,也得公道一点。Copilot 对我们也并非百分之百没用,我现在的做法是把它从“主力”降级成“杂务助手”,只在自己完全掌控的隔离环境里用它。
5.1 边界清晰、上下文闭环的孤岛任务
我目前愿意让 Copilot 发挥的场景有三个共同特征:单文件内可完成、外部依赖少、输入输出可验证。
第一个是写一次性的数据迁移脚本。这种脚本生命周期短,跑完就扔,错了也无所谓,只要能尽快生成可用逻辑就行。第二个是写代码生成器的模板片段。比如 MyBatis 风格的 XML 映射文件,格式固定、模式统一,Copilot 的补全效率比我手写快不少。第三个是写单元测试里那些“给定条件→执行→断言”的样板代码,只要被测函数的逻辑不复杂,它生成的测试骨架大都能用。
还有一个场景也值得一提:当我不确定某段基础代码的正确写法时,会用 Copilot 生成一个“候选版本”作为参考。它生成的代码不一定能直接用,但它的思路有时候能帮我打开视野。这个用法有点像拿同事当时的草稿当灵感来源,心态对的话,它是有帮助的。
5.2 我把 Copilot 降级成“结对实习生”之后,心态反而变好了
心态转变很重要。之前我老拿 Copilot 当一个全能的自动驾驶系统,觉得它应该理解我的项目、理解我的业务,结果每次发现它不理解,就失望一次。后来我想通了,我只在两种情况下使用它:
一是它给我写我不关心的代码,比如 DTO、枚举映射、基础 CRUD 样板,这部分我本来也是机械劳动,交给它省时间。二是它给我“打样”,让我能快速看到一种写法的雏形,然后再由我根据项目实际做修正。
这个定位下,Copilot 的价值是真实存在的,但价值上限也是清晰可见的。它不是那个能让工程师从八小时变成一小时的“生产力神器”,充其量是让工程师从八小时变成七小时四十五分钟的“舒适度小助手”。这种结论可能不浪漫,但我觉得对大多数做业务系统的团队来说,这才是诚实的答案。
最后再分享一条实操经验:如果你也打算在业务项目里试点 Copilot,先别直接铺到核心模块上。挑一个隔离性好的小工具模块或者测试模块跑两周,用真实代码和真实业务规则去验证它的产出质量。两周之后你大概率会有自己的判断——要么觉得我写的这些太保守了,要么跟我一样默默把它从生产环境依赖里卸掉,然后只留下一个“补全插件”的配置。