Copilotトライアル④ VSCode(C#)ではじめてみたGitHub Copilotのプレビュー

はじめに

/// Github Copilotとは副操縦士のことです。
/// 副操縦士は、操縦士が操作している飛行機の操縦を補助する役割を担います。
/// うまく副操縦士指示を出すことで、操縦士の負担を軽減することができます。
/// プログラミングの生成においてはコンテキストに応じたコードを生成することができます。
/// 良い感じにコンテキストを渡すためには、コードを書く前にコンテキストを整える必要があります。
/// 今日は、いくつかのサンプルコードを紹介します。
※赤字はcopilotで生成。

というわけでございまして、今日はVSCodeC#)ではじめてみたGitHub Copilotのプレビューを書いて見たいと思います。 システム本部、ツネイと申します。

導入部は割愛として、 課題はシンプルに下記の機能の作成としてみます。

  • ランダムな文字列を生成する
  • 使用する文字種は引数で設定可能とする

聞いてみる

  /// <summary>
  /// ランダムな文字列を生成する
  /// 文字種は引数で設定可能とする
  /// </summary>
  public static string GenerateRandomString(int length, CharType charType)
  {
      var sb = new StringBuilder();
      var random = new Random();

      for (int i = 0; i < length; i++)
      {
          switch (charType)
          {
              case CharType.Alphabet:
                  sb.Append((char)('a' + random.Next(0, 26)));
                  break;
              case CharType.Number:
                  sb.Append((char)('0' + random.Next(0, 10)));
                  break;
              case CharType.AlphabetAndNumber:
                  sb.Append((char)('a' + random.Next(0, 26)));
                  sb.Append((char)('0' + random.Next(0, 10)));
                  break;
              default:
                  throw new ArgumentException("不正な文字種が指定されました。");
          }
      }
      return sb.ToString();
  }

summaryだけ入力して作っていただきました。 いまいち、「使用する文字種は引数で設定可能とする」のところがイメージと違うので補正してみます。 私の日本語が曖昧でした。 "12345abcde&%$#"のような使用可能な文字リストから選出して、作成いただきたいです。

  /// <summary>
  /// ランダムな文字列を生成する
  /// 引数で設定した文字列の中からランダムに文字を選択して指定した長さの文字列を生成する
  /// </summary>
  public static string GetRandomString(int length, CharType charType)
  {
      var sb = new StringBuilder();
      var random = new Random();
      var charList = GetCharList(charType);
      for (int i = 0; i < length; i++)
      {
          sb.Append(charList[random.Next(charList.Length)]);
      }
      return sb.ToString();
  }

概ねイメージと近くなりました。

軌道修正

実際にこれをプロダクトコードの中に埋め込むとすると、継続的なテストを用意したいので、 ランダム性の部分の検証でなく、自前で用意したロジック部分のテストは保証したいです。 ライブラリに依存するRandom部分のテストは不要です。

  /// <summary>
  /// ランダムな文字列を生成する
  /// 引数で設定した文字種の中からランダムに文字を選択して指定した長さの文字列を生成する
  /// ランダマイザは外部から注入する
  /// </summary>
  public static string GenerateRandomString(int length, CharType charType, Random randomizer)
  {
      var sb = new StringBuilder();
      for (int i = 0; i < length; i++)
      {
          sb.Append(GetRandomChar(charType, randomizer));
      }
      return sb.ToString();
  }

もう少し、目的に寄せて詳しく伝えてみます。

  /// <summary>
  /// ランダムな文字列を生成する
  /// 利用できる文字種は引数で指定する
  /// 生成される文字数は引数で指定する
  /// ランダマイザは外部から注入する
  /// ランダマイザはインターフェース化しているので、テスト時にモックを注入することができる
  /// </summary>
  public static string GenerateRandomString(int length, string charSet, IRandomizer randomizer)
  {
      var sb = new StringBuilder();
      for (int i = 0; i < length; i++)
      {
          var index = randomizer.Next(charSet.Length);
          sb.Append(charSet[index]);
      }
      return sb.ToString();
  }

インターフェイスは合ってきました。 テストコードを生成してみます。

  /// <summary>
  /// GenerateRandomStringのテスト
  /// </summary>
  [TestMethod]
  [DataRow(10, "0123456789", "0123456789")]
  [DataRow(10, "0123456789", "0123456789")]
  [DataRow(10, "0123456789", "0123456789")]
  [DataRow(10, "0123456789", "0123456789")]

どうもうまく行きません。 そうです、忘れていました。周辺をしっかり定義しないといけませんね。

  /// <summary>
  /// テスト用にランダマイザを注入するためのインターフェース
  /// </summary>
  public interface IRandomizer
  {
      int Next(int maxValue);
  }

「一通りテストまで揃えてみたい」が目的なので、いったんシンプルに下記とします。

  /// <summary>
  /// テスト用にランダマイザを注入するためのクラス
  /// ランダム性の検証を目的としていないので、1と2を交互に返す
  /// </summary>
  public class MockRandomizer : IRandomizer
  {
      private int _count = 0;
      public int Next(int maxValue)
      {
          _count++;
          if(_count % 2 == 0)
          {
              return 1;
          }
          else
          {
              return 2;
          }
      }
  }

これでどうでしょうか。

  /// <summary>
  /// GenerateRandomStringのテストコード
  /// MockRandomizerを注入して、生成される文字列が期待通りかを検証する
  /// </summary>
  [TestMethod]
  [DataRow(10, "0123456789", "1111111111")]
  [DataRow(10, "0123456789", "2222222222")]
  [DataRow(10, "0123456789", "3333333333")]
  [DataRow(10, "0123456789", "4444444444")]
  [DataRow(10, "0123456789", "5555555555")]
  [DataRow(10, "0123456789", "6666666666")]
  [DataRow(10, "0123456789", "7777777777")]
  [DataRow(10, "0123456789", "8888888888")]
  [DataRow(10, "0123456789", "9999999999")]
  [DataRow(10, "0123456789", "0000000000")]
  public void GenerateRandomStringTest(
      int length, string chars, string expected)
  {
      var randomizer = new MockRandomizer();
      var result = StringUtil.GenerateRandomString(length, chars, randomizer);
      Assert.AreEqual(expected, result);
  }

目的に近づいてきました。

  • テストに失敗してしまう
  • 意味の薄い「モックのテスト」の色合いに近いので、有効なテストにするには補正が必要

なので、まだアレンジが必要だとは思いますが、ここまで一式で揃えば後は苦労なく、道なりで仕上げられそうです。

感想

/// Github copilotの有効な活用には、しっかりと機能要件を理解しておく必要があります。
/// 曖昧な機能要件を与えると、コードを生成することができません。
/// まずは、機能要件を整理してみましょう。

/// copilotの生成したコードが有効かどうかを検証するのは、人間の仕事です。
/// 基礎的なコードは生成できますが、コンテキストによっては意図しないコードが生成されることもあります。
/// 生成されたコードを判断するためには、コードを読む力が必要です。
/// 読む力を養うためには、コードを読む習慣をつけることが大切です。
/// これからAIが大部分のコードを生成する時代が来るかもしれませんが、コードを読む力は必要です。
※赤字はcopilotで生成。

プロジェクトによって扱っている題材やフェーズも違いますし、なかなか最適解というのは短い期間中にはわかりませんでしたが、 場面ごとにうまくフィットする活用法を模索して行ければ!と思いました!

例えば、未検証ですが。

Webアプリケーションのエンドポイントの外部仕様は決まっている。
markdownのような表記で仕様書にする。
⇒ それを読み込ませてControllerクラスとメソッドスタブを量産させるようなことができたらいいな。

のような人間の負担が少ない定型作業の効率化は可能性を大いに感じます。
何か成果が出たらまたの機会で紹介させてください!

クラウドだけに雲をつかむような結びとなりました!
まずは立派な主操縦士になるべく引き続き精進したいと思います。

カカクコムでは、ともにサービスをつくる仲間を募集しています!

カカクコムのエンジニアリングにご興味のある方は、ぜひこちらをご覧ください!

カカクコム採用サイト