😊

(使いこなしたい)github copilotを使用して人のコードまとめてもらう

2023/08/28に公開

概要


人のコードを読む時が往々にしてあると思うのですが、自分は読むより書くの方が好きなので、できれば読む時間は短縮したいと思って(断じてサボりなどではなく
github copilotを使用して諸々調査してみた。
肝心のgithub copilotの導入方法などは書くのがめんどくさいすごく綺麗にまとめている記事が既出なのでリンクだけ拝借させていただきます。

導入方法などはこちらがわかりやすいです。
https://dev.classmethod.jp/articles/github-copilot-introduction/

経緯


github copilotを開発業務が増えてきたのと個人的興味があったことから個人で契約開始。
その後、会社一部メンバーにテスト運用として、導入してもらえることが決まったので、社内導入後も同じ様に使わさせていただいている。

方法


基本的にはざっくり言うとコメントを追記して、(vscodeならば)ctrl+Enterにてsuggestionを提示してもらいます。

""" XXXXXの処理フローを記載してください。
"""

処理として使用したテストコードはこちら。
s3のファイルをダウンロードし、sparkで読み込んでからredshiftにマージするようなコードです。
(コード詳細、最適性、正常性は本記事観点とは無関係ですので省略させていただきます)

from pyspark.sql.types import StructType, StructField, IntegerType, StringType, BooleanType, DoubleType
from pyspark.sql import SparkSession
import boto3
import psycopg2

class S3FileLoader:
    def __init__(self, bucket, key):
        self.bucket = bucket
        self.key = key

    def fetch(self):
        s3 = boto3.client('s3')
        s3.download_file(self.bucket, self.key, 'local.csv')


class SparkProcessor:
    def process(self):
        schema = StructType([
            StructField("id", IntegerType(), True),
            StructField("name", StringType(), True),
            StructField("age", IntegerType(), True),
            StructField("email", StringType(), True),
            StructField("phone", StringType(), True),
            StructField("address", StringType(), True),
            StructField("country", StringType(), True),
            StructField("is_active", BooleanType(), True),
            StructField("salary", DoubleType(), True),
            StructField("department", StringType(), True),
        ])

        spark = SparkSession.builder.master(
            "local").appName("S3 to Redshift").getOrCreate()
        df = spark.read.csv("local.csv", header=True, schema=schema)
        return df


def merge_to_redshift(df):
    redshift_conn = psycopg2.connect(
        host="your_redshift_host",
        database="your_database",
        user="your_user",
        password="your_password",
        port="your_port"
    )
    cursor = redshift_conn.cursor()

    cursor.execute("TRUNCATE customer_temp;")

    for row in df.rdd.collect():
        cursor.execute("""
        INSERT INTO customer_temp VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
        """, tuple(row))

    cursor.execute("""
    MERGE INTO customer AS target
    USING customer_temp AS source
    ON target.id = source.id
    WHEN MATCHED THEN
        UPDATE SET
            name = source.name,
            age = source.age,
            email = source.email,
            phone = source.phone,
            address = source.address,
            country = source.country,
            is_active = source.is_active,
            salary = source.salary,
            department = source.department
    WHEN NOT MATCHED THEN
        INSERT (id, name, age, email, phone, address, country, is_active, salary, department)
        VALUES (source.id, source.name, source.age, source.email, source.phone, source.address, source.country, source.is_active, source.salary, source.department);
    """)

    cursor.close()
    redshift_conn.commit()
    redshift_conn.close()


if __name__ == "__main__":
    s3_loader = S3FileLoader("your_bucket", "your_key")
    s3_loader.fetch()

    processor = SparkProcessor()
    df = processor.process()

    merge_to_redshift(df)

ただ、そのコメントを記載する箇所を複数箇所試します。

  • 外部ファイルで質問するためにファイルパスをコピー
    • そのファイルは開いていて、なおかつピン止めしている。
    • そのファイルは開いている。
    • そのファイルは開いていない。
  • 外部ファイルで質問するために相対パスをコピー
    • そのファイルは開いていて、なおかつピン止めしている。
    • そのファイルは開いている。
    • そのファイルは開いていない。
  • 処理ファイル内で質問する。
  • 処理ファイル内で質問するが、全く関係のない処理ファイルも開いている。

結果


ではゆるりと実験結果を記載していきます。

  • 外部ファイルで質問するためにファイルパスをコピー
    まずは、別タブで対象ファイルを開きつつ、vscodeのパスをコピーして、別markdownファイルにて質問したところ、まず第一候補が処理しきれていないようなSuggestionでしたので効果なしでした。
Suggestion 1: ほぼ何も提案されない

<!-- s

Suggestion 2: 参考にならないファイル名入力からプログラム終了までのフロー

<!-- 1. ファイル名を入力してください。 -->
<!-- 2. ファイルパスを入力してください。 -->
<!-- 3. ファイルを読み込んでください。 -->
<!-- 4. ファイルを読み込みました。 -->
<!-- 5. ファイルの中身を表示してください。 -->
<!-- 6. ファイルの中身を表示しました。 -->
<!-- 7. ファイルを閉じてください。 -->
<!-- 8. ファイルを閉じました。 -->
<!-- 9. プログラムを終了します。 -->

Suggestion 3: よくわからない文面の繰り返し

<!-- ファイルパスをコピーして質問する。 -->

## おまけ
- このリポジトリのコードをコピペして使ってください。
{この文面の繰り返し}

Suggestion 4: この処理の仕様を知るためのチェックリスト

<!-- 以下の質問を回答してください -->
<!-- 1. このファイルはどのような処理を行っていますか? -->
<!-- 2. この処理の中で、どのような処理が遅いですか? -->
<!-- 3. この処理の中で、どのような処理が早いですか? -->
<!-- 4. この処理の中で、どのような処理がメモリを消費しているか? -->
<!-- 5. この処理の中で、どのような処理がCPUを消費しているか? -->
<!-- 6. この処理の中で、どのような処理がネットワークを消費しているか? -->
<!-- 7. この処理の中で、どのような処理がディスクを消費しているか? -->
<!-- 8. この処理の中で、どのような処理がOSのシステムコールを消費しているか? -->
<!-- 9. この処理の中で、どのような処理がデータを消費しているか? -->
<!-- 10. この処理の中で、どのような処理がデータを生成しているか? -->
<!-- 11. この処理の中で、どのような処理がデータを変換しているか? -->
<!-- 12. この処理の中で、どのような処理がデータを変更しているか? -->
<!-- 13. この処理の中で、どのような処理がデータを複製しているか? -->
<!-- 14. この処理の中で、どのような処理がデータを削除しているか? -->
~~~
その他のSuggestionも参考になるクオリティのものはなし。

対象ファイルが開いていない場合も同様で、ピン留めした上でのSuggestionではそれっぽいのが微妙に出てきたのですが、中身としては無関係で、参考にできるとは程遠いような内容でした。

=======
Suggestion 1

<!-- {対象ファイル}の処理フローを記載してください -->
# このファイルの処理フロー
- 1. ファイルパスを取得
- 2. ファイルの内容を読み込む
- 3. ファイルの内容を表示
- 4. ファイルの内容を返す
- 5. ファイルを閉じる
<!-- ここまで -->

## その他
### ファイルのパスを取得する
# ファイルパスを取得する
import os
dir_path = os.path.dirname(os.path.abspath(__file__))
### ファイルの内容を読み込む
# ファイルの内容を読み込む
with open(file_path, 'r') as f:
    file_content = f.read()
### ファイルの内容を表示
# ファイルの内容を表示
print(file_content)
### ファイルの内容を返す
# ファイルの内容を返す
return file_content
### ファイルを閉じる
# ファイルを閉じる
f.close()
  • 外部ファイルで質問するためにファイルパスをコピー
    copilotによるSuggestionは別markdownファイルのまま、対象ファイルを相対パスで指定する様にしてもあまり効果は表れませんでした。

Suggestion 1

<!-- s

=======
Suggestion 2

<!-- その他、説明したいことがあれば記載してください -->

## ディレクトリ構成

.
├── README.md
├── s3fileloader.py
├── s3fileloader_test.py
├── tests
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_s3fileloader.py
│   └── test_s3fileloader_test.py
└── tmp
    ├── test1
    │   ├── test1-1
    │   │   └── test1-1-1
    │   │       └── test1-1-1-1
    │   │           └── test1-1-1-1-1
    │   │               └── test1-1-1-1-1-1
    │   │                   └── test1-1-1-1-1-1-1
    │   │                       └── test1-1-1-1-1-1-1-1
    │   │                           └── test1-1-1-1-1-1-1-1-1
    │   │                               └── test1-1-1-1-1-1-1-1-1-1
    │   │                                   └── test1-1-1-1-1-1-1-1-1-1-1
    │   │                                       └── test1-1-1-1-1-1-1-1-1-1-1-1
    │   │                                           └── test1-1-1-1-1-1-1-1-1-1-1-1-1
    │   │                                               └── test1-1-1-1-1-1-1-1-1-1-1-1-1-1
    │   │                                                   └── test1-1-1-1-1-1-1-1-1-1-1-1-1-1-1
    │   │

=======
Suggestion 3

<!-- 1. インスタンスを作成する -->
<!-- 2. ファイルを開く -->
<!-- 3. ファイルを読み込む -->
<!-- 4. ファイルを閉じる -->
<!-- 5. 例外処理 -->
<!-- 6. ファイルを閉じる -->
<!-- 7. インスタンスを削除する -->
<!-- 8. 例外処理 -->
<!-- 9. エラー処理 -->
<!-- 10. インスタンスを削除する -->
<!-- 11. エラー処理 -->
<!-- 12. エラー処理 -->
{同様の文面の繰り返し}
~~~
その他のどのケースのSuggestionも参考になるクオリティのものはなし。
  • 処理ファイル内で質問する
    試しに対象ファイルで処理フローを記載するようにコメントを記載すると以下のようにこれまでとは打って変わっての実際の処理フローに近いSuggestionが出力されました。

"""s3fileloader.pyの処理フローを記載してください 

Suggestion 結果
--- 

Synthesizing 10/10 solutions

=======
Suggestion 1

1. S3FileLoaderクラスのfetchメソッドを呼び出し、CSVファイルをローカルにダウンロードする
2. SparkProcessorクラスのprocessメソッドを呼び出し、ダウンロードしたCSVファイルをSparkのDataFrameに読み込む
3. merge_to_redshift関数を呼び出し、DataFrameをRedshiftのテーブルにマージする

=======
Suggestion 2

1. S3FileLoaderクラスを定義してください。コンストラクタにはbucketとkeyを渡してください。
2. S3FileLoaderクラスにfetchメソッドを定義してください。fetchメソッドでは、S3からCSVファイルをローカルにダウンロードしてください。
3. SparkProcessorクラスを定義してください。processメソッドでは、CSVファイルをSparkのDataFrameに変換してください。
4. merge_to_redshift関数を定義してください。この関数では、S3からダウンロードしたCSVファイルをRedshiftのcustomerテーブルにマージしてください。

=======
Suggestion 3

1. S3FileLoaderクラスのインスタンスを生成し、fetchメソッドを呼び出す
2. SparkProcessorクラスのインスタンスを生成し、processメソッドを呼び出す
3. merge_to_redshift関数を呼び出す

=======
Suggestion 4


1. S3FileLoaderクラスのfetchメソッドを呼び出し、S3のファイルをローカルにダウンロードする
2. SparkProcessorクラスのprocessメソッドを呼び出し、ローカルにダウンロードしたファイルを読み込み、DataFrameを返す
3. merge_to_redshift関数を呼び出し、DataFrameをRedshiftにマージする

=======
Suggestion 5


1. S3FileLoaderクラスのfetchメソッドを実行する
2. SparkProcessorクラスのprocessメソッドを実行する
3. merge_to_redshift関数を実行する

=======
Suggestion 6

s3fileloader.pyはS3からファイルをダウンロードし、
Sparkによってデータを整形してから、Redshiftにデータを格納します

1. S3FileLoaderを初期化する
2. S3FileLoaderのfetchメソッドを実行する
3. SparkProcessorを初期化する
4. SparkProcessorのprocessメソッドを実行する
5. SparkProcessorのprocessメソッドの処理結果を変数に格納する
6. merge_to_redshift関数を実行する

=======
Suggestion 7

1. S3FileLoaderを呼び出し、S3からファイルを取得
2. SparkProcessorを呼び出し、Sparkでファイルを読み込み、DataFrameを返却
3. RedshiftにDataFrameをマージ

=======
Suggestion 8

1. S3FileLoaderクラスを定義し、S3からファイルを取得するfetchメソッドを実装してください
2. SparkProcessorクラスを定義し、Sparkを使ってデータを加工するprocessメソッドを実装してください
3. merge_to_redshift関数を定義し、Redshiftにデータをマージする処理を実装してください

=======
Suggestion 9

    1. S3FileLoaderクラスを作成し、S3上のファイルをダウンロードするfetchメソッドを作成してください
        - コンストラクタの引数はbucketとkeyを受け取ってください
        - fetchメソッドはS3からファイルをダウンロードする処理を記載してください
    2. SparkProcessorクラスを作成し、CSVファイルを読み込むprocessメソッドを作成してください
        - processメソッドはS3からダウンロードしたCSVファイルを読み込み、DataFrameを返す処理を記載してください
        - CSVファイルのスキーマは以下の通りです
            - id: int
            - name: string
            - age: int
            - email: string
            - phone: string
            - address: string
            - country: string
            - is_active: boolean
            - salary: double
            - department: string
    3. merge_to_redshift関数を作成してください
        - この関数はSparkProcessorクラスのprocessメソッドで取得したDataFrameをRedshiftにマージする処理を記載してください
        - マージ処理は以下の通りです
            - customer_tempテーブルを一度TRUNCATEする
            - DataFrameのデータをcustomer_tempテーブルに一度INSERTする
            - customer_tempテーブルとcustomerテーブルをidでJOINし、一致したデータはUPDATEし、一致しないデータはINSERTする
        - Redshiftの接続情報は以下の通りです
            - host: your_redshift_host
            - database: your_database
            - user: your_user
            - password: your_password
            - port: your_port
    4. __main__関数を作成してください
        - ここでは、上記の3つの

=======
Suggestion 10

1. S3FileLoaderクラスを作成してください
    1.1. S3FileLoaderクラスのコンストラクタで、S3バケット名とキーを受け取ってください
    1.2. fetchメソッドを作成してください
        1.2.1. boto3のS3クライアントを作成してください
        1.2.2. S3クライアントを使って、S3バケットからキーを指定してファイルをダウンロードしてください
2. SparkProcessorクラスを作成してください
    2.1. processメソッドを作成してください
        2.1.1. SparkSessionを作成してください
        2.1.2. SparkSessionを使って、S3からダウンロードしたファイルを読み込んでください
        2.1.3. 読み込んだファイルのスキーマを定義してください
        2.1.4. スキーマを使って、読み込んだファイルのデータフレームを作成してください
        2.1.5. データフレームを返してください
3. merge_to_redshift関数を作成してください
    3.1. psycopg2でRedshiftに接続してください
    3.2. Redshiftのcustomer_tempテーブルをtruncateしてください
    3.3. データフレームの各行をループしてください
        3.3.1. データフレームの各行をRedshiftのcustomer_tempテーブルにinsertしてください
    3.4. Redshiftのcustomer_tempテーブルとcustomerテーブルをmergeしてください
        3.4.1. マッチした場合は、customerテーブルのレコードを更新してください
        3.4.2. マッ


"""

Suggestion9なんでほぼそのまま使用できるくらいのクオリティだと思いました。
また、処理フローの粒度が荒くても対象ファイル内で質問をしましたら、求めていた読み込みカラムもまとめてくれました。

"""s3fileloader.pyの処理フローを記載してください 
1. S3FileLoaderクラスを定義し、コンストラクタでbucketとkeyを受け取る
2. fetchメソッドでS3からCSVファイルをダウンロードする    
3. SparkProcessorクラスを定義し、processメソッドでCSVファイルを読み込む
4. SparkProcessorクラスのprocessメソッドで読み込んだCSVファイルをDataFrameに変換する
5. DataFrameを返す
6. merge_to_redshift関数でDataFrameをRedshiftにマージする


"""

↓ "読み込みカラム及び処理フロー" という一文を加えた後にもう一度Suggestion

"""s3fileloader.pyの処理フローを記載してください 
1. S3FileLoaderクラスを定義し、コンストラクタでbucketとkeyを受け取る
2. fetchメソッドでS3からCSVファイルをダウンロードする    
3. SparkProcessorクラスを定義し、processメソッドでCSVファイルを読み込む
    読み込みカラム及び処理フロー
    - id: IntegerType
    - name: StringType
    - age: IntegerType
    - email: StringType
    - phone: StringType
    - address: StringType
    - country: StringType
    - is_active: BooleanType
    - salary: DoubleType
    - department: StringType
4. SparkProcessorクラスのprocessメソッドで読み込んだCSVファイルをDataFrameに変換する
5. DataFrameを返す
6. merge_to_redshift関数でDataFrameをRedshiftにマージする


"""

全く関係のない処理ファイルを開いた状態で実験した場合も対象ファイル内でのSuggestionならば一定精度のSuggestionを出力してくれました。

結論


人が書いたコードを読んでもらう際は、以下の様に基本的には進め、もし外部情報など、ファイル内では情報が足りないのでしたら、Suggestionを出力する前のコメントにそれらの必須情報も提示することで精度が上がります。

- 前提として対象ファイル内でSuggestisonをもらった方が精度が格段に上がる。
- できたらのレベルだが対象ファイルに関係ないファイルは別タブで開かない。
- 対象ファイル内ならば、粒度が荒くても追加質問してSuggestionをすることで自分の望む粒度のSuggestionが出力される。

備考


本編とは少し逸れるのですが、メソッドとかのコメントスタイルなども真似てくれるので、一つのメソッドを記載すれば他メソッドのコメントなども自動作成してくれるのが痒いところに手が届く感じで自分は好きです。

参考文献


https://dev.classmethod.jp/articles/github-copilot-introduction/

Discussion