web-dev-qa-db-ja.com

例外を返すのはアンチパターンですか?

私は2つの簡単な方法があります:

public void proceedWhenError() {
   Throwable exception = serviceUp();

   if (exception == null) {
      // do stuff
   } else {
      logger.debug("Exception happened, but it's alright.", exception)
      // do stuff
   }
}

public void doNotProceedWhenError() {
   Throwable exception = serviceUp();

   if (exception == null) {
      // do stuff
   } else {
      // do stuff
      throw new IllegalStateException("Oh, we cannot proceed. The service is not up.", exception)
   }
}

3番目のメソッドはプライベートヘルパーメソッドです。

private Throwable serviceUp() {
    try {
        service.connect();
        return null;
    catch(Exception e) {
       return e;
    }
}

ここで使用されているパターンについて、私の同僚と少し話をしました。

serviceUp()メソッドからException(またはThrowable)オブジェクトを返す

最初の意見:

例外を使用してワークフローを制御することはアンチパターンであり、serviceUp()からブール値のみを返し、例外オブジェクト自体を返すことはできません。引数は、例外を使用してワークフローを制御することはアンチパターンであるということです。

セカンドオピニオン:

後で2つの最初のメソッドでオブジェクトを処理する必要があり、Exceptionオブジェクトまたはブール値を返してもワークフローがまったく変わらないので、問題ありません。

1)または2)が正しいと思いますか特に、なぜですか?注:質問は、メソッドserviceUp()とその戻り値のタイプ-boolean vs Exceptionオブジェクト。

注:ThrowableまたはExceptionオブジェクトを使用するかどうかは疑問ではありません。

19
Martin Linha

例外が非例外的な状況でスローされた場合にのみ例外を使用してフローを指示することはアンチパターンです*。たとえば、コレクションの最後に到達したときに例外をスローしてループを終了することは、アンチパターンです。

一方、実際の例外でフローを制御することは、例外の適切なアプリケーションです。メソッドで処理できない例外的な状況が発生した場合、例外をスローする必要があります。これにより、呼び出し元のフローを例外ハンドラーブロックにリダイレクトします。

メソッドから「裸の」Exceptionオブジェクトを返すのではなく返すことは、確かに直感に反します。操作の結果を呼び出し元に伝える必要がある場合は、例外を含むすべての関連情報をラップするステータスオブジェクトを使用することをお勧めします。

_public class CallStatus {
    private final Exception serviceException;
    private final boolean isSuccess;
    public static final CallStatus SUCCESS = new CallStatus(null, true);
    private CallStatus(Exception e, boolean s) {
        serviceException = e;
        isSuccess = s;
    }
    public boolean isSuccess() { return isSuccess; }
    public Exception getServiceException() { return serviceException; }
    public static CallStatus error(Exception e) {
        return new CallStatus(e, false);
    }
}
_

これで、呼び出し元はCallStatusからserviceUpを受け取ります。

_CallStatus status = serviceUp();
if (status.isSuccess()) {
    ... // Do something
} else {
    ... // Do something else
    Exception ex = status.getException();
}
_

コンストラクタはプライベートなので、serviceUpは_CallStatus.SUCCESS_を返すか、CallStatus.error(myException)を呼び出します。

* 何が例外的で何が例外的でないかは、コンテキストに大きく依存します。たとえば、非数値データはScannernextIntで例外を引き起こします。これは、そのようなデータが無効であると見なされるためです。ただし、完全に有効であるため、同じ正確なデータはhasNextIntメソッドで例外を引き起こしません。

30
dasblinkenlight

セカンドオピニオン(「大丈夫」)は成立しません。例外をスローするのではなく返すことは実際には慣用的ではないため、コードは大丈夫ではありません。

また、私は最初の意見を買いません(「例外を使用してワークフローを制御することはアンチパターンです」)。 service.connect()は例外をスローしているため、この例外に対応する必要があります。したがって、このisは実質的にフロー制御です。 booleanまたはその他の状態オブジェクトを返し、例外を処理する代わりにこれを処理します。例外に基づいてフローを制御しないと考えるのは単純です。もう1つの欠点は、例外(IllegalArgumentExceptionなどでラップ)を再スローすることを決定した場合、元の例外が発生しなくなることです。実際に何が起こったのかを分析しようとすると、これは非常に悪いことです。

だから私は古典をやる:

  • serviceUpに例外をスローします。
  • serviceUp:を呼び出すメソッドで
    • try-catch、デバッグを記録し、例外を続行する場合は例外を飲み込みます。
    • try-catchそして、別の例外にラップされた例外を再スローして、何が起こったかについての詳細を提供します。あるいは、実質的なものを追加できない場合は、元の例外をthrowsを介して伝播させます。

元の例外を失わないことが最も重要です。

17
lexicore

両方とも間違っています

後で2つの最初のメソッドでオブジェクトを処理する必要があり、Exceptionオブジェクトまたはブール値を返してもワークフローがまったく変わらないので、問題ありません。

大丈夫ではありません。例外の概念は、例外がスローされることを意味します。それらは、処理される場所で(または、少なくともローカルクリーンアップ/ログ記録などの後、再スローされる場所で)キャッチされることを意図しています。これらは、このように(つまり、「ドメイン」コードで)渡されることを意図していません。

人々は混乱するでしょう。本当のバグは簡単に忍び寄ることができます-たとえば、ここでネットワーキング以外のソースからのExceptionがある場合はどうでしょうか。あなたが予期していなかったもの、それは本当に悪いです(プログラミングエラーによって作成した境界外の例外のように)?

そして、新鮮なプログラマは無限に混乱し、そして/または、そのアンチパターンを、それが属していない場所にコピーします。

たとえば、同僚は最近、非常に複雑なインターフェイスを実装しました(Java interface)ではなく、マシンツーマシンインターフェイスのように)、彼は同様の例外処理を行いました。黙って無視されたバリエーション(いくつかのログメッセージをモジュロ)言うまでもなく、any彼が実際に考えられる最悪の方法で混乱全体を壊すことを期待していなかったという例外; fail fast

例外を使用してワークフローを制御することはアンチパターンであり、serviceUp()からブール値を返すだけで、Exceptionオブジェクト自体は返さないでください。引数は、例外を使用してワークフローを制御することはアンチパターンであるということです。

例外は、ワークフローを中止するか、ユーザーに表示されるエラーメッセージにリダイレクトするという点で、ワークフローを確実に制御します。例外のためにコードの一部を「無効」にすることは絶対に可能です。つまり、例外処理は、トップレベルの制御以外のどこかで確実に許可されます。

しかし、例外を返すことは確かにアンチパターンです。誰もそれを期待していません、それは奇妙で、偽のエラー(戻り値を無視するのは簡単です)などにつながります。

したがって、あなたのserviceUp()の場合、それをvoidにするか、99%の確率で動作するか、例外をスローします。または、どこかで失敗することを完全に受け入れるという点で、booleanにします。エラーメッセージを処理する必要がある場合は、Stringとして実行するか、邪魔にならない場所に保存するか、戻り値として使用しないでください。特にthrow後でもう一度。

簡単な標準ソリューション

このソリューションはより短く(行が少なく、変数が少なく、ifが少ない)、よりシンプルで、沼地の標準であり、まさに望みどおりに機能します。保守しやすく、理解しやすい。

public void proceedWhenError() {
   try {
      serviceUp();
      // do stuff (only when no exception)
   }
   catch (Exception exception) {
      logger.debug("Exception happened, but it's alright.", exception)
      // do stuff (only when exception)
   }
}

public void doNotProceedWhenError() {
   try {
      serviceUp();
      // do stuff (only when no exception)
   }
   catch (Exception exception) {
      // do stuff (only when exception)
      throw new IllegalStateException("Oh, we cannot proceed. The service is not up.", exception)
   }
}

private void serviceUp() {
    service.connect();
}
7
AnoE

たとえば、ServiceStateRUNNINGWAITINGなどのCLOSEDを返します。メソッドの名前はgetServiceStateになります。

enum ServiceState { RUNNING, WAITING, CLOSED; }

実行の結果として例外を返すメソッドを見たことはありません。私にとって、メソッドが値を返すとき、それはメソッドがその作業を終了したことを意味します問題なし。結果を取得して、エラーを含むことを解析したくありません。結果自体は、失敗は発生しなかった-すべてが計画どおりに進んだことを意味します。

一方、メソッドが例外をスローする場合、何が間違っていたかを把握するためにspecialオブジェクトを解析する必要があります。 結果がないなので、結果を解析しません。

例:

public void proceedWhenError() {
   final ServiceState state = getServiceState();

   if (state != ServiceState.RUNNING) {
      logger.debug("The service is not running, but it's alright.");
   }
   // do stuff
}

public void doNotProceedWhenError() {
   final ServiceState state = getServiceState();

   if (state != ServiceState.RUNNING) {
      throw new IllegalStateException("The service is not running...");
   }
   // do stuff
}

private ServiceState getServiceState() {
    try {
        service.connect();
        return ServiceState.RUNNING;
    catch(Exception e) {
        // determine the state by parsing the exception
        // and return it
        return getStateFromException(e);
    }
}

サービスによってスローされた例外が重要である、および/または別の場所で処理される場合、状態とともにServiceResponseオブジェクトに保存できます。

class ServiceResponse {

    private final ServiceState state;
    private final Exception exception;

    public ServiceResponse(ServiceState state, Exception exception) {
        this.state = state;
        this.exception = exception;
    }

    public static ServiceResponse of(ServiceState state) {
        return new ServiceResponse(state, null);
    }

    public static ServiceResponse of(Exception exception) {
        return new ServiceResponse(null, exception);
    }

    public ServiceState getState() {
        return state;
    }

    public Exception getException() {
        return exception;
    }

}

現在、ServiceResponseを使用すると、これらのメソッドは次のようになります。

public void proceedWhenError() {
   final ServiceResponse response = getServiceResponse();

   final ServiceState state = response.getState();
   final Exception exception = response.getException();

   if (state != ServiceState.RUNNING) {
      logger.debug("The service is not running, but it's alright.", exception);
   }
   // do stuff
}

public void doNotProceedWhenError() {
   final ServiceResponse response = getServiceResponse();

   final ServiceState state = response.getState();
   final Exception exception = response.getException();

   if (state != ServiceState.RUNNING) {
      throw new IllegalStateException("The service is not running...", exception);
   }
   // do stuff
}

private ServiceResponse getServiceResponse() {
    try {
        service.connect();
        return ServiceResponse.of(ServiceState.RUNNING);
    catch(Exception e) {
        // or -> return ServiceResponse.of(e);
        return new ServiceResponse(getStateFromException(e), e);
    }
}
3
Andrew Tobilko

明らかな認知的不協和は、​​ここでのアンチパターンです。読者は、フロー制御に例外を使用していることを確認し、開発者はすぐに再コーディングを試み、そうしないようにします。

私の本能は、次のようなアプローチを示唆しています。

// Use an action name instead of a question here because it IS an action.
private void bringServiceUp() throws Exception {

}

// Encapsulate both a success and a failure into the result.
class Result {
    final boolean success;
    final Exception failure;

    private Result(boolean success, Exception failure) {
        this.success = success;
        this.failure = failure;
    }

    Result(boolean success) {
        this(success, null);
    }

    Result(Exception exception) {
        this(false, exception);
    }

    public boolean wasSuccessful() {
        return success;
    }

    public Exception getFailure() {
        return failure;
    }
}

// No more cognitive dissonance.
private Result tryToBringServiceUp() {
    try {
        bringServiceUp();
    } catch (Exception e) {
        return new Result(e);
    }
    return new Result(true);
}

// Now these two are fine.
public void proceedWhenError() {
    Result result = tryToBringServiceUp();
    if (result.wasSuccessful()) {
        // do stuff
    } else {
        logger.debug("Exception happened, but it's alright.", result.getFailure());
        // do stuff
    }
}

public void doNotProceedWhenError() throws IllegalStateException {
    Result result = tryToBringServiceUp();
    if (result.wasSuccessful()) {
        // do stuff
    } else {
        // do stuff
        throw new IllegalStateException("Oh, we cannot proceed. The service is not up.", result.getFailure());
    }
}
1
OldCurmudgeon

メソッドの呼び出し元が、操作が成功または失敗する可能性があることを期待しており、いずれかのケースを処理する準備ができている場合、メソッドは通常、戻り値を介して操作が失敗したかどうかを示す必要があります。呼び出し元が障害を有効に処理する準備ができていない場合、障害が発生したメソッドは、呼び出し元にそうするためのコードを追加するのではなく、例外をスローする必要があります。

1つのしわは、一部のメソッドには、障害を適切に処理する準備ができている呼び出し側とそうでない呼び出し側がいるということです。私の好ましいアプローチは、そのようなメソッドが、失敗した場合に呼び出されるオプションのコールバックを受け入れることです。コールバックが提供されない場合、デフォルトの動作は例外をスローすることです。そのようなアプローチは、呼び出し元が障害を処理する準備ができている場合に例外を構築するコストを節約し、そうではない呼び出し元の負担を最小限に抑えます。このような設計の最大の難しさは、後でそのようなパラメーターを変更するのが難しい傾向があるため、そのようなコールバックがどのパラメーターを取るかを決定することです。

1
supercat

例外を返すことは、パターンのアンチパターンです。サービスの状態を説明するためではなく、実行中のエラーのために例外を予約する必要があります。

serviceUp()のコードにバグがあり、それによってNullPointerExceptionがスローされる場合を想像してください。バグがサービス内にあり、同じNullPointerExceptionconnect()からスローされるとします。

私のポイントを参照してください?

もう1つの理由は、要件の変更です。

現在、サービスには2つの条件があります。アップまたはダウンです。
現在

Tommorow、サービスには次の3つの条件があります。アップ、ダウン。または警告とともに機能します。翌日、jsonでサービスに関する詳細を返すメソッドも必要になります.....

1

前述の「例外はイベントです」で述べたように、取得される例外オブジェクトはイベントの詳細にすぎません。例外が「キャッチ」ブロックでキャッチされ、再スローされない場合、イベントは終了します。オブジェクトは例外/スロー可能なクラスですが、例外ではなく詳細オブジェクトのみを持っていることを投稿してください。

例外オブジェクトを返すことは、詳細が保持されているため便利かもしれませんが、呼び出しメソッドは「例外を処理し、例外に基づいてフローを制御する」ため、あいまいさ/混乱を追加します。返されたオブジェクトの詳細を使用して判断するだけです。

私の意見では、より論理的で混乱の少ない方法は、単に例外オブジェクトを返すのではなく、例外に基づいてブール/列挙を返すことです。

0
dev. K

これはアンチパターンです:

catch(Exception e) {
   return e;
}

Throwableを返すための唯一の合理的な言い訳は、価格を支払うことなく、障害が予想される高速パスに詳細な障害情報を提供することです1 例外処理の。ボーナスとして、呼び出し側がこの特定の状況を処理する方法を知らない場合、スローされた例外に簡単に変換できます。

しかし、あなたのシナリオでは、その費用はすでに支払われています。発信者に代わって例外をキャッチすることは、誰にも有利ではありません。


1例外的に、例外をキャッチする直接的なコスト(一致するハンドラーを見つける、スタックの巻き戻し)は非常に低いか、とにかく行う必要があります。戻り値と比較したtry/catchのコストのほとんどは、スタックトレースの構築などの付随的なアクションにあります。したがって、スローされない例外オブジェクトが効率的なエラーデータの適切なストレージになるかどうかは、この偶発的な作業が構築時またはスロー時に行われるかどうかに依存します。したがって、例外オブジェクトを返すことが妥当かどうかは、管理対象プラットフォームによって異なる場合があります。

0
Ben Voigt

2)意見が間違っているとは言えません。ワークフローは実行したいとおりに実行されます。論理的な観点からは、必要に応じて実行されるので正しいです。

しかし、それは本当に奇妙であり、推奨されない方法です。第一に、例外がそれを行うように設計されていないためです(つまり、アンチパターンを実行していることを意味します)。これは、スローしてキャッチするように設計された特定のオブジェクトです。したがって、設計どおりに使用し、それを返すように選択し、ifを使用してキャッチするのは奇妙です。さらに、単純なブール値をフラグとして使用するのではなく、パフォーマンスの問題の原因に直面する可能性がありますが、オブジェクト全体をインスタンス化します。

最後に、関数の目的が何かを取得することである場合、関数が何かを返すことが推奨されていません(これはあなたのケースではありません)。サービスを開始する関数になるように再設計する必要がありますが、何かを取得するために呼び出されないため、何も返さず、エラーが発生した場合は例外をスローします。また、サービスがpublic boolean isServiceStarted()のようなこの情報を提供するように設計された関数を作成するかどうかを知りたい場合。

0
vincrichaud