簡単なサービスを記述するインターフェースがあるとしましょう
public interface AccountService
{
public int getUserId(String userName) throws UserNotFoundException;
//...
}
私はUserNotFoundException
例外を自分で書きましたが、これはこのサービスに固有のものです。それとは別に、問題が発生する可能性のあるその他のことは実装固有です。
私が持っている場合
public class DatabaseAccountService
{
@Override
public int getUserId(String userName) throws UserNotFoundException
{
//some stuff that might throw an SQLException
}
}
接続が壊れている、テーブルが存在しない、などの可能性があります。代わりにFileAccountService
を使用すると、ファイルに権限の問題が発生したり、ファイルが存在したり、フォーマットが無効になったりする可能性があります。実装固有の多くの問題両方のケースで発生する可能性があります。
ことは、私が自分のインターフェースに可能なすべての実装のすべての可能な問題を説明することはできないということです。 「うまくいかなかった、実装固有のもの」を説明する必要があります。そのような例外をRuntimeException
にラップするか、OperationFailedException
のような別の例外を署名に追加する必要がありますか?
実装固有の例外をUserNotFoundException
にラップすることもできません。それは嘘であり、ユーザーが存在しない可能性がありますが、ファイルが単に読み取れない可能性があります。
RuntimeException
sで私が目にする問題は、ユーザーがキャッチしない可能性があることです。おそらく、何かもっと良いことがあったときにアプリケーションがクラッシュする可能性があります。
OperationFailedException
で発生する問題は、非常に汎用的であり、メソッドシグネチャに情報を追加しないことです。
public interface AccountService
{
public int getUserId(String userName) throws UserNotFoundException,
OperationFailedException; //dumb, any method can fail
}
また、OperationFailedException
を追加するのが最善の方法である場合、そのような一般的な例外のラッピングタイプが標準ライブラリに存在すると思います。ただし、私が知る限り、このような一般的な例外は存在しないため、例外をできるだけ具体的に記述してプログラマーを支援する必要があります。
他のオプションはありますか? Java標準ライブラリからのものと同じくらい、私のコードからだれもが除外するようにしたい場合、どれが最適ですか?
このような例外をRuntimeExceptionにラップするか、OperationFailedExceptionなどの別の例外をシグネチャに追加する必要がありますか?
後者のようなものをお勧めします。 AccountService
のより一般的な例外を作成します。 AccountServiceException
またはServiceException
のような名前を付け、AccountService
の各メソッドにチェック例外として宣言させます。これは、OperationFailedException
を使用するという考え方に似ていますが、アプリケーションに固有であり、AccountService
に合わせて調整できます。
interface AccountService
{
int getUserId(String userName) throws UserNotFoundException, AccountServiceException;
Bar foo() throws AccountServiceException;
//...
}
各レイヤーは、下のレイヤーからの例外をキャッチして処理またはラップする必要があります。これにより、実装固有の例外から切り離され、チェック済み例外を使用することで、例外ケースが処理され、見逃されないことが保証されます。
OperationFailedException
を追加するのが最善の方法である場合、そのような一般的な例外のラッピングタイプは標準ライブラリに存在すると思います。
OperationFailedException
は本質的に単なるJava.lang.Exception
。 Java.lang.Exception
は、問題に関するコンテキストを提供せずに失敗した操作を説明するために使用されるクラスです。 AccountServiceException
はより説明的であり、アプリケーションの他のサービスからの例外をキャッチせずにキャッチできます。すべてのクラスからException
またはOperationFailedException
を経由する場合、例外をキャッチすることは悪夢になります。
これは、JREの他の場所で使用されているのと同じパターンです。 SQLException
とJAXBException
は、それらが定義されているドメインに固有の例外の2つの例ですが、そのドメインの多くのメソッドによってスローされます。
私の意見では、API設計は潜在的なユーザーによって推進されるべきです。そこで、ユーザー(呼び出し元)の視点を取り上げます。これは議論につながる可能性があることをよく知っています...
throws
句に広範な例外リストを記述したくありません。それは怠惰ではありません-ほとんどの場合彼とは無関係な20の異なる例外タイプを発信者に処理させる必要はありません。
メソッドシグネチャによって強制されるため、例外をキャッチ、ラップ、および再スローしたくありません。これは私にとって醜いだけでなく、多くの例外レイヤーを元のレイヤーの周りにラップし、ログを読みにくくします。
メソッド呼び出しの一部が失敗した場合、通常、契約を履行する代替方法がないため、自分の仕事を実行できなかったことを呼び出し元に通知する必要があります。ここでは、発信者に波及した例外を単に許可することを好みます。私にとって、これは、Cのような言語の旧式の失敗の結果のチェック結果スタイルに対する例外の最大の利点です。
メソッド呼び出しの一部が失敗し、そのステップに別の方法がある場合は、最初の方法が失敗した理由に関係なく、それを試します。
したがって、通常、取得した例外の種類は気にしません。ロギングには重要ですが、発信者としての私には重要ではありません。
ロギングの目的で、取得した例外に情報を追加することが理にかなっている場合があります(例:例外の原因となった呼び出し引数)。次に、メソッド全体をtry-catchで囲み、toString()に表示されるコンテキスト情報と原因としての元の例外を含むラッパー例外をスローします。
あなたの質問にはどういう意味がありますか?
1つのメソッド呼び出しから複数の無関係な例外を処理しなければならないのは、私にとってはやりがいのないことです。したがって、UserNotFoundException
とAccountServiceException
には、少なくとも共通の基本クラスが必要です。
アプリケーションのすべてのサービスに独自の無関係な例外クラスツリーがある場合でも、私はメソッドで多くの異なる例外タイプを処理する必要があります(確かに、私のメソッドはAccountServiceを呼び出すだけではないため)、それでも問題は同じです。
すべてのアプリケーション固有の型の派生元となる共通のルート例外クラスApplicationExceptionを使用したいと思います(すべてのアプリケーションを制御できるかどうかはわかりません)。これにより、短いthrows
リストを使用して、例外を発信者に波及させ、ソフトウェアレイヤーでthrows ApplicationException
高くなる傾向があります。そして、この基本クラスは、コンテキスト情報などを含むユーザー通知テキストなどをサポートできます。
アプリケーションに共通の例外基本クラスを導入できない場合は、RuntimeExceptions(または特定のサブクラス)を取得したいと思います。これは、95%のユースケースをサポートする(例外に波及させる)ためです。 「それらがユーザーに捕らえられず、アプリケーションのクラッシュを引き起こす可能性がある」ことを恐れないでください。アプリケーションの最上位のアクティビティがRuntimeException(NullPointerExceptionなど)を取得するときにクラッシュし、適切なアクションを実行しない場合、これはプログラマーの不良です。
したがって、アプリケーション全体で使用できるApplicationException基本クラス(推奨)またはRuntimeExceptionサブクラスのいずれかを使用します。