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

リソース: https://github.com/yuhattor/copilot-handson-sample

GitHub Copilot のロケールを設定

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

alt text

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

alt text

ファイルを作成

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

import random
from typing import List, Callable, Optional, Dict, Any

class KitchenObjectSO:
    def __init__(self, name: str):
        self.name = name

class RecipeSO:
    def __init__(self, kitchen_object_so_list: List[KitchenObjectSO]):
        self.kitchen_object_so_list = kitchen_object_so_list

class RecipeListSO:
    def __init__(self, recipe_so_list: List[RecipeSO]):
        self.recipe_so_list = recipe_so_list

class PlateKitchenObject:
    def __init__(self):
        self._kitchen_object_so_list = []
    
    def get_kitchen_object_so_list(self) -> List[KitchenObjectSO]:
        return self._kitchen_object_so_list

class KitchenGameManager:
    _instance = None
    
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = KitchenGameManager()
        return cls._instance
    
    def is_game_playing(self) -> bool:
        return True

class DeliveryManager:
    _instance = None
    
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = DeliveryManager()
        return cls._instance
    
    def __init__(self):
        self.on_recipe_spawned_callbacks = []
        self.on_recipe_completed_callbacks = []
        self.on_recipe_success_callbacks = []
        self.on_recipe_failed_callbacks = []
        
        self.recipe_list_so = None
        self.waiting_recipe_so_list = []
        self.spawn_recipe_timer = 0.0
        self.spawn_recipe_timer_max = 4.0
        self.waiting_recipes_max = 4
        self.successful_recipes_amount = 0
    
    def add_on_recipe_spawned_callback(self, callback: Callable[[], None]):
        self.on_recipe_spawned_callbacks.append(callback)
    
    def add_on_recipe_completed_callback(self, callback: Callable[[], None]):
        self.on_recipe_completed_callbacks.append(callback)
    
    def add_on_recipe_success_callback(self, callback: Callable[[], None]):
        self.on_recipe_success_callbacks.append(callback)
    
    def add_on_recipe_failed_callback(self, callback: Callable[[], None]):
        self.on_recipe_failed_callbacks.append(callback)
    
    def set_recipe_list_so(self, recipe_list_so: RecipeListSO):
        self.recipe_list_so = recipe_list_so
    
    def update(self, delta_time: float):
        self.spawn_recipe_timer -= delta_time
        if self.spawn_recipe_timer <= 0.0:
            self.spawn_recipe_timer = self.spawn_recipe_timer_max
            
            if KitchenGameManager.get_instance().is_game_playing() and len(self.waiting_recipe_so_list) < self.waiting_recipes_max:
                waiting_recipe_so = self.recipe_list_so.recipe_so_list[random.randint(0, len(self.recipe_list_so.recipe_so_list) - 1)]
                
                self.waiting_recipe_so_list.append(waiting_recipe_so)
                
                for callback in self.on_recipe_spawned_callbacks:
                    callback()
    
    # レシピの材料と皿の材料が一致しているかどうかを確認する
    def deliver_recipe(self, plate_kitchen_object: PlateKitchenObject):
        for i in range(len(self.waiting_recipe_so_list)):
            waiting_recipe_so = self.waiting_recipe_so_list[i]
            
            if len(waiting_recipe_so.kitchen_object_so_list) == len(plate_kitchen_object.get_kitchen_object_so_list()):
                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_kitchen_object.get_kitchen_object_so_list():
                        if plate_kitchen_object_so == recipe_kitchen_object_so:
                            ingredient_found = True
                            break
                    if not ingredient_found:
                        plate_contents_matches_recipe = False
                
                if plate_contents_matches_recipe:
                    self.successful_recipes_amount += 1
                    
                    self.waiting_recipe_so_list.pop(i)
                    
                    for callback in self.on_recipe_completed_callbacks:
                        callback()
                    for callback in self.on_recipe_success_callbacks:
                        callback()
                    return
        
        for callback in self.on_recipe_failed_callbacks:
            callback()
    
    def get_waiting_recipe_so_list(self) -> List[RecipeSO]:
        return self.waiting_recipe_so_list
    
    def get_successful_recipes_amount(self) -> int:
        return self.successful_recipes_amount

GitHub Copilot Chat を効率的に使いこなすためには、キーボードショートカットを覚えることが有用です。ここでは、主要なショートカットをいくつか紹介します。これらは、エディタやコードビュー内で素早く Copilot Chat を起動したり、会話を開始したりするのに役立ちます。 macOS:

ショートカットキー

アクション

Ctrl + Command + I

Chat ビューを開いて Copilot との会話を開始

Shift + Command + I

Copilot Edits ビューを開き、複数ファイルの編集セッションを開始

Shift + Option + Command + L

Quick Chat を開いて素早く Copilot に質問

Command + I

インラインチャットを開始し、エディタ内から直接 Copilot とチャット

Windows:

ショートカットキー

アクション

Ctrl + Alt + I

Chat ビューを開いて Copilot との会話を開始

Ctrl + Shift + I

Copilot Edits ビューを開き、複数ファイルの編集セッションを開始

Ctrl + Alt + Shift + L

Quick Chat を開いて素早く Copilot に質問

Ctrl + I

インラインチャットを開始し、エディタ内から直接 Copilot とチャット

これらのショートカットを使うことで、GitHub Copilot Chat を効率的に活用できます。自然言語での問い合わせや、/ コマンドを使用した特定の指示も可能です。

エージェント

チャット中に @ を入力すると、ドメインエキスパート(特定の分野の専門家)などのチャット参加者のリストが表示されます。
たとえば、以下のように入力できます。

@workspace どうやって認証を実装するの?

Copilot Chat は自動的に質問を適切な参加者へルーティングすることも可能です。

スラッシュコマンド

/ を入力すると、特定のアクション(コードブロックの解説やテスト生成、ドキュメンテーション生成など)を簡潔に指示できます。

コマンド

説明

/explain

選択したコードブロックやプログラミング概念を Copilot に解説してもらう

/tests

選択した関数やメソッドに対してテストコードを自動生成

これらのショートカットやコマンドを活用することで、GitHub Copilot Chat からより素早く、より豊かなアシスタンスを得ることができます。

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

  1. delivery_manager.py の解説を自分のプロンプトで Copilot Chat に尋ねてみましょう。 (必要に応じて説明してもらいたい箇所をカーソルで選択しましょう)

例:

このコードは何をしているのか教えてください

alt text 2. /explain を使ったとき、 Copilot Chat がどのような解説を返してくるか確認してみましょう。 alt text

  1. Copilot Chat にこのコードの悪い部分を尋ねてみましょう。

例:

このコードの悪い部分は何かありますか?
  1. プロンプトを工夫しましょう

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

改善してみましょう。

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

例えば、以下のように聞いてみましょう。

効率の良いループ処理を提案してください

alt text

  1. 提案されたコードが「どのように」効率的なのかをコメントに書くようにお願いしましょう。

以下のコードを logic_errors.py として保存してください。

def calculate_average(nums):
    sum = 0

    for i in range(len(nums) + 1):
        sum += nums[i]

    avg = sum / len(nums)
    return avg

numbers = [1, 2, 3, 4, 5]
avg = calculate_average(numbers)
print(f"The average is {avg}.")

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

python logic_errors.py を実行してみましょう。 インデックスエラーが発生するはずです。

もし、Python の環境準備ができない場合でも、python logic_errors.py というコマンドを実行してみましょう。

bash: python: command not found

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

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

多様なコンテキスト変数を使って、コードの読解を助けるような質問をしてみましょう。

Link: こちらのドキュメントをご覧ください 以下のようにMarkdownテーブルに変換しました。#selection のようなコンテキスト変数はバッククォートで囲んでいます。

コンテキスト変数

説明

クイックピックでコンテキストを選択 (⌘/)

ワークスペースのファイル、シンボル、現在のエディタ選択範囲や表示内容、ターミナルの選択範囲や最後に実行したコマンド、VS Code APIなどから、チャットのプロンプトに関連するコンテキストを選択できます。

コードベースのコンテキスト (#codebase)

ワークスペースの関連コンテンツをプロンプトのコンテキストとして追加します。

エディタのコンテキスト (#editor)

アクティブなエディタの表示内容をプロンプトのコンテキストとして追加します。

選択範囲のコンテキスト (#selection)

現在のエディタの選択範囲をプロンプトのコンテキストとして追加します。

ターミナル選択のコンテキスト (#terminalSelection)

現在のターミナルの選択範囲をチャットプロンプトのコンテキストとして追加します。

最後のターミナルコマンド (#terminalLastCommand)

最後に実行したターミナルコマンドをプロンプトのコンテキストとして追加します。

VS Code API (#VSCodeAPI)

VS Code拡張機能開発に関する質問をする際に、VS Code APIをプロンプトのコンテキストとして追加します。

ファイル選択 (#file)

ワークスペースからファイルを選択し、プロンプトのコンテキストとして追加します。

ファイル名指定 (#)

# に続けてファイル名を入力すると、ワークスペースのファイルの候補が表示され、コンテキストとして追加できます。

シンボル選択 (#sym)

ワークスペースからシンボルを選択し、プロンプトのコンテキストとして追加します。

シンボル名指定 (#)

# に続けてシンボル名を入力すると、ワークスペースのファイルのシンボル候補が表示され、コンテキストとして追加できます。

ドラッグ&ドロップ

ファイルやエディタをチャットにドラッグ&ドロップすると、そのファイルがコンテキストとして追加されます。

Copilot Chat は、実行の遅いコードを高速化する方法を提案できます。

ここからのハンズオンでは3のハンズオンのように「コードの改善を確認」するだけではなく、実際に実行してみましょう。また、工夫して早くする方法をAIからどの様に引き出すのか、コードをどの様にモジュールとして書かせるのかなどをトライしてみましょう。

既存のコードが正しく機能するものの非効率的で、パフォーマンスのボトルネックになっていることがあります。パフォーマンスに悪影響を与える可能性があるコーディングの例としては、効率の悪いループ、不要な計算、過剰なメモリ割り当てなどがあります。

Copilot Chatは、パフォーマンスのホットスポットを特定して、データ構造の最適化(配列の代わりにハッシュマップを使うなど)、冗長な計算の削減、アルゴリズムの効率の向上などのリファクタリング戦略を提案できます。

サンプルシナリオ

次のPythonコードでは、10,000番目の素数まで、1,000番目ごとの素数が出力されます。これにはタイマーが含まれており、コードの実行にかかった時間が出力の最後の行として表示されます。プログラムの完了には数秒かかります。

import time

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

def every_1000th_prime():
    primes = []
    num = 2
    while len(primes) < 10000:
        if is_prime(num):
            primes.append(num)
        num += 1
    return [primes[i] for i in range(999, 10000, 1000)]

if __name__ == "__main__":
    start_time = time.time()
    primes = every_1000th_prime()
    for prime in primes:
        print(prime)
    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"Time taken: {elapsed_time:.10f} seconds")

プロンプトの例

このコードのパフォーマンスを最適化してください

応答の例

Copilot は、エラトステネスの篩の関数を使って素数をより効率的に生成することを提案します。このプログラムを実行すると、ほんの一瞬で完了します。

import time

def sieve_of_eratosthenes(limit):
    sieve = [True] * (limit + 1)
    sieve[0] = sieve[1] = False
    for start in range(2, int(limit**0.5) + 1):
        if sieve[start]:
            for multiple in range(start*start, limit + 1, start):
                sieve[multiple] = False
    return [num for num, is_prime in enumerate(sieve) if is_prime]

def every_1000th_prime():
    limit = 105000  # Higher than needed to ensure 10,000 primes
    primes = sieve_of_eratosthenes(limit)
    return [primes[i] for i in range(999, 10000, 1000)]

if __name__ == "__main__":
    start_time = time.time()
    primes = every_1000th_prime()
    for prime in primes:
        print(prime)
    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"Time taken: {elapsed_time:.10f} seconds")

Copilot Chat は、ビジネス ロジックからデータ アクセス コードを切り離し、アプリケーションの保守とスケーリングを容易にする方法を提案できます。

SQL クエリがハードコーディングされ、データ アクセス コードが密結合されていると、データベースのスケーリングや切り替えが困難になり、ロジックの繰り返しにつながることがよくあります。

サンプルシナリオ

この Python コードは SQLite データベースに接続し、ユーザー レコードを取得して、ユーザー データを返します。ただし、データベース接続ロジックを抽象化できず、SQL インジェクションに対して脆弱なハードコーディングされたクエリが使用されます。

import sqlite3

def get_user_by_id(user_id):
    conn = sqlite3.connect('database.db')
    cursor = conn.cursor()
    cursor.execute(f"SELECT display_name FROM users WHERE id = {user_id}")
    user = cursor.fetchone()
    conn.close()
    return user

プロンプトの例 1

安全で更新・拡張しやすいようにこのコードを改善する方法は何ですか?可能な改善点を挙げてください(修正コードは示さなくて結構です)。

応答の例 1

Copilot は、コードの改善に関する提案をいくつか行います。

データベース接続の詳細を抽象化します。 パラメーター化されたクエリを使って、SQL インジェクションを防ぎます。 エラーが発生する場合は、コンテキスト マネージャーを使って、データベース接続が適切に終了されていることを確認します。 エラー処理を追加します。

プロンプトの例 2

このコードをより拡張性が高く保守しやすいようにリファクタリングしてください。コンテキストマネージャを使用し、ハードコードされたSQLクエリや密結合されたデータアクセスコードを避けてください。代わりに、リポジトリパターンを使用してデータベース操作を抽象化し、コードをよりモジュール化し再利用可能にしてください。可能な場合はパフォーマンス向上のためにコードを最適化してください。エラートラップを含め、SQLインジェクションに対して脆弱でないことを確認してください。

応答の例 2

Copilot は、それぞれが特定の目的を持つ個別のファイルにコードを分割することを提案します。また、ユーザーの詳細を返すコード例も示します。

提案された変更では、リポジトリ パターンが実装され、パラメーター化されたクエリを使って SQL インジェクションが防止されています。

ファイルが実行できない場合、実行処理もコードに追加する様に Copilot に尋ねてみましょう。

修正内容によってはエラーが出る可能性があります。 もしエラーが出た場合は、Copilot Chat にエラーの解決を依頼してみましょう。

# database.py
import sqlite3

class Database:
    def __init__(self, db_path):
        self.db_path = db_path

    def __enter__(self):
        self.conn = sqlite3.connect(self.db_path)
        self.cursor = self.conn.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type or exc_val or exc_tb:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.conn.close()

# user_repository.py
class UserRepository:
    def __init__(self, db):
        self.db = db

    def get_user_by_id(self, user_id):
        try:
            self.db.cursor.execute("SELECT display_name FROM users WHERE id = ?", (user_id,))
            user = self.db.cursor.fetchone()
            return user
        except sqlite3.Error as e:
            print(f"An error occurred: {e}")
            return None

# main.py
from database import Database
from user_repository import UserRepository

def get_user_by_id(user_id):
    with Database('database.db') as db:
        user_repo = UserRepository(db)
        return user_repo.get_user_by_id(user_id)

# Example usage
if __name__ == "__main__":
    user = get_user_by_id(1)
    if user:
        print(user)
    else:
        print("User not found or an error occurred.")

ここでは、GitHub Copilot の slash コマンドを使ってテストを自動生成したり、テストフレームワークを設定したりする方法を学びます。slash コマンドは Copilot Chat 内で利用でき、コマンドを入力することで特定のアクションを実行してくれます。
このハンズオンを通じて、/tests/setupTests/fixTestFailure といった slash コマンドの使い方を覚え、テストコードを書くプロセスを支援してもらいましょう。

Copilot は、あなたのコードベースの関数やメソッドに対してテストを自動生成できます。以下の slash コマンドを使うことで、テストに関するさまざまな支援を受けることができます。

Action

Description

/tests

エディタ内で選択されたメソッドや関数、または全てに対するテストを自動生成します。生成されたテストは、既存のテストファイルに追記されるか、新しいテストファイルが作成されます。

/setupTests

あなたのコードに合ったテストフレームワークの設定方法を支援してくれます。どのフレームワークが適切か、設定手順、VS Code 用のテスティング拡張機能の提案を受けることができます。

/fixTestFailure

失敗しているテストを修正するためのヒントや提案を行ってくれます。

これらの slash コマンドは Copilot Chat 内で利用することができ、より効率的かつ体系的なテスト作成フローをサポートしてくれます。

いくつかの方法で、テストを書いてみましょう GitHub Copilot Chat で /tests を使った場合と、GitHub Copilot の自動補完機能を使ってテストを書いた場合の違いを見てみましょう。 また、setupTests/fixTestFailure を試してみましょう。

# テストのためのモジュールをインポート
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)

エクササイズ:

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

エクササイズ:

# テストのためのモジュールをインポート
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):

次にコードを読んでみましょう。 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)
);

エクササイズ:

選択した範囲をマークダウンテーブルに変換して
選択した範囲をマークダウンテーブルに変換してください。以下の列を持つテーブルを書いてください No., カラム名, 型, 長さ(length), Decimal, 必須(Yes/No), 主キー, 詳細(日本語)

以下に 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 show_article(article_id: int, db: Session = Depends(get_db)):
    return article.show_article(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_article(article_id: int, db: Session = Depends(get_db)):
    return article.delete(article_id, db)

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

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

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

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

変換をしてみましょう。

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

erDiagram
    USERS {
        string id PK
        string first_name
        string last_name
        string first_name_kana
        string last_name_kana
        date birthday
        string gender
        string uid
        string email
        boolean email_verified
        string phone_number
        string picture
        string zipcode
        string prefecture
        string city
        string town
        string address1
        string address2
        string building_name
        string organization
        string industry
        string occupation
        string employment_type
        integer service_years
        integer annual_income
        float estimated_interest_rate
        integer down_payment
        integer budget
        integer household_income
        integer anual_income
        integer own_capital
        integer liabilities
        integer loan_repayment_period
        integer family_structure
        integer dependents
        boolean marriage
        boolean has_spouse
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    INQUIRIES {
        string id PK
        string user_id FK
        string listing_id
        text message
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    RESERVATIONS {
        string id PK
        string user_id FK
        string listing_id
        datetime appointment
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    FILTERS {
        string id PK
        string user_id FK
        string name
        string description
        string text
        string code
        string sort
        string sort_by
        string handover
        string parking_lot_availability
        string share
        integer min_price
        integer max_price
        integer min_age
        integer max_age
        integer min_area
        integer max_area
        string land_rights
        json floorplans
        json directions
        json facilities
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    FAVORITES {
        string id PK
        string user_id FK
        string share
        string title
        string description
        json listings
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    INVITATIONS {
        string id PK
        string user_id FK
        string email
        datetime invited_at
        datetime canceled_at
        datetime activated_at
        string activated_by FK
        datetime discarded_at
        datetime created_at
        datetime updated_at
    }
    
    USERS ||--o{ INQUIRIES : "has"
    USERS ||--o{ RESERVATIONS : "has"
    USERS ||--o{ FILTERS : "has"
    USERS ||--o{ FAVORITES : "has"
    USERS ||--o{ INVITATIONS : "sends"
    USERS ||--o{ INVITATIONS : "activates"

Copilot Chat は、同じ操作を別のプログラミング言語で実行するようにコードを書き直すのに役立ちます。

あるプログラミング言語から別のプログラミング言語にコードを移行する理由はさまざまです。各プログラミング言語にはそれぞれの長所と短所があるため、別の言語で使用できる機能を使いたい場合があります。

シナリオ例

次のPerlスクリプトは、ユーザーにテキスト ファイルのパスの入力を求めるものです。ユーザーが入力した内容をチェックし、そのパスでテキスト ファイルが見つかった場合は、ファイルの内容の単語数と文字数を出力します。

#!/usr/bin/perl
use strict;
use warnings;
use File::Spec;
use File::Basename;

print "Please enter the path to a local text file: ";
chomp(my $file_path = <STDIN>);

if ($file_path =~ /[`|;&<>(){}\[\]]/ || !File::Spec->file_name_is_absolute($file_path) && dirname($file_path) ne '.') {
    die "Invalid file path. Please provide a valid path.\n";
}
if (!-e $file_path) {
    die "File not found. Please provide an existing file path.\n";
}

open(my $fh, '<', $file_path) or die "Could not open file '$file_path': $!\n";

my $is_binary = 0;
while (read($fh, my $buffer, 1024)) {
    if ($buffer =~ /[^\x09\x0A\x0D\x20-\x7E]/) {
        $is_binary = 1;
        last;
    }
}
close($fh);

if ($is_binary) {
    die "The file '$file_path' is a binary file. Please provide a text file.\n";
}

open($fh, '<', $file_path) or die "Could not open file '$file_path': $!\n";
my $content = do { local $/; <$fh> };
close($fh);

my $word_count = scalar(split /\s+/, $content);
my $char_count = length($content);

print "The file '$file_path' contains $word_count words and $char_count characters.\n";

プロンプトの例

このPerlスクリプトをPythonに変換する方法を教えてください。新しいスクリプトの機能と出力は既存のものと全く同じでなければなりません。また、新しいファイルを実行する方法も教えてください。

応答の例

Copilot により、次の Python コードが生成されます。

import os
import re

def is_binary(file_path):
    try:
        with open(file_path, 'rt', encoding='utf-8') as file:
            file.read(1024)
        return False
    except UnicodeDecodeError:
        return True

file_path = input("Please enter the path to a local text file: ")

if re.search(r'[`|;&<>(){}\[\]]', file_path) or (not os.path.isabs(file_path) and os.path.dirname(file_path) != '.'):
    raise ValueError("Invalid file path. Please provide a valid path.")

if not os.path.exists(file_path):
    raise FileNotFoundError("File not found. Please provide an existing file path.")

if is_binary(file_path):
    raise ValueError(f"The file '{file_path}' is a binary file. Please provide a text file.")

with open(file_path, 'r', encoding='utf-8') as file:
    content = file.read()

word_count = len(content.split())
char_count = len(content)

print(f"The file '{file_path}' contains {word_count} words and {char_count} characters.")

このファイルを wordcount.py として保存します。

次に、新しいファイルを実行する方法が Copilot から説明されます。

  1. Python をインストールします。まだインストールしていない場合は、公式 Web サイト (https://www.python.org/) から Python をダウンロードしてインストールします。
  2. コマンドラインまたはターミナルを開きます。
  3. Python スクリプトがあるディレクトリに移動します。
  4. 以下のコマンドを使用して Python スクリプトを実行します:
python wordcount.py

これで、元の Perl スクリプトと同じ機能を持つ Python スクリプトが完成し、実行方法も説明されました。

GitHub Copilotでは、リポジトリ固有のカスタムインストラクションを設定することで、特定のプロジェクトに関連する質問に対してより適切な回答を得ることができます。このセクションでは、リポジトリカスタムインストラクションの設定方法と活用方法を学びましょう。

リポジトリカスタムインストラクションとは

リポジトリカスタムインストラクションは、特定のリポジトリに関連するCopilot Chatの質問に自動的にコンテキスト情報を追加する機能です。チームの開発スタイル、プロジェクト固有の規約、使用するフレームワークなどの情報を設定しておくことで、毎回同じ情報を質問に含める必要がなくなります。

これらの指示はチャット上には表示されませんが、Copilotが回答を生成する際に参照され、より質の高い回答を提供するために使用されます。

リポジトリカスタムインストラクションの設定方法

  1. リポジトリのルートに .github ディレクトリを作成します(存在しない場合)
  2. .github ディレクトリ内に copilot-instructions.md ファイルを作成します
  3. Markdown形式で自然言語の指示をファイルに追加します

例えば、次のような内容を追加できます:

このプロジェクトではTypeScriptを使用し、コードスタイルにはESLintとPrettierを使用しています。

すべてのコンポーネントはReact関数コンポーネントとして実装し、Hooksを積極的に活用してください。

APIリクエストにはAxiosを使用し、状態管理にはReduxを使用しています。

効果的な指示の書き方

効果的なリポジトリカスタムインストラクションを作成するためのポイントは以下の通りです:

実践演習:リポジトリカスタムインストラクションを作成する

  1. 現在のプロジェクトディレクトリに .github フォルダを作成します
  2. その中に copilot-instructions.md ファイルを作成します
  3. 以下のような内容を追加してみましょう:
このプロジェクトでは以下の規約を守ってください:

1. JavaScriptコードは、ダブルクォートとタブインデントを使用します
2. 関数にはJSDocコメントを必ず付けてください
3. コンポーネント名はPascalCase、変数・関数名はcamelCaseで命名します
4. テストは必ずJestを使用して記述してください
  1. ファイルを保存し、Copilot Chatで質問してみましょう(例:「このプロジェクトでボタンコンポーネントを作成してください」)
  2. Copilotがカスタムインストラクションに従った回答をしているか確認してください

リポジトリカスタムインストラクションの有効化/無効化

Copilot Chatでカスタムインストラクションを使用するかどうかを選択できます:

  1. Chatパネルの上部、またはイマーシブページの右上にある歯車アイコン(⚙️)をクリックします
  2. 「Enable custom instructions」または「Disable custom instructions」を選択します

この設定は変更するまで保持されます。

終了した方は、 Agent Hands-onにすすみましょう。

Agent Hands-on