🤤

Patternsでキモチェェ

2024/03/11に公開

今回はコードレビューの際にDart3から追加されたPatternsを利用して条件分岐を見やすくする提案ができて気持ち良くなったのでこの気持ちよさを共有したく記事にします。

前提

まず前提として、公開できないプログラムなので、似たような処理を行う代替のコードを用意して解説します。

今回はUsecaseクラスの実装のプルリクでした。
機能はとあるAPIを2本叩くのですが、エラーハンドリングとして、両方失敗したのか、片方だけ失敗したのか、その場合どちらが失敗したのかをUIに表示するという仕様でした。
今回叩くAPIを仮にaApiとbApiとすると

a 成功 a 失敗
b 成功 1 3
b 失敗 2 4

1 両方成功、2 aは成功bは失敗、3 aは失敗bは成功、4 両方失敗の4パターンがあります。

また、今回はResultクラスを利用しており、Repositoryが持つメソッドの返り値はSuccessかFailureで返却されることになっていました。

Resultクラスsealed修飾子を利用して下記のように定義されています。

sealed class Result<T> {
  const Result._();
  void when({
    required void Function(T value) success,
    required void Function(Exception exception, StackTrace stackTrace) failure,
  });
}

class Success<T> extends Result<T> {
  final T value;
  const Success(this.value) : super._();

  
  void when({
    required void Function(T value) success,
    required void Function(Exception exception, StackTrace stackTrace) failure,
  }) {
    success(value);
  }
}

class Failure<T> extends Result<T> {
  final Exception exception;
  final StackTrace stackTrace;
  const Failure(this.exception, this.stackTrace) : super._();

  
  void when({
    required void Function(T value) success,
    required void Function(Exception exception, StackTrace stackTrace) failure,
  }) {
    failure(exception, stackTrace);
  }
}

簡単にいうとAPIが成功した場合はレスポンスをSuccessに詰めて、失敗した場合はFailureに詰めて返却することでどちらもResult型として扱えるのでエラーハンドリング書きやすいよね〜と解釈していただければ一旦差し支えないです。

元のコード

今回私が提案する前のコードはこんな感じでした。

class SampleUsecase {
  final SampleRepository _repository;

  SampleUsecase(this._repository);

  Future<String> execute() async {
    final result = await Future.wait([
      _repository.postA(),
      _repository.postB(),
    ]);

    final aResult = result[0];
    final bResult = result[1];

    // aに失敗した場合
    if (aResult is Failure) {
      // 両方に失敗した場合
      if (bResult is Failure) {
        return 'Both Failure';
      }
      return 'Only A Failure';
    }

    // bに失敗した場合
    if (bResult is Failure) {
      return 'Only B Failure';
    }

    // 両方成功した場合
    return 'Success';
  }
}

各条件において早期リターンで返しているためネストは深くなっていないし、結果も4パターンだけなので、問題なく理解できると思います。コメントも書いてくれているので、後からコードを追う際に困ることはないと思います。

あなたならこれをどう改善しますか??

修正後のコード

class SampleUsecase {
  final SampleRepository _repository;

  SampleUsecase(this._repository);

  Future<String> execute() async {
    final result = await Future.wait([
      _repository.postA(),
      _repository.postB(),
    ]);

    final aResult = result[0];
    final bResult = result[1];

    return switch ((aResult, bResult)) {
      (Success(), Success()) => 'Success',
      (Failure(), Success()) => 'Only A Failure',
      (Success(), Failure()) => 'Only B Failure',
      (Failure(), Failure()) => 'Both Failure',
    };
  }
}

一目瞭然ですね!
簡潔に書けるだけではなくコード解いてもだいぶ読みやすくなったと思います。実際のプルリクではもう少し複雑なコードだったもののifの分岐がswitchになることで読みやすくなったかなと思います。

まとめ

今回はDart3から利用できるようになったPatternsをうまく利用できた例かなと思ったので紹介させていただきました。Patterns便利そうだしなんとなくわかるけど、どこで使えばいいのみたいな声をちらほら観測してたので、ピッタリな使用シーンが現れてキモチェぇとなっていたので感動を共有しました。

おわりに

最後まで読んでいただきありがとうございました。
2024年は技術発信も頑張ろうと思っているので、記事が参考になった方はいいねとフォローをしていただけると励みになります!GitHubのフォローも!
よろしくお願いします!

https://github.com/miyasic

Discussion