APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

GitHub Copilotにわざと低品質なコードを書かせてみた

はじめに

こんにちは、コンテナソリューション事業部の髙井です。

GitHub Copilot for Businessが提供されたことにより、お客様の了承のもと、SIでもCopilotを使えるようになってきました。
本当に Copilot があると開発者体験がよいので、関係各所には大感謝です。

という手前もあり、せっかくなのでブログ記事にしておこうと思います。

一言で感想をまとめると、「非常に便利、なかった時代にはもう戻れない」という感じですが、それだけだと具体性に欠けるので前半はその素晴らしさを説きつつ、後半ではCopilotにわざと低品質なコードを書かせられないか試して遊びます。

いいか悪いかで言えば、超いい

まず、使えるなら絶対に使った方がいいです。
ものによってはserver-sideとclient-sideを横断したサジェストなどもしてくれます。

Next.jsのpages/api配下にあるserverless funciionのapiに対し、フロント側のコードでそのapiを呼び出すときに引数やパスがよしなに補間されたのにはかなり驚きました。

これまでのエディタでは、フロントのコンポーネント間とかで、地続きの型情報や呼び出し関係があった場合には正しく推測されサジェストが出るといった機序だったと思うのですが、Copilotはそういうの関係なしに横断的なサジェストをしてくれるんですよね。かなり強いです。

また、ファイル横断的なサジェスト以外でもCopilotが得意な分野があって、それが繰り返しです。

プログラムって、可能な限り抽象化するといっても「どうしても似たようなことを書かないといけないシーン」があるものです。でも、似てるけど違うものを書くんですよね(まったく同じものを何度も書くのはありえないので)。

そんなときに、まるで意図を汲んだかのようにサジェストしてくれるのはとても強いです。

const someFunc(a: A, b: B, c: C) => {
  validate(A_SCHEMA, a);

上記のようなところまで書くと、以下のような補間はほぼ確実にしてくれます。

const someFunc(a: A, b: B, c: C) => {
  validate(A_SCHEMA, a);
  validate(B_SCHEMA, b);
  validate(C_SCHEMA, c);

上記で、ABstringCだけarrayみたいなシーンだと、Cだけ型が違うのでstringに変換してから渡したい、みたいなこともありますよね。 そんなときにc.join(",")など、型を合わせようと提案してくれたりすることもあります。異常に頭がいいです。

他にもイイ感じのサジェストの出させ方みたいなのもあるのですが、後段で実例を交えながら説明していきます。

より使いこなすためには

Copilotはマジで最高なんですが、注意すべき点もあると言えばあります。

それは、いい加減なコードには、いい加減なサジェストが出るということです。

AIはさながら加速装置です。いい方向にも悪い方向にも加速されます。だからこそ型や命名には魂を注がないといけません。

つまり、人間に読みやすいコードはAIにも読みやすいので、いいサジェストが出やすいよってことですね。その逆も然りでしょう。

せっかくなので低品質なコードを用意する

既存コードがあるような状況を想定して、せっかくなので低品質なコードを題材にしましょう(一般的な表現を避け、少し社会性のある単語を使いました)。

どれだけ本人が美しいコードを書ける開発者だったとしても、参加するプロジェクトのコードは必ずしも美しいとは限りません。

さて、ここにオブジェクトの配列をソートする処理を用意しました

やっていることは単純で、{foo: string, bar: number}[]の配列をbarの値で昇順ソートしています。まあ1行で書けるような処理です。

しかし、とても読みにくいコードになっています(ということを想定して私が頑張ってわざと冗長かつ低品質にしてみました)。

const a: any = [
    { foo: 'a', bar: 2 },
    { foo: 'b', bar: 200 },
    { foo: 'c', bar: 100 },
    { foo: 'd', bar: 50 },
];
const b = [];
const list_foo = [];
const list_bar = [];
const iAndBar = [];

for (let i = 0; i < a.length; i++) {
    list_foo.push(a[i].foo);
}

for (let i = 0; i < a.length; i++) {
    list_bar.push(a[i].bar);
}

for (let i = 0; i < list_bar.length; i++) {
    iAndBar.unshift([list_bar[i], i]);
}
const sort_listBar = iAndBar.sort((a, b) => a[0] - b[0]);
for (let i = 0; i < sort_listBar.length; i++) {
    b.push({ foo: list_foo[sort_listBar[i][1]], bar: sort_listBar[i][0] });
}

console.log(b);

うおー、これはひどい。見るだけで頭が痛くなります。

私は聖人君子なのでこのようなコードをレビュー依頼されても終始笑顔で対応できますが、相手が始皇帝とかだったら命の危険すらあります。

コードは、人間が読むために書かれるものです。人間が読めないコードは、コードとして成立していません1

使用している言語は TypeScript のようですが、型を使っていないので、コードの意味を理解するのが難しいです。 また、変数名がabといった名前になっているので、どのような値が入っているのかもわかりません。 それ以外の変数の名前も、キャメルケースとスネークケースが混在していたり、list_foolist_barといった名前になっていたりと、一貫性がありません。

Copilotにコードを読ませる

このコードを、GitHub Copilot に読ませてみましょう。

灰色の部分がCopilotによるサジェスト

素晴らしいですね。この冗長なコードの意味を理解して、1 行で説明してくれています。

このようにコメントを活用していい感じにサジェストさせるのは常套手段です。

Copilotは低品質なコードを苦しまずに読めてしまう

しかし、このコードを修正しようとしたとき、Copilot は空気を読んでしまいます。

Copilotは人間と違って、淡々と低品質なコードを読めてしまうので、そのまま意図を汲もうとするわけです。

さて、ここで元の値となる配列の型が、

  • {foo: string, bar: number}[]から
  • {foo: string, bar: number, baz: number}[]

変化したとしましょう。

その際に出てきたサジェストは以下のようなものになります。

`baz`が`bar`の3倍の値であることを予測しサジェストしており超優秀

typescriptなのでキャメルケースとしたいがCopilotとしては前例踏襲するしかない

非効率な処理を淡々と、かつ完璧に前例踏襲していく

非の打ちどころなく完璧に非効率なコードが非効率なまま機能拡張された

ご覧の通り、低品質なコードを完璧に踏襲したサジェストが出てきています。

ただ、元の処理がひどかっただけで、出てくるサジェストはパーフェクトに意図を汲んだ完全なコードです。

Copilotは素晴らしい。しかし、使う人間が愚かだと愚かさの再生産が行われます。

正しい Copilot の使い方

まず、元のコードが冗長であるという事実を認識しないことには始まりません。

つまり、人間は人間として、ちゃんと判断力をもって司令塔になりましょう。

正しい疑いの目を持って Copilot に要約してもらいます。

以下のコードは、以上のコードを可能な限り簡潔に書き直すとまでを私が入力し、残りをすべて Copilot に書いてもらったものです。

最初のコメントの途中まで書いたら、それ以降は Tab と Enter しか押していません

// 上記のコードを可能な限り簡潔に書き直すと、以下のようになる。

const a = [
    { foo: 'a', bar: 2, baz: 6 },
    { foo: 'b', bar: 200, baz: 600 },
    { foo: 'c', bar: 100, baz: 300 },
    { foo: 'd', bar: 50, baz: 150 },
];

const b = a.sort((a, b) => a.bar - b.bar);

20 行ほどのコードが、1 行になりました。Copilot を正しく使用するとこれくらいのことはやってくれます。

とはいえ型付けが不十分なので、それも Copilot にやってもらいましょう。 再びコメントだけ入力して、あとは Tab と Enter キーに任せた結果が以下です。

// 簡潔に書き直したコードは型定義が不十分で、命名も不明瞭である。
// 十分に型定義を伴った明快な命名の関数に書き直すと、以下のようになる。

type FooBarBaz = {
    foo: string;
    bar: number;
    baz: number;
};

const a: FooBarBaz[] = [
    { foo: 'a', bar: 2, baz: 6 },
    { foo: 'b', bar: 200, baz: 600 },
    { foo: 'c', bar: 100, baz: 300 },
    { foo: 'd', bar: 50, baz: 150 },
];

const sortByBar = (a: FooBarBaz[]): FooBarBaz[] => {
    return a.sort((a, b) => a.bar - b.bar);
};

これで、元のコードと同じ処理を行うコードがいったん仕上がりました。
型定義をもう少し抽象化したり、aを別の変数名に変えたほうが良いかもしれませんが、今回はここまでとしましょう。

使い方が正しければ、当然、ちゃんとしたコードが出てきます。

逆にCopilotはシンプルな処理から低品質なコードを生み出せるのか?

本当はここで終わりにするつもりだったんですが、気になったのでやろうと思います。

お利口ちゃんのCopilotをいかに堕落させられるか、これはプライドの勝負です(?)

type FooBar =  {foo: number, bar: number};
const sortByBar = (obj: FooBar[]): FooBar[] => obj.sort((a, b) => a.bar - b.bar);

通常の処理を用意しました。では低品質なコードに誘導しましょう。

// 上記のsortByBarを可能な限り冗長で分かりにくく低品質なコードに置き換えると以下のようになる

ここまで書けばどうでしょうか。

// Path: sample.ts
type FooBar =  {foo: number, bar: number};
const sortByBar = (obj: FooBar[]): FooBar[] => obj.sort((a, b) => {
      if (a.bar < b.bar) return -1;
  if (a.bar > b.bar) return 1;
  return 0;
}
);

……。

うーん……。

たしかに冗長だけど、私が最初に用意した例ほどには低品質になりませんね。

さらに促してみましょう。

// 上記の書き換えではまだ冗長性や可読性の低さが不足している。
// さらに分かりにくい変数名や、統一感の欠如を伴うコードに書き換えると以下のようになる。

// Path: sample.ts
type FooBar =  {foo: number, bar: number};
const sortByBar = (obj: FooBar[]): FooBar[] => obj.sort((a, b) => {
      if (a.bar < b.bar) return -1;
  if (a.bar > b.bar) return 1;
  return 0;
}
);

変わりませんね。

これは人間様の大勝利ということでよさそうです。

ガハハ、AI風情には後れを取りませんわ。

おわりに

さて、Copilotの実力、どうだったでしょうか?

ちなみに「実はこの文章自体も Copilot にサジェストさせながら書いている」んですよね。

以下の GIF 動画は、実際に文章をサジェストさせながら書いている様子です。

このように比較的ポンポンとサジェストしてくれます。
個人的な感想としては、いまいち味気なくウィットに欠けた文章になるため、いったん最後まで書ききったあとに文章を修正したりしています。

補足

VSCode の Copilot はデフォルトでは、Markdown 内でのサジェストを行いません。

上記の設定のようにmarkdownをtrueにすると、Markdown 内でもサジェストを行うことができます。


私達ACS事業部はAzure・AKSを活用した内製化のご支援をしております。ご相談等ありましたらぜひご連絡ください。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
切磋琢磨しながらスキルを向上できる、エンジニアには良い環境だと思います。ご興味を持っていただけたら嬉しく思います。

www.ap-com.co.jp

本記事の投稿者: 髙井 比文
AKSをメインにしたインフラとアプリの領域際をご支援することが多いです。Azureは11冠です。
Hifumi Takai - Credly


  1. この文はCopilotが生成しましたので、私の意見と一致することを意味しません。