AI工程师系列——面向copilot编程
前言
笔者已经使用copilot协助开发有一段时间了,但一直没有总结一个协助代码开发的案例,特别是怎么问copilot,按照什么顺序问,哪些方面可以高效的生成需要的代码,这一次,笔者以IP解析需求为例,沉淀一个实践案例,供大家参考
当然,其实也不局限于copilot本身,类似的VsCode插件有很多,本文也只是拿chat的AI大模型做例子,只要是deepseek-v3就好
需求文档
为了聚焦,具体需求做了些抽象,简单的说,需要对系统一个IP解析功能进行更新:
1.以前使用了A库和B库进行解析,现在需要增加C库进行解析,
2.需要对三个库解析的结果进行优先级判断,确保把最优结果进行输出
前期准备
这一步很重要,因为很多时候当我们拿到需求文档的时候,希望直接给到IDE的AI助手,结果一般事与愿违,因为AI助手适合在一个限定范围内学习和给出高质量意见
所以要做一些简单前期工作——目的是让copilot学习尽量少的代码资料,从而减少幻觉的输出
1.写代码把C库加载进来
最好把三个库加载的代码先尽量写到一个文件中,比如:
func init() {slog.Info("loading A.dat file")if info, err := os.Stat("A.dat"); err == nil && !info.IsDir() {slog.Info("A.dat file loading from /data/A.dat")tobdb, err = ipcity.LoadV2("/data/A.dat")if err != nil {slog.Error("load /data/A.dat file fail")panic("cannot initialize A data")}} slog.Info("loading B.dat file")if info, err := os.Stat("B.dat"); err == nil && !info.IsDir() {slog.Info("B.dat file loading from /data/B.dat")tobdb, err = ipcity.LoadV2("/data/B.dat")if err != nil {slog.Error("load /data/B.dat file fail")panic("cannot initialize B data")}} slog.Info("loading C.dat file")if info, err := os.Stat("C.dat"); err == nil && !info.IsDir() {slog.Info("C.dat file loading from /data/C.dat")tobdb, err = ipcity.LoadV2("/data/C.dat")if err != nil {slog.Error("load /data/C.dat file fail")panic("cannot initialize C data")}}
}
这里有两个注意的点:
(1) 以终为始:
我们是希望copilot能够准确的学习到这是三个库的加载方法,所以这里要写的教条一点:即三个函数相似度要高,而且要通过注释、日志反复增强对函数作用的说明,这样copilot会更准确的学习这里的业务逻辑
(2)日积月累:
如果前期代码就是这样“教条”的撰写风格,那么这段代码本身就可以用copilot生成
2.找到解析函数代码
IP解析函数中,要包含对A、B库的调用及综合算法
因为涉及综合算法,最好把综合算法放到一个文件中,这样copilot就可以读更少的文件
然后把确定输入代码的地方,写个注释,表示要在这里写
比如:
// ParseIP parse passed in ip string and return country, region, city, isp.
func ParseIP(clientIP string) (ip, country, region, city, isp string) {ip = clientIPswitch IPLibraryVersion {case "v1":、、、case "v2":// patch result from C// patch result from AresultA := A.Search(net.ParseIP(clientIP))if meta != nil {country = meta.Country()region = meta.Province()city = meta.City()isp = meta.ISP()}// patch result from B, espicially for ISPresultB := B.Search(net.ParseIP(clientIP))if resultB != nil {if country == "" || country == "未知" { // if cannot found country from A, then turn to Bcountry = resultB.Country()region = resultB.Province()city = resultB.City()isp = resultB.ISP()} else if (strings.HasPrefix(isp, "Error") && (B.ISP() != "未知" && B.ISP() != "")) || isp == "未知" || isp == "" { // if B's ISP is prefix with 'Error', so replace it with tobdb's ISPisp = B.ISP()}}
这里也有两个注意的点:
(1)写好注释:
一般来说,写注释是研发同学最难以为继的事情,但随着copilot的到来,大家可以写完一个函数后,让copilot帮忙写注释,对于研发同学来说,甚至只需要输入“//”,然后等待copilot生成就好
(2)变量简单命名:
除了注释,变量名的清晰明了也是可以让copilot来更好的学习,同时,这里最好写的比较有规律,比如resultA、resultB,这样剩下的变量名也可以自助生成
3.把相关的文件放到copilot中,选择deepseek- v3模型
这里Copilot类似工具有很多,笔者用的是VSCode的IDE,大家可以随意选择,本质上是DeepSeek-v3模型就好
开始提效
这一步就需要把需求文档的内容,进行输入,当然,很多时候,需求充满着未添加的背景信息和口语的表述,作为一名研发,有时需要做一些逻辑转换用语
一、输入清晰的业务逻辑
这里可以看一个业务逻辑输入示例:
我想写一段逻辑,现在有三种数据源获取了country,region,city,isp四个数据,我希望A库的数据优先级最高,只有当A库的country识别不出来,或者country识别出来,但region的识别不出来的时候,才使用B库的数据;然后只有B库识别isp为“Error”打头时或者为空或者为“未知”,且C库识别isp不为空或者“未知”,才使用C库数据,如果三者都识别不出来,ISP如果有英文则用英文的版本,否则用A;其中一个重要逻辑是,最后使用的country,region,city,isp必须整体使用某一个数据源,且如果region识别为国内的广东等省必须加省后缀…
这是一个失败的表述,虽然可能完全来自与需求文档/会议结论/邮件输出,但对于copilot来说,逻辑十分的混乱
我们来分析下为什么混乱:
- 没有按照1、2、3标题,这让copilot难以分段
- 逻辑这里最终输出是if、while的类型,所以需要写清晰优先级
- “其中一个重要逻辑”,这种十分的口语,其实对于程序员也会难以理解
- 最后的特殊逻辑,太过于细节,可以在生成基础代码的基础上再进行“优化”
!17 “看到这里,很多读者可能也觉得这个逻辑十分混乱,但笔者想说的是,这是一个真实的业务内容,之所以大家觉得逻辑混乱是因为,撰写的人具备大量的背景知识,所以撰写者觉得清晰明了,但其他人和copilot完全不知所云!”!
那怎么才能写出清晰的业务逻辑输入呢?
笔者认为:清晰的逻辑输入需要清晰的输入格式
我重新梳理一下规则,1,代码实现在ParseIP的v2逻辑路径中;2.三个数据源分别来自resultA、resultB、resultC,最终要选择某一个的数据源来获取country,region,city,四个数据,3.第一优先级是获取resultA的数据,当resultA分析的country为空/未知或者region为空/未知时,才用resultB数据;4.第二优先级是获取resultB数据,当resultB分析的country为空/未知或者region为空/未知时,才使用resultC数据;5.如果三者都识别不出来,ISP如果有英文则用英文的版本,否则用A,帮我写下这个代码
这里对上述的问题进行了修正,同时增加几点优化:
- 而且明确了代码撰写在什么地方,更加限定了copilot的使用场景
- 描述逻辑时用了大量相似的语句,减少的copilot分析难度,即输入“教条”一些
我们看下结果:
case "v2":// 从三个数据源获取信息resultA := A.Search(net.ParseIP(clientIP))resultB := B.Search(net.ParseIP(clientIP))resultC := C.Search(net.ParseIP(clientIP))// 定义最终使用的数据源var finalSource *IPInfo// 第一优先级:resultAif resultA != nil && resultA.Country() != "" && resultA.Country() != "未知" &&resultA.Province() != "" && resultA.Province() != "未知" {finalSource = resultA} else {// 第二优先级:resultBif resultB != nil && resultB.Country() != "" && resultB.Country()