Zenn
🙆

GitHub Copilotを使ってみた!

2025/03/12に公開

はじめに

近年ソフトウェア開発において、効率的な作業を実現するためにさまざまなツールが登場しています。

その中でも、GitHub Copilotは開発者のペアプログラマーとして、単なるコーディングの補助だけでなく、コードレビュー、リファクタリング、ドキュメンテーション生成など、さまざまな作業をサポートしてくれる優れたツールです。

本記事では、個人的な生成AIの技術調査としてGitHub Copilot機能を実際に使ってみた使用感について説明します。

GitHub Copilotできること

GitHub Copilotは、OpenAIのLLM(大規模言語モデル)をベースとしたAI開発支援ツールです。多くの機能が搭載されていますが、今回は実際に私が気になった機能に絞って紹介します。

https://docs.github.com/ja/copilot/about-github-copilot/github-copilot-features

  1. コード補完
    書きかけたコードに基づいて、次に必要なコードを自動で補完します。これにより、基本的な構造やよく使われるアルゴリズムを素早く書き進めることができます。

  2. コードレビュー
    スペルミス、冗長なコードや非効率なコード、セキュリティの脆弱性などを検出し、簡潔な形に修正する提案をしてくれます。

  3. コード生成
    仕様や要件をコメントまたはコードで伝えることで、Copilotがそのロジックに基づいたコードを生成します。

環境

今回はVisual Studio Codeの拡張機能として、GitHub Copilotをインストールして使いました。※今回は無料版を使用しています。

以下の記事を参考に、以下手順で導入しました。
https://qiita.com/RyoWakabayashi/items/1207128e88669c76bf5f

  1. GitHubアカウントの登録
    https://github.com/
  2. Visual Studio Codeのインストール
    https://code.visualstudio.com/download
  3. GitHub Copilot 用の Extension (拡張機能)を Visual Studio Code に追加
  4. Visual Studio Code上からのGitHubへのサインイン

以上でGitHub Copilotの導入は完了です。

実際に使ってみた

実際にそれぞれの機能を使用してみました。
※灰色文字部分が、GitHub Copilotが提示している文字部分になります。

1. コード補完

Javaでリストを昇順・降順でソートするメソッドを作成しました。

早速SortExampleにヒットしたのか、候補が出てきました。

逆順のソートも関数名から推測して、候補を提示してくれました。

現在使用しているIDEでは、候補がステップや変数単位でしか表示されませんが、GitHub Copilotでは関数(メソッド)全体の候補を出してくれる点が新鮮でした。この枠組みを活用すれば、開発のスピードが上がると感じました。

2. コードレビュー

次に、以下のコードについてレビューしてもらいました。いくつかのバグを埋め込んでみました。

# バグ
1 スペルミス
2 例外処理をせずにDB接続
3 nullチェックを入れない
  • レビューしてもらうコード
import java.util.List;

/**
 * ユーザーサービス.
**/
public class UserService {

    /**
     * データベースに接続し、ユーザー情報を取得.
     * ユーザーが19歳以上であればメール送信、以下であればスキップ.
    **/
    public void processUserData(List<String> userIds) {
        for (String userId : userIds) { //③userIdのnullチェックを入れていない。
            // データベースに接続してユーザー情報を取得
            User user = Database.getUserById(userId);  //②例外処理をせずDB接続 
            
            if (user != null) {
                // ユーザー情報を処理
                if (user.getAge() > 18) {
                    sendEmail(user);
                } else {
                    System.out.println("User is under 18, skopping."); //①skippingのスペル
                }
            }
        }
    }


    private void sendEmail(User user) {
        System.out.println("Sending email to " + user.getEmail());
    }
}

/**
 * ユーザー情報.
**/
class User {
    private String email;
    private int age;

    public User(String emaiel, int age) {
        this.email = emaiel; // ①email のスペルミス
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public int getAge() {
        return age;
    }
}

/**
 * データベース接続.
**/
class Database {
    public static User getUserById(String userId) {
        // シンプルなダミーデータ返却 (実際はDB接続が必要)
        return new User("user" + userId + "@example.com", 20);
    }
}

GitHub Copilotにチャット機能でレビューを依頼したところ、埋め込んだバグ全てを指摘してくれました!
さらに、リファクタリングまで提案してくれました。複雑な実装についてはさらに検証が必要ですが、今回のサンプルコードへのレビュー結果には期待以上でした。
また、AIによるレビューであれば、受ける側も心理的なプレッシャーが少なく、非常に有効だと感じました。

  • GitHub Copilotによって提案された改善コード
import java.util.List;

/**
 * ユーザーサービス.
**/
public class UserService {

    /**
     * データベースに接続し、ユーザー情報を取得.
     * ユーザーが19歳以上であればメール送信、以下であればスキップ.
    **/
    public void processUserData(List<String> userIds) {
        if (userIds == null) {
            System.out.println("User IDs list is null"); 
            return;
        }

        for (String userId : userIds) {
            try {
                // データベースに接続してユーザー情報を取得
                User user = Database.getUserById(userId);

                if (user != null) {
                    processUser(user);
                } else {
                    System.out.println("User not found for ID: " + userId);
                }
            } catch (Exception e) {
                System.out.println("Error processing user ID: " + userId);
                e.printStackTrace();
            }
        }
    }

    private void processUser(User user) {
        // ユーザー情報を処理
        if (user.getAge() > 18) {
            sendEmail(user);
        } else {
            System.out.println("User is under 18, skipping.");
        }
    }

    private void sendEmail(User user) {
        System.out.println("Sending email to " + user.getEmail());
    }
}

/**
 * ユーザー情報.
**/
class User {
    private String email;
    private int age;

    public User(String email, int age) {
        this.email = email;
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public int getAge() {
        return age;
    }
}

/**
 * データベース接続.
**/
class Database {
    public static User getUserById(String userId) {
        // シンプルなダミーデータ返却 (実際はDB接続が必要)
        return new User("user" + userId + "@example.com", 20);
    }
}

3. コード生成

最後に、3で作成したUserServiceに対してGitHub Copilotにユニットテストを作成するよう依頼しました。


まずは、指示として「このコードのテストを書いて」と依頼しました。
バリエーションはしっかり網羅してくれていますが、各テスト名称や、検証の中身など、業務で作っているテストとは違いがあったため、もう少し詳細な指示を与えてみました。

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.List;

import static org.mockito.Mockito.*;

public class UserServiceTest {

    private UserService userService;
    private Database databaseMock;

    @BeforeEach
    public void setUp() {
        userService = new UserService();
        databaseMock = Mockito.mock(Database.class);
    }

    @Test
    public void testProcessUserData_withValidUserIds() {
        List<String> userIds = Arrays.asList("1", "2", "3");

        when(databaseMock.getUserById("1")).thenReturn(new User("user1@example.com", 20));
        when(databaseMock.getUserById("2")).thenReturn(new User("user2@example.com", 17));
        when(databaseMock.getUserById("3")).thenReturn(new User("user3@example.com", 25));

        userService.processUserData(userIds);

        verify(databaseMock).getUserById("1");
        verify(databaseMock).getUserById("2");
        verify(databaseMock).getUserById("3");
    }

    @Test
    public void testProcessUserData_withNullUserIds() {
        userService.processUserData(null);
        // 何も起こらないことを確認
    }

    @Test
    public void testProcessUserData_withEmptyUserIds() {
        userService.processUserData(Arrays.asList());
        // 何も起こらないことを確認
    }

    @Test
    public void testProcessUserData_withUserNotFound() {
        List<String> userIds = Arrays.asList("1");

        when(databaseMock.getUserById("1")).thenReturn(null);

        userService.processUserData(userIds);

        verify(databaseMock).getUserById("1");
    }

    @Test
    public void testProcessUserData_withException() {
        List<String> userIds = Arrays.asList("1");

        when(databaseMock.getUserById("1")).thenThrow(new RuntimeException("Database error"));

        userService.processUserData(userIds);

        verify(databaseMock).getUserById("1");
    }
}

「このテスト名を日本語にして、追加で呼出検証もつけて」とお願いしてみました。
業務でいつも書いている、テストに近づきました。
実際使っていくとき、他のクラス群との平仄を考えると、プロンプトへの指示によって生成してくれるコードもかわるため、テンプレートを用意するとうまく活用できそうだと感じました。

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

import static org.mockito.Mockito.*;

public class UserServiceTest {

    private UserService userService;
    private Database databaseMock;
    private Logger loggerMock;

    @BeforeEach
    public void setUp() {
        userService = new UserService();
        databaseMock = Mockito.mock(Database.class);
        loggerMock = Mockito.mock(Logger.class);
    }

    @Test
    public void ユーザーIDリストが有効な場合のテスト() {
        List<String> userIds = Arrays.asList("1", "2", "3");

        when(databaseMock.getUsersByIds(userIds)).thenReturn(Arrays.asList(
                new User("user1@example.com", 20),
                new User("user2@example.com", 17),
                new User("user3@example.com", 25)
        ));

        userService.processUserData(userIds);

        verify(databaseMock).getUsersByIds(userIds);
        verify(loggerMock).info("Sending email to user1@example.com");
        verify(loggerMock).info("User user2@example.com is under 18, skipping email.");
        verify(loggerMock).info("Sending email to user3@example.com");
    }

    @Test
    public void ユーザーIDリストがnullの場合のテスト() {
        userService.processUserData(null);
        // 何も起こらないことを確認
        verify(loggerMock, never()).info(anyString());
        verify(loggerMock, never()).warning(anyString());
    }

    @Test
    public void ユーザーIDリストが空の場合のテスト() {
        userService.processUserData(Arrays.asList());
        // 何も起こらないことを確認
        verify(loggerMock, never()).info(anyString());
        verify(loggerMock, never()).warning(anyString());
    }

    @Test
    public void ユーザーが見つからない場合のテスト() {
        List<String> userIds = Arrays.asList("1");

        when(databaseMock.getUsersByIds(userIds)).thenReturn(Arrays.asList((User) null));

        userService.processUserData(userIds);

        verify(databaseMock).getUsersByIds(userIds);
        verify(loggerMock).warning("User not found in database.");
    }

    @Test
    public void 例外が発生する場合のテスト() {
        List<String> userIds = Arrays.asList("1");

        when(databaseMock.getUsersByIds(userIds)).thenThrow(new RuntimeException("Database error"));

        userService.processUserData(userIds);

        verify(databaseMock).getUsersByIds(userIds);
        verify(loggerMock).severe("Error processing user ID: 1");
    }
}

まとめ

噂には聞いていたものの、生成AIのすさまじさを実感しました。どの機能も実際に業務で活用するとしたら即戦力になると思います。特にコードレビュー(レビュー者負担の軽減、心理的安全性の向上)、テストコード生成(ユニットテスト作成時間の大幅な削減)で大きな効果を発揮すると感じました。

もちろん、すべての提案が完璧というわけではなく、提案されたコードを確認する必要はありますが、日々の開発作業で時間を節約できる点から、取り入れる価値は十分にあると思います。

今回は1つのクラスに対して操作を行いましたが、今後は複数クラスにまたがった場合についても各機能について試していきたいと思いました。
また、他のクラス群との整合性を考えると、プロンプトへの指示によって生成されるコードも変わるため、テンプレートを用意するとうまく活用できそうだと感じました。

なお、掲載したソースコードはサンプルになります。本ソースコードを使用することで発生するいかなる損害や不利益について、当社は一切の責任を負いませんので自己の責任においてご利用ください。

Discussion

ログインするとコメントできます