株式会社クイックのWebサービス開発blog

HAPPYなサービスプランナー・エンジニア・デザイナーのブログです。

クラス設計が重要だと実感した話

あけましておめでとうございます。
エンジニアのみっきーです。

昨年、既存システムをカスタマイズして、新しいシステムを作成するプロジェクトに携わりました。 その際にクラス設計で得られた効果が絶大だったので、お伝えしたいと思います。

前提

  • 似て非なるシステムだったのでカスタマイズ箇所は全体
  • 既存システムはメンテナンスの難しさが課題に上がっていた
  • メンテナンスの難しさも改善することをプロジェクトで決めた

※勝手にやってたわけじゃないよ!

対応内容

まずはこの二つ

ECサイトを例に前後比較をすると以下の図の通りです。

f:id:aimstogeek:20220107150913p:plain
ユースケース
f:id:aimstogeek:20220107152347p:plain
クラス図 前後比較

アクターが管理者とお客さんで、それぞれに商品に関する機能を提供するシステムだとします。※実際はもっと複雑だと思いますが説明のために簡略化

既存システムでは「商品」というくくりでクラスを切り分けています。
この時、税率が変わって合計金額に関する処理を変更したいとします。
修正対象を探すには「商品サービス」の中から、合計金額に関する処理を見つけ出す必要があります。
変更後のテストは「商品サービス」クラスの全てを確認する必要が出てきます。

少しの変更に対して、行わなければならない作業が多いですね。
めんどくさーと思ってしまう

商品に関する操作を増やす場合も、1クラスがさらに大きくなってしまうことがわかると思います。

一方、新規システムではアクターへ提供する機能ごとに切り分けを行なっています。
クラス図には購入処理のみを記載しましたが、他の処理も同じような切り分けをしています。

合計金額に関する処理を変更する場合、ドメインの「合計金額算出」クラスを見にいけばいいんです。
変更後のテストも「合計金額算出」クラスのみ実施すればいいのです。
もっというと税率はマジックナンバーからオブジェクトに置き換えたので、該当の数値を変えてあげるだけで修正は完了します。 かんた〜ん!

機能を追加するときは、新たに1つクラスを追加するだけです。
追加内容がわかりやすいのがイメージできますでしょうか!!

次にこの二つ

  • アプリケーションサービスとドメインサービスを切り分けた
  • データ操作をリポジトリに押し込めた

既存システムではデータのやり取りがサービスにはみ出している箇所がありました。

商品サービスを例に挙げるとこんなイメージです。

namespace App\Services;

class GoodsService
{
    public function add()
    {
        // 商品追加処理
    }

    // ... いろんな処理のメソッドが入ってる
    
    public function update(array $goods): 
    {
        if(){
            // 引数の妥当性チェックして〜
        }
        
        // 商品情報を更新するかチェックして〜
        
        $queryBuilder = new queryBuilder();
        
        // 商品情報更新して〜
        $query = $queryBuilder
            ->update()
            ->whereXXX($goods['xxx'])
            ->whereYYY($goods['YYY'])
            ->whereZZZ($goods['ZZZ']);

        // 関連テーブルも更新して〜
        
        // 更新が成功したかどうかチェックして結果を返却〜
    }
}

これではサービスが肥大化してしまって、何をしているのか読み解くのが大変です。
各サービスで同じ操作をしたい時に、同じものを書くことにもなってしまいます。
プログラム量が増えてメンテナンスしづらそうですね。

新規システムでは、データ操作を全てリポジトリに移しました。
業務に必要な知識はドメインサービスに移して、引数も商品オブジェクトを用意することで更新処理のみに集中できるようにしています。
商品更新サービスでは何が行われるのかパッと確認できちゃうんです!

namespace App\Services\Goods;

use App\Domains\Goods\UpdateService;

class UpdateService
{
    private $updateService;

    public function __construct(UpdateService $updateService)
    {
        $this->updateService = $updateService;
    }
    
    public function update(Goods $goods): bool
    {
        // 更新処理はドメインに委譲してるので呼び出すだけ
        return $this->updateService->update($goods);
    }
}

実感した効果

クラス設計がされていることによるメリットは以下のようなものがありました。

  • 不具合が起きづらくなった
  • 修正範囲の確認のためにプログラムを追う時間が減った
  • 影響範囲が1機能内に収まっているので、改修時の確認時間が減った
  • 改修時のテスト範囲を適正量に減らせた
  • エンジニアの実装スキルが上がった

要因は他にもあるのですが、クラス設計は確実に影響があったと思います。
メンテナンスがしやすくなったので、開発陣も利用する側もみんなハッピー。

大変だったこと

ここまで良かったことばかり記載しましたが、改修自体はとても大変でした。
個人的に大変だった部分は…

  • 設計書を読んだだけではクラス設計ができないので、仕様や業務知識を把握する必要があった(当たり前かもしれないけど)
  • 差分確認しつつ要件満たすように修正しつつリファクタの実施は、限られたスケジュールの中で作業量が多く、焦りを感じた
  • リファクタするための知識が0の状態からスタートしたので、レビューで鬼のようにダメ出しされた(めげない心大事)
  • 勉強の嵐
  • コンフリクトした時の絶望感

など

まとめ

クラス設計はみんなを幸せにする!

PS

経験豊富なテックリードを始め、改修にGOを出してくれたプロジェクト、調整を引き受けてくれたプランナー陣、めげずに進んだエンジニア陣、みんなで成し遂げられたプロジェクトだったなと改めて実感しています。
まだまだ課題は残っていますが、今回メンテナンスのしやすいシステムになったことで要望により答えやすくなりました。
なんでもかんでも改修すればいいわけじゃないけど、関わったプロジェクトでは少しでもメンテナンスがしやすく役に立つシステムを作っていきたいです。


\\『真のユーザーファーストでマーケットを創造する』仲間を募集中です!! //

919.jp