前言
?
筆者已經使用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()