GitHub Copilotワークショップへようこそ!このワークショップでは、GitHub Copilot を使ってコードの解説や改善を行う方法を学びます。 GitHub Copilot Chat は Chat 体験を通じて AI との対話を行うことができます。 ぜひ、このワークショップを通じて GitHub Copilot の使い方を学んでみましょう。
このワークショップでは、以下のGitHubリポジトリを使用します:
プロジェクトURL: https://github.com/moulongzhang/20250708-Python-Project
以下のいずれかの方法でプロジェクトを開始できます:
ローカルにVS Codeがインストールされている場合:
git clone https://github.com/moulongzhang/20250708-Python-Project.git
cd 20250708-Python-Project
code .
プロジェクトを開いたら、以下の拡張機能をインストールしてください:
GitHub Copilotの基本的なコード補完機能を体験してみましょう。
VS Codeでサインインが完了していることを確認してください。
新しいPythonファイルを作成して、以下のコメントを入力してみましょう:
# Fibonacci数列を計算する関数
def fibonacci(n):
Copilotが自動的にコードを提案してくれることを確認してください。
⚙️ github.copilot.nextEditSuggestions.enabled
は、GitHub Copilotの次世代編集提案機能を有効にする設定です。この機能により、より高度なコード編集の提案を受け取ることができます。
以下のいずれかの方法で設定画面を開きます:
File
→ Preferences
→ Settings
Code
→ Settings...
→ Settings
Ctrl + ,
Cmd + ,
Ctrl + Shift + P
(Windows/Linux) または Cmd + Shift + P
(macOS)Preferences: Open Settings (UI)
を選択設定画面の検索ボックスに以下を入力:
github.copilot.nextEditSuggestions.enabled
false
を true
に変更する設定が正しく適用されているか確認:
Ctrl + Shift + P
(Windows/Linux) または Cmd + Shift + P
(macOS)Preferences: Open User Settings (JSON)
を選択{
"github.copilot.nextEditSuggestions.enabled": true
}
Ctrl + S
(Windows/Linux) または Cmd + S
(macOS)プロジェクトに含まれている point.py
ファイルを開いてください。このファイルには、二次元空間の点を表すクラスが含まれています:
import math
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
def __str__(self):
return f"Point2D({self.x}, {self.y})"
今、このクラスを三次元空間の点を表すクラスに拡張したいと考えています。まずは、クラス名を手動で Point3D
に変更してみましょう。すると、GitHub Copilotが次の編集候補を提案してくれます。
提案では以下のような変更が示されるはずです:
__init__
メソッドに z
パラメータの追加self.z = z
の追加distance_to
メソッドでの三次元距離計算への拡張__str__
メソッドでのz座標の表示この状態で Tab
キーを押すと、GitHub Copilotが提案をしている箇所にカーソルが移動します。そこで、提案を受け入れるには、再度 Tab
キーを押します。
すると、GitHub Copilotは次の編集候補を提案してくれるはずです。この提案も、Tab
キーを押すことで受け入れることができます。このように、Next Edit Suggestionを使うことで、コードの編集を効率的に行うことができます。
Point2DクラスをPoint3Dに拡張する作業を続けてみましょう。すべてのメソッドを三次元空間に対応させることができるはずです。
期待される最終的なコードの例:
import math
class Point3D:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def distance_to(self, other):
dx = self.x - other.x
dy = self.y - other.y
dz = self.z - other.z
return math.sqrt(dx * dx + dy * dy + dz * dz)
def __str__(self):
return f"Point3D({self.x}, {self.y}, {self.z})"
1行目でコメントアウトされている二次元
を 三次元
に置き換えてください。
# 二次元空間の点を表すクラス
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_to(self, other):
# TODO: ここに距離計算のコードを追加
pass
def __str__(self):
# TODO: 文字列表現を返す
pass
TODOコメントの後にカーソルを置いて、Copilotの提案を確認してみてください。
下記のファイルを delivery_manager.py
として保存してください。
import time
import random
from typing import List, Callable, Optional
from dataclasses import dataclass, field
from enum import Enum
class EventArgs:
"""イベント引数の基底クラス"""
pass
class Event:
"""C#のeventに相当するクラス"""
def __init__(self):
self._handlers: List[Callable] = []
def add_handler(self, handler: Callable):
"""イベントハンドラーを追加"""
if handler not in self._handlers:
self._handlers.append(handler)
def remove_handler(self, handler: Callable):
"""イベントハンドラーを削除"""
if handler in self._handlers:
self._handlers.remove(handler)
def invoke(self, sender, args: EventArgs = None):
"""イベントを発火"""
for handler in self._handlers:
handler(sender, args or EventArgs())
@dataclass
class KitchenObjectSO:
"""キッチンオブジェクトのデータクラス"""
name: str
object_id: int
@dataclass
class RecipeSO:
"""レシピのデータクラス"""
name: str
kitchen_object_so_list: List[KitchenObjectSO] = field(default_factory=list)
@dataclass
class RecipeListSO:
"""レシピリストのデータクラス"""
recipe_so_list: List[RecipeSO] = field(default_factory=list)
class PlateKitchenObject:
"""皿のキッチンオブジェクト"""
def __init__(self):
self._kitchen_object_so_list: List[KitchenObjectSO] = []
def add_kitchen_object(self, kitchen_object: KitchenObjectSO):
"""キッチンオブジェクトを追加"""
self._kitchen_object_so_list.append(kitchen_object)
def get_kitchen_object_so_list(self) -> List[KitchenObjectSO]:
"""キッチンオブジェクトリストを取得"""
return self._kitchen_object_so_list.copy()
class KitchenGameManager:
"""キッチンゲームマネージャー(Singleton)"""
_instance: Optional['KitchenGameManager'] = None
def __init__(self):
self._is_game_playing = False
@classmethod
def get_instance(cls) -> 'KitchenGameManager':
"""Singletonインスタンスを取得"""
if cls._instance is None:
cls._instance = cls()
return cls._instance
def is_game_playing(self) -> bool:
"""ゲームが進行中かどうか"""
return self._is_game_playing
def start_game(self):
"""ゲーム開始"""
self._is_game_playing = True
def stop_game(self):
"""ゲーム停止"""
self._is_game_playing = False
class DeliveryManager:
"""配達管理クラス(Python版)"""
_instance: Optional['DeliveryManager'] = None
def __init__(self, recipe_list_so: RecipeListSO):
# イベント定義
self.on_recipe_spawned = Event()
self.on_recipe_completed = Event()
self.on_recipe_success = Event()
self.on_recipe_failed = Event()
# プライベート変数
self._recipe_list_so = recipe_list_so
self._waiting_recipe_so_list: List[RecipeSO] = []
self._spawn_recipe_timer = 0.0
self._spawn_recipe_timer_max = 4.0
self._waiting_recipes_max = 4
self._successful_recipes_amount = 0
self._last_update_time = time.time()
@classmethod
def get_instance(cls, recipe_list_so: RecipeListSO = None) -> 'DeliveryManager':
"""Singletonインスタンスを取得"""
if cls._instance is None:
if recipe_list_so is None:
raise ValueError("初回作成時にはrecipe_list_soが必要です")
cls._instance = cls(recipe_list_so)
return cls._instance
def update(self):
"""フレーム更新処理(UnityのUpdate相当)"""
current_time = time.time()
delta_time = current_time - self._last_update_time
self._last_update_time = current_time
self._spawn_recipe_timer -= delta_time
if self._spawn_recipe_timer <= 0.0:
self._spawn_recipe_timer = self._spawn_recipe_timer_max
kitchen_game_manager = KitchenGameManager.get_instance()
if (kitchen_game_manager.is_game_playing() and
len(self._waiting_recipe_so_list) < self._waiting_recipes_max):
# ランダムにレシピを選択
waiting_recipe_so = random.choice(self._recipe_list_so.recipe_so_list)
self._waiting_recipe_so_list.append(waiting_recipe_so)
# イベント発火
self.on_recipe_spawned.invoke(self)
def deliver_recipe(self, plate_kitchen_object: PlateKitchenObject):
"""レシピの材料と皿の材料が一致しているかどうかを確認する"""
for i, waiting_recipe_so in enumerate(self._waiting_recipe_so_list):
plate_ingredients = plate_kitchen_object.get_kitchen_object_so_list()
# 材料数が一致するかチェック
if len(waiting_recipe_so.kitchen_object_so_list) == len(plate_ingredients):
plate_contents_matches_recipe = True
# レシピの各材料をチェック
for recipe_kitchen_object_so in waiting_recipe_so.kitchen_object_so_list:
ingredient_found = False
# 皿の材料と照合
for plate_kitchen_object_so in plate_ingredients:
if plate_kitchen_object_so == recipe_kitchen_object_so:
ingredient_found = True
break
if not ingredient_found:
plate_contents_matches_recipe = False
break
# 材料が完全に一致した場合
if plate_contents_matches_recipe:
self._successful_recipes_amount += 1
self._waiting_recipe_so_list.pop(i)
# 成功イベント発火
self.on_recipe_completed.invoke(self)
self.on_recipe_success.invoke(self)
return
# 一致するレシピが見つからなかった場合
self.on_recipe_failed.invoke(self)
def get_waiting_recipe_so_list(self) -> List[RecipeSO]:
"""待機中のレシピリストを取得"""
return self._waiting_recipe_so_list.copy()
def get_successful_recipes_amount(self) -> int:
"""成功したレシピ数を取得"""
return self._successful_recipes_amount
# 使用例
if __name__ == "__main__":
# サンプルデータ作成
tomato = KitchenObjectSO("Tomato", 1)
lettuce = KitchenObjectSO("Lettuce", 2)
bread = KitchenObjectSO("Bread", 3)
# サンプルレシピ
sandwich_recipe = RecipeSO("Sandwich", [bread, lettuce, tomato])
salad_recipe = RecipeSO("Salad", [lettuce, tomato])
recipe_list = RecipeListSO([sandwich_recipe, salad_recipe])
# ゲームマネージャーとデリバリーマネージャーを初期化
game_manager = KitchenGameManager.get_instance()
game_manager.start_game()
delivery_manager = DeliveryManager.get_instance(recipe_list)
# イベントハンドラーの設定
def on_recipe_spawned(sender, args):
print("新しいレシピが生成されました!")
def on_recipe_success(sender, args):
print("レシピ配達成功!")
def on_recipe_failed(sender, args):
print("レシピ配達失敗...")
delivery_manager.on_recipe_spawned.add_handler(on_recipe_spawned)
delivery_manager.on_recipe_success.add_handler(on_recipe_success)
delivery_manager.on_recipe_failed.add_handler(on_recipe_failed)
# サンプル実行
print("ゲーム開始...")
# 5秒間更新処理を実行
start_time = time.time()
while time.time() - start_time < 5:
delivery_manager.update()
time.sleep(0.1) # 100ms間隔で更新
print(f"待機中のレシピ数: {len(delivery_manager.get_waiting_recipe_so_list())}")
# サンプル配達テスト
plate = PlateKitchenObject()
plate.add_kitchen_object(bread)
plate.add_kitchen_object(lettuce)
plate.add_kitchen_object(tomato)
print("サンドイッチを配達...")
delivery_manager.deliver_recipe(plate)
print(f"成功したレシピ数: {delivery_manager.get_successful_recipes_amount()}")
Copilot Chat にこのコードを解説させてみましょう。
Ctrl+Alt+I
(macOSでは Ctrl+Cmd+I
) でChatパネルを開くチャットのモードが「質問」になっていることを確認します(「エージェント」モードは後ほど紹介します)。
/explain
と入力します/explain
コマンドが展開され、候補として @workspace /explain と @termianl /explain が表示されます#delivery_manager.py
と入力します。delivery_manager.py
ファイル全体を解説してくれますCopilot Chat にこのコードの悪い部分を尋ねてみましょう。
まずは、クラス全体としてこのコードはどのような問題を抱えているか聞いてみましょう。
Copilot Chatに以下のように質問してみてください:
このDeliveryManagerクラス全体を見て、どのような問題や改善点がありますか?設計パターン、コードの品質、保守性の観点から教えてください。
その後、deliver_recipe()
メソッドに絞って、このメソッドを改善するためにはどのような方法があるか聞いてみましょう。
#deliver_recipe
と入力しますdeliver_recipe
メソッドを選択しますこのdeliver_recipeメソッドを改善するためにはどのような方法がありますか?可読性、パフォーマンス、エラーハンドリングの観点から提案してください。
現在のコードを改善するために、Copilot Chatに以下のように質問してみましょう:
このPythonコードを改善してください。パフォーマンス、可読性、エラーハンドリングの観点から提案をお願いします。
このコードにセキュリティ上の問題はありますか?
Pythonのベストプラクティスに従っているか確認してください。
ここまでは「質問」モードでCopilot Chatを使ってきましたが、次は「エージェント」モードを使ってみましょう。エージェントは、ユーザーの意図を理解し、より自律的にタスクを実行することができます。実例を通して、エージェントがどのように機能するかを学びます。
まず delivery_manager.py
ファイルを開いている状態で、Copilot Chatのモード選択から「エージェント」を選択します。
その後、以下のプロンプトを入力してみましょう。
DeliveryManagerクラスに存在する問題点を列挙してください。そして、それぞれの問題点を解決するための改善案を提示してください。
すると、複数の改善点を提案してくれるはずです。
同じ質問を異なるモデルで試すことで、各モデルの特徴を比較できます。
では、実際に提案してもらった改善案を実装してもらいましょう。
提示してくれたすべての改善案を実装してください。
すると、Copilotはエディタで開かれているコードに対して直接コードの変更を行います。しかし、これはまだ提案の段階であり、この変更を受け入れるかどうかはユーザーが決定します。受け入れるかどうかは、チャット欄の上にある「保持」もしくは「元に戻す」ボタンをクリックすることで行います。
ここで、エージェントが返してくれたコメントを確認してみましょう。エージェントは単に指示に従ってコードを変更しただけでなく、コードを変更後にエラーが発生していることを確認し、そのエラーも修正しようとする場合があります。適切な環境下では、エージェントはコードの変更後に発生したエラーを自動的に検出し、修正を試みます。このように、エージェントはユーザーの意図を理解し、より自律的にタスクを実行することができます。
エージェントモードを使っていると、Copilotがコマンドを実行して良いかどうかを尋ねてくることがあります。これは、Copilotが何かのコマンドを実行する前に、必ずユーザーに確認を求めるためです。コマンドの内容を確認し、実行しても問題ない場合は「Allow this time」をクリックします。これにより、Copilotはそのコマンドを実行し、必要な変更を行います。
ここまでで、VS Code上で利用できるGitHub Copilotの基本的な使い方を学びました。次は、実際にアプリケーションを開発してみましょう。
今回のハンズオンでは、ポモドーロタイマーアプリケーションを開発します。このアプリケーションは、作業時間と休憩時間を設定し、タイマーを管理する機能を持っています。
以下のようなUIを持つアプリケーションを作成することを目指します。
では、まずVS Code上で、新しいPythonファイルを作成しましょう。今回はWebアプリケーションとして作成したいので、Flaskを使用します。メインファイル名は「app.py」としましょう。
ポモドーロテクニック用のWebタイマーアプリケーションを作成します。
まず、いきなり実装を始めるのではなく、どういった方針・設計で進めるかをCopilotに相談してみましょう。ここから先は、すべてエージェントモードで進めていきます。
今回のようにUIを持ったWebアプリケーションを作成するにあたって役に立つのが、Copilot Chatに画像をアップロードする機能です。これを使うことで、アプリケーションのUIイメージをCopilotに理解させることができます。
前ページのUIイメージをまずはプロジェクトのルートに pomodoro.png
として保存してください。その後、チャット欄の Add Context
をクリックし、「Image from Clipboard」または「Files & Folders...」を選択します。そして、UIイメージの画像を選択します。
画像のアップロードができたら、Copilot Chatに画像が表示されます。
その上で、次のプロンプトを入力してみましょう。
このプロジェクトでポモドーロタイマーのWebアプリを作成する予定です。添付の画像はそのアプリのUIモックです。FlaskとHTML/CSS/JavaScriptを使用してこのアプリを作成するにあたって、どのような設計で進めるべきか、アーキテクチャの提案をしてください。
すると、推奨のWebアプリケーションアーキテクチャを提案してくれます。
このアーキテクチャに対して、もっとこうした方が良いという点や考慮不足の点があれば、それを指摘してみましょう。例えば次のような指摘です。
ユニットテストのしやすさという点を考慮して、今のアーキテクチャにもし改善や追加が必要な点があればそれも書き出してください。
このやり取りを経て、アーキテクチャの設計が固まったら、一度その内容をファイルに保存してもらいましょう。そうすることで、別のチャットセッションを開いても、同じアーキテクチャの内容を参照することができます。
ここまでの会話でアーキテクチャについては固まったので、これまでの会話の内容を踏まえて、プロジェクトのルートにarchitecture.mdというファイルに、Webアプリケーションアーキテクチャ案をまとめてください。
ここまでで、UIモックとアーキテクチャの設計が固まりました。具体的にどのような機能を実装する必要があるかを検討していきましょう。これもCopilot Chatに相談してみます。その際、pomodoro.pngとarchitecture.mdを添付しましょう。
このポモドーロタイマーアプリケーションを作成するにあたって、実装する必要のある機能を洗い出してください。
この内容もCopilotとのチャットを通して、改善していきましょう。内容が固まったら、アーキテクチャの時と同様にこの内容もfeatures.mdというファイルにまとめて保存しておきましょう。
ありがとうございます。その内容で良さそうなので、実装する必要のある機能一覧をfeatures.mdというファイルに書いてください。
では、ここから実装を始めるわけですが、Copilotを使いこなすコツとしては、一度に大きな機能を実装しようとするのではなく、まずは小さな機能から実装していくことです。これにより、Copilotが提案するコードの精度が上がり、よりスムーズに開発を進めることができます。
今回のアプリケーション開発を、どのような粒度で細分化して実装していくかについても、Copilotに相談してみましょう。ここでは、pomodoro.png、architecture.md、features.mdを添付しましょう。
このポモドーロタイマーアプリケーションを段階的に実装していきたいと考えています。添付の画像とアーキテクチャ、機能一覧を踏まえて、どのような粒度で機能を実装していくべきか、段階的な実装計画を提案してください。
私が試したところ、6つのステップからなる計画を提案してくれました。この点についても、もっとこうしてほしいなどがあれば、Copilotに指摘してみましょう。そして、この内容も後で参照できるように、plan.mdというファイルにまとめて保存しておきましょう。その際、どういうプロンプトで指示するべきかは、みなさん自身で考えてみてください。
ここまでの準備が整ったので、いよいよ実装に取り掛かりましょう。前のステップで提案された実装計画に従って、段階的に機能を実装していきます。
まずは、今回のアーキテクチャに従ったプロジェクトのディレクトリ構成を作成しましょう。
まずは、architecture.md
のようなアーキテクチャを実現するにあたって、現在のプロジェクトのフォルダ構成を修正してください。必要に応じてファイルの移動や、設定ファイルの変更も行ってください。
その後、pomodoro.png
, architecture.md
, plan.md
を添付した上で、次のようにCopilotに指示を出してみましょう。
plan.mdのステップ1を実装してください。その際、すでにこのプロジェクトにあるファイルを別のディレクトリに移動する必要があれば、その作業も実行してください。もし追加で考慮が必要なことがあれば、私に質問してください。
すると、私のケースでは以下のように検討が必要な質問をしてきました。こういった場合には、必要な情報を提供しましょう。
その後、Copilotは、ステップ1の実装を行います。実装が完了したら、Copilotは自らの判断でプロジェクトのビルドを行い、エラーがないかを確認します。エラーが発生した場合は、そのエラーを解決するために追加で修正を行います。このような自律的な動作が、エージェントモードの特徴です。
実装が完了したら、以下の点を確認してみましょう:
以下が、私の場合のステップ1の実装結果です。この段階でどのようなアプリケーションになっているかは人によって異なるでしょう。
このまま実装を続ける前に、実装した機能に対してユニットテストを書いておきましょう。ユニットテストを書くことで、後のステップでの変更が既存の機能に影響を与えないことを確認できます。
もし前ページの段階でユニットテストも実装されている場合は、このページは読み飛ばしてください。
次のようなプロンプトを実行してみましょう。
現在の実装に対して、ユニットテストが全くないので、ユニットテストを実装してください。
すると、Copilotエージェントはユニットテスト用の依存関係をインストールするために、コマンドを使って良いかどうかを尋ねてきます。このように、エージェントが何かのコマンドを実行する前には、必ずユーザーに確認を求めます。ここでは、必要なコマンドを実行することを許可するために、「Continue」をクリックします。
すると、CopilotはVS Code内のターミナル内で、先ほどのコマンドを実行し、必要な依存関係をインストールします。それ以降も同様に、Copilotが何かのコマンドを実行する前には、必ずユーザーに確認を求めます。もし、そのコマンドを実行してエラーが発生した場合は、そのエラーを解決するために、エージェントは追加の修正を行います。
ここからは、自由課題として、残りの機能を段階的に実装していきましょう。
いくつか役に立つであろうポイントをここでは紹介します。
UI上の特定の要素に対して指示を出したい場合は、UIのスクリーンショットをCopilotにアップロードすることで、その要素を認識させることができます。その際、スクリーンショットの上に特に指摘したい箇所を丸で囲むなり、矢印を引くなりして、どの要素に対して指示を出したいのかを明確にすると良いでしょう。
または、現状のスクリーンショットと、期待するスクリーンショットを2枚アップロードすることで、その差分を確認してもらい、期待するUIにできるだけ近づくように指示を出すこともできます。
プロンプトを書いたり、文脈を指定する際に、頻繁に同じような指示を出している場合は、Copilotにその指示を覚えさせることができます。具体的には、プロジェクト内に .github/copilot-instructions.md
というファイルを作成し、その中に指示を書いておきます。このファイルがあると、Copilotはその指示を自動的に読み込み、以降のチャットでその指示を参照することができます。
以下にカスタム指示のサンプルを示します。
このプロジェクトは、ポモドーロタイマーをFlaskで実装するものです。
以下はプロジェクトの重要なファイルです。ユーザーの指示に対して、必要に応じてこれらのファイルを参照してください。
- `pomodoro.png`: アプリケーションのUIモックです。
- `architecture.md`: アプリケーションのアーキテクチャドキュメントです。
- `features.md`: 実装する機能の一覧です。
- `plan.md`: 段階的な実装計画です。
そのほかにも、プロジェクトをビルドするコマンドやテストを実行するコマンドなど、プロジェクトに特有のコマンドを記載しておくと、Copilotはそのコマンドを自動的に使用するようになります。
このような場合には、以下のアプローチを試してみましょう。
このワークショップでは以下のことを学びました:
お疲れさまでした!