GitHub Copilot
從初階到進階:程式碼重構的藝術
當你的程式碼能正常運作後,下一個挑戰來了:如何讓它更優雅、更易維護?
問題的浮現
看看我們目前的程式碼:
double applePrice = 35;
double appleDiscount = 0.1;
Console.WriteLine("蘋果折扣後價格是:" + applePrice * (1 - appleDiscount));
double bananaPrice = 25;
double bananaDiscount = 0.15;
Console.WriteLine("香蕉折扣後價格是:" + bananaPrice * (1 - bananaDiscount));
這段程式碼有幾個明顯的問題:
- 違反 DRY 原則(Don't Repeat Yourself):計算折扣的邏輯重複多次
- 缺乏抽象化:沒有使用物件導向的設計
- 難以擴展:每增加一種水果就複製貼上一次,未來若修改計算方式,每一段都要重新調整。
- 不易測試:商業邏輯(BLL) 與 輸出輸入(IO)混在一起
這時候,就需要進行程式碼重構(Refactoring)。
使用 Copilot 進行重構
傳統的重構方式需要:
- 思考如何設計類別結構
- 手動建立類別(class)和屬性(property)
- 實作類別中的方法(method)
- 修改原有的呼叫程式碼,並且確保正確無誤。
但有了 Copilot,這件事情變得簡單許多。
Step 1: 用註解描述你的設計意圖
在程式碼最尾端新增加一行,然後在其中寫下這段「咒語」(註解):
//建立一個計算折扣後價格的類別,具有水果名稱、單價、折扣這三個屬性,以及一個計算折扣後價格的方法
這段註解就像是給 Copilot 的需求規格書,也是一種提示詞。好的提示詞應該清晰明確,且包含:
- 要做什麼(建立一個類別)
- 會有什麼(三個屬性、一個方法)
- 能實現什麼(計算BMI)
Step 2: 讓 AI 生成類別
按下 Enter 後,Copilot 會自動生成類似這樣的類別:
public class Fruit
{
public string FruitName { get; set; }
public double Price { get; set; }
public double Discount { get; set; }
public double CalculateDiscountedPrice()
{
return Price * (1 - Discount);
}
}
這個類別展現了良好的物件導向設計:
- 封裝(Encapsulation):將資料和行為包裝在一起
- 單一職責原則(Single Responsibility Principle):只負責計算折扣價格
- 易於測試:計算邏輯獨立於輸出
Step 3: 重構主程式
現在,我們可以使用 Inline Coding(內嵌程式碼建議)的方案,來重構主程式。只需開始輸入:
var apple = new
Copilot 會自動建議:
var apple = new Fruit
{
FruitName = "蘋果",
Price = 35,
Discount = 0.1
};
Console.WriteLine($"{apple.FruitName}折扣後價格是:{apple.CalculateDiscountedPrice()}");
注意 Copilot 可能還會自動使用字串插值(String Interpolation)語法 $"{...}",這比字串串接更現代、更易讀。
你也可以選取片段程式碼,使用 (CTRL+I),在跳出的視窗中,直接輸入: 『改成使用底下的 Fruit 類別進行計算』
看看結果如何?
影片
完整的重構結果
最終,我們的程式碼變成:
public class Fruit
{
public string FruitName { get; set; }
public double Price { get; set; }
public double Discount { get; set; }
public double CalculateDiscountedPrice()
{
return Price * (1 - Discount);
}
}
// 主程式
var apple = new Fruit { FruitName = "蘋果", Price = 35, Discount = 0.1 };
Console.WriteLine($"{apple.FruitName}折扣後價格是:{apple.CalculateDiscountedPrice()}");
var banana = new Fruit { FruitName = "香蕉", Price = 25, Discount = 0.15 };
Console.WriteLine($"{banana.FruitName}折扣後價格是:{banana.CalculateDiscountedPrice()}");
var orange = new Fruit { FruitName = "橘子", Price = 30, Discount = 0.2 };
Console.WriteLine($"{orange.FruitName}折扣後價格是:{orange.CalculateDiscountedPrice()}");
這段程式碼的優勢:
- ✅ 更易維護:修改折扣計算邏輯只需改一個地方
- ✅ 更易擴展:新增水果只需建立新的物件實例
- ✅ 更易測試:可以針對
CalculateDiscountedPrice()方法撰寫單元測試 - ✅ 更易閱讀:程式碼意圖一目了然
Copilot 的工作原理:不只是程式碼補全
你可能會好奇:Copilot 到底是如何做到這些的?
上下文理解(Context Awareness)
Copilot 會分析:
- 檔案中的現有程式碼 : 了解你的編碼風格和邏輯模式
- 註解內容 : 理解你的意圖
- 檔案名稱與專案結構 : 推測程式碼的用途
- 已開啟的其他檔案 : 獲取更廣泛的上下文
例如,當你寫完蘋果的計算邏輯,Copilot 注意到:
- 變數命名模式:
xxxPrice、xxxDiscount - 計算邏輯模式:
price * (1 - discount) - 輸出模式:
Console.WriteLine顯示結果
基於這些模式,它推論你可能需要計算其他水果的價格。
機率性預測(Probabilistic Prediction)
Copilot 使用大型語言模型(Large Language Model, LLM),基於數十億行開源程式碼訓練而成。當你輸入程式碼時,它會:
- 計算下一段程式碼最可能的樣子
- 生成多個候選建議
- 根據上下文排序建議
- 呈現最佳建議給你
這就是為什麼按下 Ctrl + Enter 時,你會看到多個替代方案——每個都是 Copilot 認為「可能」的解決方案, 但機率不同。
最佳實踐:如何讓 Copilot 更貼心
1. 寫清楚的註解
不好的註解:
//計算
好的註解:
//建立一個計算折扣後價格的類別,具有水果名稱、單價、折扣這三個屬性,以及一個計算折扣後價格的方法
清楚的註解包含:
- 動作(建立)
- 對象(類別)
- 細節(三個屬性、一個方法)
- 目的(計算折扣後價格)
2. 提供範例程式碼
Copilot 從模式中學習。先寫一個完整的範例,它就能推論出後續的模式:
// 第一個範例(你寫的)
double applePrice = 35;
double appleDiscount = 0.1;
Console.WriteLine("蘋果折扣後價格是:" + applePrice * (1 - appleDiscount));
// 後續範例(Copilot 生成的)
double bananaPrice = 25; // Copilot 自動建議
...
3. 使用一致的命名慣例
保持變數、函數命名的一致性,能幫助 Copilot 更好地理解你的意圖:
// 一致的命名
double applePrice, appleDiscount;
double bananaPrice, bananaDiscount;
// 而非
double applePrice, discount_for_apple;
double price_banana, bananaDisc;
4. 善用快捷鍵探索選項
不要總是接受第一個建議。
使用 Ctrl + Enter 查看其他選項,可能會發現更好的解決方案:
- 第一個建議可能使用 for 迴圈
- 第二個建議可能使用 LINQ
- 第三個建議可能使用函數式程式設計
選擇最適合你需求的方案。
5. 段落式開發(Incremental Development)
不要一次讓 Copilot 生成太複雜的程式碼。分階段進行:
- 先寫基本邏輯
- 再用註解描述重構方向
- 逐步接受 Copilot 的建議
- 每個階段都測試驗證
這樣能保持對程式碼的掌控,也能讓 Copilot 提供更精準的建議。
相關資源: