🛤️

AI駆動開発におけるRailsという選択肢

に公開

はじめに

AI駆動開発、バイブコーディングなど、AIに強力にアシストしてもらう開発が今後のメインストリームになると思われます(もうなりつつある)
まだまだ黎明期というのもあり、いろんな情報が飛び交っています。
Railsエンジニアとして、自分の思考の整理も兼ねて、こういう方針はどうかな?と思ったのをまとめていこうと思います。

AI駆動開発におけるポイント

最終的なジャッジは人間

まず、AIによるコード生成が進化しても、最終的にそのプログラムの責任を負うのは人間です。
これはたぶん、どんなにAIが賢くなっても当分変えられないんじゃないかと思います。
さらに、それが本当にビジネス要件を満たし、安全で、保守性があるかを判断するのは開発者自身です。

このような状況はしばらく変わらないはずで、とすると、AI駆動開発で最も重要なのは 「指示(プロンプト)」と「レビュー」かなと。
AIに任せきりにするのではなく、“どんな構造で書くべきか”を人間側があらかじめ設計し、その枠組みの中でAIを活用することが、やりやすい開発なんじゃないかと思います。

ジャッジしやすさから考えるRailsという選択肢

Railsのように「Rails Way」として推奨される構造や命名・責務の分担が決まっているフレームワークは、AI駆動開発と相性が良いんじゃないかと思います。

たとえば以下のような点がAIと人間の両方にとってメリットになります:

  • インタラクターやサービス層などの設計ルールを明確にすることで、AIに正確な指示が出せる
  • バリデーションや更新処理など、迷いやすいポイントをあらかじめルール化できる
  • 規約があることで「それが正しいかどうか」の判断基準(ジャッジ)が人間にもAIにもわかりやすい

一方で、頻繁に破壊的変更があり、推奨構成も頻繁に移り変わるようなフレームワークでは、AIにとっても人間にとっても「正解が分かりにくい」状態になりやすく、人間側の指示やレビュー負荷が逆に増してしまう可能性もあります。

AIが活躍する開発のためには、「こんな簡単なプロンプトで、パパッとTODOアプリが作れるよ!」というところではなく、予測可能性と一貫性のある構造が良いんじゃないでしょうか。

結局のところ、AIにコードを書かせること自体が目的なのではなく、人間がより少ない労力でより安全に、高品質なソフトウェアを届けることが肝です。

AI駆動開発のざっくりとした方針

AIと協働してコードを書く開発スタイルにおいては、これまでの人間中心の設計と少し異なる発想が求められます。
特に、AIが迷わない構造を最初から用意することは大切なんじゃないでしょうか(少なくとも、AIがプログラムに責任を負えるようになるまでの間は)。
そのための設計方針は、シンプルで、予測可能で、そして反復可能かなと思います。

1. 「どこに何が書いてあるか」を明確に、単純化する

AIにとって最大の困難は、構造の曖昧さです(たぶん今は)
構造が複雑だと、それをコントロールするのに多くのコンテキストが必要です。
責務が混在していたり、似たような処理が複数の場所に分散していたりすると、AIはどこを変更すべきか迷ってしまいます。
ファイル構成・命名・責務の粒度を統一し、「この処理はここを見る」と言い切れる設計が良いんじゃないでしょうか。

2. 自動テストが書きやすい構造であること

AIによる大規模な変更やリファクタリングは、テストがなければ危険です。AIはコードを一切の躊躇なく書き換えてきます。
逆に言えば、テストが充実していれば、ある程度、安心してAIに任せることができます。
そのため、テストが簡単に書ける・壊れにくい構造が良さそうです。

3. ベストプラクティスは、あえて踏襲する

奇抜な設計や独自アーキテクチャは、AIにとっても人間にとっても判断を困難にします。Railsであれば「Rails Way」に従う、というように、既存のベストプラクティスをそのまま使うことが、判断基準を明快にし、レビューの助けになります。
これにより、AIがコードを書くときも「この文脈ならこの構造が正しい」と迷わず進められるようになります。

4. 面白くない開発になる

こうした設計方針は、柔軟な設計や創造的な実装をある程度封じる方向に働きます。
エンジニアとしての自由度は下がり、正解を選ぶ開発になると思います。
そのため、自由度の高い設計をしたいエンジニアにとっては、退屈で、創造性を奪われたように感じるかもしれないですね。。。

AI駆動開発を支える、具体的なRailsの縛り

1. フォルダの責務を固定する —— バリデーションはバリデーション層へ

たとえば、バリデーションは「モデルに書いてもいい」「フォームオブジェクトにも書ける」と選択肢があると、AIが迷ってしまいます。
そこで、すべてのバリデーションは app/validators に書くというルールを徹底します。

app/
└── validators/
    ├── model/
    │   └── user_validator.rb # ユーザーなどのモデルに紐づくバリデーション
    ├── attribute/
    │   └── email_format_validator.rb # メールアドレスのフォーマットなどの、モデルを超えた、特定の値に関するバリデーション
    └── domain/
        └── date_range_validator.rb # データの幅などの特定ドメインに関するバリデーション

2. Interactorパターンで単一責務の粒度に分割する

複数の役割が混ざったサービスクラスではなく、ユースケースごとにInteractorを作ると素敵なのかもしれない。
詳解Railsデザインパターン:Interactor

これは、ソフトウェア固有のMCP(Model-Context-Protocol)やFunction Callingを設計しておくようなもので、AIの使いやすい“操作カタログ”を作るイメージです。

app/
└── interactors/
    └── users/
        ├── register.rb
        ├── update_profile.rb
        └── deactivate.rb

それぞれのInteractorに対して、自動テストを書くことで、AIの出力の正当性を担保したいです。
「サービスクラスよりも不自由」だからこそ、AIが“逸脱しない”コードを書く助けになるんじゃないかと。

Interactorパターン vs サービスクラス —— AI駆動開発の観点から

🧩 1. 役割の明確さ

観点 Interactor サービスクラス
ユースケースの明確さ ✅ 明示的に1ファイル1責務(例:Users::Register) ❌ 複数の責任が混ざりやすい(例:UserService に登録/更新/削除すべて)
命名から意図が読めるか ✅ Users::Deactivate でやることが一目瞭然 ❌ UserService の中身を見ないと分からない
AIへの指示のしやすさ ✅ 「この操作は 〇〇Interactor に書いて」と明示できる ❌ 「どこに書くか?」からAIが迷う

🧪 2. テストのしやすさ

観点 Interactor サービスクラス
単体テストの設計 ✅ 入出力が明確なのでテストしやすい ❌ 共通ロジックが混ざるとモック地獄になりがち
責務ごとのテスト ✅ spec/interactors/users/register_spec.rb など明示的 ❌ user_service_spec.rb で分岐が増えると煩雑

🛠 3. AIが扱いやすい構造?

観点 Interactor サービスクラス
AIがコードを追加・修正しやすい ✅ 単一責務なので影響範囲が小さく、指示がシンプル ❌ ロジックが多くなりがち、修正による副作用が読みにくい
ファイル数の多さの影響 ✅ AIはファイル数が多くても探索できる ❌ 人間は把握しにくくなるが、AIには好都合
再利用性 処理を共通化したくなったとき、Interactorだと微妙かも ✅ 共通処理を集約できるが、その分分岐も混ざりがち

3. いつどのメソッドを使うか、明文化しておく

Railsは柔軟な分、選択肢が多いです。
だからこそ、使用ルールを明文化しておくとAIと人間の両方が楽になるんじゃないかと思います。

メソッド 使うべきタイミング 使ってはいけないタイミング 理由
update ユーザー操作で、1件ずつ確実に更新したいとき 大量のレコードを一括更新したいとき バリデーションとコールバックが実行され、安全性が高い
update_all バッチ処理で高速に一括更新したいとき バリデーションが必要な場合、または個別のロジックがある場合 バリデーションやコールバックが完全にスキップされるため高速だが危険
preload N+1を防ぎつつ、関連条件の絞り込みは不要なとき 関連のカラムで検索・ソートをしたいとき 関連を別クエリで読み込むため、条件やソートには使えない
eager_load 関連を使ってwhere句やorder句を使いたいとき 関連が必ず存在することを前提にしてよい場合 LEFT OUTER JOINを使って関連も含めて1クエリで取得できる
joins INNER JOINで、関連が必ず存在する前提の検索 関連が存在しない可能性があるとき 関連が存在しないとレコード自体が取れなくなる
find ID指定で、必ず存在しているはずのレコードを取るとき 存在しない可能性があるIDを扱うとき 見つからなかった場合に例外が発生するため、前提が崩れると落ちる
find_by 存在しない可能性がある1件を柔軟に取得したいとき 見つからなかったときに例外処理を書かないと危険な場面 条件に合わなければnilを返すので、nilチェックが必須
pluck 値だけ取得したいとき(id, email など) モデルのバリデーションやメソッドを使いたいとき モデルではなく「配列」で返るため、ActiveRecord的な操作はできない
select 一部カラムのみ取得しつつ、モデルで扱いたいとき モデルのすべての属性が必要な場面 モデルインスタンスは使えるが、未取得カラムにアクセスするとエラー

避けるべきメソッドも明記

たとえばRailsにはfind_or_create_byといった、1行で「検索」+「新規作成」までやれちゃう便利メソッドがあるんですが、 実直に、find + create で分けて書く方がAIには分かりやすそうです。

# この1行で何が起きるか人間でも追いにくい
user = User.find_or_create_by(email: params[:email])
# 実直に分けて書く
user = User.find_by(email: params[:email])

unless user
  user = User.create(email: params[:email])
end

まとめ “AIの判断力”を信用する前に、“判断させない設計”

AI駆動開発は「AIに迷わせない」ことかなと思います。
あらかじめ粒度・責任・使用ルールを人間が整備することで、AIは「選ぶ」のではなく「従う」だけで済みます。
そうすると、人間側からしても、「指示を出しやすい」「レビューしやすい」と双方幸せな開発になるんじゃないかと思います。

もちろん、実際にやってみて、ルールや設計は都度変わると思います!
ここまで長々と書きましたが、あくまで構想としてどうぞ🙏

参考

https://qiita.com/kumai_yu/items/0aa2fc294f8e1347e36c
https://izanami.dev/post/998085c0-3801-44dd-8270-c6d1c5e7413d
https://zenn.dev/dyoshikawa/articles/developers-still-need-to-understand-ais-code
https://zenn.dev/mk668a/articles/49ec17a104acf6
https://zenn.dev/notahotel/articles/e70325e770ffa6

Discussion