【Dart】例外クラスを自作する

FlutterやDartでエラーハンドリングをする際、どのような方針で実装をしていますか？
基本的にはどの言語でも以下のように`try-catch`でハンドリングを行うと思います。
```Dart
try {
  // 正常処理
} catch(e) {
  // 例外処理
}
```

しかし、上記のような単調な`try-catch`ではいかなるエラーもすべて同様の処理をしなくてはなりません。
つまり、アプリの中で想定しうるエラーと想定外エラーはしっかり線引きをしようというのがこの記事の目的です。

## 実装
### 下準備1. エラー情報を格納する抽象クラス
エラーメッセージでよくあるエラーコード・タイトル・メッセージを格納するクラスです
```dart
abstract class ErrorCode {
  const ErrorCode();

  /// エラーコード
  String get errorCode;

  /// タイトル
  String get errorTitle;

  /// メッセージ
  String get errorMessage;
}
```

### 下準備2. 共通のExceptionクラス
このクラスは直接使用しません。あくまで土台となくabstructクラスです

```dart
import 'package:custom_exception/exception/error_code.dart';

abstract class CustomException implements Exception {
  const CustomException(
    this.errorCode, {
    this.info,
  });

  final ErrorCode errorCode;
  final dynamic info;

  @override
  String toString() {
    return 'CustomException{errorCode: ${errorCode.errorCode}, title: ${errorCode.errorTitle}, message: ${errorCode.errorMessage}, info: $info}';
  }
}
```

### 実装1. サーバー共通エラーの定義
下準備で実装した`ErrorCode`を`enum`に実装します。
```dart
/// サーバー共通エラーコード
enum ServerCommonErrorCode implements ErrorCode {
  systemError(
    'ER001',
    'システムエラー',
    'エラーが発生しました。しばらくしてもう一度お試しください。\n\n何度か試しても改善しない場合はアプリを再起動してください。',
  ),
  // 400エラー
  badRequestError(
    'ER002',
    'リクエストエラー',
    'リクエストが不正です。',
  ),
  // 403エラー
  forbiddenError(
    'ER003',
    'アクセス権限エラー',
    'アクセス権限がありません。',
  ),
  // 500エラー
  internalServerError(
    'ER004',
    'システムエラー',
    'エラーが発生しました。しばらくしてもう一度お試しください。\n\n何度か試しても改善しない場合はアプリを再起動してください。',
  ),
  ;

  const ServerCommonErrorCode(
    this._errorCode,
    this._errorTitle,
    this._errorMessage,
  );

  final String _errorCode;
  final String _errorTitle;
  final String _errorMessage;

  @override
  String get errorCode => _errorCode;

  @override
  String get errorTitle => _errorTitle;

  @override
  String get errorMessage => _errorMessage;

  static ServerCommonErrorCode? fromCode(String errorCode) =>
      values.firstWhereOrNull((element) => element.errorCode == errorCode);
}
```

### 実装2. カスタムExceptionクラスの実装
土台となる`CustomException`クラスと上記で実装した`ServerCommonErrorCode`を使ってカスタムExceptionクラスを実装します。

```Dart
class ServerCommonError extends CustomException {
  const ServerCommonError(
    ServerCommonErrorCode errorCode, {
    dynamic info,
  }) : super(errorCode, info: info);

  // factoryでエラーから生成する
  factory ServerCommonError.fromCode(String errorCode) {
    final errorInfo = ServerCommonErrorCode.fromCode(errorCode);
    // 取得に失敗した場合、一律システムエラーとする
    if (errorInfo == null) {
      throw const ServerCommonError(ServerCommonErrorCode.systemError);
    }
    return ServerCommonError(errorInfo);
  }
}
```

### 実際に使ってみよう
さて、実際に上記で実装した`ServerCommonError`クラスを使用してみます。
今回は同じレイヤーで実装してしまいますが、実際はrepositoryやserviceクラスなどでハンドリングする方針が好ましいかなと思います。

お試して、[PokeAPI](https://pokeapi.co/)を用いてAPIリクエストをした場合を想定して実装します。

```dart
  Future<void> fetchPikachuInfo() async {
    try {
      final Uri requestUrl =
          Uri.parse("https://pokeapi.co/api/v2/pokemon/pikachu");
      final response = await http.get(requestUrl);
      // ステータスコード別に例外を返す
      switch (response.statusCode) {
        case 200:
          // 200 OK
          debugPrint(response.toString());
          break;
        case 400:
        case 403:
          // 403 Forbidden
          throw const ServerCommonError(ServerCommonErrorCode.forbiddenError);
        case 500:
          // 500 Internal Server Error
          throw const ServerCommonError(
              ServerCommonErrorCode.internalServerError);
        default:
          // その他のステータスコード
          throw const ServerCommonError(ServerCommonErrorCode.systemError);
      }
    } on ServerCommonError catch (errorInfo) {
      // サーバー共通エラーの場合、定義したエラー情報を取得できる
      debugPrint(errorInfo.errorCode.toString());
      debugPrint(errorInfo.info.toString());
    } on Exception catch (_, stack) {
      debugPrint(stack.toString());
    } catch (error) {
      debugPrint(error.toString());
    }
  }
```

### Github
https://github.com/Kurogoma939/custom_exception