Zenn
😃

Enumとパターンマッチでコードの意図を明確にする

2025/03/26に公開

コードリーディングをしているときに、「この条件でこの関数を呼んでいるのは分かるが、なぜそうしているのかが分からない」と感じたことはありませんか?

そんなときに効果的なのが Enumパターンマッチを使った書き方です。

Enumで「なにをしたいか(意図)」を名前として明示し、パターンマッチで「その意図に応じた処理」を分けることで、意図が明確なコードを書くことができます。

Enumとパターンマッチングを使った手法はよく知られていると思いますが、最近リファクタリングをする中で、整理できる場面に遭遇したので、実際にあったPHPのコードを例に紹介します。

元のコード

以下のコードは、外部システムと連携するための entity を作成している箇所です。
ModelはEloquentのModelのようなものを想像してください。

class EntitySyncAdapter
{
    function packEntity(Model $model, bool $dirty_mode)
    {    
        $entity = $this->getEntity($model);
        if (!$model->isLinkedExternalSystem()) {
            $entity->setAll($model);
        } else {
            if ($dirty_mode) {
                $entity->fillByDirtyData($model);
            } else {
                $entity->fillByModel($model);
            }
        }
        $this->addEntity($entity);
    }
}

このコードを見ると、条件に応じて適切な関数を呼んでいることは分かります。
しかし、「外部連携されていると何が違うんだろう、外部連携されているときにさらになんで分岐しているんだろう」などの理由は、コンテキストを深く知っていたり、コードを深く追わないと見えてきません。

この関数を読み解いていくと、

  • 外部連携がまだされていない場合は新規作成として空の Entity を作り、setAll でセットする
  • すでに外部システムと連携されている場合は既存の Entity を取得し、 dirty_mode に応じて差分更新 (fillByDirtyData) or フル更新 (fillByModel)

というように、処理が複数のケースに分かれていることがわかりました。

この処理を( 命名や副作用がある関数を直したいという気持ちをグッと堪えて )、Enumとパターンマッチングを使ってリファクタリングしていきたいと思います。

リファクタリング例

enum SyncMode: string
{
    // 新規作成
    case CREATE = 'create';

    // 変更のあった項目のみ同期
    case PARTIAL_UPDATE = 'partial_update';

    // 全項目を再同期(変更の有無を問わず)
    case FULL_UPDATE = 'full_update';
}

class EntitySyncAdapter
{
    function determineSyncMode(Model $model, bool $dirty_mode)
    {
        if(!$model->isLinkedExternalSystem())
        {
            return SyncMode::CREATE;
        }
        return $dirty_mode ? SyncMode::PARTIAL_UPDATE : SyncMode::FULL_UPDATE;
    }

    function packEntity(Model $model, bool $dirty_mode)
    {    
        // まずは何をするか(SyncMode)を決める
        $sync_mode = $this->determineSyncMode($model, $dirty_mode);

        // そして mode に応じて実際の処理を振り分ける
        match ($sync_mode) {
            SyncMode::CREATE => $entity->setAll($model),
            SyncMode::PARTIAL_UPDATE => $entity->fillByDirtyData($model),
            SyncMode::FULL_UPDATE => $entity->fillByModel($model),
        };
        $this->addEntity($entity);
    }
}

「どの条件でどの処理が呼ばれるのか」がEnumとパターンマッチで明確に定義されます。これによって、意図がコードから読み取りやすくなります。

このコードでは、dirty_modeの意味や意図が呼び出し側に委ねられているため、さらに改善の余地はありますが、この記事では一旦ここまでとします。

意図を明確にするコツ

コツとしては、単に「〇〇の時は△△の関数を呼ぶ」と条件と処理を直接結びつけるのではなく、
「〇〇の時は、◻︎◻︎だから、△△の関数を呼ぶ」のように、間にある理由を考えてコードに落とし込むことだと思います。

先ほどの例では「外部連携されていない時は、新規作成だから、空のEntityに値を詰める」のように、なぜそうするのかを表現することが重要です。

この 「◻︎◻︎だから」 の部分こそが、コードにおける意図になります。その意図をEnumに落とし込み、 パターンマッチで分岐させることで、意図が明確なコードを書くことができます。

最後に

最近、X(旧Twitter)でも if / if-else のネストが話題になりましたが、このようにifやif-elseのネストが入り乱れている時には、意図が欠落しているケースが多いように思います。

なのでifやif-elseのネストが入り乱れているコードを見かけたら、Enumとパターンマッチを使って「なぜその処理をするのか?」をコードに表す工夫をしてみてください。

レバテック開発部

Discussion

ログインするとコメントできます