95%以上をLLMが実装。『みらいまる見え政治資金』を45日で完成させた、AIネイティブな開発手法についてご紹介
はじめまして。チームみらい 永田町エンジニアチームの伊藤と申します!エンジニアチームではエディと呼ばれています。
どーやって作ったの?
先日チームみらいでは、政治資金の流れを透明性を持って公開するプラットフォーム「みらい まる見え政治資金」をリリース、ソースコードも OSS として公開し、サービス開始から約2日で20万PVと、大きな反響をいただきました。
アプリケーションの設計や技術的な詳細については、上記の記事にまとめているので、ぜひ読んでみてください!
ところで、こちらのアプリ、実は95%以上のコードをLLM(コーディングエージェント)が実装しているんです。
自分でコーディングの手を動かさない開発手法を確立できたことで、15,000行程度の中規模アプリケーションを、開発開始から約45日でリリースすることが可能になったと思っています。(なお私は別件でフルタイムの本業があり、パートタイムとしてチームみらいにコミットしています)
ということで、この記事では、開発手法に着目して、どのような工夫によって、ほとんどのコードをLLMに実装させつつ外部品質・内部品質の高いアプリケーションを構築しようとしたのかについてご紹介しようと思います。
※ 品質について
詳細は上記の記事に書いていますが、体験スコアやアクセシビリティ、SEOなど客観的なスコアで高い点数が出ていますし、内部品質的にも比較的しっかり整理され、変更しやすい状態になっているのではないかと思っています。
とても大事な注意事項
この記事に書いてあることは、「僕はこうやったら結構うまくいったよ」くらいのものであり、こうすればうまくいく!という絶対的な方法論ではないことにご注意ください。
フルスクラッチの開発であり、既存のレガシーコードとの整合性を考えなくて良いこと
Next.js + TypeScript というLLMにとって得意な土俵で完結するプロダクトであること
コード総行数15,000行程度と、中規模サイズであること
など、コーディングAIにとってやりやすい環境であったことは間違いなく、どんなケースでも使える手法ではないという前提のもと、一つの事例として読んでいただければと思います。
プロジェクト全体の設計
まずプロジェクト全体の設計レイヤーの工夫について紹介していきます。個人的にはこのレイヤーが、成否の6~7割を占めるくらい大切だと思っています。
ここから、リポジトリ > パッケージ > サーバー処理 とマクロからミクロへ、3段階のレイヤー構造でご紹介していこうと思います。
モノレポ設計による、LLMにとって見通しの良い構造
まず本プロジェクトは、パッケージマネージャpnpmを用い、以下3パッケージを一つのディレクトリで扱っています。
webapp (エンドユーザー向け画面)
admin (管理画面)
shared (共有されるドメインオブジェクト)
基本的には、adminとwebappは疎結合に作っており、キャッシュクリアくらいしか直接の通信経路はないのですが、機能によってはadminとwebappをまたいで作業するケースも多いです。
このとき、適度にサービスを分けつつモノレポ構成にすることは、単一サービスに閉じた要件においてはコンテキストを狭められるし、必要に応じてパッケージをまたいで参照もできるという意味で、LLM開発においてとても効果的だったように感じています。
クライアントとサーバーを明確に意識させるディレクトリ構成
パッケージ内の構造という観点でみると、ややNextのお作法としては珍しいかもしれないですが、webapp/adminともに下記のような構成にしています。
marumie/webapp/src/
├── app/ # Next.js App Router
├── client/ # クライアントサイドコンポーネント
├── server/ # サーバーサイドロジック
└── types/ # 共有される型定義
Next.jsに特有なのですが、LLMに任せて開発を行っていると、サーバー専用モジュールをクライアントから呼び出そうとし、実行時エラーになることがしばしば見られます。
それをなるべく防ぐ意図で、今回のプロジェクトでは上記の様にパッケージ内の最上位ディレクトリでserverとclientを分け、実行環境を明確化しています。
// サーバー側で動作することを期待する処理
import "server-only" // クライアントサイドから読み込むとコンパイルエラー
また、サーバー側で動作することを期待する処理には上記のように"server-only"をつけ、クライアントから読み込むと静的エラーが出るようにルール化することで、混同が起きにくいような工夫を多重に行っています。
サーバー処理の厳格なレイヤー分け
さらに、サーバー処理の分割を見ていきましょう。
サーバー処理には、クリーンアーキテクチャ風に、更に細かなレイヤー分けを厳格に適用しています。
marumie/admin/src/server/
├── loaders/ # エントリーポイント(参照系)
├── actions/ # エントリーポイント(書き込み系)
├── usecases/ # ビジネスロジックの組み立て
├── lib/ # データ加工・変換処理
├── repositories/ # データアクセス層
└── auth/ # 認証関連処理
これはLLM開発ならではのポイントで、人間が手で書くと「こんなシンプルな処理にもUsecase作るの面倒だな」なんて思いそうな部分も、LLMにとっては苦ではないため、しっかりレイヤー構造を作らせることで見通しが悪化しにくいと考えています。
※ この図では表現できてないですが、エントリーポイントで依存注入することで、Usecase以下の全てのモジュールをテスト可能にしています。
プロジェクト全体の設計のまとめ
見てきた通り、リポジトリ→パッケージ→サーバー処理 と各レイヤー内でのルールをしっかり作り厳格に守らせることが、LLMにとっての明快さや、レビュー時の明快さを生んだと思います。
LLM中心の開発を行う際は、実装量が苦にならないというLLMの特長を活かし、より例外なくルールを適用して統一感を高めるほうに倒すほうが、トータルでスピードが出るという感覚を持っています。
実装段階で効果を発揮する工夫
つづいて、一つひとつの機能を実装していくときの工夫についても紹介していきます。
CLAUDE.mdに厳選した設計ルールを記載し守らせる
コーディングAIとしては主にClaude Codeを利用していました。
設定ファイルであるCLAUDE.mdには、以下のような内容を記載しています。(実際のファイル全文はこちら)
設計作業ルール: 設計ドキュメントのファイル名規則や保存場所
Next.js実装ルール: サーバーコンポーネントとクライアントコンポーネントの使い分け、データ取得パターン
コード構成: ディレクトリ構造と各ディレクトリの責務
GitHub操作ルール: ブランチ戦略、PR作成時の注意事項、デプロイ前のチェック項目
あまり長くても毎回の作業のコンテキストを圧迫するので、どのような作業をする場合でも意識してほしいことに絞って記載しています。
複雑な機能は、設計ドキュメントを先に作らせる
複雑な機能を実装する際、いきなりコードを書かせ始めると、LLMが迷走したり、手戻りが大きくなったりしがちです。
そういった機能については、まずdocsディレクトリにマークダウンで機能設計書を書いてもらい、対話的にやりとりしてドキュメントの中身が納得いくものになってから実装を開始してもらうのがおすすめです。
特に、実装が複雑な機能というよりは、大量のコンテキストを必要とする機能(大量のファイルを統一的に修正するなど)においては、この方法が有効です。
デザインもFigma MCPを使えばLLMに任せられる!
Claude Codeは、MCP(Model Context Protocol)経由でFigmaのデザインデータを直接読み取ることができます。
今回の開発では、ほとんど全てのデザインをFigma MCP(Framelink)経由で行っており、私が実際にHTMLやcss(tailwind)を書いた部分はゼロだと思います。
ただ、もちろんデザインというものは単純な要素でも情報が多いので、純粋な関数を書くようなケースと比べて、ステップバイステップで指示をしたほうがうまく行きやすいです。例えば「このFigmaに合わせてヘッダーを全部実装して」のような粗い指示よりは、
このFigmaファイルのヘッダーの外枠だけを実装して
つづいてロゴ部分を実装して
つづいてハンバーガーメニュー部分を、閉じているときの見た目だけ実装して
つづいてハンバーガーメニューのクリック時の挙動を実装して
といった形で、一つひとつ切り分けて指示するなどの工夫が効果的です。
なお、Figma MCPを使った開発がうまくいくかは、元のFigmaファイルの構造の綺麗さにも左右されます。今回のFigmaファイルは、参院選愛知県候補だった山根さんが作ってくれたのですが、見栄えのみならず構造も美しかったので大変やりやすかったです。
たまに大局的にレビューをもらうことで全体感を修正する
実装が一段落したタイミングなどで、下記のようなプロンプトを投げることで、設計の原則とズレている部分が発見でき、早期に修正するできることが何度かありました。たまにはやってみるのも良いかもしれません。
「現在のリポジトリ構成を確認して、CLAUDE.md に書いてある設計ルールから逸脱している部分がないか指摘してください」
AIの書いたコードをレビューする際の工夫
最後に、コーディングAIの書いたコードをどうレビューするかについても紹介します。
【重要】 AIの書いたコードは人間がちゃんと読む
一定水準の品質を保ちたいのであれば、こちらの記事にあるように人間がAIのコードをちゃんと読むのはかなり大切だと考えています。
自分なりのコツは、一つひとつのロジックを読んで正しさを確かめるというよりは、大まかにどんなことをやっていて、それが上記のレイヤーの責務とあっているかをチェックしていくイメージです。
責務のレイヤーが違うときは書き直しを依頼し、レイヤーは合ってるけど処理として正しいか分からないときは、処理を追うのではなくユニットテストで確かめる、というふうにするのが疲れにくくて良いと思います。
重要な処理にはユニットテストを書かせる
ビジネスロジックを担う重要な層(変換やバリデーションを担う層)のコードを目で追って確かめるのは効率がよくないので、ユニットテストで挙動を保証するのが効率が良いと思います。自分の場合は server/lib に入る処理には基本すべてしっかりテストを書いています。
また、ClaudeCodeは、見きれないほどの過剰なテストケースを書きがちなので、まずは正常系1ケースだけ書いてもらい、それが通ったら確かめておきたいエッジケースなどをいくつか指定して、テストコードが冗長なケースで肥大化しないように気をつけるのがおすすめです。
コミットごとの自動フォーマット
今回のリポジトリでは、simple-git-hook + Biomeを使って、コミットのたびに自動フォーマットをかけるようにしています。PRがリモートでlint errorなどになることが激減するので、開発サイクルの高速化の点でおすすめです。
CIでのチェック
もはや当たり前ですが、GitHubのCIでformat/lint/testを実施しています。フォーマッター・リンターとしてはBiomeを利用しています。prettier + eslintと比較し、実行が高速、かつデフォルトでルールが厳しいため、LLMにルールを守ってもらう前提においてはかなりおすすめです。
DevinのPRはVercel の Branch-based Previewでポチポチ動作確認する
今回、ほとんどの機能をローカルのClaude Codeに実装させていたので、流れで試しやすかったのですが、一部の単純な修正はDevinにSlackで依頼することもありました。
その際は、VercelのBranch-basedプレビュー機能で動作確認を行えるのは、大変便利でした。
LLMには実装できなかった部分は?
ないです。タイトルで95%といったのも低く見積もった体感の話で、もしかしたら99%以上LLMと言っても良いかもしれません。やろうと思えば100%にできる感覚もあります。
基本的に上記で紹介した方法を駆使すれば、バックエンド/フロントエンド・ビジュアル/ロジックなど関係なく、全ての領域をLLMに任せることができました。
ちなみに僕は、「1行console.logを削除する」などの手でやったほうが早そうなこともけっこうLLMに依頼しています。下手に協業するよりは、編集は全てをLLMに任せるほうが、コンテキスト齟齬が減り、トータルで混乱を生まないケースが多いと考えているためです。
まとめ
というわけで、
プロジェクト全体の設計
実装段階の工夫
コードレビュー時の工夫
の3レイヤーに分けて、AIに実装を担当してもらう方法をご紹介してきました。振り返ると、どのレイヤーにおいても、AIだから特別な工夫があるということはなく、人間にとっても有効な取り組みをしっかりやり切ることで、一定のクオリティを担保できるという話かもしれません。
ただ一点違うとすれば、コーディングエージェントは人間とは比べ物にならない忍耐強さを持つため、厳密なレイヤーごとのルールを定め、より例外なくルールに従ってもらうことが可能かつ効果的という点が大切なポイントなのではないかと思いました。
また繰り返しになりますが、既存レガシーコードや、より大規模で複雑なシステムには今回の方法は適さないこともあると思います。
あくまで一例とご理解いただきつつ、LLMにコーディングを任せきる実装を試してみてはいかがでしょうか!
合わせておすすめ
そんな感じでLLMが作り上げた『みらいまる見え政治資金』について、技術選定や、実装の工夫などについてこちらの記事にまとめてあります。合わせて読んでいただくともっと理解が深まると思います!
コメント