👻

AIに「書かせない」コードを決めた話

に公開

はじめに

クラシルでAndroidエンジニアをしているkenzoです。
AIでコードを書くようになった今、私たちのチームではあえてAIに書かせないようにしているところがあります。アナリティクス用のイベントログに関するクラス群です。

画面実装からテストまでAIに幅広く書かせている一方で、このコードだけはAIの編集を禁止しています。イベントクラスが数百ファイルあり、いずれもコード生成ツール(CSVからKotlinコードを生成するスクリプト)が出力しています。

元々は各エンジニアがこのツールを直接叩く運用でしたが、チームで日々の開発にAIを取り入れた頃、このフローも一度スキル経由でAIに任せる形に変えました。そこで問題が観測され、改めて考え直した結果、ツールのみを使う運用に戻しました。この記事では、その経緯と現在の仕組みについて書きます。

イベントログという領域

私たちのアプリでは、ユーザー行動のトラッキングにイベントログを使っています。アクションごとにイベントクラスがあり、それぞれがイベント名やプロパティを持ちます。

イベントの名前、プロパティの型、説明文など、イベントに関する情報はすべて別の仕様書で管理されており、そこが唯一の情報源(Single Source of Truth)です。コード側に独自の判断が入る余地はありません。

生成ツールの仕組み

仕様書(CSV)からKotlinのイベントクラスを出力するツールがあります。
たとえば「complete_login」というイベントが定義されているとしたら、以下のようなクラスが生成されます。

CompleteLoginEvent.kt
/**
 * complete_login
 * ログインが完了した
 *
 * @param provider プロバイダ識別子 (apple, google)
 */
class CompleteLoginEvent(
    provider: String,
) : LogEvent(
    name = "complete_login",
    properties = listOf(
        Property("provider", provider),
    ),
)

(説明のために簡略化したコードです)

同じ仕様からは必ず同じコードが出力されます。

試行錯誤の経緯

現在の「ツールだけで完結させる」形に至るまでには、3つのフェーズがありました。

Phase 1: 人間が生成ツールを手動で実行

生成ツールは私たちが業務でAIを使うようになる前から存在していました。イベントを追加するときは、担当するエンジニアが手元で生成ツールを実行し、生成されたファイルをコミットしてPRを出します。AIでコードを書くようになるまではこの運用でした。

Phase 2: AIに生成ツールを使わせる

チームでAI前提の開発をするようになり、このイベントクラスの追加フローもAIに任せる形に変えました。イベント送信処理の実装時にAIが生成ツールを実行してイベントクラスを生成し、更にはその送信処理まで作ってもらうような流れで使っていました。

しかし、これはなかなかうまくいきませんでした。運用してみて、主に3つの課題を感じました。

AIに生成ツールを必ず使わせるのは難しかった。 生成ツールを使うルールにしていましたが、「既存のイベントクラスを参考にすれば書ける」とAIが判断してツールを使わずにコードを書く、ということが起きました。大抵は正しいコードを書いてくれるのですが、本当に正しいのかを確認する必要があり、手間もかかるし事故のリスクもある状態でした。

AIにスキルを必ず使わせるのも難しかった。 生成ツールを使ってイベントクラスを作るスキルを用意しましたが、それもうまくいきませんでした。スラッシュで直接スキルを実行すれば、そのスキル経由でツールを使ってくれました。しかし、イベント送信処理を作る流れでイベントクラスを作る際には、そのスキルを使ってくれるとは限りませんでした。AIがツールを使わずにコードを書いてしまい、手動でツールを実行して生成し直すことがありました。

AIにツールを使わせるべきでもなかった。 ツールで機械的に生成できるコードの生成にAIを使う必要はありませんでした。AIに書かせることで、間違いリスクやレビューのコストが生じるし、トークンも無駄になります。先にツールで生成したコードをAIに使わせるのが正解でした。

Phase 3: AIを挟まずツールで完結

AI経由の運用はやめ、エンジニアが生成ツールを実行する形に戻しました。
ラッパーとしてスクリプトを作成し、それを使用してイベントクラスの生成からPR作成まで行えるようにしました。実際の機能実装に入るときには、生成済みのイベントクラスをAIに使わせます。「AIが実装を始める時点ではイベントクラスが揃っている」状態を先に作っておく運用です。

ラッパーは既存の生成ツールを呼び出す薄いスクリプトで、やることは次の3つです。

  1. 生成ツールを実行してイベントクラスを生成
  2. ブランチを切ってコミット
  3. 決まったフォーマットでPRを作成

日々の運用では、人間もAIも生成コードには触りません。一応 .claude/settings.json で生成コードの配置先ディレクトリへの書き込みは禁止していますが、そもそもAIを使う前にツールで生成を済ませておく運用なので、AIが生成コードを変更しにいく場面はほぼありません。

なお、機能の追加・削除が早いプロダクトなので、頻繁にイベントクラスも削除することがありますが、削除はAIに任せています。

結果として、AIコストをかけて正しいコードができるか不確実な状態から、AIコストをかけずに仕様どおりのコードが生成される状態になりました。

まとめ

AIにコードを書かせるのが前提になってきた今でも、仕様から機械的に作れるコードはAIに書かせずツールで完結させた方が、AIコストを使わずに確実な出力が得られるのでおすすめです。AIに任せた方が速い領域はたくさんありますが、仕様から機械的に作れる領域にAIを挟むのは、検証コストを抱えたままAIのコンテキストを無駄に消費するだけでした。

この手法が効くのは、イベントログやAPIレスポンスの型のように、外部の仕様をそのままコードに写す領域に限られます。私たちもAPIレスポンスの一部は同じ考え方でツール生成にしています。逆にUI実装やビジネスロジックのように設計判断が求められる領域には向きません。

以前はスクリプトやツールを作るのに少し工数がかかりましたが、今はAIで簡単に作れるので、気軽に作って試せます。AIコーディングを考えるときは「どう書かせるか」を考えることが多いですが、「AIに書かせず確定的なスクリプトやツールに任せる」という考え方も持っておきたいところです。

Kurashiru Tech Blog

Discussion