web-dev-qa-db-ja.com

関数が成功した場合はtrue / falseを返し、失敗した場合はvoidを返し、失敗した場合は例外をスローする

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

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

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

22
Accollativo

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

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

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

35
Kilian Foth

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

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

この場合、callerにはとにかく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を保持してください。


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

  1. 真/偽を返す
  2. voidを使用して、(チェックされた)例外をスローする
  3. 中間resultオブジェクトを返します。

Return 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で例外を確認したので、例外を使用する理由はありませんnot。基本的な仮定が(ソケットの使用のように)呼び出しは成功するはずであると仮定されているが、失敗する可能性があり、3番目のオプションを使用する場合、チェックされた予測を使用します呼び出しが成功するかどうかが非常に不明確な場合(データの検証など)。しかし、これについては異なる意見があります。

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

31
Polygnome

これは本当に失敗がexceptionalであるかexpectedであるかに関係します。

エラーが通常予想されるものではない場合、APIユーザーが特別なエラー処理を行わずにメソッドを呼び出すだけである可能性がかなり高いと考えられます。例外をスローすることで、スタックが気付かれる場所にスタックすることができます。

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

常に、それを使用している人の心の中でAPIを設計してください。

falseを返すつもりがない場合は、ブール値を返さないでください。メソッドvoidを作成し、失敗すると例外がスローされることを文書化します(IOExceptionが適しています)。

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

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

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

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

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

また、プロシージャの最後のステップではないときにメソッドが例外をスローするのも面倒です。 try-catchを書き、catchブロックに制御フローを流用する必要があるのは、スパゲッティの良いレシピであり、避ける必要があります。

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

3
ccControl