文章目录
- [GitHub Copilot 提效不靠补全:把意图、上下文和验收写清楚](#GitHub Copilot 提效不靠补全:把意图、上下文和验收写清楚)
-
- [1. 拒绝许愿式编程](#1. 拒绝许愿式编程)
- [2. 它看不见你脑子里的架构](#2. 它看不见你脑子里的架构)
- [3. 反复要说的话,写进仓库](#3. 反复要说的话,写进仓库)
- [4. 真值钱的一步:先补测试](#4. 真值钱的一步:先补测试)
- [5. 别的活我怎么用](#5. 别的活我怎么用)
- [6. 有几类事情我直接不让它碰](#6. 有几类事情我直接不让它碰)
- [7. 你要是只改一件事](#7. 你要是只改一件事)
GitHub Copilot 提效不靠补全:把意图、上下文和验收写清楚
面向:Copilot 已经开通了,补全能用,但时好时坏,想知道怎样少踩坑的人。
我见过最常见的用法,是在 Chat 里丢一句:「写个订单创建,VIP 要打折,信用卡走风控。」几分钟后你拿到能编译的 if-else。库存预占没有事务,风控是 pass,折扣写死 0.9。演示好看,提测难看。
我现在基本不这么用。不是 Copilot 突然变聪明了,是我把活拆开了:先让它围着一段真实代码补测试,人看着跑;绿了再允许它碰实现。它还是会写错断言。我是靠 pytest 红了才知道,不是靠它自觉。
下面拿一个短函数走一遍。日期解析这种东西不性感,但前后端都在调、没测试、没人敢改------Copilot 在这种地方才值钱。订单状态机、从 Figma 出组件那些,我放到后面几句带过,单独开十章没有意义。
1. 拒绝许愿式编程
这段是我从老项目里摘出来改过名字的。前端传字符串,后端收成闭区间再查库。空值用当年首末日。没有测试。
python
from datetime import date, datetime
def parse_date_range(start, end, year):
# 空值用当年首末日;非法格式抛 ValueError;start > end 也抛
if not start:
start_d = date(year, 1, 1)
else:
start_d = datetime.strptime(start, "%Y-%m-%d").date()
if not end:
end_d = date(year, 12, 31)
else:
end_d = datetime.strptime(end, "%Y-%m-%d").date()
if start_d > end_d:
raise ValueError("start after end")
return start_d, end_d
你要是跟它说「给这个方法补全单测,覆盖各种情况」,它会吐一串 test_parse_date_range_1。日期是魔法数字,更讨厌的是:查库用的是闭区间(BETWEEN 含当天),它经常按程序员习惯写成左闭右开。你不跑测试,肉眼不一定看得出来。账单少算一天,要到对账才炸。
我在 VS Code Chat 里会先选中函数,再把调用方文件挂上,提示词写成这样:
text
#selection #file:app/api/orders.py
为 parse_date_range 生成 pytest。
返回闭区间,end 当天算进去。
start 或 end 为空,用 year 的首日 / 末日。
非法日期或 start > end 抛 ValueError。
不要测 strptime 怎么实现的。
用例名写场景,不要 test_1、test_2。
生成完不要改生产代码。
啰嗦一点没关系。少写那句「不要改生产代码」,它有时会顺手「优化」函数,测试倒是好写了,语义已经偏了。
入口我也分着用,懒得记成方法论,就是少踩过几次:
敲代码时的灰色补全,让它补当前文件下一行就行。DTO 字段、样板 if,我接受率很高。跨文件的设计别指望它从补全里长出来。
要解释、要测试、要对着一块选区提问,开 Chat。/tests、/explain、/fix 我常用。别在这儿让它「把整条下单链路写完并保存」。
Agent 能改多文件,我只在已经有测试护着的时候开。无保护的大重构、删列、改主键,我自己来。它改得又快又勤,回滚成本在你这边。
还有个很多人不知道的细节:仓库里的 .github/copilot-instructions.md 只作用于 Chat,行内补全不读它。别指望写了这份文件,敲代码时的幽灵字就会变听话。
2. 它看不见你脑子里的架构
Copilot 不理解「我们是微服务」。它看见的是当前文件、你的选区、用 # 挂上去的文件、这轮 Chat 里说过的话,以及------如果有的话------那份 copilot-instructions.md。
所以我花在「把提示词写得更文学」上的时间不多,花在「把无关文件拿掉」上的时间更多。对话脏了就 /clear。它开始 import 一个仓库里不存在的包,或者突然换成隔壁模块的命名,多半是上下文圈歪了。接着跟它说「再优化一下」没有用,它会在错误上下文里继续优化。
前后端一起写的时候也是这个道理。前端要写 fetchUserDetail,别默认它记得 Java DTO 里哪个字段可空。把 OpenAPI 或那份 DTO #file 挂上,写明「字段名和空值和后端一致,不要发明字段」。没有 schema 还夸「写一端知另一端」,就是碰运气。运气好的时候你以为 Copilot 很强,运气不好就是联调现场对字段。
遗留代码看不懂,我会选中,然后:
text
/explain
只讲控制流和会抛哪些异常。别评价该不该重构。
后半句是因为我懒得看它抒情。解释是只读的,没打算改就别把 Agent 打开。
3. 反复要说的话,写进仓库
团队里如果每回都要贴「我们用 pytest、不要吞异常」,说明这些话该进文件。我自己的版本很短。再长它会开始选择性失聪。
markdown
# .github/copilot-instructions.md
本仓库 Python 3.11,测试用 pytest。
生成代码:公共函数带类型;错误往上抛,不要吞;不要新增 requirements 里没有的包。
生成测试:用例名写场景;断言行为,别断言私有实现;不准为了把测试变绿去改生产代码。
缩进、import 顺序、行宽这种事,交给 ruff / prettier 和 CI。Copilot 管格式,又慢又不稳,Code Review 还会吵回大括号。命名和「这个包不许用」,instructions 里写一眼,提交前人再扫一眼。业务对不对,只看测试和你自己。
/doc 我偶尔用来起注释草稿。它能把参数类型翻译成人话,翻译不了「为什么空值要用当年 1 月 1 日」------那是产品规则。代码改了边界,注释改那一行就行。不要搞「用 AI 把文档整体重生」这种活动,重生完往往和实现各说各话。
4. 真值钱的一步:先补测试
parse_date_range 短、有边界、现网在用。给这种函数补测试,是我用 Copilot 最不亏的场景。我负责把行为说清楚,它负责铺一堆用例,我负责删。
选中函数,/tests,把上面那些约束贴进去。出来的东西通常掺三股:
有用的:空值、正常闭区间、start > end、非法字符串。这些留。
多余的:专门测 strptime 格式串。你以后要是换解析方式,这批测试会无意义地红。删。
错的:闭区间被写成半开。下面这种我见过不止一次,直接扔掉。
python
def test_end_is_exclusive():
# 它把查库的闭区间,猜成了编程里的 [start, end)
start_d, end_d = parse_date_range("2026-01-01", "2026-01-31", 2026)
assert end_d == date(2026, 1, 30) # 错:31 号当天应包含
留下来的最好能当说明书读:
python
import pytest
from datetime import date
from billing.dates import parse_date_range
def test_empty_start_uses_first_day_of_year():
start_d, end_d = parse_date_range(None, "2026-06-01", 2026)
assert start_d == date(2026, 1, 1)
assert end_d == date(2026, 6, 1)
def test_empty_end_uses_last_day_of_year():
start_d, end_d = parse_date_range("2026-02-01", None, 2026)
assert end_d == date(2026, 12, 31)
def test_invalid_date_raises():
with pytest.raises(ValueError):
parse_date_range("2026-13-01", "2026-01-31", 2026)
def test_start_after_end_raises():
with pytest.raises(ValueError):
parse_date_range("2026-05-02", "2026-05-01", 2026)
覆盖率以你本机 pytest --cov 为准。没跑出来的数,别写进周报。我见过有人把 Copilot 聊天框里的「已覆盖主要分支」直接贴上去,那种数字没有意义。
测试红了,VS Code 会建议 Fix Test Failure,Chat 里也有 /fixTestFailure。这里我被坑过,所以写得难听一点:
它会改生产代码,去迁就一条错误断言。
test_end_is_exclusive 要是还在,它完全可能把结束日减一天。测试绿了,查询少算一天,账是错的。所以我会写死:
text
/fixTestFailure
只许改测试。闭区间语义不准动。
测试和注释打架,删测试,别改函数。
改完你自己再跑一遍。绿只说明断言被满足了,不说明断言是对的。这个区别,用 Copilot 之前其实也成立,只是它把「写错断言」的速度提高了一个数量级。
有测试护着,我才让它动实现。比如把 year 改成仅关键字参数、补返回类型------这种机械活它干得比我快。提示词里还是要带刹车:
text
#file:billing/dates.py #file:tests/test_dates.py
把 parse_date_range 的 year 改为仅关键字参数,补上返回类型。
不要改日期怎么算。现有测试失败就回退,不许删用例。
订单、库存、风控那种「复杂业务」,我也不再让它一次性生成。骨架可以要。它在副作用位置填 pass,我就当这行不存在,自己接事务和远程调用。它要是生成一堆 if user_level == "VIP" 还美其名曰策略模式------那不是策略模式,是还没拆的条件。真要拆,接口和测试你先写,实现类再交给它填。
5. 别的活我怎么用
/fix 对付编译器和 linter 已经指着鼻子骂的问题,很合适。资源没关、明显 NPE,它补 try-with-resources 之类的,我接受得很快。并发安全、事务边界、权限,它没有运行现场,我只当线索,不直接合并。
慢 SQL 可以问,但要把建表、现有索引、EXPLAIN 一起贴进去。没有执行计划,它给的「给 order_date 加个复合索引」跟猜的差不多。生产上有数据倾斜的表,我被这种建议浪费过下午。
学新框架时,我会把正在看的官方文档片段,加上仓库里一份旧写法,让它按这个仓库的目录和依赖翻译,别另起一套包名。对照着看能学到东西。把它的输出当成「最新最佳实践」,你吃到的可能只是训练截止日期以前的博客。
前端也一样。有组件库,让它在库里拼。拿张截图就想出生产代码,DOM 能跑,和现有设计 token、无障碍、状态管理通常对不上。当草稿可以,当 PR 不行。
这些没有新原则。输入写得像你在给实习生交代任务,输出才值得审。输入像许愿,输出就像演示视频。
6. 有几类事情我直接不让它碰
.env、token、内网地址,不要贴进 Chat。灰色补全有时会从旁边文件续出「很像密钥」的字符串。看到就按泄露处理:轮换。不要只是把那一行删掉然后心安。
删列、改主键、不可逆的数据修复,生成脚本可以,跑之前的备份和核对是人的。它不会替你留后路。
「把这个包改成策略模式,顺便把查询都优化了」这种句子,我会拆开,或者干脆不发。它会同时改结构、改语义、改测试,最后你不知道绿的是行为还是它把测试改顺了。先锁行为,再动结构,索引是最后才谈的事。
说到底没那么玄:不变量你定,草稿它写,进 git 的那一版你负责。线上炸了,用户不会去找 Copilot。
7. 你要是只改一件事
去找一个没有测试、但每周都有人改的纯函数。/tests,删掉闭区间写反、测实现细节的那些,自己跑绿,然后再考虑让它改实现。
copilot-instructions.md 可以下周再写,四十行以内就够。团队提示词库里的「写个 XX」可以删了------需求四行都说不清,发给 Copilot 也是浪费一次上下文。