そらえふのブリキ

Result型

Dart言語には、例外とエラーという概念があります。これらはどちらもthrowされる対象であり、プログラムを停止させることができるものですが、厳密には異なるものとされています。

例外は、catchするべき対象です。つまり、「自分のところでは対応しませんが、呼び出し側で適切に処理してください」という意思を表現しています。

一方、エラーはcatchの対象ではありません。エラーが発生した時点でプログラムは不正な状態になっており、プログラマーはそもそもエラーが発生しないように、メソッドの呼び出しをコントロールする必要があります。

例外とエラーは異なる概念であるにもかかわらず、どちらも同じthrowという仕組みを使って発生させられるため、適切に使われていないことがしばしばあります。

この問題を解決するのに適したものが、Result型です。

Result型とは

Result型は、ある処理が成功した場合の値、または失敗した場合のエラーのどちらかを表現できる型です。
例えば、Result<User, Exception> login()というメソッドがあるとします。このメソッドは、ログインに成功した場合はUserを返し、失敗した場合はExceptionを返すことを示しています。

Result型を使うことで、例外を型として表現することが可能になります。呼び出し側では、次のようなコードでloginメソッドを処理できます。

final result = login();
if (result.isFailure) {
    // エラーが発生した場合の処理
    showErrorMessage();
} else {
    // ユーザー名を表示する処理
    showWelcomeMessage(result.success.name);
}

例外を型として表現することで、必ず呼び出し側で例外を処理させることができるため、非常に有用な型です。

いつエラーを発生させるべきか

Result型は、例外を型として表現し、処理を呼び出し側に委ねられる便利な型です。しかし、Result型を使えば自動的に例外とエラーを適切に区別できるわけではありません。

Result型を使い始めるとよくある間違いは、エラーを出さずにすべてをResult型で返してしまうことです。これは、例外を処理しないよりは良いのですが、エラーがUIまで伝わり、バグの原因になる可能性があります。

例えば、認証サービスの設定が正しく行われていない場合に、login()を呼び出すと「SDKの設定を確認してください」というエラーダイアログが表示される、といったケースです。

では、エラーはどのような場合に発生させるべきでしょうか?
私の基準では、「呼び出し側がコントロール可能な不具合」に対してエラーを発生させるべきだと考えています。

例えば、Dart言語でList型においてインデックスを指定してデータを取得する操作を行う場合、存在しないインデックスを指定するとエラーになります。これは、インデックスが正しい範囲内かどうかを確認してから操作するべき、という言語側の意図を示しています。範囲外の場合にエラーを発生させることで、意図しないインデックスへのアクセスが確実に検出できるようにしているのです。

エラーを発生させるかResult型を返すか迷った場合は、「呼び出し側が問題を予測し、回避することが可能か」を考えてみましょう。そうすることで、不具合の少ないソフトウェアを作ることができるでしょう。

そらえふ

ソフトウェアエンジニア。趣味は競馬、写真、ゲーム。

お問い合わせはXのDMでお願いします。