アプリケーションが予期しないすべての条件に対して作成された例外があります。 UserNameNotValidException
、PasswordNotCorrectException
など.
ただし、これらの条件に対して例外を作成しないでくださいと言われました。私のUMLでは、これらはメインフローの例外ですが、なぜ例外ではないのですか?
例外を作成するためのガイダンスやベストプラクティスはありますか?
私の個人的なガイドラインは次のとおりです。現在のコードブロックの基本的な仮定が偽であることが判明すると、例外がスローされます。
例1:任意のクラスを検査し、そのクラスがList <>を継承する場合にtrueを返すことになっている関数があるとします。この関数は、「このオブジェクトはリストの子孫ですか?」という質問をします。この関数は、操作に灰色の領域がないため、例外をスローすることはありません。すべてのクラスがList <>を継承するか、継承しないため、答えは常に「yes」または「no」です。
例2:リスト<>を調べ、長さが50を超える場合はtrueを返し、長さが短い場合はfalseを返す別の関数があるとします。この関数は、「このリストには50を超えるアイテムがありますか?」という質問をします。しかし、この質問は仮定を与えます-それは与えられたオブジェクトがリストであると仮定します。 NULLを渡すと、その仮定は誤りです。その場合、関数が返す場合 どちらか 本当 または falseの場合、独自の規則に違反しています。関数は戻ることができません 何でも 質問に正しく答えたと主張します。そのため、戻りません-例外をスローします。
これは、 "loaded question" 論理的誤fallに匹敵します。すべての関数は質問をします。それが与えられた入力がその質問を誤acyにしている場合、例外をスローします。この行は、voidを返す関数を使用して描画するのは難しくなりますが、一番下の行は、入力に関する関数の仮定に違反した場合、通常の戻り値ではなく例外をスローする必要があるということです。
この方程式のもう一方の側面は、例外を頻繁にスローする関数を見つけた場合、おそらくそれらの仮定を改良する必要があるということです。
それは通常起こることだからです。例外は制御フローメカニズムではありません。ユーザーはパスワードを間違えることがよくありますが、例外的なケースではありません。例外は、UserHasDiedAtKeyboard
型の状況では、本当にまれなことです。
私の小さなガイドラインは、すばらしい本「Code complete」の影響を強く受けています。
ユーザー名が無効であるか、パスワードが正しくない場合も例外ではありません。これらは、通常の操作の流れで予想されるものです。例外は、通常のプログラム操作の一部ではなく、まれなものです。
編集:例外を使用するのは好きではありません。なぜなら、呼び出しを見ただけではメソッドが例外をスローするかどうかを判断できないからです。そのため、例外を適切な方法で処理できない場合にのみ例外を使用する必要があります(「メモリ不足」または「コンピューターが起動している」と考えてください)。
経験則の1つは、通常は予測できない何かの場合に例外を使用することです。例としては、データベース接続、ディスク上のファイルの欠落などがあります。予測可能なシナリオ、つまりユーザーが不正なパスワードでログインしようとする場合、ブール値を返す関数を使用し、状況を適切に処理する方法を知ってください。誰かがパスワードを誤って入力したからといって、例外をスローして実行を突然終了させたくないのです。
他のユーザーは、ユーザーがタイプミスした場合、通常のフローでは不正なログインが予想されるため、例外を使用しないことを提案します。私は同意せず、理由付けが得られません。ファイルを開くことと比較してください。ファイルが存在しないか、何らかの理由で利用できない場合は、フレームワークによって例外がスローされます。上記のロジックを使用することは、Microsoftによる間違いでした。エラーコードが返されているはずです。解析、ウェブリクエストなどにも同じです。
通常のフローの悪いログイン部分は考慮していませんが、例外的です。通常、ユーザーは正しいパスワードを入力しますが、ファイルは存在します。例外的なケースは例外的であり、それらに例外を使用してもまったく問題ありません。スタックのnレベルを介して戻り値を伝播することでコードを複雑にすることはエネルギーの浪費であり、コードが乱雑になります。おそらく動作する可能性のある最も簡単なことを行います。エラーコードを使用して時期尚早に最適化しないでください。定義による例外的なことはめったに発生せず、例外をスローしない限り、コストはかかりません。
たとえば、無効なパスワードを提供するユーザーがいる場合、例外はややコストのかかる影響です。通常、失敗フラグまたは無効であることを示す他のインジケータを返すことをお勧めします。
これは、例外の処理方法、真の不正な入力、および一意のクリティカルストップアイテムが例外であり、失敗したログイン情報ではないためです。
現在の状態から抜け出すためにできることは何もない場合にのみ、例外をスローすべきだと思います。たとえば、メモリを割り当てていて、割り当てるものがない場合。あなたが言及した場合、それらの状態から明確に回復でき、それに応じて呼び出し元にエラーコードを返すことができます。
この質問への回答など、「例外的な」状況でのみ例外をスローするように、多くのアドバイスが表示されます。それは表面的には理にかなっているように見えますが、1つの質問(「例外をいつスローすべきか」)を別の主観的な質問(「例外的なもの」)に置き換えるため、欠陥のあるアドバイスです。代わりに、Herb Sutterのアドバイスに従ってください(C++の場合、 Dr Dobbsの記事いつ、どのように例外を使用するか 、およびAndrei Alexandrescuの本で利用可能です- C++ Coding Standards):次の場合にのみ例外をスローします
なぜこれが良いのですか?質問をseveral前提条件、事後条件、および不変式に関する質問に置き換えませんか?これは、いくつかの関連する理由により優れています。
throw
の決定は実装の詳細です。設計とその実装を別々に考慮する必要があり、メソッドを実装する際の私たちの仕事は、設計の制約を満たすものを作成することであることに留意する必要があります。catch
句に移動されます。例外をいつ使用するかについての厳格なルールはありません。ただし、それらを使用するか使用しないのには十分な理由があります。
例外を使用する理由:
例外を使用しない理由:
一般に、宣言されているかどうかにかかわらず、例外は基本的に関数の正式なインターフェイスの一部であると考えているため、例外保証を変更すると、C++やC#よりもJavaで例外を使用する傾向が強くなります。呼び出しコードを中断します。 Java IMOでそれらを使用する最大の利点は、呼び出し元が例外を処理する必要があることを知っていることです。これにより、正しい動作の可能性が向上します。
このため、どの言語でも、コードまたはAPIのレイヤー内のすべての例外を共通クラスから常に派生させるため、呼び出しコードは常にすべての例外をキャッチすることを保証できます。また、APIまたはライブラリを記述するときに実装固有の例外クラスをスローすることは悪いと考えます(つまり、呼び出し元が受け取る例外がインターフェイスのコンテキストで理解できるように、下位層から例外をラップします)。
Javaは一般的な例外とランタイム例外を区別することに注意してください。後者は宣言する必要がないからです。エラーがプログラムのバグの結果であることがわかっている場合にのみ、ランタイム例外クラスを使用します。
例外クラスは「通常の」クラスに似ています。新しいクラスは、異なるフィールドと異なる操作で、異なるタイプのオブジェクトである場合に作成します。
経験則として、例外の数と例外の粒度のバランスをとる必要があります。メソッドが4〜5を超える異なる例外をスローする場合、おそらくそれらの一部をより「一般的な」例外にマージし(例:「AuthenticationFailedException」)、例外メッセージを使用して問題の詳細を説明できます。コードがそれぞれ異なる方法で処理しない限り、多くの例外クラスを作成する必要はありません。そして、もしそうなら、発生したエラーを列挙型で返すだけでいいのでしょうか。この方法は少しきれいです。
ループ内で実行されるコードが繰り返し例外を引き起こす可能性がある場合、例外をスローするのは良いことではありません。大きなNの場合はかなり遅いからです。しかし、パフォーマンスがそうでない場合、カスタム例外をスローしても問題はありません。問題。 BaseExceptionなどと呼ばれる、すべてが継承する基本例外があることを確認してください。 BaseExceptionはSystem.Exceptionを継承しますが、すべての例外はBaseExceptionを継承します。例外タイプのツリーを使用して同様のタイプをグループ化することもできますが、これはやり過ぎかもしれません。
したがって、簡単な答えは、重大なパフォーマンスの低下を引き起こさない場合(多くの例外をスローする場合を除き)、先に進むことです。
私はそこへのジャポロックの方法に同意します-手術の結果について不確かな場合に受け入れを投げます。 APIの呼び出し、ファイルシステムへのアクセス、データベース呼び出しなど。プログラミング言語の「境界」を越えて移動するときはいつでも。
追加したいので、気軽に標準の例外を投げてください。 「異なる」こと(無視、電子メール、ログ、Twitterのクジラの写真などを見せることなど)を行う場合を除き、カスタムの例外を気にしないでください。
例外をスローするための経験則は非常に簡単です。コードが回復不能な無効状態になったときにそうします。データが危険にさらされたり、発生した処理をその時点まで巻き戻せない場合は、終了する必要があります。実際、他に何ができますか?処理ロジックは最終的に他の場所で失敗します。何らかの方法で回復できる場合は、それを行い、例外をスローしません。
あなたの特定のケースでは、お金の引き出しを受け入れるような愚かなことをせざるを得なかった場合、ユーザー/パスワードを確認するだけで、例外をスローしてプロセスを終了する必要があります。
一般的に、すべての原理主義は地獄につながると思います。
例外駆動型のフローになりたくないのは確かですが、例外を完全に回避することも悪い考えです。両方のアプローチのバランスを見つける必要があります。私がしたくないことは、例外的な状況ごとに例外タイプを作成することです。それは生産的ではありません。
私が一般的に好むのは、システム全体で使用される2つの基本タイプの例外を作成することです:LogicalExceptionおよびTechnicalException。これらは必要に応じてサブタイプでさらに区別できますが、通常は必要ありません。
技術的な例外は、データベースサーバーがダウンしている、Webサービスへの接続がIOExceptionをスローしているなど、本当に予期しない例外を示します。
一方、論理的例外は、それほど深刻ではないエラー状況を上位層に伝播するために使用されます(一般に、検証結果)。
論理的な例外でさえ、プログラムのフローを制御するために定期的に使用することを意図したものではなく、フローが実際に終了する状況を強調することに注意してください。 Javaで使用される場合、両方の例外タイプはRuntimeExceptionサブクラスであり、エラー処理は高度なアスペクト指向です。
そのため、ログインの例では、AuthenticationExceptionのようなものを作成し、具体的な状況をsernameNotExisting、PasswordMismatchなどの列挙値で区別するのが賢明かもしれません。巨大な例外階層を持ち、キャッチブロックを維持可能なレベルに保つことができます。また、例外を分類し、ユーザーに何をどのように伝達するかをよく知っているため、一般的な例外処理メカニズムを簡単に使用することもできます。
通常の使用方法は、ユーザーの入力が無効であるWebサービスの呼び出し中にLogicalExceptionをスローすることです。例外はSOAPFaultの詳細にマーシャリングされ、クライアントで再び例外に非マーシャリングされます。その結果、特定のWebページの入力フィールドで検証エラーが表示されます。
これは確かに唯一の状況ではありません。例外をスローするためにWebサービスにアクセスする必要はありません。例外的な状況(フェイルファーストが必要な場合など)で自由に行うことができます。すべてはあなたの裁量です。
例外は、異常な動作、エラー、障害などのイベントを対象としています。機能的な動作、ユーザーエラーなどは、代わりにプログラムロジックで処理する必要があります。不正なアカウントまたはパスワードは、ログインルーチンのロジックフローの予想される部分であるため、例外なくこれらの状況を処理できる必要があります。
一般に、「例外」であるアプリケーションで発生する可能性があるすべての例外をスローする必要があります。
あなたの例では、これらの例外は両方とも、パスワード/ユーザー名の検証によって呼び出しているように見えます。その場合、誰かがユーザー名/パスワードを間違って入力することは本当に例外的ではないと主張することができます。
それらは、UMLのメインフローに対する「例外」ですが、処理における「分岐」です。
Passwdファイルまたはデータベースにアクセスしようとしてアクセスできなかった場合、例外的なケースになり、例外をスローする必要があります。
まず、APIのユーザーが特定のきめ細かい障害に関心がない場合、それらの特定の例外を持つことは価値がありません。
多くの場合、ユーザーにとって何が役立つかを知ることができないため、より良いアプローチは特定の例外を持ち、共通のクラス(たとえば、std :: exceptionまたはC++の派生物)から継承するようにすることです。これにより、クライアントは、選択した場合は特定の例外をキャッチし、気にしない場合はより一般的な例外をキャッチできます。
例外の使用に哲学的な問題があります。基本的には、特定のシナリオが発生することを期待していますが、明示的に処理するのではなく、問題を「他の場所」で処理するようにプッシュしています。そして、その「他の場所」がどこにあるかは誰でも推測できます。
キャッチする条件は3種類あります。
入力の不良または欠落は例外ではありません。クライアント側のjsとサーバー側の正規表現の両方を使用して、属性を検出、設定し、メッセージを含む同じページに転送します。
AppException。これは通常、コードで検出してスローする例外です。言い換えれば、これらはあなたが期待するものです(ファイルは存在しません)。ログに記録し、メッセージを設定して、一般的なエラーページに戻ります。このページには通常、何が起こったのかに関する少しの情報があります。
予期しない例外。これらはあなたが知らないものです。詳細を記録して、一般的なエラーページに転送します。
お役に立てれば
私にとっては、必要な技術的またはビジネスルールが失敗した場合に例外がスローされます。たとえば、車のエンティティが4つのタイヤの配列に関連付けられている場合... 1つ以上のタイヤがnullの場合...「NotEnoughTiresException」が発生する必要があります。システムのさまざまなレベルでキャッチされ、重大なロギングによる意味。それに、もし我々がヌルをフロー制御し、車のインスタンス化を防止しようとするなら。問題の原因を決して見つけられないかもしれません。
単純な答えは、操作が不可能な場合はいつでも(ビジネスロジックに違反するため、いずれかのアプリケーションORのため)です。メソッドが呼び出され、そのメソッドが実行するように書かれていることを実行できない場合、例外をスローします。良い例は、提供されたパラメーターを使用してインスタンスを作成できない場合、コンストラクターが常にArgumentExceptionsをスローすることです。別の例はInvalidOperationExceptionです。これは、クラスの別のメンバーの状態が原因で操作を実行できない場合にスローされます。
あなたの場合、Login(username、password)のようなメソッドが呼び出され、ユーザー名が有効でない場合、UserNameNotValidException、またはパスワードが正しくない場合はPasswordNotCorrectExceptionをスローするのは確かに正しいです。提供されたパラメーターを使用してユーザーをログインすることはできません(つまり、認証に違反するため不可能です)。そのため、例外をスローします。 ArgumentExceptionから継承する2つの例外がありますが。
そうは言っても、ログイン失敗は非常に一般的である可能性があるため、例外をスローしない場合は、代わりに、さまざまな失敗を表す型を返すメソッドを作成する方法があります。以下に例を示します。
{ // class
...
public LoginResult Login(string user, string password)
{
if (IsInvalidUser(user))
{
return new UserInvalidLoginResult(user);
}
else if (IsInvalidPassword(user, password))
{
return new PasswordInvalidLoginResult(user, password);
}
else
{
return new SuccessfulLoginResult();
}
}
...
}
public abstract class LoginResult
{
public readonly string Message;
protected LoginResult(string message)
{
this.Message = message;
}
}
public class SuccessfulLoginResult : LoginResult
{
public SucccessfulLogin(string user)
: base(string.Format("Login for user '{0}' was successful.", user))
{ }
}
public class UserInvalidLoginResult : LoginResult
{
public UserInvalidLoginResult(string user)
: base(string.Format("The username '{0}' is invalid.", user))
{ }
}
public class PasswordInvalidLoginResult : LoginResult
{
public PasswordInvalidLoginResult(string password, string user)
: base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
{ }
}
ほとんどの開発者は、例外をスローすることによって生じるオーバーヘッドのため、例外を回避するように教えられています。リソースを意識することは素晴らしいことですが、通常はアプリケーションの設計を犠牲にしません。これがおそらく、2つの例外をスローしないように言われた理由です。例外を使用するかどうかは、通常、例外が発生する頻度によって決まります。かなり一般的またはかなり期待できる結果である場合、これはほとんどの開発者が例外を回避し、代わりにリソースの消費が想定されるため、失敗を示す別のメソッドを作成するときです。
Try()パターンを使用して、上記のようなシナリオで例外の使用を回避する例を次に示します。
public class ValidatedLogin
{
public readonly string User;
public readonly string Password;
public ValidatedLogin(string user, string password)
{
if (IsInvalidUser(user))
{
throw new UserInvalidException(user);
}
else if (IsInvalidPassword(user, password))
{
throw new PasswordInvalidException(password);
}
this.User = user;
this.Password = password;
}
public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
{
if (IsInvalidUser(user) ||
IsInvalidPassword(user, password))
{
return false;
}
validatedLogin = new ValidatedLogin(user, password);
return true;
}
}
私の考えでは、根本的な問題は、条件が発生した場合に呼び出し元が通常のプログラムフローを続行することを期待するかどうかです。わからない場合は、doSomethingメソッドとtrySomethingメソッドを別々に用意します。前者はエラーを返し、後者は返さないか、失敗した場合に例外をスローするかどうかを示すパラメーターを受け入れるルーチンを使用します)。リモートシステムにコマンドを送信し、応答を報告するクラスを検討してください。特定のコマンド(例:再起動)により、リモートシステムは応答を送信しますが、一定時間応答しなくなります。したがって、「ping」コマンドを送信し、例外がスローされない場合にリモートシステムが例外をスローすることなく、妥当な時間内に応答するかどうかを確認できると便利です(呼び出し側は、おそらく最初のいくつかのping」の試行は失敗しますが、最終的には機能します)。一方、次のような一連のコマンドがある場合:
exchange_command( "open tempfile"); exchange_command( "temptempfile data {whatever}"); exchange_command( "tempfile file data {whatever}"); exchange_command( "write tempfile data {whatever}"); exchange_command( "write tempfile data {whatever}"); exchange_command( "close tempfile"); exchange_command (「一時ファイルを実ファイルにコピー」);
操作の失敗がシーケンス全体を中止するようにしたいと思うでしょう。各操作をチェックして成功したことを確認できますが、コマンドが失敗した場合にexchange_command()ルーチンに例外をスローさせるとより便利です。
実際、上記のシナリオでは、いくつかの障害処理モードを選択するパラメーターを用意すると便利です。例外をスローしない、通信エラーのみの例外をスローする、コマンドが「成功」を返さない場合は例外をスローする「表示。
例外のスローを回避する主な理由は、例外のスローに伴うオーバーヘッドが大きいことです。
以下の記事で述べられていることの1つは、例外は例外的な条件とエラーに関するものだということです。
間違ったユーザー名は、必ずしもプログラムエラーではなく、ユーザーエラーです...
.NET内の例外の適切な出発点は次のとおりです。 http://msdn.Microsoft.com/en-us/library/ms229030(VS.80).aspx
セキュリティはあなたの例と混同されます:ユーザー名が存在することを攻撃者に伝えるべきではありませんが、パスワードは間違っています。これは、共有する必要のない追加情報です。 「ユーザー名またはパスワードが間違っています」と発声してください。
例外をスローすると、スタックが巻き戻され、パフォーマンスに影響があります(認められた、最新の管理された環境が改善されました)。ネストされた状況で例外を繰り返しスローおよびキャッチすることは、悪い考えです。
おそらくそれよりも重要なのは、例外は例外的な条件を意味することです。通常の制御フローには使用しないでください。コードの可読性が損なわれるためです。
例外が適切かどうかを判断する際に考慮すべきいくつかの有用な事項:
例外候補が発生した後に実行するコードのレベル、つまり、コールスタックの何層を巻き戻すか。通常、例外は発生した場所にできるだけ近い場所で処理する必要があります。ユーザー名/パスワードの検証では、通常、例外を発生させるのではなく、同じコードブロックで障害を処理します。したがって、例外はおそらく適切ではありません。 (OTOH、3回のログイン試行の失敗後、制御フローは他の場所に移動する場合があり、ここで例外が適切な場合があります。)
このイベントは、エラーログに表示したいものですか?すべての例外がエラーログに書き込まれるわけではありませんが、エラーログのこのエントリが役立つかどうかを確認することは有用です。
例外には主に2つのクラスがあります。
1)システム例外(データベース接続の喪失など)または2)ユーザー例外。 (たとえば、ユーザー入力検証、「パスワードが正しくありません」)
私は独自のユーザー例外クラスを作成すると役立つことがわかりました。ユーザーエラーをスローしたい場合は別の方法で処理したい(つまり、リソースエラーをユーザーに表示します)メインエラーハンドラーで必要なのはオブジェクトタイプをチェックすることです:
If TypeName(ex) = "UserException" Then
Display(ex.message)
Else
DisplayError("An unexpected error has occured, contact your help desk")
LogError(ex)
End If
「PasswordNotCorrectException」は、例外を使用する良い例ではありません。ユーザーがパスワードを間違えるのは当然のことなので、ほとんど例外ではありません。おそらく、そこから回復してNiceエラーメッセージを表示するので、妥当性チェックにすぎません。
未処理の例外は、最終的に実行を停止します-これは良いことです。 false、null、またはエラーコードを返す場合は、プログラムの状態をすべて自分で処理する必要があります。どこかで条件を確認するのを忘れると、プログラムが間違ったデータで実行され続ける可能性があり、whatが起こってwhereを理解するのが難しくなります。
もちろん、空のcatchステートメントでも同じ問題を引き起こす可能性がありますが、少なくともそれらを見つけるのは簡単であり、ロジックを理解する必要はありません。
だから、経験則として:
必要のない場所、または単にエラーから回復できない場所で使用してください。
その条件には、少し一般的な例外を使用できます。たとえばArgumentExceptionは、メソッドのパラメーターに問題が発生した場合に使用することを意図しています(ArgumentNullExceptionを除く)。通常、LessThanZeroException、NotPrimeNumberExceptionなどの例外は必要ありません。メソッドのユーザーを考えてください。彼女が特に処理したい条件の数は、メソッドがスローする必要がある例外のタイプの数と同じです。これにより、どの程度詳細な例外が発生するかを判断できます。
ところで、ライブラリのユーザーが例外を避けるための方法を常に提供するようにしてください。 TryParseは良い例で、int.Parseを使用して例外をキャッチする必要がないように存在します。あなたの場合、ユーザー名(またはあなた)が多くの例外処理をする必要がないように、ユーザー名が有効であるかパスワードが正しいかどうかをチェックするいくつかの方法を提供することができます。これにより、コードが読みやすくなり、パフォーマンスが向上することが期待されます。
最終的には、例外処理を使用してこのようなアプリケーションレベルのエラーを処理する方が役立つか、ステータスコードを返すなどの独自のホームメカニズムを介して処理する方が役立つかが決定されます。私はどちらが優れているかについての厳格なルールはないと思いますが、私は考慮します: