成功するとtrue / false vs voidを返し、失敗すると例外をスローする関数


22

ファイルをアップロードする関数であるAPIを作成しています。ファイルが正しくアップロードされた場合、この関数は何も/ voidを返さず、何らかの問題が発生したときに例外をスローします。

なぜ偽りではなく例外なのか?例外の内部で、失敗の理由(接続なし、ファイル名の欠落、パスワードの誤り、ファイルの説明の欠落など)を指定できるためです。カスタム例外を作成したかった(APIユーザーがすべてのエラーを処理するのに役立つ列挙型を使用)。

これは良い習慣ですか、それともオブジェクトを返す方が良いですか(内部にブール値、オプションのエラーメッセージ、エラーの列挙を含む)?



59
真と偽はうまくいっています。無効と例外はうまくいっています。猫と税金のように、真実と例外は一緒になります。いつかあなたのコードを読んでいるかもしれません。私にこれをしないでください。
candied_orange

4
成功または失敗のいずれかをカプセル化するオブジェクトが返されるパターンが非常に好きです。たとえば、Rust Result<T, E>Tは、成功した場合は結果Eが、失敗した場合はエラーが表示されます。例外をスローする(Javaではそれほど確かではない可能性があります)ことは、呼び出しスタックの巻き戻しを伴うため、費用がかかる可能性がありますが、Exceptionオブジェクトの作成は安価です。明らかに、使用している言語の確立されたパターンに固執しますが、このようなブール値と例外を組み合わせないでください。
ダンパントリー

4
ここで注意してください。あなたはウサギの穴に向かっているかもしれません。プログラムが問題に対処できる場合、通常、前提条件をチェックすることで、問題を検出して対処する方が簡単です。例外をキャッチすることはほとんどなく、そのデータをコードで調べ、問題を修正してから再試行します。そのため、非常に多くの例外タイプを持つことはめったに価値がありません。問題をログに記録しようとしている場合、これはかなり理にかなっていますが、大量のコードを含むcatchブロックを大量に書くことを期待しないでください。
jpmc26

4
@DanPantry Javaでは、例外をスローするときではなく、例外を作成するときに呼び出しスタックがチェックされます。fillInStackTrace();クラスのスーパーコンストラクターで呼び出されますThrowable
サイモンフォースバーグ

回答:


35

例外をスローすることは、メソッドに値を返すための単なる追加の方法です。呼び出し元は、例外をキャッチしてチェックするのと同じくらい簡単に戻り値をチェックできます。したがって、どちらを決定するかはthrowreturn他の基準が必要です。

プログラムの効率を危険にさらす場合は、例外をスローすることは避けてください(例外オブジェクトの構築と呼び出しスタックの巻き戻しは、コンピューターに値をプッシュするよりもはるかに手間がかかります)。ただし、メソッドの目的がファイルのアップロードである場合、ボトルネックは常にネットワークおよびファイルシステムのI / Oになります。したがって、returnメソッドを最適化することは無意味です。

APIユーザーの期待に違反するため、単純な制御フロー(たとえば、値を見つけて検索が成功した場合)に例外をスローすることもお勧めしません。しかし、その目的を達成できないメソッド例外的なケースです(少なくともそうあるべきです)ので、例外をスローしない理由はありません。そして、それを行う場合、カスタムのより有益な例外にすることもできます(ただし、標準のより一般的な例外のサブクラスにすることをお勧めしますIOException)。


7
ファイルアップロードリクエストが失敗することが予想される結果である場合、(特にメソッドシグネチャに戻り値がある場合)自分の顔に例外がスローされることに驚かされます。
-hoffmale

1
標準例外を拡張する理由は、多くの(おそらくほとんどの)呼び出し元が問題を修正しようとするコードを書かないことに注意してください。その結果、ほとんどの呼び出し元は、すべてを明示的にリストすることなく、単純な「この大きな例外グループをキャッチする」ことを望みます。
jpmc26

4
@gbjbaanbこれが、修正不可能な状況が本当にある場合に例外を使用する理由です。ファイルを開こうとしていて、ファイルを読み取れない場合、これは例外の良いケースです。ファイルの存在を確認するときは、ファイルが存在しない可能性が高いため、ブール値を使用する必要があります。
マルコム

21
Kilianは、再帰的なスローガン「例外は例外的な状況に対するものです」で、例外的な状況は機能がその目的を果たすことができない場合であると言っています。関数がファイルを読み取ることになっていて、ファイルを読み取ることができない場合は例外的です。関数がファイルが存在するかどうかを通知することになっている場合(潜在的なTOCTOUを気にしないでください)、存在しないファイルは例外ではありません。関数がファイルが存在することをアサートすることになっている場合、「アサート」が意味するので、存在しないことは例外的です。
スティーブジェソップ

10
ファイルが存在するかどうかを示す関数と、ファイルが存在することをアサートする関数との唯一の違いは、ファイルが存在しない場合が例外的であるかどうかであることに注意してください。人々は、それが「例外的」であるかどうかに関係なく、エラー状態の特性であるかのように話すことがあります。エラー条件のプロパティではなく、エラー条件とそれが発生する関数の目的を組み合わせたプロパティです。それ以外の場合は、例外をキャッチしません。
スティーブジェソップ

31

失敗時にtrue戻らない場合、成功時に戻る理由はまったくありませんfalse。クライアントコードはどのように見えるべきですか?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

この場合、呼び出し元はとにかくtry-catchブロックを必要としますが、次のように書くことができます:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

そのため、true戻り値は呼び出し元にとって完全に無関係です。メソッドをそのままにしてくださいvoid


一般に、フェイルモードを設計するには3つの方法があります。

  1. true / falseを返します
  2. 使用void、スロー(チェック済み)例外
  3. 中間結果オブジェクトを返します。

戻るtrue/false

これは、いくつかの古い、主にCスタイルのAPIで使用されます。欠点は明白であり、何が悪かったのかわかりません。PHPはこれを非常に頻繁に行い、次のようなコードを導きます。

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

マルチスレッドコンテキストでは、これはさらに悪化します。

(チェック済み)例外をスローする

これは良い方法です。ある時点で、失敗が予想されます。Javaはこれをソケットで行います。基本的な前提は、呼び出しは成功するはずですが、特定の操作が失敗する可能性があることは誰もが知っています。その中にソケット接続があります。そのため、呼び出し元は障害の処理を強制されます。呼び出し元が実際に障害を処理することを確認し、呼び出し元に障害に対処するためのエレガントな方法を提供するため、素晴らしいデザインです。

結果オブジェクトを返す

これは、これを処理するもう1つの優れた方法です。解析や、単に検証が必要なものによく使用されます。

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

呼び出し元にとっても、すてきできれいなロジック。

2番目のケースを使用するタイミングと3番目のケースを使用するタイミングについては、少し議論があります。一部の人々は、例外は例外的であるべきであり例外の可能性を念頭に置いて設計すべきではないと考え、ほとんどの場合、3番目のオプションを使用します。いいね しかし、Javaで例外をチェックしたため、例外を使用しない理由はありません。呼び出し成功する(ソケットを使用するなど)という基本的な仮定がある場合は、チェック済みの調査を使用しますが、失敗する可能性があります。しかし、これについてはさまざまな意見があります。

あなたの場合、私はvoid+で行きExceptionます。ファイルのアップロードが成功することを期待しますが、そうでない場合は例外です。ただし、呼び出し側はその失敗モードを処理する必要があり、発生したエラーの種類を適切に記述する例外を返すことができます。


5

これは失敗が例外的である予想されるかに本当に由来します。

エラーが一般的に予想されるものでない場合、APIユーザーは特別なエラー処理をせずにメソッドを呼び出すだけです。例外をスローすることで、スタックを気付く場所までバブルアップできます。

一方、エラーが一般的である場合は、エラーをチェックしている開発者向けに最適化する必要try-catchがあり、句はif/elifor switchシリーズよりも少し扱いに​​くいです。

常にAPIを使用する人の考え方でAPIを設計してください。


1
私の場合、誰かが指摘したように、APIユーザーがファイルをアップロードできないという例外的なエラーになるはずです。
-Accollat​​ivo

4

戻りたくない場合はブール値を返さないでくださいfalse。失敗しvoidた場合に例外をスローする(IOException適切な)メソッドとドキュメントを作成します。

これは、ブール値を返すと、APIのユーザーがこれを実行できると結論付ける可能性があるためです。

if (fileUpload(file) == false) {
    // Handle failure.
}

もちろんそれはうまくいきません。つまり、メソッドのコントラクトとその動作との間に不一致があります。チェック済み例外をスローする場合、メソッドのユーザーは失敗を処理する必要があります。

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

メソッドに戻り値がある場合、例外をスローするとそのユーザーを驚かせる可能性があります。メソッドがブール値を返す場合、成功した場合はtrueを返し、失敗した場合はfalseを返し、if-else句を使用してコードを構造化します。例外が列挙型に基づいている場合、Windows HResultsと同様に、代わりに列挙型値を返し、メソッドが成功した場合に列挙型値を保持することもできます。

また、プロシージャの最後のステップではないメソッドに例外をスローさせるのも面倒です。try-catchを記述し、catchブロックに制御フローを迂回させることは、スパゲッティの良いレシピであり、避けるべきです。

例外を使用する場合は、代わりにvoidを返してみてください。例外がスローされない場合、ユーザーは成功したと判断し、if-else句を使用して制御フローを迂回しようとするユーザーはいません。


1
列挙型は、APIユーザーがエラーメッセージを解析せずにさまざまな種類のエラーを処理できるようにするためのアイデアにすぎません。したがって、私の場合、あなたの観点から列挙型を返し、「OK」の列挙型を追加する方が良いでしょう。
-Accollat​​ivo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.