誰能真正替代你?AI輔助編碼工具深度對比(chatGPT/Copilot/Cursor/New Bing)

2023-03-30 06:00:34

寫在開頭

這幾個月AI相關新聞的火爆程度大家都已經看見了,作為一個被裹挾在AI時代浪潮中的程式設計師,在這幾個月裡我也是異常興奮和焦慮。甚至都興奮的不想拖更了。不僅僅興奮於AI對於我們生產力的全面提升,也焦慮於Copilot等AI輔助編碼工具,會將程式設計師這個工種和我們所熟悉的傳統軟體開發流程徹底顛覆,用計算機的極高效率碾壓人類的低效率。

當然這些也都是後話了,我們目前能做的,就是保持敏銳的嗅覺,嘗試去迎接AI時代的來臨。做「未來世界的倖存者」。

本文是我深度體驗了Github Copilot, ChatGPT等產品後,對於這些AI輔助開發工具的一次橫向評測。寫本文的初衷是幫助大家快速篩選出一款合適你的AI輔助工具。相信我,請不要再懷疑這些工具是否能給你帶來效率提升。當你嘗試使用後,很快就會習慣它們,甚至是離不開它們。

本文評測的工具有:

  • Github Copilot
  • ChatGPT(GPT-3.5)
  • New Bing
  • Cursor.so

這些工具可以結合起來使用,提升你的開發效率。所以這些工具之間並不是互斥關係。文章的最後會給出總結以及我的一些想法。

Github Copilot

Github Copilot是由Github和OpenAI合作推出的一個人工智慧程式碼輔助工具,採用了OpenAI的GPT技術,能夠為開發人員提供實時的程式碼提示和生成功能,類似於一個AI助手,幫助開發人員更快速、更方便地編寫程式碼。

當前的Github Copilot基於GPT-3模型,它可以分析上下文並根據已有的程式碼和註釋來推斷出應該寫什麼程式碼。通過使用Github Copilot,開發人員可以減少手動輸入程式碼的時間,提高程式碼的質量和效率。它支援多種程式語言,如Python、JavaScript、TypeScript、Ruby等,並可以與主流的整合式開發環境(IDE)和文字編輯器配合使用。

使用體驗

我已經深度使用了Copilot接近一個月,但每當我和同事朋友們聊到Copilot的使用體驗,以及它在哪方面能夠提高我的效率時,我仍很難用語言去描述,我只能粗略的總結為下面幾個結論:

  • 編寫你熟悉的語言時,他僅能幫助你減少一些重複模板程式碼的編寫。編寫你不熟悉的語言時,他能夠準確推斷你的意圖,直接生成程式碼,免除了查詢如何使用API的耗時工作
  • 它順著你的心流生成片段程式碼,但很難從0開始為你創造整段程式碼,即使它是可以生成整段程式碼的,但是也常常是需要你手工修改的。

接下來看一下我常用的幾種使用方式。

1. 根據上下文生成程式碼

它可以根據函數名,類名,註釋,來推斷你想寫的程式碼,幫你填充。

2. 根據程式碼生成註釋

反過來,它可以嘗試理解你的程式碼,為你生成註釋,你只需要給他一個 // 字首

3. 幫你起變數名

它可以幫你器變數名,這可是很多英語不好的程式設計師的大福音。畢竟,編碼的30%時間,是在想變數名。

4. 和你聊天,當然,是聊程式碼!

你沒有聽錯,copilot可以在你的程式碼裡聊天,但是顯然不能和他嘮家常。它並不是chatGPT,無法給你常識回答(應該是被故意限制了交流範圍),只會和你扯皮。

正確的使用方式是讓它和你討論你寫程式碼,他會總結你的上下文程式碼,並且給你一個它認為「合理」的解釋。

以上就是我常用的幾種方式,我查閱了很多資料,基本上面涵蓋了大部分操作方式。當然,可能還有我沒挖掘到的使用方式。

編碼能力

說完使用體驗,我想要引出我本次橫向評測的一個評測標準,就是通過相近的試題,看下這幾個工具的編碼能力有何區別,給大家直觀地對比。我們就先從Copilot開始。

獨立編寫:單例模式

我給它們設定的題目是獨立寫一個單例模式,這個題目是國內Java開發者「常考題」,裡面除了基本的程式碼,還有很多細節需要注意,也可以很好的用來向AI們提問,看看它們是否真正地理解它們寫的內容。

讓我們歡迎第一位選手,Github Copilot。

上面的編碼動畫也是我認為最符合我日常使用copilot的案例,從建立類檔案後,copilot幫我自動生成了私有變數,私有構造方法,以及獲取單例的公有方法。在寫公有方法的期間,它最開始的程式碼沒有考慮雙重檢查鎖,我給了它一些提示,它補全了剩餘的程式碼。

需要注意的是,由於單例模式在網上有太多的學習資料,Copilot肯定也吸取了大量優秀程式碼,才能寫的如此高效。並不代表所有複雜的程式碼題都能夠幫你自動補全,並保證正確性。所以大家謹慎看待它的獨立編碼能力,也不要過分樂觀。

補全專案現有程式碼

第二個測試,我們讓它補全專案的現有程式碼,我拿一個我自己寫的程式碼舉例,其中有一個DiffDTO實體類,有一個addDiffDTO方法一行都沒寫,需要補全。

可以看到它自動補全了程式碼,並且會用到上面已有的方法和變數。這也是它的強項,根據上下文資訊推斷程式碼該怎麼補全,寫出來的程式碼質量很高,不能說每次都可直接使用,但80%的情況下你是無需做大幅度修改的。

不足之處

Github Copilot有不足之處嗎?我想了想,有以下幾點:

  • 所有程式碼上下文通過https傳到雲端(微軟的伺服器),儘管我通讀了微軟的隱私保護政策,它承諾不會有任何程式碼被洩露。但放在國內網路安全大環境下,這樣的處理資料方式,在國內公司內使用很可能會受到影響,需要使用者注意。
  • 對於複雜的程式碼邏輯,比如連你都一下子看不懂的屎山程式碼,它的理解能力同樣欠缺。它擅長的是網路上有很多公開資料的程式碼段落和實現方式。

ChatGPT

讓我們來看下一位選手,最近火出圈的chatGPT。

chatGPT是一款基於GPT模型的聊天機器人,它可以像人類一樣進行自然語言對話。它不僅可以回答使用者的各種問題,還可以學習和理解使用者的需求,提供更恰當的回答和建議。

得益於其基於GPT模型的強大語言處理能力。GPT模型可以理解程式碼中的自然語言描述部分。其次,chatGPT在訓練階段使用了大量的程式碼相關文字語料,包括程式碼庫、API檔案、程式設計師交流論壇等,因此具有一定的程式碼理解和識別能力。此外,chatGPT還可以通過不斷的學習和自我優化,逐漸提高其對於程式碼相關問題的處理能力和準確度。

我文中使用的chatGPT是GPT-3.5版本。我們直接進入正題,看看它的編碼能力。由於是對話方式,所以我們需要向它描述我們的需求。

編碼能力

獨立編寫:單例模式

我:

請用Java寫一個單例模式

ChatGPT:

public class Singleton {
​
 private static Singleton instance;
​
 private Singleton() {
 // 私有建構函式
 }
​
 public static synchronized Singleton getInstance() {
     // 雙重檢查鎖定
     if (instance == null) {
        synchronized (Singleton.class) {
           if (instance == null) {
               instance = new Singleton();
           }
       }
     return instance;
      }
   }
}
​
// 使用方法
Singleton s = Singleton.getInstance();

寫的不錯,很有面試題刷多了的開發人員的風格,寫了經常會被問到的雙重校驗鎖,但是沒有用volatile,讓我們繼續發問。

我:

為何要用雙重檢查鎖定?

ChatGPT:

雙重檢查鎖定(double-checked locking)是一種常見的單例模式的實現方式。它的目的是在不使用同步鎖的情況下實現單例模式的執行緒安全,避免了每次獲取物件範例時都進行同步鎖的開銷。但是,這種實現方式也有可能存線上程安全問題。

具體來說,雙重檢查鎖定是在 getInstance 方法中加入了雙重檢查,即先判斷範例是否已經存在,如果不存在再進行同步鎖的操作。這樣可以減少同步鎖的開銷,提高了效率。

但是,在多執行緒環境下,雙重檢查鎖定也有可能出現執行緒安全問題,原因是在某些情況下,多個執行緒可能同時通過第一重檢查,然後同時進行同步鎖的操作,導致出現多個範例。

為了避免這種情況,需要將 getInstance 方法設定為 synchronized 關鍵字,保證在同一時刻只有一個執行緒可以存取該方法,從而避免多個範例的出現。但是,這樣會帶來一定的效能開銷,因為每次存取 getInstance 方法都需要進行同步鎖操作。

講得不錯,但很難不讓人覺得是看多了面試文章。我們順著volatile的問題繼續問問他為啥不寫volatile。

我:

上面的程式碼依然有漏洞,可以優化下嗎

ChatGPT:

是的,即使使用了雙重檢查鎖定,上面的程式碼仍然有漏洞,也就是可能出現執行緒安全問題。為了解決這個問題,可以使用 Java5 中引入的 volatile 關鍵字,通過禁止指令重排來保證執行緒安全。

優化後的程式碼如下:

public class Singleton {
private static volatile Singleton instance;
​
private Singleton() {
// 私有構造方法
}
​
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
            instance = new Singleton();
            }
        }
    return instance;
    }
}
}
​

上面的程式碼中,我們將 instance 欄位新增了 volatile 關鍵字,這樣可以保證在多執行緒環境中,instance 變數的讀取和賦值操作是原子性的,即保證了執行緒的可見性和有序性,從而避免了出現多個範例的情況。

注意,這裡的 volatile 關鍵字只是作用於 instance 欄位本身,而不是整個 getInstance 方法。這樣做可以減小同步鎖的範圍,提高了效率。

它自己想到並解決了volatile的細節問題,我們再結合他寫的程式碼,問問一些面試官會問的問題。

我:

上面的程式碼中,同步的是一個Singleton.class,可以用int變數代替嗎

ChatGPT:

不可以,使用 int 變數代替 Singleton.class 進行同步是不可行的。

因為在 Java 中,每個類都有一個唯一的 Class 物件,多個執行緒使用同一個類的 Class 物件進行同步,可以保證執行緒安全。而使用 int 變數進行同步,由於 int 是基本資料型別,它在記憶體中的每一份拷貝都可能不同,因此不能保證多個執行緒都使用同一個 int 變數進行同步,從而無法保證執行緒安全。

此外,使用 Class 物件進行同步還有一個好處,即可以保證範例變數的可見性,因為同步程式碼塊中的讀寫操作都是在同一條執行緒中執行的。

因此,使用 Singleton.class 物件進行同步是保證執行緒安全的最佳方式,不建議使用其他方式進行同步。

回答的不錯,理解了我提示語中的 Singleton.class 是指的它自己程式碼中的類,最後問一個volatile的細節。

我:

上面的程式碼中,如果不使用volatile,有可能會出現重排序問題,能具體展開講講嗎

ChatGPT:

是的,如果沒有使用volatile關鍵字,就有可能出現重排序問題。

在Java中,編譯器和處理器都會對指令進行重排序,這是為了優化程式效能。例如,在一個if語句中,為了避免每次都進行判斷,編譯器可能會將判斷條件提前到if語句外面。在單執行緒環境下,這種重排序是沒有問題的,因為程式執行的結果是一樣的。但是,在多執行緒環境下,重排序可能會導致執行緒安全問題。

考慮下面的程式碼:

public class Singleton {
private static Singleton instance;
​
private Singleton() {}
​
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
            instance = new Singleton();
            }
        }
    return instance;
    }
}
}

在這個程式碼中,如果沒有使用volatile關鍵字,就有可能出現下面的重排序:

  1. 執行緒A呼叫getInstance()方法,發現instance為null,進入同步塊。
  2. 執行緒B呼叫getInstance()方法,由於instance還沒有被初始化,也發現instance為null,也進入同步塊。
  3. 執行緒A執行instance = new Singleton(),在執行該語句時,可能會被重排序為下面這樣:
memory = allocate(); //1:分配物件的記憶體空間
ctorInstance(memory); //2:初始化物件
instance = memory; //3:將物件賦值給instance變數

這個重排序是允許的,因為在單執行緒環境下,這個順序和原來的順序沒有區別。但是,在多執行緒環境下,這個重排序可能會導致執行緒安全問題,因為執行緒B此時檢查instance不為null,就會直接返回instance,這個instance還沒有被初始化,就會導致錯誤。

為了避免這種情況,可以使用volatile關鍵字來確保instance變數的可見性,從而避免重排序問題。

上面的對話,是一個典型的面試題,網上有很多文章也會順著這個思路寫下來,所以chatGPT回答的得心應手,並不代表在所有程式碼優化方面都能回答的這麼細節和流暢。但不得不說,如果你第一次用chatGPT,你還是會被它的能力和思路所震驚。

接下來我們讓chatGPT解決一個kotlin中並行介面的設計,看看它對於不那麼常見的複雜問題的解決能力

獨立編寫:Kotlin+Reactor設計並行介面

這是我在實際開發過程中遇到的一個比較難寫的程式碼例子,在kotlin中使用reactor框架來寫一個批次查詢介面,由於效能要求,該介面中的每個查詢必須並行執行(執行緒池執行)。我把這個遇到的實際問題作為測試的case,讓我們來問問chatGPT。

我:

Kotlin中,如何用reactor框架寫一個並行處理的方法,要求入參是List,方法中將字串後面拼接同樣的字串,輸出List

ChatGPT:

您可以使用Reactor框架中的Flux來實現這個要求。下面是一個範例程式碼:

import reactor.core.publisher.Flux
import reactor.core.scheduler.Schedulers
​
fun processStrings(strings: List<String>): List<String> {
      val flux = Flux.fromIterable(strings)
      .flatMap { str -> Mono.fromCallable { str+str }
      .subscribeOn(Schedulers.parallel())
  }
  return flux.collectList().block() ?: emptyList()
}
​

在這個例子中,我們使用Flux.fromIterable方法建立一個Flux,並使用flatMap方法處理每個字串。在flatMap中,我們使用Mono.fromCallable方法來建立一個非同步執行的函數,該函數將每個字串轉為大寫。同時我們使用Schedulers.parallel()指定執行該函數的排程器,以實現並行執行的效果。

最後,我們使用collectList()方法等待所有非同步操作完成並將結果作為List<String>返回。

需要注意的是,我們使用了並行執行,因此結果順序可能與輸入順序不同。如果您需要保持結果順序與輸入順序相同,可以使用concatMap方法代替flatMap

在它給出的程式碼上,我加入了一個隨機休眠的函數來驗證是否程式碼真的執行了非同步,並且給了它入參 [0-9],然後執行,事實證明chatGPT給出的程式碼直接可以跑通,並且真正是並行執行的。給出結果:

Current ThreadName: parallel-1  sleep time: 188
Current ThreadName: parallel-2  sleep time: 483
Current ThreadName: parallel-3  sleep time: 785
Current ThreadName: parallel-4  sleep time: 524
Current ThreadName: parallel-5  sleep time: 175
Current ThreadName: parallel-6  sleep time: 894
Current ThreadName: parallel-7  sleep time: 440
Current ThreadName: parallel-8  sleep time: 158
Current ThreadName: parallel-9  sleep time: 227
Current ThreadName: parallel-10  sleep time: 300
Result:[99, 33, 66, 1010, 22, 11, 44, 77, 88, 55]

但是它的程式碼中使用了flatMap並沒有保證返回結果List中的順序和入參順序相同,對於我的需求來說這是不可接受的,chatGPT在剛才的回答中考慮到了這個問題,告訴了我解決方案,使用concatMap代替,於是我修改為concatMap,結果如下:

Current ThreadName: parallel-1  sleep time: 933
Current ThreadName: parallel-2  sleep time: 715
Current ThreadName: parallel-3  sleep time: 278
Current ThreadName: parallel-4  sleep time: 182
Current ThreadName: parallel-5  sleep time: 14
Current ThreadName: parallel-6  sleep time: 313
Current ThreadName: parallel-7  sleep time: 228
Current ThreadName: parallel-8  sleep time: 650
Current ThreadName: parallel-9  sleep time: 554
Current ThreadName: parallel-10  sleep time: 334
Result:[11, 22, 33, 44, 55, 66, 77, 88, 99, 1010]

看上去一切ok,但其實問題很大。使用了concatMap後,實際上使用concatMap的這段程式碼並沒有真正的並行執行,而是類似序列的方式,得到了結果。(本文不具體展開解釋為何使用了concatMap後會有如此效果)

既然答案和我想要的有偏差,那就繼續追問chatGPT。

我:

這段程式碼如何在保證返回值順序和入參順序相同的情況下,真正的並行執行?

ChatGPT:

可以使用flatMapSequential操作符替換concatMap操作符來實現真正的並行執行,並且保證返回值順序和入參順序相同。

修改後的程式碼如下:

fun processStrings(strings: List<String>): List<String> {
     val flux = Flux.fromIterable(strings)
         .flatMapSequential { str ->
             Mono.fromCallable {
                 str+str
             }.subscribeOn(Schedulers.parallel())
         }
     return flux.collectList().block() ?: emptyList()
}
Current ThreadName: parallel-1  sleep time: 755
Current ThreadName: parallel-2  sleep time: 342
Current ThreadName: parallel-3  sleep time: 340
Current ThreadName: parallel-4  sleep time: 562
Current ThreadName: parallel-5  sleep time: 405
Current ThreadName: parallel-6  sleep time: 399
Current ThreadName: parallel-7  sleep time: 250
Current ThreadName: parallel-8  sleep time: 351
Current ThreadName: parallel-9  sleep time: 169
Current ThreadName: parallel-10  sleep time: 118
Result:[33, 22, 11, 44, 55, 66, 77, 88, 99, 1010]

chatGPT回答基本正確了。

不過,有時候提問同樣的問題,chatGPT會給出很多種錯誤答案,有的是編譯錯誤,有的是序列執行不符合我的要求,以至於我不得不重新提問了好幾次。這個過程中,我沒有好好做截圖。但我發現它其實就是在給出一些網路上常見的部落格寫的解法,很多時候都是錯誤的,或者說其實並不對應我對它的訴求。

不足之處

chatGPT針對程式碼方面的回答做了很多特殊的調優,所以它能夠作為一個幫助程式設計師編碼的工具。很多時候,我都更願意問chatGPT而不是自己去谷歌答案。這已經證明了我足夠信任它。如果要說有什麼不足之處,我想到以下幾點:

  • 面對不常見的複雜程式碼設計題,沒有做到優秀的水平,但相信未來不斷地訓練後,會變得更加完善。
  • 它不能全程輔助你編碼,這一點比不上Copilot。
  • 它不能閱讀你整個專案的程式碼,無法和Copilot一樣有強大的上下文能力。當然你可以手動給他很多上下文程式碼,但是相比Copilot肯定還是差距很大。畢竟Copilot可能閱讀了你整個專案後給出一些建議。

New Bing

參照New Bing官網的介紹,New Bing 就像您在搜尋⽹絡時身邊有⼀個研究助理、個⼈規劃師和創意夥伴。您可以問你的實際問題,當你提出複雜的問題時,Bing 會給你詳細的答覆。 得到⼀個實際的答案。 Bing 會檢視⽹絡上的搜尋結果,為您提供⼀個總結性的答案。 要有創意。當你需要靈感時,必應可以幫你寫詩、寫故事,甚⾄為你創造⼀個全新的形象。

總的來說,你可以理解為New Bing是一個chatGPT + Bing搜尋引擎內網路資訊 的加強版對話機器人。

使用體驗

New Bing目前已經和諧了國內的IP,用國內IP存取任何new Bing相關的網頁會強制跳轉的Bing搜尋首頁。所以需要開啟科學工具後使用,本文不具體展開。網上有很多攻略可查。

編碼能力

我們仍然使用剛才的測試例子(單測+Kotlin並行介面)來測試NewBing的程式碼編寫能力。Bing有三種對話模式可選,需要選到「更多 精確」,這種模式下,它會認認真真地給我們寫程式碼。

獨立編寫:單例模式

我們直接提問,請看截圖。

給出的程式碼比較基礎,讓我們追問下,讓他修改。

它修改的很快,現在程式碼基本已經成型了。讓我們和之前測試chatGPT提問一模一樣的問題。

可以看到,頁面下方,他還會給一些符合你問題上下文的推薦提示。

獨立編寫:Kotlin+Reactor設計並行介面

讓我們來欣賞一下NewBing面對複雜問題的編碼能力。

將它寫的程式碼放入Demo,編譯不通過。

繼續質問它

最終它完成了程式碼,但是寫法在我看來有些奇怪。整體來看,和chatGPT的使用體驗是相似的。

不足之處

NewBing和chatGPT相比,給我感覺不分伯仲,面對不常見的複雜程式碼設計題,依然有一些力不從心。並且,不知道是不是使用了搜尋引擎內的原因,比chatGPT更容易出現錯誤的結果。

相比Github Copilot,則和chatGPT一樣,由於沒有足夠的程式碼上下文,對你的幫助遠沒有Copilot那麼好用。

Cursor.so

Cursor.so 是 OpenAI 最近推出的一款IDE,它可以幫助你提供想法並編寫程式碼。最值得一提的是,它是一款免費軟體,OpenAI 還承諾將持續更新和改進,為使用者帶來更多新的功能和體驗。

為什麼它會受到關注,主要是因為它能夠免費使用內建了類似Github Copilot的外掛,畢竟Copilot是付費軟體,很多小夥伴還沒法免費體驗到。

編碼能力

獨立編寫:單例模式

cursor主要有兩個功能,一個可以自動生成程式碼edit,一個是根據當前程式碼進行聊天chat。各自有獨立的快捷鍵來啟動。

我們讓他寫一個Java中的單例模式類,快捷鍵command+K,輸入中文 」寫一個執行緒安全的單例模式「,它給出瞭如下答案。

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

寫的不錯,帶上了雙重校驗和volatile,很像是熟讀面試程式碼的面試者。既然本段程式碼沒什麼細節問題,那就試用下聊天功能command+L

解釋的和chatGPT以及NewBing都類似,並且還支援聊天曆史記錄展示。

補全專案現有程式碼

讓他補全之前Copilot補全過的同樣測試程式碼,寫入threadLocal變數的程式碼,可以完成。

當我準備更進一步問問他對於這個程式碼怎麼看的時候,它服務開始了長時間的不穩定。

不足之處

服務不穩定

不支援外掛

沒有外掛市場,甚至沒有整合版本控制,比如Git,在UI中沒法檢視改動的程式碼,所以幾乎已經告別了開發大型專案了。只要稍微做一些程式碼改動,你就會忘記了程式碼的改動是哪裡。

基礎功能缺失

比如開啟一個專案資料夾後居然不支援關閉,檔案型別沒有高亮區分,整體用下來感覺Cursor.so還處於一個很早期的階段。

總結

最後,總結下這幾個產品作為一個AI輔助編碼工具的優缺點。

Github Copilot:

優點:

  • 可以根據提示自動生成程式碼,提高開發效率。
  • 可以學習你專案中的程式碼風格,獲取足夠多的上下文,並根據其生成程式碼。
  • 支援多種程式語言,適用範圍廣。

缺點:

  • 可能會存在隱私問題

chatGPT和New Bing:

優點:

  • 隨時隨地可用,不依賴程式碼專案,是你查詢谷歌時的完美替代品。

缺點:

  • 它不能全程輔助你編碼,這一點比不上Copilot,並且無法和Copilot一樣有強大的上下文能力。
  • 對於複雜的程式碼邏輯,理解能力未必能讓你滿意。

Cursor.so

優點:

  • 免費的同時能夠體驗AI輔助程式設計,就是最大的優點

缺點:

  • 基礎功能缺失,不能稱之為一個可靠的IDE
  • 服務不穩定

一句話總結,如果你希望將這幾個產品用於輔助你程式設計,提高編碼效率,使用Github Copilot結合chatGPT是一個可行的方式。你可以在編寫程式碼時使用Github Copilot,遇到問題時再求助chatGPT。如果暫時不想為Github Copilot付費,可以只使用chatGPT。不過,由於Cursor.so的使用體驗不夠好,且不易替代Github Copilot,建議還是等待Cursor.so之後的版本。