やる気ない時は GitHub Copilot に頼ろうと思った

Strapを開発している、あげです🙋‍♂️
最近、GitHub Copilot の業務利用が許可されたので、使って嬉しかった場面を紹介します。

このコードの問題点わかりますか?

期待する動作

  1. anyObject の arrayValue と objectValue はどちらも必須
  2. arrayValue は配列である
  3. arrayValue は空ではない
  4. objectValue はオブジェクトである

実装(TypeScript)

const isValidObject = (anyObject: any): anyObject is ValidObject => {
  if (!anyObject.arrayValue || !anyObject.objectValue) return false;
  if (typeof anyObject.objectValue !== "object") return false;
  if (
    !Array.isArray(anyObject.arrayValue) &&
    Array.isArray(anyObject.objectValue)
  ) {
    return false;
  }
  if (anyObject.arrayValue.length <= 0) return false;
  return true;
};

ぱっと見は問題なさそうなコードです。 export していない内部実装なので、テストは書かないことにしました。

export する必要が生じたので、テストコードを書くことに

しかし、外部から呼び出す必要が発生したので、export するように変更しました。こうなってくるとテストコードが必要になります。でも正直、export しただけなのに数十行のテストコードを書かないといけないなんて、めんどくさいですよね?それにモチベーションが低いので、自分でも意識しないうちにテストコードが雑になったり、網羅性が低くなる気がします。そこで今回は、GitHub Copilot を使ってテストコードを自動生成してみました。

以下は、GitHub Copilot に作ってもらったものを、わかりやすいように少し修正したものです。(Jest のテストコードです)

describe("isValidObject", () => {
  const subject = () => {
    return isValidObject(anyObject);
  };
  let anyObject = {} as ValidObject;

  describe("anyObject is inValid", () => {
    describe("anyObject has no arrayValue", () => {
      beforeEach(() => {
        anyObject = {
          objectValue: {},
        } as any;
      });

      it("should return false", () => {
        expect(subject()).toBe(false);
      });
    });

    describe("anyObject has no objectValue", () => {
      beforeEach(() => {
        anyObject = {
          arrayValue: [],
        } as any;
      });

      it("should return false", () => {
        expect(subject()).toBe(false);
      });
    });

    describe("anyObject.arrayValue is not array", () => {
      beforeEach(() => {
        anyObject = {
          arrayValue: {},
          objectValue: {},
        } as any;
      });

      it("should return false", () => {
        expect(subject()).toBe(false);
      });
    });

    describe("anyObject.arrayValue is empty array", () => {
      beforeEach(() => {
        anyObject = {
          arrayValue: [],
          objectValue: {},
        } as any;
      });

      it("should return false", () => {
        expect(subject()).toBe(false);
      });
    });

    describe("anyObject.objectValue is not object", () => {
      beforeEach(() => {
        anyObject = {
          arrayValue: [],
          objectValue: [],
        } as any;
      });

      it("should return false", () => {
        expect(subject()).toBe(false);
      });
    });
  });
});

このテストを実行した結果、 anyObject.arrayValue is not array が通らないことが判明しました。どうやら問題があったようです。「 arrayValuearray じゃない」と言っているので、以下のコードに問題がありそうです。

if (
  !Array.isArray(anyObject.arrayValue) &&
  Array.isArray(anyObject.objectValue)
) {
  return false;
}

少し複雑なので、分解して考えてみましょう。

arrayValue に object が入っていて array ではないので、以下のコードでは true が返ってきます。ここは問題ないです。

!Array.isArray(anyObject.arrayValue);

objectValue には object が入っていて array ではないので、以下のコードでは false が返ってきます。ここも問題ないです。

Array.isArray(anyObject.objectValue);

問題点は && です。両方 true でないと false が返ってきます。正しくは以下のように || にすべきです。

!Array.isArray(anyObject.arrayValue) || Array.isArray(anyObject.objectValue);

ただし、直感的でないと感じたので、少々愚直ですが以下のようにリファクタしました。

const isValidObject = (anyObject: any): anyObject is ValidObject => {
  if (!object.arrayValue) return false;
  if (!object.objectValue) return false;
  if (!Array.isArray(object.arrayValue)) return false;
  if (typeof anyObject.objectValue !== "object") return false;
  if (Array.isArray(object.objectValue)) return false;
  if (object.arrayValue.length <= 0) return false;
  return true;
};

これでテストが通るようになりました ✨

まとめ

結局のところ、バグを起こすのは人間です。テストを書こうにも、テスト仕様が間違ってたり網羅性が低かったりします。その原因を詰めていくと、バイアスが生じていたり、書くときのモチベーションなどが関わっていると思います。人のアウトプットの品質を保つにはモチベーションというのはとても大事でバカにできません。今回に関してはテストコードを書くモチベーションが低かったので、品質を落とす可能性がありました。そこで GitHub Copilot のような AI 支援ツールを使うと、品質にムラがなくなるというお話でした 🙋‍♂️ ただし、「人間が読みやすい」という意味の可読性は、結局人の思考で監修しないといけません。リファクタの前後で、どちらも正しいコードですが、どちらがチームにとって、あるいは自分にとって見やすいか判断する必要があります。そういった意味で、これからは人間と AI の人馬一体が求められるのかもしれません。

尚、自分はこのような記事を書くモチベーションが低いので、品質が低くなりがちです。なので、4 割くらいは GitHub Copilot 使って執筆を手伝ってもらいました 😉

おしまい