このワークショップでは、GitHub Copilot を使ってコードの解説や改善を行う方法を学びます。 GitHub Copilot Chat は Chat 体験を通じて AI との対話を行うことができます。 ぜひ、このワークショップを通じて GitHub Copilot の使い方を学んでみましょう。

GitHub Copilot のロケールを設定

Visual Studio Code の設定を開きます。

alt text

設定画面に github.copilot.chat.localeOverride を打ち込み、ロケールを ja に設定してください。

alt text

ファイルを作成

下記のファイルを DelivaryManager.cs として保存してください。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeliveryManager : MonoBehaviour
{
    public event EventHandler OnRecipeSpawned;
    public event EventHandler OnRecipeCompleted;
    public event EventHandler OnRecipeSuccess;
    public event EventHandler OnRecipeFailed;


    public static DeliveryManager Instance { get; private set; }


    [SerializeField] private RecipeListSO recipeListSO;


    private List<RecipeSO> waitingRecipeSOList;
    private float spawnRecipeTimer;
    private float spawnRecipeTimerMax = 4f;
    private int waitingRecipesMax = 4;
    private int successfulRecipesAmount;


    private void Awake()
    {
        Instance = this;


        waitingRecipeSOList = new List<RecipeSO>();
    }

    private void Update()
    {
        spawnRecipeTimer -= Time.deltaTime;
        if (spawnRecipeTimer <= 0f)
        {
            spawnRecipeTimer = spawnRecipeTimerMax;

            if (KitchenGameManager.Instance.IsGamePlaying() && waitingRecipeSOList.Count < waitingRecipesMax)
            {
                RecipeSO waitingRecipeSO = recipeListSO.recipeSOList[UnityEngine.Random.Range(0, recipeListSO.recipeSOList.Count)];

                waitingRecipeSOList.Add(waitingRecipeSO);

                OnRecipeSpawned?.Invoke(this, EventArgs.Empty);
            }
        }
    }

    // レシピの材料と皿の材料が一致しているかどうかを確認する
    public void DeliverRecipe(PlateKitchenObject plateKitchenObject)
    {
        for (int i = 0; i < waitingRecipeSOList.Count; i++)
        {
            RecipeSO waitingRecipeSO = waitingRecipeSOList[i];

            if (waitingRecipeSO.kitchenObjectSOList.Count == plateKitchenObject.GetKitchenObjectSOList().Count)
            {
                bool plateContentsMatchesRecipe = true;
                foreach (KitchenObjectSO recipeKitchenObjectSO in waitingRecipeSO.kitchenObjectSOList)
                {
                    bool ingredientFound = false;
                    foreach (KitchenObjectSO plateKitchenObjectSO in plateKitchenObject.GetKitchenObjectSOList())
                    {
                        if (plateKitchenObjectSO == recipeKitchenObjectSO)
                        {
                            ingredientFound = true;
                            break;
                        }
                    }
                    if (!ingredientFound)
                    {
                        plateContentsMatchesRecipe = false;
                    }
                }

                if (plateContentsMatchesRecipe)
                {
                    successfulRecipesAmount++;

                    waitingRecipeSOList.RemoveAt(i);

                    OnRecipeCompleted?.Invoke(this, EventArgs.Empty);
                    OnRecipeSuccess?.Invoke(this, EventArgs.Empty);
                    return;
                }
            }
        }

        OnRecipeFailed?.Invoke(this, EventArgs.Empty);
    }

    public List<RecipeSO> GetWaitingRecipeSOList()
    {
        return waitingRecipeSOList;
    }

    public int GetSuccessfulRecipesAmount()
    {
        return successfulRecipesAmount;
    }

}

Copilot Chat にこのコードを解説させてみましょう。

  1. DelivaryManager.cs の解説を自分のプロンプトで Copilot Chat に尋ねてみましょう。 (必要に応じて説明してもらいたい箇所をカーソルで選択しましょう) alt text
  2. /explain を使ったとき、 Copilot Chat がどのような解説を返してくるか確認してみましょう。 alt text
  1. Copilot Chat にこのコードの悪い部分を尋ねてみましょう。
  2. プロンプトを工夫しましょう

DeliverRecipe() においてループ処理で効率化できる可能性がわかりました。

改善してみましょう。

  1. GitHub Copilot Chat に改善コードを提案させてみましょう。
  2. 改善箇所を選択して Inline Chat を使って提案してもらいましょう。インラインチャットは (Cmd + I, Ctrl + I) で開くことができます。
  3. Inline Chat で何のコマンドを使えるか確認しましょう。

alt text

以下のコードを logic-errors.js として保存してください。

// ソース: https://tutorial.eyehunts.com/js/javascript-logic-errors/

function calculateAverage(nums) {
  let sum = 0;

  for (let i = 0; i <= nums.length; i++) {
    sum += nums[i];
  }

  let avg = sum / nums.length;
  return avg;
}

let numbers = [1, 2, 3, 4, 5];
let avg = calculateAverage(numbers);
console.log(`The average is ${avg}.`);

このコードを解説させてみましょう。

node logic-errors.js を実行してみましょう。 The average is NaN. と表示されるはずです。

もし、node の環境準備ができない場合でも、node logic-errors.js というコマンドを実行してみましょう。

bash: node: command not found

のようにでた場合は、#terminalLastCommand を使って、エラーの内容を解説してもらいましょう。 なぜ実行できなかったかは自明ですが、Copilot Chat もターミナルをちゃんと見てくれるか確認してみましょう。

#terminalSelection を使って、選択した部分について質問してみましょう。任意のコマンドを選択して、そのコマンドや、そのコマンドの実行結果について質問することができます。

#editor#file というコンテキスト変数を使って、コンテキストを指定することができます。 また、@workspace を使うことによってエディタ全体をコンテキストに含めることができます。

#editor は、エディタの内容について解説をしてもらうことができます。 #file は、ファイルの内容について解説をしてもらうことができます。

単体テストを実行する

ここで、スラッシュコマンドの /tests を使って、単体テストを作成してみましょう。

# じゃんけんゲームを書いてください
import random
import unittest

# 勝敗の判定ロジックをハンドルするjudge関数を定義

def judge(player_hand, computer_hand):
    # プレイヤーの手とコンピューターの手を比較して結果を表示
    if player_hand == computer_hand:
        return "あいこです。"
    elif player_hand == "グー":
        if computer_hand == "チョキ":
            return "あなたの勝ちです。"
        else:
            return "あなたの負けです。"
    elif player_hand == "チョキ":
        if computer_hand == "パー":
            return "あなたの勝ちです。"
        else:
            return "あなたの負けです。"
    elif player_hand == "パー":
        if computer_hand == "グー":
            return "あなたの勝ちです。"
        else:
            return "あなたの負けです。"
    else:
        return "グー、チョキ、パーのいずれかを入力してください。"

# ロジックを全てハンドルするmain関数を定義
def main():
    # プレイヤーの手を入力
    player_hand = input("じゃんけんをしましょう!(グー、チョキ、パー):")
    # プレイヤーの手を表示
    print("あなたの手は" + player_hand + "です。")
    # コンピューターの手をランダムに選択
    computer_hand = random.choice(["グー", "チョキ", "パー"])
    # コンピューターの手を表示
    print("コンピューターの手は" + computer_hand + "です。")
    # judge関数を呼び出して結果を表示
    print(judge(player_hand, computer_hand))

次に異なるテストを書いてみましょう GitHub Copilot Chat で /tests を使った場合と、GitHub Copilot の自動補完機能を使ってテストを書いた場合の違いを見てみましょう。

# テストのためのモジュールをインポート
import unittest

# 文字列が渡された場合は、エラーを起こす
# それ以外の場合は、掛け算を行う
def multiply(a, b):
    # 数値のみを受け付ける
    if not (isinstance(a, (int, float)) and isinstance(b, (int, float))):
        raise ValueError("引数には数値を指定してください")
    return a * b

# テストを書く
class 

自動補完機能で、class と入力すると、以下のように補ってもらえます。

# テストのためのモジュールをインポート
import unittest

# 文字列が渡された場合は、エラーを起こす
# それ以外の場合は、掛け算を行う
def multiply(a, b):
    # 数値のみを受け付ける
    if not (isinstance(a, (int, float)) and isinstance(b, (int, float))):
        raise ValueError("引数には数値を指定してください")
    return a * b

# テストを書く
class TestMultiply(unittest.TestCase):
    def test_multiply(self):
        self.assertEqual(multiply(2, 3), 6)

エクササイズ:

duration 00:04:00

次に、テストのバリエーションを増やします。

エクササイズ:

# テストのためのモジュールをインポート
import unittest

def multiply(a, b):
    # 数値のみを受け付ける
    if not (isinstance(a, (int, float)) and isinstance(b, (int, float))):
        raise ValueError("引数には数値を指定してください")
    return a * b


# テストを書く
class TestMultiply(unittest.TestCase):
    # 10 通りのテストパターン
    def test_multiply(self):

duration 00:04:00

次にコードを読んでみましょう。 GitHub Copilot Chat に以下のコードをマークダウンにしてもらうように聞いてみましょう。

CREATE TABLE documents (
    document_id VARCHAR(63) PRIMARY KEY, -- Primary key, unique ID
    store_facility VARCHAR(63) NOT NULL, -- Storage facility (name of the SST where actual data exists)
    sst_document_id VARCHAR(63) NOT NULL, -- Communication document ID
    title VARCHAR(255) NOT NULL, -- Title
    from_adr TEXT NOT NULL, -- Sender's address
    to_adr TEXT NOT NULL, -- Recipient's address
    data_type VARCHAR(63) NOT NULL, -- Content data type
    extra_data TEXT, -- Additional data type
    pt_guid VARCHAR(255), -- Patient guide in case of a patient
    password_text VARCHAR(63), -- Security password
    create_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, -- Creation date
    last_status VARCHAR(63), -- Last status
    last_caption VARCHAR(255), -- Last description
    last_update TIMESTAMP WITHOUT TIME ZONE, -- Last update date
    limit_date TIMESTAMP WITHOUT TIME ZONE, -- Expiry date
    fix_flg VARCHAR(1), -- Fixed flag (0: normal, 1: fixed)
    del_flg VARCHAR(1) -- Delete flag (1: deleted, 0: valid)
);

エクササイズ:

以下に Python の FastAPI で書かれたコードがあります main.py のファイルを咲くえいしてして、/explain 機能を使って解説を出力する際に、より詳細な仕様が返ってくるようにフォーマットを整えてみましょう。 どのように詳細仕様書に変換することができますか?

@router.get('/', response_model=schema.Showarticle)
def all_articles(db: Session = Depends(get_db)):
    return article.get_all(db)

@router.get('/{article_id}', status_code=status.HTTP_200_OK, response_model=schema.Showarticle)
def all_articles(article_id: int, db: Session = Depends(get_db)):
    return article.show_all_articles(article_id, db)

@router.post('/', status_code=status.HTTP_201_CREATED)
def create(articles: schema.articleBase, db: Session = Depends(get_db)):
    return article.create(articles, db)

@router.put('/{article_id}', status_code=status.HTTP_202_ACCEPTED)
def update_article(article_id: int, articles: schema.articleBase, db: Session = Depends(get_db)):
    return article.update(article_id, articles, db)

@router.delete('/{article_id}', status_code=status.HTTP_204_NO_CONTENT)
def delete_articles(article_id: int, db: Session = Depends(get_db)):
    return article.delete(article_id, db)

次に以下のようなフォーマットを与えて書いてもらいましょう。

以下のリストを網羅できるように、API について仕様を書き出してくだくさい

- 処理概要
- アクセスURL
- プロトコル
- メソッド
  - GET
  - POST
  - PUT
  - DELETE
- リクエスト
  - フォーマット
    - JSON
    - その他
  - パラメータ
    - 数
    - 名前(例:`recipe_id`)
    - タイプ(数値、文字列など)
    - 長さ
    - 必須(必須/任意)
    - 説明
    - 備考
- レスポンス
  - フォーマット
    - JSON
    - その他
  - パラメータ
    - 数
    - 名前(例:`chef`)
    - タイプ(数値、文字列など)
    - 長さ
    - 繰り返し(はい/いいえ/回数)
    - 説明
    - 備考
- サンプル

GitHub Copilot を使うと、Markdown で使える Mermaid表記を使って以下のように図を描いてくれます。

変換をしてみましょう。

https://gist.github.com/yuhattor/cab9ae84bd41608e5d61cb96b76458b6

classDiagram
    class users{
        +id: string
        first_name: string
        last_name: string
        email: string
        ...
    }
    class inquiries{
        +id: string
        user_id: string
        listing_id: string
        message: text
        ...
    }
    class reservations{
        +id: string
        user_id: string
        listing_id: string
        appointment: datetime
        ...
    }
    class filters{
        +id: string
        user_id: string
        name: string
        description: string
        ...
    }
    class favorites{
        +id: string
        user_id: string
        share: string
        title: string
        ...
    }
    class invitations{
        +id: string
        user_id: string
        email: string
        invited_at: datetime
        ...
    }
    users "1" -- "*" inquiries : has
    users "1" -- "*" reservations : has
    users "1" -- "*" filters : has
    users "1" -- "*" favorites : has
    users "1" -- "*" invitations : has

Zero-shotプロンプティング モデルに事前の例示(example)やトレーニングを与えずに、直接質問やタスクを提示する方法です。

練習: GitHub Copilot Chat で未知の技術問題(例えば、「このコードのセキュリティを強化するにはどうすればよいか?」)をAIに問いかけ、どのような解答やアプローチを示すか分析する。

Few-shotプロンプティングは、少数の例を用いてモデルに特定のタスクの解法を理解させる方法です。

練習: ソフトウェアの仕様書の出力例を数件提示し、それに基づいて新しいコードの仕様書をAIに作らせる。

Chain-of-Thoughtプロンプティングは問題解決の過程をステップバイステップで説明させることで、より複雑な問題への対処を可能にする方法です。

練習: システム設計問題(例えば、コードやインフラ設計の最適化)を提示し、その解決策を段階的に説明させる。

矛盾しない一貫した回答を得るために、同じ質問を異なる形で複数回尋ねることができます。

練習: ソフトウェアの要件定義を異なる表現で複数回尋ね、AIの回答の一貫性を検証する。

知識生成プロンプティングはモデルに新しい情報やアイデアを生成させることに重点を置いたプロンプティングです。

練習: ITに関する創造的な問題(例:「このドキュメントを改善するにはどのようにすればよいですか」)をAIに問いかけ、革新的なアイデアを探求する。

Tree of Thoughts はある問題に対する複数の解答ルートや思考プロセスを探索させるアプローチです。

練習: 特定のIT課題(例えば、「リモートワークを効果的にサポートするITシステムの設計」)に対し、異なる観点からの解決策をAIに考えさせる。