0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Fat Controllrの解消をしてRails設計を学ぶ〜UseCaseクラス導入とその効果〜

Posted at

はじめに

個人開発でRailsアプリを作成する中で、コントローラーが肥大化していくことに悩んでいました。1つのアクションにロジックが詰め込まれ、「動くけど読みにくい」状態になっていました。この記事では、Fat Controllerをどう設計改善したか、UseCaseクラス導入のプロセスと学びをまとめます。

Fat Controllerとは

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

必要以上にコントローラ内のコードがある状態をFatControllerと言います。

Fat Controllerはなぜよくないのか

  • コード量が増えすぎて、可読性が下がる
  • ロジックが埋もれて、再利用しづらい
  • テストがしにくく、保守性が悪化

Fat Controllerについては下記の記事がわかりやすく書いてありました。

解消方法

ここで必要となってくるのが単一責任の原則DRY原則という考え方です。

単一責任の原則(SRP: Single Responsibility Principle)

定義

「ソフトウェアの各コンポーネント(クラス、モジュールなど)は、それぞれ1つの責任のみを持つべきだという原則」
つまり、1つの役割・目的だけを持つようにすることで、責務が明確になり、他のコードへの影響が最小限になる。

DRY原則(Don't Repeat Yourself)

定義

「同じロジック・知識を複数箇所に書かないこと」
同じコードを何度も書くことを避け、1か所にまとめて記述することで、保守性や可読性を向上させる。

実際のコード

Before

#app/controllers/users_controller.rb
class UsersController < ApplicationController
  def update_password
    @user = current_user

    if @user.authenticate(params[:user][:current_password])
      if @user.update(params.require(:user).permit(:password, :password_confirmation))
        flash[:notice] = "パスワードを変更しました"
        redirect_to edit_password_user_path
      else
        flash.now[:alert] = "パスワードの更新に失敗しました"
        render :edit_password, status: :unprocessable_entity
      end
    else
      @user.errors.add(:current_password, "が正しくありません")
      flash.now[:alert] = "変更に失敗しました"
      render :edit_password, status: :unprocessable_entity
    end
  end
end

この書き方の問題点

  • 認証・更新、エラー処理、画面遷移が1アクションに纏められている
  • コントローラのコード量が増え、可読性が下がる
  • テストもしづらく、変更時の影響範囲が広い

After

UseCaseクラスによる責務分散

#app/controllers/users_controller.rb
def update_password
  service = UpdatePassword.new(
    current_user,
    current_password: params[:user][:current_password],
    new_params: password_params
  )

  if service.call
    flash[:notice] = "パスワードを変更しました"
    redirect_to edit_password_user_path
  else
    flash.now[:alert] = "変更に失敗しました"
    render :edit_password, status: :unprocessable_entity
  end
end

private

def password_params
  params.require(:user).permit(:password, :password_confirmation)
end
#app/usecases/update_password.rb
class UpdatePassword
  def initialize(user, current_password:, new_params:)
    @user = user
    @current_password = current_password
    @new_params = new_params
  end

  def call
    if @user.authenticate(@current_password)
      @user.update(@new_params)
    else
      @user.errors.add(:current_password, "が正しくありません")
      false
    end
  end
end

設計改善ポイント

単一責任の原則(SRP)を守れる

以前のFat Controllerでは、1つのメソッドが「認証」「更新」「フラッシュメッセージの出力」「画面遷移」など、複数の責務を抱えていました。

UseCaseを導入したことで、認証・更新処理はUseCaseに委譲
フラッシュや画面遷移はControllerに限定と、役割を明確に分けられ、どこを変更すればよいかもはっきりしました。

Fat Controller を避けられる

責務の分離により、コントローラーの処理が短く、読みやすくなりました。update_password の中身が「処理を呼び出す」だけになったことで、流れが明確に把握でき、保守性が格段に上がりました。

DRY原則の実現

同じような処理(バリデーションやパラメータ処理)が複数箇所に分散していると、修正漏れやバグの温床になります。
UseCase導入によって、重複しやすいロジック(認証や更新)を1か所にまとめられたことで、変更時の影響範囲を最小限にできました。

テストがしやすくなる

UseCaseクラスはコントローラーから切り離されているため、RSpecでの単体テストが可能になりました。
たとえば、認証失敗や更新失敗のケースを明示的にテストできます。

得られた学び

今回の改善を通して、設計や保守性に対する考え方が大きく変わりました。

まず、Fat Controllerがなぜ問題なのかを実感しました。認証や更新、フラッシュメッセージの制御といった処理を1つのアクションに詰め込むことで、コードの見通しが悪くなり、バグの温床になりやすいことを痛感しました。

そこでUseCaseクラスを導入することで、パスワード変更という単一の責任を切り出し、コントローラーでは処理の流れだけに集中できるようになりました。結果として、単一責任の原則(SRP)を意識した保守しやすい設計へと改善されました。

また、重複しやすいロジックをUseCaseに集約したことで、DRY原則(Don't Repeat Yourself)も実践できました。1か所を修正すれば全体が改善される設計になり、将来的な仕様変更にも柔軟に対応できます。

さらに、UseCaseは外部依存が少なく、RSpecでの単体テストが非常にしやすくなりました。認証失敗やバリデーション失敗といったシナリオを分離して検証できることで、品質保証にもつながりました。

コードの可読性、保守性、再利用性、テストのしやすさという点で、UseCaseパターンは非常に有効であると体感しました。

「とりあえず動く」から一歩進み、コードの役割を明確に分離する設計を目指しました。UseCaseクラスによってコードの見通しが良くなり、保守や機能追加のしやすさにもつながっています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?