🥊

【Rails】なぜファットコントローラを避けるべきなのか?腹落ちするまで向き合ってみた

2024/02/04に公開

はじめに

お疲れ様です!
おおくまです!

今回は、「なぜファットコントローラを避けるべきなのか?腹落ちするまで向き合ってみた」ということで、私なりにファットコントローラについてまとめてみました!

少しでも皆様の参考になりますと幸いです!

対象読者

注意点

なぜファットコントローラについてまとめようと思ったのか

私自身、Ruby on Railsを学習し始めて約10ヶ月が経った段階ですが、なぜファットコントローラを避けるべきなのか、いまいち理解していません。笑
私が所属している会社でも、コード規約で、「なるべくモデル以上に寄せること」と定められているのですが、内心、「全部コントローラに書いた方がファイルを行き来しなくて良いから楽じゃない?」と思っています。笑
しかし、様々なところでファットコントローラという言葉を聞くので、やはりあまり良くないんだな〜と思い、なぜ良くないのかを理解し、腹落ちした上でコードを書きたいと思い、まとめることにしました!

ファットコントローラとは

まず、Ruby on Railsファットコントローラについて調べてみました!

  • 処理をコントローラ内で引き受けすぎてしまっていること
  • コントローラ内のコードの量も、ロジックの量も増えていること
  • コントローラ内にあらゆる実装を書き込んでいること

というように出てきました!
必要以上にコントローラ内のコードの量が多い状態のことみたいですね!


ChatGPTが考えるファットコントローラのイメージ画像

なぜファットコントローラを避けるべきなのか?

様々な記事を読んだところ、大きく分けると4つの理由があることが分かりました!

可読性

  • ファットコントローラになると、どこにどの処理を書いたのかが分かりにくく、可読性が落ちてしまう
  • 共通化すべきエラーハンドリングも各アクションごとに例外処理を実装すると、コードが冗長化してしまう

保守性

  • ファットコントローラは、同じ処理を至る所に書いているケースが多く、改修漏れが発生しやすくなる
  • 処理の一部のコードを修正すると、他の部分に影響が出てしまいやすくもなる

DRY原則

DRY原則とは、Don't Repeat Yourselfの略で、「ソフトウェア開発全体において情報を重複させない」という原則です!
ファットコントローラになってしまっていると、多くの場合、このDRY原則に反してしまっています!

https://rsato.hateblo.jp/entry/2020/08/08/145310

MVCモデル

  • MVCモデルとは、アプリケーションの設計を構造化するための一般的なアーキテクチャパターンです!
  • MVCは「Model-View-Controller」の略で、アプリケーションを3つの主要な部分に分割します!
    • Model
      • データとビジネスロジックを扱う
      • アプリケーションのデータ構造を定義し、データベースやファイルシステムとのやり取り、データの加工・処理を行う
    • View
      • アプリケーションのユーザーが実際に見る画面やページのレイアウト、デザインを扱う
      • モデルから受け取ったデータをもとに、ユーザーに表示するコンテンツを生成する
    • Controller
      • ユーザーの入力とシステムの出力を仲介
      • ユーザーからのアクションを受け取り、それに対する処理をモデルに指示し、その結果をビューに渡して反映させる

また、このMVCモデルでは、

  • Controllerは薄く保ち、ビジネスロジックはModelに切り出すこと
  • ControllerModelViewの操作だけを記述し、肥大化を避けること

がお作法とされているみたいです!

私のコントローラのイメージはこうなりました!笑

たくさんの武器(モデル内のメソッドやロジック)を装備している戦士(コントローラ)

https://pikawaka.com/rails/mvc

具体例

ChatGPTファットコントローラの具体例のコードを書いてもらい、同時にファットコントローラの解消もお願いしてみました!

ファットコントローラの例

下記がChatGPTに書いてもらったファットコントローラのコード例です!

app/controllers/posts_controller.rb
class PostsController < ApplicationController

  def show
    @post = Post.find(params[:id])
    @comments = @post.comments.where(approved: true)
    @related_posts = Post.where(category: @post.category).limit(3)
    # 他にも多くのビュー関連の処理がここに含まれる可能性がある
  end

  def create
    @post = Post.new(post_params)
    @post.user_id = current_user.id
    if @post.save
      # 通知の送信、関連するモデルの更新、ログの記録など
      send_notification_to_followers(@post)
      update_user_statistics(current_user)
      log_post_creation(@post)
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update
    @post = Post.find(params[:id])
    if @post.update(post_params)
      # 同じく、複数の後処理がここに含まれる可能性がある
      update_user_statistics(current_user)
      log_post_update(@post)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, :category)
  end

  def send_notification_to_followers(post)
    # フォロワーに通知を送信
  end

  def update_user_statistics(user)
    # ユーザー統計の更新
  end

  def log_post_creation(post)
    # 記事作成のログ記録
  end

  def log_post_update(post)
    # 記事更新のログ記録
  end
end

様々なメソッドやロジックがコントローラに書かれていて、コード量がかなり多いですね!

モデルにビジネスロジックを切り出し、ファットコントローラを解消した例

次に、ChatGPTに「モデルに切り出す方向で、このファットコントローラを解消して」とお願いしました!

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :comments

  # コールバックを使用してビジネスロジックを実行
  after_create :send_notification_to_followers, :update_user_statistics, :log_post_creation
  after_update :update_user_statistics, :log_post_update

  private

  def send_notification_to_followers
    # フォロワーに通知を送信
  end

  def update_user_statistics
    # ユーザー統計の更新
    # self.user で Post の user にアクセスできる
  end

  def log_post_creation
    # 記事作成のログ記録
  end

  def log_post_update
    # 記事更新のログ記録
  end
end
app/controllers/posts_controller.rb
class PostsController < ApplicationController

  def show
    @post = Post.find(params[:id])
    @comments = @post.comments.where(approved: true)
    @related_posts = Post.where(category: @post.category).limit(3)
  end

  def create
    @post = current_user.posts.build(post_params)
    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update
    @post = Post.find(params[:id])
    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, :category)
  end
end

修正後は、コントローラのコードがかなりスッキリしました!
モデルにメソッドやロジックを切り出し、コントローラをそれらを呼び出すものとして、きちんと役割分担されたコードになっています!
確かに、DRY原則MVCモデルについて学び、コントローラの本来の役割の観点からコードを読むと、修正した方のコードの方が正しいような気がします!笑

さいごに

ファットコントローラとは何かというところから、避ける理由、具体例までをまとめてみましたが、理由なくファットコントローラがダメと言われているのではないことが分かりました!
これからは、きちんと腹落ちして、ファットコントローラを避けてコードを書くことが出来そうです!笑
最後まで読んでいただき、ありがとうございました!

参考文献

https://engineer-first.net/fat-controller-workarounds

https://techracho.bpsinc.jp/hachi8833/2021_10_05/112108

https://qiita.com/yatmsu/items/b4a84c4ae78fd67a364c

https://qiita.com/s_emoto/items/975cc38a3e0de462966a

https://qiita.com/yuku_t/items/961194a5443b618a4cac

GitHubで編集を提案
株式会社リンクエッジ

Discussion