KINTO Tech Blog
Development

GitHub Copilotとプログラミングして分かったAIとの付き合い方(モバイルエンジニア編)

Cover Image for GitHub Copilotとプログラミングして分かったAIとの付き合い方(モバイルエンジニア編)

はじめに

VSCodeとCopilotの組み合わせが最高に楽しいです。楽しすぎて本業を忘れてしまいそうです。
本日はそんな楽しすぎる開発体験をみなさんにもぜひ知って欲しいという思いで記事を書きます。

この記事は KINTOテクノロジーズアドベントカレンダー2024 の25日目の記事です🎅🎄

今回の内容

去年辺りから熱くなったAI界隈ですが、今年もAIの話題が尽きない一年でしたね!
KTCでは積極的にAIを取り入れて開発生産性低減の道を探っています。

今回はVSCode x Copilotの組み合わせでできることを紹介します。
でも、AIってなんか微妙なコードしか出してこないし、自分で書いた方が早いから使わないよね、という方もいるかもしれません。

僕も初めはそうでしたが、先日開催されたGitHub Universeで発表されたCopilot Chat/Editsを見て、AIとの共同作業が楽しくなるかもしれないと感じました。
と言うことで、今回は実際にCopilot Chat/Editsを使って、AIと共同でプログラミングをする中で、普段使いで有効だと思うTipsを紹介します。

https://reg.githubuniverse.com/flow/github/universe24/attendee-portal/page/livestream?tab.day=20241029

なんでFlutter?

モバイル開発をする上で、iOSもAndroidも各OSの標準IDEを使って開発をするのが主流です。
しかし、今回はGitHub Copilotを使ってプログラミングするという観点で、VSCodeを使って開発することを前提とします。
そう考えると、モバイル開発で公式にVSCodeをサポートしているのはFlutterがメジャーです。

先日FlutterKaigiにも参加し得るモノが多かったので、今回はFlutterを使ってAIとの共同作業をすることにします。ちなみに、FlutterKaigiのアプリ、本当に些細なIssueですが、僕もコントリビュートしました😀

実際に何ができるのか?

GitHub Universeで発表された機能はPreview機能が多く、使用できる機能は限られていますが、すでにリリースされているCopilot Chat/Editsを使いこなすだけでも、AIとの共同作業が楽しくなり、開発者体験の向上が実感できました。

エンジニアの皆さんがAIを使ってコーディングするというと、ノーコードでアプリを作るとか、コードを自動生成するとか、そういったイメージがあるかもしれません。確かに、コードを自動生成する事は可能ですが、それはAIのほんの一部の機能でしかないです。
また、複雑な機能を持つコードを一度の指示で動作保証できるほどのコードを生成することは(指示出しが)難しいです。

この記事で紹介するのは、テストコードの作成、コードの整形・分割、リファクタリングなど、普段皆さんがコーディングでやっていることをAIとの共同作業を通じて行い、コードの品質を向上させることができるCopilot Chat/EditsのTipsになります。

それでは、ここからTipsの紹介です。

コードの要約

file summary file summring with copilot

Copilot Chatの機能を使うと、コードの要約を簡単に作成できます。これを使えば、オンボーディング時のコード理解が早まります。

コメント記載

BEFORE AFTER
beforechat after

コードにコメントを追加する際、Copilot Editsを使うと、Copilotが直接ファイルにコメントを追加してくれます。これにより、コードの理解が深まります。
また、すでにあるコメントを修正する際も、Copilotが適切なコメントを修正してくれます。
例えば上のクラスのaddメソッドに対して、バリデーションを追加した場合以下の様に指示するとコメントを更新してくれます。

BEFORE AFTER
beforechat after

テストコードの作成

select target file selected target file

copilot editsのテキスト入力欄の上に現在のファイル以外に参照できるファイルを増やす事ができるので、テストコードを書いてもらうときは、
テスト用のファイルを作成し、そのファイルを開いたまま、参照ファイルに対象ファイルを追加すれば、テストコードを書いてくれます。

select target file

コードの整形・分割

Flutter等の宣言型UIを採用している言語でUIを作成しているとどうしてもネストが深くなってしまう問題が頻発します。

sample code ui
整形前のコード
class SomethingPage extends StatelessWidget {
  const SomethingPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Something Page'),
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(16.0),
          color: Colors.blueAccent,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                padding: const EdgeInsets.all(8.0),
                color: Colors.redAccent,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Container(
                      padding: const EdgeInsets.all(4.0),
                      color: Colors.greenAccent,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Container(
                            padding: const EdgeInsets.all(2.0),
                            color: Colors.yellowAccent,
                            child: const Text('Deeply Nested Widget 1'),
                          ),
                          Container(
                            padding: const EdgeInsets.all(2.0),
                            color: Colors.purpleAccent,
                            child: const Text('Deeply Nested Widget 2'),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

こういった問題もCopilot Editsを使うと、コードを見やすい単位で分割、整形してくれます。

auto divide

整形後のコード
class SomethingPage extends StatelessWidget {
  const SomethingPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Something Page'),
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(16.0),
          color: Colors.blueAccent,
          child: const NestedContainer(),
        ),
      ),
    );
  }
}

class NestedContainer extends StatelessWidget {
  const NestedContainer({super.key});

  
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: const [
        InnerContainer(),
      ],
    );
  }
}

class InnerContainer extends StatelessWidget {
  const InnerContainer({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.redAccent,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          DeeplyNestedContainer(),
        ],
      ),
    );
  }
}

class DeeplyNestedContainer extends StatelessWidget {
  const DeeplyNestedContainer({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(4.0),
      color: Colors.greenAccent,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [
          DeeplyNestedWidget1(),
          DeeplyNestedWidget2(),
        ],
      ),
    );
  }
}

class DeeplyNestedWidget1 extends StatelessWidget {
  const DeeplyNestedWidget1({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(2.0),
      color: Colors.yellowAccent,
      child: const Text('Deeply Nested Widget 1'),
    );
  }
}

class DeeplyNestedWidget2 extends StatelessWidget {
  const DeeplyNestedWidget2({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(2.0),
      color: Colors.purpleAccent,
      child: const Text('Deeply Nested Widget 2'),
    );
  }
}

リファクタリング

歴史あるコードでよく見かける、依存度が複雑に絡み合ってしまったメソッドも、Copilot Editsを使うことで、SOLID原則に基づいたリファクタリングが可能です。

依存度が複雑なメソッドを持つクラス
class SomethingLogic {
  Future<void> performComplexOperation() async {
    // Fetch data from remote server
    final response = await http.get(Uri.parse('https://example.com/data'));
    if (response.statusCode != 200) {
      throw Exception('Failed to load data');
    }
    final data = jsonDecode(response.body);

    // Save data to local database
    final database = openDatabase(
      join(await getDatabasesPath(), 'app_database.db'),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)',
        );
      },
      version: 1,
    );
    final db = await database;
    await db.insert(
      'data',
      {'id': data['id'], 'value': data['value']},
      conflictAlgorithm: ConflictAlgorithm.replace,
    );

    // Save data to shared preferences
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('data', jsonEncode(data));

    // Perform some complex logic
    if (data['value'] == 'special') {
      // ...existing code...
      print('Special value found!');
      // ...existing code...
    } else {
      // ...existing code...
      print('Regular value found.');
      // ...existing code...
    }
  }
}
SOLID原則に基づいたリファクタリングをしてもらったコード
class DataFetcher {
  final http.Client client;

  DataFetcher(this.client);

  Future<Map<String, dynamic>> fetchData(String url) async {
    final response = await client.get(Uri.parse(url));
    if (response.statusCode != 200) {
      throw Exception('Failed to load data');
    }
    return jsonDecode(response.body);
  }
}

class DatabaseHelper {
  Future<Database> getDatabase() async {
    return openDatabase(
      join(await getDatabasesPath(), 'app_database.db'),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)',
        );
      },
      version: 1,
    );
  }

  Future<void> saveData(Database db, Map<String, dynamic> data) async {
    await db.insert(
      'data',
      {'id': data['id'], 'value': data['value']},
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }
}

class SharedPreferencesHelper {
  Future<void> saveData(Map<String, dynamic> data) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('data', jsonEncode(data));
  }
}

class SomethingLogic {
  final DataFetcher dataFetcher;
  final DatabaseHelper databaseHelper;
  final SharedPreferencesHelper sharedPreferencesHelper;

  SomethingLogic(this.dataFetcher, this.databaseHelper, this.sharedPreferencesHelper);

  Future<void> performComplexOperation() async {
    final data = await dataFetcher.fetchData('https://example.com/data');
    final db = await databaseHelper.getDatabase();
    await databaseHelper.saveData(db, data);
    await sharedPreferencesHelper.saveData(data);

    if (data['value'] == 'special') {
      // ...existing code...
      print('Special value found!');
      // ...existing code...
    } else {
      // ...existing code...
      print('Regular value found.');
      // ...existing code...
    }
  }
}

ここまでのリファクタリングを自分でやると、時間がかかりますが、Copilot Editsを使うことで、短時間でリファクタリングが可能です。
だいぶ見やすいコードになったので、ここから自身の経験をもとに、さらにリファクタリングを行う事も可能です。

その他

これ以外にも、基本的なSuggestionsの機能が強力で、普段であれば実装に困った際はブラウザとIDEを行き来していましたが、ほとんどの困りごとをCopilotとの相談で解決できるので、開発への集中が途切れなくなりました。これは地味に大きいです。

指示だしのコツ

AIとの共同作業は、新しい発見がたくさんありますが、AIに対する指示だしが重要な要素だなと感じました。
僕の周りにもAIが使いづらく感じているエンジニアは多くいますが、AIに対して具体的な指示を出すことに慣れると、AIとの共同作業が楽しくなると思います。

僕が感じた指示だしのコツは

  • シンプルな内容
  • 具体的な内容
    • どのファイル
    • 何をする
    • どのようにする
  • 一度に複数の指示を出さない

特に、指示を出す側がしっかりとしたプログラミング原則や設計思想を理解していることが重要だと感じます。

まとめ

今回はVSCode x Copilotの組み合わせによって、AIとできることを紹介しました。
Copilot Chat/Editsを使うことで、コードの要約、コメント記載、テストコードの作成、コードの整形・分割、リファクタリングが簡単にできる事が少しでも伝えられたら幸いです。

エンジニア界隈ではAIに対して賛否両論あり、
僕の周りのエンジニアもまだまだAIに対して抵抗がある人が多いです。

世間の流れは確実にAIに向かっていて、今後もAIは進化し、どんどんできることが増えていくことが予想されます。
僕らエンジニアはどうAIを使っていくかを考える時代になったのかなと感じでいます。

この記事が少しでもAIとの共同作業に対して興味を持っていただけたら幸いです。

Facebook

関連記事 | Related Posts

We are hiring!

【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、クルマのサブスクリプションサービス『KINTO ONE』のWebサイトコンテンツの開発・運用業務を担っていただきます。

生成AIエンジニア/生成AI活用PJT/東京・名古屋・大阪

生成AI活用PJTについて生成AIの活用を通じて、KINTO及びKINTOテクノロジーズへ事業貢献することをミッションに2024年1月に新設されたプロジェクトチームです。生成AI技術は生まれて日が浅く、その技術を業務活用する仕事には定説がありません。