ブクブク太ってしまったControllerをChatGPTを使ってダイエットさせる実験

# はじめに
Laravel9系で写真を投稿するWebアプリを作ってみた。
もう少し詳しく話すと、家を建てる際に写真や図面がめちゃくちゃ増えて夫婦間で情報共有するのに苦労をしたという経験から、家づくり中に施主夫婦が使う写真投稿＆共有アプリを作った。
リポジトリは以下だが、本当は「house(家)」にするべきなのにスペルミスって「hause」になってて恥ずかしい。

https://github.com/haruharu3220/hause_maker4


はじめてWebアプリのフロントからサーバサイドまで全て自分で作る中で
最初こそ設計を意識していたが、そのうち動くこと優先でコードを書いてしまいソースコードがとても乱雑になってしまった。
当初目標としていた機能の実装が完了したので、ChatGPTの力を借りてリファクタリングをしてみた実験結果を記事にします。

:::note info
**リファクタリング** とは正しく動くソースコードをそのまま動く状態を保ちながら、もっと効率の良いコードに書き換えることです。
:::
https://products.sint.co.jp/obpm/blog/refactoring


この記事は
 MVCってなに？ファットコントローラってなに？って方に概要を伝えることと、
 ChatGPTに(1メソッドだけですが)リファクタリングをさせてみた結果を伝えることを目的としています。
バリバリ開発をしている方向けではないのでご了承を。


# ファットコントローラーとは
特に自分がやってしまったアンチパターンは **ファットコントローラー（直訳：太った操縦端）**　というもので、本来、
 * データベースとデータのやり取りを行う **モデル(M)** 
 * 画面を作る **ビュー(V)** 
 * モデルとビューを制御する役目を担う **コントローラー(C)**

といったようにそれぞれの役割ごとに分離されたMVCモデルのうちの**コントローラー(C)** にいろんな処理を書いてしまい、
コントローラーだけ異常に長いコードになってしまう現象が起きた。

ファットコントローラになると以下のような弊害が起きます。
 * コードが読みにくい、追いにくい
 * 仕様変更が発生した時に改修漏れが発生しやすい

僕も開発中は上記の苦しみを味わいましたが、個人開発ということもあり、ゴリ押しでやり切りました。。。(チーム開発だったら絶対詰んでた。)


ファットコントローラーについては以下の記事が分かりやすかったです。
(記事内ではコントローラの痩せすぎも良くないと書いていますが、自分は痩せすぎの判断ができないので
まずはどんどんダイエットしようという考えのもとリファクタリングしていきます！)

https://docs.sakai-sc.co.jp/article/software-engineering/what-controller-in-mvc-should-do.html


最初は「MとVとCをちゃんと分けたらいいだけでしょ」と軽く思っていたが、
コントローラーはなんでもできてしまうのでついついそこに全部の処理を書いてしまった。。

開発途中にリファクタリングをする余裕も技術もなく、
僕のコントローラは日に日に太って、最終的にほとんど**VC** (ビューとコントローラ)モデルになってしまった。


# ダイエット前のコントローラ(Before)
以下のメソッドをリファクタリングします。
投稿フォームから送られてきた画像を受け取り、ファイル名の頭に投稿日時を付与してファイル名を更新した後にstorageのimageフォルダに画像ファイルを保存。
そして画像を投稿したユーザとそのユーザが所属するチーム情報をDBのphotosテーブルに保存。
最後に写真に付与されたタグ(ハッシュタグ的なもの、複数可)をtagsとphotosの中間テーブルに保存しています。

```PHP:Storeメソッド(リファクタリング前)
public function store(Request $request)
{
    $photo = new photo();
    
    // バリデーションは省略

    if(request('image')){
        $original = $request->file("image")->getClientOriginalName();
        $name = date("Ymd_His")."_".$original;
        request() ->file("image")->move("storage/image",$name);
        $photo -> image = $name;
    }
        
    // 投稿フォームから送信されてきたデータとユーザIDをマージし，DBにinsertする
    $data = $request->merge(['user_id' => Auth::user()->id])->all();

    $photo -> user_id = Auth::user() -> id;
    $photo -> team_id = Auth::user() -> team_id;

    $photo -> type_id = $request->type;
    $photo -> save();

    //photoに紐づくtagを中間テーブルに登録
    $photo -> tags()->attach($request->tags);
    
    //一覧ページに移動
    return redirect()->route('photo.index');
}
```


# ChatGPTにこうお願いした
プロンプトを使えばより正確な返事が返ってくるみたいだが、あまり意識せず以下のようにざっくりとしたお願いをしてみた。
(ただしGPT-4(有料)を使いました。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322871/35a25da1-26c6-108b-8d88-d36fa6ff3538.png)


# ダイエット後のコントローラ(After)
結論、GPTの回答通りにコードを書き換えてもエラーなくアプリが動きました。リファクタリング成功！すごい！あんなに投げやりな質問だったのに！

そんなGPTの回答を以下に書きます。


```PHP:Storeメソッド（リファクタリング後）
use App\Models\Photo;

public function store(Request $request)
{
    // バリデーションは省略

    // 編集 フォームから送信されてきたデータとユーザIDをマージし，DBにinsertする
    $data = $request->merge(['user_id' => Auth::user()->id])->all();
    
    // 画像のアップロード処理とDBへの保存
    $photo = Photo::uploadAndCreate($data);

    // photoに紐づくtagを中間テーブルに登録
    $photo->attachTags($request->tags);

    // photo.index」にリクエスト送信（一覧ページに移動）
    return redirect()->route('photo.index');
}
```


コードが短くなった。

理由は先ほど説明をした、
>　画像を受け取り、ファイル名の頭に投稿日時を付与してファイル名を更新した後にstorageのimageフォルダに画像ファイルを保存。そして画像を投稿したユーザとそのユーザが所属するチーム情報をDBのphotosテーブルに保存。

の部分をuploadAndCreateメソッドとしてモデル側に定義したため。

あともう一つ

>最後に写真に付与されたタグ(ハッシュタグ的なもの、複数可)をtagsとphotosの中間テーブルに保存しています。

の部分をattachTagsメソッドしてモデル側に定義したためです。
モデル側に処理を書いたのでコントローラー側はそのメソッドを呼ぶだけに徹しています。
上記で**モデルとビューを制御する役目を担うのがコントローラ** と説明した通りのコードになっています。


モデル側のコードは以下のとおり。
これもGPTがそのまま書いてくれた。

```PHP:Photoモデル
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Photo extends Model
{
    use HasFactory;

    // 省略

    public static function uploadAndCreate($data)
    {
        $photo = new Photo();

        if (request('image')) {
            $original = request()->file('image')->getClientOriginalName();
            $name = date('Ymd_His') . '_' . $original;
            request()->file('image')->move('storage/image', $name);
            $photo->image = $name;
        }

        $photo->user_id = Auth::user()->id;
        $photo->team_id = Auth::user()->team_id;
        $photo->type_id = request('type');
        $photo->save();

        return $photo;
    }

    public function attachTags($tags)
    {
        $this->tags()->attach($tags);
    }
}
```

# 最後に
自身のアウトプット用の記事なので本記事はあまり有益な情報を提供できていません。
もっと有益な情報をアウトプットできるように自己研鑽に励みます。

今回の実験で
簡単なメソッドのリファクタリングならChat-GPTに雑めにお願いしてもコーディングしてくれることが分かりました。
また、コントローラとモデルの分離を体験することができ設計の勉強になりました。

もっと設計もコーディングも精進します！