蒼時弦也
蒼時弦也
資深軟體工程師
發表於

AI 輔助開發 - Copilot 與文件和註解

最近因為公司有提供 GitHub Copilot 給我們當作工具,我也就順勢將 Copilot 在 Vim 中啟用。這幾個月體驗上跟當初釋出試用版相比,反應速度雖然有提升然而仍然沒有比自己思考的速度還快,但也有改變了開發習慣。

不寫註解

一直以來我都是「不寫註解」的類型,在 Ruby 的語法特性下,只要願意花時間思考命名,大多能很好的表達語意(Semantic)即使在其他類型的語言也能有不錯的效果。

然而,有一些朋友在 Copilot 的應用方式,則會採取「撰寫註解」作為提示(Prompt)來讓 AI 可以幫助他們生成近似預期的程式碼。就這點而言,我是不太認同的,一方面是殘留跟實作重複的說明,另一方面我們仍該追求容易理解、閱讀的程式碼。

以最近在做的 OPass Serverless 專案作為例子,以下是在沒有註解的狀況下讓 AI 輔助生成的,仍然很容易閱讀。

 1export class AttendeeAccess {
 2    // ...
 3  	async getScenarios(token: string): Promise<AttendeeScenario> {
 4		const attendee = await this.attendeeRepository.findByToken(token)
 5		if (!attendee) {
 6			return {}
 7		}
 8
 9		const ruleset = await this.rulesetRepository.findByEventId(attendee.eventId, attendee.role)
10		if (!ruleset) {
11			return {}
12		}
13
14		await runRuleset(attendee, ruleset)
15
16		return buildAttendeeScenario(ruleset.visibleScenarios)
17	}
18}

我認為,當我們程式碼中的脈絡(Context)足夠完善時,對 AI 來說是不需要依賴註解去了解意圖,而是能順著工程師的思考分段的推測出所需的內容。

因為還是有出錯的可能性,在生成後還是要回頭檢查,這件事情可以讓「測試」這件事情來輔助,因此寫測試也可能是 AI 時代的重要技巧。

型別文件

型別議題一直以來在軟體開發中都是一種很難下定論的問題,單從 AI 輔助開發的角度來看,我認為他是一種很好的文件,因此像是 TypeScript、Ruby 的 RBS 這類方式,從這個角度來看是個不錯的設計。

接續上一段的實作,我們要將一個實體(Entity)轉換成 UseCase(使用案例)的回傳,來確保 Presentation Layer(表現層)不會對 Domain Layer(領域層)有過多的認知,假設在沒有型別輔助的狀況下,對 AI 來說要判斷這段程式碼如何產生是會有點吃力的。

 1function buildAttendeeScenario(scenarios: Record<string, Scenario>): Record<string, ScenarioInfo> {
 2	let result: Record<string, ScenarioInfo> = {}
 3
 4	for (const scenarioId in scenarios) {
 5		const scenario = scenarios[scenarioId]
 6		result[scenarioId] = {
 7			order: scenario.order,
 8			availableTime: scenario.availableTime,
 9			displayText: scenario.displayText,
10			usedAt: scenario.usedAt,
11			locked: scenario.isLocked,
12			lockReason: scenario.lockReason,
13			metadata: scenario.metadata,
14		}
15	}
16
17	return result
18}

我們希望將 Scenario 這個實體中的資訊,拆解出來變成 ScenarioInfo 這個 Data Transfer Object(DTO)來回傳,在 TypeScript 中用 { ... } 的情境可能是任意的內容,然而在型別的輔助下,AI 就相對容易猜到要填入 ScenarioInfo 的欄位進去,同時能透過語意反推填入的來源是 Scenario 身上有的屬性。

那麼,用「文件」的角度去看型別,就可以得到兩種好處。第一種是對人類來說,更容易理解一段程式碼的實作、使用方式,另一種則是對 AI 來推測生成的內容,更容易正確的預測所需的內容。

Ruby 的 RBS 是在 Matz 認為未來會有更好的輔助工具時,選擇不讓 Ruby 內建型別的情境下產生的,如同寫測試我們可能會預期「都要測試」一樣,對於有 AI 輔助的世界,撰寫 RBS 的意義可能更多在於「描述規格(約定)」這件是情上,也就是一種文件

加速開發

目前來看,Copilot 的價值是幫助工程師省下打字和查閱程式碼的時間,因為我們不需要耗費腦力去翻閱過往的程式實做,只需用精確的關鍵字描述意圖,就能根據以往的物件推導出接近預期的程式碼。這跟今年 WebConf 保哥的演講活用 GitHub Copilot 開發 Web 應用程式的結論基本上是一樣的。

想讓 AI 做到輔助的效果,對工程師的基本功要求還是非常高的。舉例來說,我們用中文跟 ChatGPT 對話時,大多可以得到中文的回應,用英文的話就會得到英文的回應。這就表示,我們用一個比較差的寫法開頭,那麼就會得到訓練資料中比較差的做法。

在 AI 輔助開發的情境中,一個提示的品質好壞還是取決於工程師起頭撰寫的程式碼是怎樣的,剩下的部分則仰賴專案中已存在的部分做為參考,那麼命名、定義等資訊足夠明確的狀況下,產生出來的程式碼品質也會更高、更準確,最後才會讓開發的效率得到提升。

除此之外,有一類型的註解我也會選擇去撰寫,那就是 RDoc 或者 TSDoc 這類基於註解產生的文件,這跟型別文件的好處是類似的,對人類更容易理解,對 AI 來說還因為多了說明和範例,在推導程式碼的效果可能還會更好。

目前是還沒有感受到 Copilot 會去掃套件中的註解範例來輔助生成的樣子,也許未來會有這樣的機制,那麼許多「不知道」的使用方式也許也能被涵蓋到,讓我們能更加善用套件來減少重複造輪子的狀況。