接手公司一个维护了5年的Spring Boot老项目,JDK8+MyBatis+JSP那套。最近用GitHub Copilot做重构,发现它特别爱推荐已经废弃的API,比如还在用RestTemplate而不是WebClient,甚至生成@SuppressWarnings来绕过编译警告。我试过在对话里强调“用最新稳定版语法”,但感觉它更吃上下文里的旧代码风格。想问下大家,是应该建个.github/copilot-instructions.md写清楚技术栈版本,还是直接在代码里多用新写法“喂”它?另外,遇到它生成的代码和项目现有工具类冲突时,大家是硬改还是干脆只让它写单元测试?有点纠结,怕重构完反而埋雷。
Copilot在重构老项目时总给出过时API,怎么调教它?
全部回复
共 73 条这问题我太有感触了,Copilot本质是个“上下文模仿大师”,你项目里旧代码占多数,它就会默认那是“标准答案”。.github/copilot-instructions.md确实有用,但别指望它一锤定音,我试过在里面写“禁止使用RestTemplate”,结果它还是偶尔抽风,后来发现得配合代码里的实时注释,比如在Controller上方写// use WebClient for non-blocking IO,它立刻老实很多。至于新写法“喂”它,见效慢但治本,我一般是重构到哪个文件,就顺手把那个文件里典型的老API改成新写法,等于给它现场示范。跟项目工具类冲突那块,我的原则是绝不让它碰核心业务逻辑,让它写单元测试或者DTO转换这种边角料最省心,硬改它的代码往往比手写还累,而且它生成的工具类通常只顾自己爽,完全无视你们封装好的RedisUtil或者JsonUtil。还有个土办法,把项目里那几个被你重构过、风格干净的文件设成“参考文件”,在对话里@它们,有时候比写一堆规则管用。
我最近也碰上这问题,Copilot对老代码库的“路径依赖”特别强,你越改它越跟着旧风格走。建议两个都做,但先建个copilot-instructions.md把JDK版本和禁用的API写死,比你在对话里吼十遍都管用。至于冲突,我一般只让它写测试和纯逻辑片段,涉及项目内部工具类就直接手写,不然来回改更费时间。你试过在重构前先跑一遍“用WebClient替换RestTemplate”的单独对话吗?有时候把上下文切干净反而更听话。
这问题我太有共鸣了,之前接手一个JDK8的Struts2老项目,Copilot简直像考古现场挖出来的助手,满屏都是Hibernate 3那种写法。我的经验是,靠对话里喊“用最新版”基本没用,模型对上下文里的代码风格权重极高,你项目里旧代码越多它越往那边靠。copilot-instructions.md我试过,有点效果但有限,尤其对API版本这种细节,它经常忽略,不如在项目里新建一个现代化的参考类或者工具包,让它有“新鲜血液”可学。至于和现有工具类冲突,我一般不让它硬改,而是让它只写测试或者写适配层,这样风险可控,毕竟它生成的代码你还要花时间review,不如把精力放在它擅长的样板代码上。另外提一句,Spring Boot老项目升级本身就得人肉决策,Copilot当个打字员还行,架构层面的判断别指望它。
我一般只让它写测试和注释,重构还是自己来,省得来回改更费劲。
老项目就这样,Copilot默认会跟着上下文里的旧代码走,光在对话里说“用新版”基本没用。我之前是在项目根目录放了个copilot-instructions.md,把JDK、Spring Boot版本和禁用API列清楚,效果比嘴上强调强不少。工具类冲突那块我一般不让它硬改,直接圈定范围让它补测试或者只重写单个方法,不然它能把好好的封装全给你绕过去。
老项目重构这个坑我也踩过,Copilot确实会跟着上下文里的代码风格走,你光在对话里说“用最新语法”基本没用,它更信你文件里实际长什么样。copilot-instructions.md可以写,但别指望它百分百听话,我的经验是配合着来:先把要重构的那个模块用新写法手写一小段,让它照着这个模式往下补,效果比光写说明文档好不少。至于它推荐RestTemplate这种,其实也不完全是过时,JDK8加老Spring版本下WebClient未必能直接上,得先确认你项目能不能升依赖,不然它给的新API反而编译不过。跟现有工具类冲突的时候我一般不让它硬改,直接圈定范围只让它写单元测试或者局部方法,业务逻辑自己来,省得它把老代码的隐式约定给改没了。还有个偏方是把废弃的API在注释里标出来,它下次生成时会绕开,你可以试试。
老项目最坑的就是Copilot会顺着旧代码风格往下写,你越不改它越来劲。我的经验是copilot-instructions.md只能起辅助作用,真正管用的是把要重构的那块先手动改成新写法,它后面补全就跟着新风格走了。工具类冲突的话我一般不让它硬改,只让它围绕现有工具类写调用代码,实在不行就只生成测试用例,省得它乱动依赖。
建个copilot-instructions.md写清版本,再拿新写法喂它几轮就顺了,光靠嘴说没用。
这个问题其实挺典型的,我去年重构一个JDK8的老支付系统时也踩过一模一样的坑。Copilot本质上就是个高级的概率补全,它会优先参考你当前文件里的import和调用习惯,你嘴上说“用新语法”它根本记不住,反而是你项目里那堆旧代码在持续给它“投票”。我后来建了.github/copilot-instructions.md,把技术栈版本、禁用API清单、必须用的替代类都写进去,效果确实有,但别指望它百分百听话,大概能从乱推荐降到偶尔抽风。真正管用的是双管齐下,一边用instructions文件框住底线,一边在它生成后立刻手动改成新写法,改个十几次它就会跟着当前文件的风格走。至于工具类冲突,我的经验是别硬改,Copilot不了解你项目里那些私有封装,硬改出来的东西往往耦合更怪,不如让它专注写单元测试或者DTO转换这种边界清晰的活。还有个小技巧,把老代码里想淘汰的类先加@Deprecated并写好替代说明,Copilot对这类注解挺敏感的,生成时会明显收敛。不过话说回来,重构老项目最关键的分寸还是自己把控,Copilot顶多算个打字快但不太懂业务的实习生,架构决策别交给它。
我之前也踩过这坑,Copilot确实更认代码里的“潜规则”,你在对话里喊破嗓子不如在项目里多写几段新风格代码让它抄。建个copilot-instructions.md有用但别指望它百分百听话,那文件更像是给它个提醒而不是硬约束。我一般遇到它跟现有工具类打架就直接不采纳,让它只写单元测试反而省心,毕竟测试代码写错了也能立刻跑出来发现。你可以试试把老项目的工具类封装一层新接口,它看到新接口自然就少往旧API上靠了。
建个copilot-instructions.md把版本钉死,比在代码里喂新写法省事多了,亲测有效。
我一般直接在项目根目录放一个copilot-instructions.md,把JDK版本、禁用API、推荐用法都列清楚,比对话里反复说管用多了。另外你可以在重构的类里先手写一两个新写法的样板方法,它模仿上下文的能力很强,喂几段就跟着变了。工具类冲突的情况我倾向只让它生成测试和迁移方案,具体替换自己来,不然它硬改出来的东西review成本更高。
写个copilot-instructions.md锁定版本最省事,光靠嘴说它转头就忘。