Effective Dart: Usage
エラー処理
エラー処理を書いていて、try catchの書き方よろしくないらしく調べていたら、Effective Dart`に推奨する書き方が解説されていた。
Dart は、プログラムでエラーが発生した場合に例外を使用します。次のベスト プラクティスは、例外のキャッチとスローに適用されます。
on句のないキャッチを避ける
on修飾子のない catch 句は、try ブロック内のコードによってスローされたものをすべてキャッチします。ポケモンの例外処理は、おそらくあなたが望むものではありません。コードはStackOverflowErrorまたは OutOfMemoryErrorを正しく処理していますか? try ブロックで誤って間違った引数をメソッドに渡してしまった場合、デバッガーに間違いを指摘してもらいたいですか、それとも役立つArgumentErrorを飲み込んでしまいたいですか?assert()スローされたAssertionErrorをキャッチしているので、そのコード内のステートメントを事実上消滅させたいですか ?
答えはおそらく「いいえ」です。その場合は、キャッチしたタイプをフィルタリングする必要があります。ほとんどの場合、on認識していて正しく処理している実行時エラーの種類に制限する条項を用意する必要があります。
まれに、実行時エラーを捕捉したい場合があります。これは通常、任意のアプリケーション コードが問題を引き起こすのを防ぐフレームワークまたは低レベル コード内にあります。ここでも、通常はすべての型をキャッチするよりも例外をキャッチした方が良いでしょう 。 Exception はすべてのランタイムエラーの基本クラスであり、コード内のプログラムのバグを示すエラーは除外されます。
on句のないキャッチからのエラーを破棄しないでください
コード領域からスローされる可能性のあるものをすべてキャッチする必要があると本当に感じている場合は、キャッチしたものを使用して何かを実行してください。ログに記録し、ユーザーに表示するか、再スローしますが、黙って破棄しないでください。
DOErrorプログラムエラーの場合にのみ実装されるオブジェクトをスローします
Errorクラスは、プログラムエラーの基本クラスです。そのタイプのオブジェクト、またはArgumentErrorなどのそのサブインターフェイスの 1 つがスローされた場合、コードにバグがあることを意味します。 API が誤って使用されていることを呼び出し元に報告したい場合、エラーをスローすると、そのシグナルが明確に送信されます。
逆に、例外がコードのバグを示さない、ある種の実行時エラーである場合、Error をスローすることは誤解を招くことになります。代わりに、コア例外クラスの 1 つまたは他の型をスローします。
Errorそれを実装する型を明示的にキャッチしないでください
リンター ルール: avoid_getting_errors
これは上記のことからわかります。エラーはコードのバグを示しているため、コールスタック全体を巻き戻し、プログラムを停止し、バグを特定して修正できるようにスタック トレースを出力する必要があります。
これらのタイプのエラーをキャッチすると、処理が中断され、バグがマスクされます。この例外を処理するエラー処理コードを事後的に追加するのではなく 、最初の段階に戻って、例外がスローされる原因となったコードを修正します。
rethrowキャッチした例外を再スローするために使用してください
リンター ルール: use_rethrow_when_possible
例外を再スローする場合は、 をrethrow使用して同じ例外オブジェクトをスローするのではなく、 ステートメントを使用することをお勧めしますthrow。 rethrow例外の元のスタック トレースを保持します。throw一方、スタック トレースを最後にスローされた位置にリセットします。
非同期性
Dart には、非同期プログラミングをサポートするためのいくつかの言語機能があります。次のベスト プラクティスは、非同期コーディングに適用されます。
生の先物を使用するよりも非同期/待機を優先します
非同期コードは、future のような優れた抽象化を使用している場合でも、読み取りとデバッグが難しいことで知られています。 /構文asyncによりawait可読性が向上し、非同期コード内ですべての Dart 制御フロー構造を使用できるようになります。
async有益な効果がない場合は使用しないでください
async非同期に関連するあらゆる関数で使用する習慣を身に付けるのは簡単です。しかし場合によっては、それは無関係です。async関数の動作を変更せずに を省略できる場合は、そうしてください。
役立つケースは次のとおりasync です。
-
を使用していますawait。 (これは明らかなことです。)
-
非同期でエラーを返しています。asyncそして はthrowよりも短いですreturn Future.error(...)。
-
値を返しており、それを暗黙的に Future でラップしたいとします。 asyncよりも短いですFuture.value(...)。
ストリームを変換するために高次のメソッドを使用することを検討してください
これは、イテラブルに関する上記の提案と類似しています。ストリームは同じメソッドの多くをサポートしており、エラーの送信や終了なども正しく処理します。
Completer を直接使用しないでください
非同期プログラミングを初めて使用する人の多くは、未来を生み出すコードを書きたいと考えています。 Future のコンストラクターはニーズに合っていないようなので、最終的に Completer クラスを見つけてそれを使用します。
コンプリーターは、新しい非同期プリミティブとフューチャーを使用しない非同期コードとのインターフェイスという 2 種類の低レベル コードに必要です。他のほとんどのコードでは、 async/await または を使用する必要がありますFuture.then()。これは、より明確でエラー処理が容易になるためです。
Future<T>型引数が次のようFutureOr<T>なものである可能性を明確にする場合にテストを実行します。Object
を使用して何か役立つことを行う前に、通常は、 または裸のを持っているかどうかを確認するチェックFutureOr<T>を行う必要があります。 type 引数が のような特定の型である場合、どのテストを使用するかは関係ありません。これら 2 つの型は互いに素であるため、どちらでも機能します。isFuture<T>TFutureOr<int>is intis Future<int>
ただし、値の型が であるObjectか、 でインスタンス化できる可能性がある型パラメーターである場合Object、2 つの分岐は重複します。Future<Object> それ自体が を実装しているObjectため、is Objector is TwhereTはインスタンス化できる型パラメータであり、Objectオブジェクトが future であっても true を返します。代わりに、Futureケースを明示的にテストします。
悪い例では、これに a を渡すとFuture<Object>、それを裸の同期値のように誤って扱います。
このドキュメントを読む前から書き方は意識していた
最近は、try catch
はよくないとFlutterに詳しい人に言われたZennの記事を書いていた人でとても素晴らしい人でしたね。誰かとは言わない方がいいかも💦
なんでも例外をキャッチしてしまうんですよね。なので、最近はAPIやFirebaseとやりとりするときは、on Exception catch
と書きますね。
でもね。全部エラーキャッチしないとログにエラーの原因でないことあります😇
これが最近の私のtry catch
の書き方:
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../entity/task/task_state.dart';
import '../../service/store/firebase_provider.dart';
part 'task_reository.g.dart';
abstract interface class TaskRepository {
Future<List<TaskState>> getTaskList(); // Modify the return type
}
(keepAlive: true)
TaskRepositoryImpl taskRepositoryImpl(TaskRepositoryImplRef ref) {
return TaskRepositoryImpl(ref);
}
class TaskRepositoryImpl implements TaskRepository {
TaskRepositoryImpl(this.ref);
final Ref ref;
Future<List<TaskState>> getTaskList() async {
try {
final taskRef = await ref.read(taskConverterProvider).get();
return taskRef.docs.map((e) => e.data()).toList();
} on Exception catch (e) {
throw Exception(e);
}
}
}
ViewModelというか、usecaseなるディレクトリに配置してるAsyncNotifierで呼び出して使ってます。FutureOr
のbuildメソッドのデータ型はリポジトリのメソッドのデータ型のList<TaskState>
と同じにします。return
で返すのが最初に実行される処理か値になるので、getTaskList
メソッドを呼び出します。これは、FutureProviderやFutureBuilderと同じように非同期で、FirestoreのQuerySnapshotのデータを取得して、1度だけ全てのデータを取得してくれます。
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../domain/entity/task/task_state.dart';
import '../../domain/repository/task/task_reository.dart';
part 'task_notifier.g.dart';
class TaskNotifier extends _$TaskNotifier {
FutureOr<List<TaskState>> build() {
return getTaskList();
}
Future<List<TaskState>> getTaskList() async {
final taskService = ref.read(taskRepositoryImplProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
return taskService.getTaskList();
});
return state.maybeWhen(
data: (data) => data,
orElse: () => throw Exception('TaskState Error!'),
);
}
}
rethrowを使った例
rethrowキーワードは、現在の例外を再度スローするために使用されます。これは、例外をキャッチして何らかの処理(例えば、エラーメッセージの出力やリソースのクリーンアップ)を行った後で、その例外を上位のコードに伝播させるために使用されます。
以下のコードでは、Exception型の例外をキャッチして、その詳細をデバッグ出力に表示しています。その後、rethrowキーワードを使用して、同じ例外を再度スローしています。
これにより、fetchTaskListメソッドを呼び出した上位のコードは、このメソッドがスローした例外をキャッチして適切に処理することができます。例えば、ユーザーにエラーメッセージを表示したり、リトライロジックを実行したりすることができます。
したがって、rethrowを使用する主な理由は、例外をキャッチして何らかの処理を行った後で、その例外を上位のコードに伝播させることです。rethrowの上にprint
だとlintの警告が出るから、debugPrint
かlogger
がようさそうですね。
// rethrowを使った例
Future<List<TaskState>> fetchTaskList() async {
try {
final taskRef = await ref.read(taskConverterProvider).get();
return taskRef.docs.map((e) => e.data()).toList();
} on Exception catch (e) {
debugPrint('error: $e');// logを出すコードをrethrowの上に書く。
rethrow;
}
}
もしlogを出すコードを書かないならいらないようですね。今回は深掘りして「〇〇さんは何を伝えたかったんだろうかってのがわかった」
最後に
私は頭が悪いです。以前深いところまで理解しておくように言われたことがある。でも意外と知らない人は多いんですよ。動けばいいんです🙃
と言いたいところだが、Lintの警告が出てくるコードを書いたり、これはよくない書き方だな〜ってコードは書かないようにしないとバカにされそうで日々探求していますね。
検索しても毎回意味不明な文献しか出てこないので、色々サンプル作って試しますね。
Discussion