MVCアプリのシステムのユーザーのパスワードを変更する関数があるとします。
_public JsonResult ChangePassword
(string username, string currentPassword, string newPassword)
{
switch (this.membershipService.ValidateLogin(username, currentPassword))
{
case UserValidationResult.BasUsername:
case UserValidationResult.BadPassword:
// abort: return JsonResult with localized error message
// for invalid username/pass combo.
case UserValidationResult.TrialExpired
// abort: return JsonResult with localized error message
// that user cannot login because their trial period has expired
case UserValidationResult.Success:
break;
}
// NOW change password now that user is validated
}
_
membershipService.ValidateLogin()
は、次のように定義されるUserValidationResult
enumを返します。
_enum UserValidationResult
{
BadUsername,
BadPassword,
TrialExpired,
Success
}
_
防御的なプログラマーであるため、ChangePassword()
から認識されないUserValidationResult
値が返された場合、上記のValidateLogin()
メソッドを変更して例外をスローします。
_public JsonResult ChangePassword
(string username, string currentPassword, string newPassword)
{
switch (this.membershipService.ValidateLogin(username, currentPassword))
{
case UserValidationResult.BasUsername:
case UserValidationResult.BadPassword:
// abort: return JsonResult with localized error message
// for invalid username/pass combo.
case UserValidationResult.TrialExpired
// abort: return JsonResult with localized error message
// that user cannot login because their trial period has expired
case UserValidationResult.Success:
break;
default:
throw new NotImplementedException
("Unrecognized UserValidationResult value.");
// or NotSupportedException()
break;
}
// Change password now that user is validated
}
_
私は常に、ベストプラクティスより上の最後のスニペットのようなパターンを検討しました。たとえば、ある開発者が、ユーザーがログインしようとしたときに、このまたはそのビジネス上の理由で、最初にビジネスに連絡する必要があるという要件を取得したらどうなるでしょうか?したがって、UserValidationResult
の定義は次のように更新されます。
_enum UserValidationResult
{
BadUsername,
BadPassword,
TrialExpired,
ContactUs,
Success
}
_
開発者はValidateLogin()
メソッドの本体を変更して、必要に応じて新しい列挙値(_UserValidationResult.ContactUs
_)を返しますが、ChangePassword()
の更新を忘れています。スイッチで例外がなければ、ユーザーは、ログイン試行を最初から検証する必要がない場合でも、パスワードを変更できます。
それは私だけですか、これはdefault: throw new Exception()
良いアイデアですか?私はそれを数年前に見ましたが、常に(それをグロッキングした後)ベストプラクティスであると想定しています。
この場合、常に例外をスローします。 InvalidEnumArgumentException
の使用を検討してください。これは、この状況でより豊富な情報を提供します。
例外がスローされて処理されなかった場合、そのスレッドの実行は停止するため、後のbreakステートメントはヒットしませんが、問題はありません。
列挙がその使用から「遠く」生きる特定のインスタンスに対してこのプラクティスを使用したことがありますが、列挙が本当に近く、特定の機能に専念している場合、それは少し多くのようです。
おそらく、デバッグアサーションエラーのほうがおそらく適切だと思います。
http://msdn.Microsoft.com/en-us/library/6z60kt1f.aspx
2番目のコードサンプルに注意してください...
私はいつもあなたが持っているものを正確にやりたいのですが、渡された引数の場合は通常ArgumentException
をスローしますが、エラーは呼び出し元ではなく新しいcaseステートメントを追加する必要がある可能性があるため、NotImplementedException
のようなものです引数をサポートされているものに変更します。
Switchステートメントに含まれるthrowステートメントの後にブレークを追加することはありません。懸念事項の少なくとも1つは、迷惑な"到達不能コードが検出されました"警告です。はい、それは良いアイデアです。
このようなアプローチは非常に役立つと思います(たとえば、 誤って空のコードパス を参照)。アサートのように、リリースされたコードでそのデフォルトのスローをコンパイルできるようにすることができればさらに良いです。そうすれば、開発およびテスト中に余分な防御力が得られますが、リリース時に余分なコードのオーバーヘッドが発生することはありません。
あなたは非常に防御的であると述べています。他の開発者はコードをテストしませんか?確かに、彼らが最も簡単なテストを行うと、ユーザーはまだログインできることがわかります。そのため、修正する必要があることに気付くでしょう。あなたがしていることは恐ろしくも間違っていることもありませんが、あなたがそれをすることに多くの時間を費やしているなら、それはただ多すぎるかもしれません。
Webアプリケーションの場合、ユーザーに意味のない何かをもたらす可能性のある例外をスローするのではなく、ユーザーに管理者に連絡してエラーを記録するよう求めるエラーメッセージを含む結果を生成するデフォルトを好むでしょう。この場合、戻り値が予想外のものになる可能性があるため、この本当に例外的な動作とは考えないでしょう。
また、追加の列挙値をもたらすユースケースでは、特定のメソッドにエラーが発生することはありません。私の期待は、ユースケースがログインにのみ適用されることです-これは、おそらくパスワードを変更する前にログインしていることを確認したいので、ChangePasswordメソッドが合法的に呼び出される前に発生します-パスワード検証からの戻り値としてContactUs値を実際に見ることはありません。ユースケースでは、ContactUsの結果が返された場合、結果の条件がクリアされるまでその認証が失敗することを指定します。そうでなければ、その条件の下でシステムの他の部分がどのように反応するかについての要件があり、これらのテストに適合するようにこのメソッドのコードを変更することを強制するテストを書くと期待しています新しい戻り値に対処します。
通常、実行時制約の検証は良いことです。そのため、はい、ベストプラクティスは「フェイルファースト」の原則に役立ち、エラー状態を検出するとき、黙って継続するのではなく停止(またはログ)します。それが当てはまらない場合もありますが、switchステートメントを考えると、あまり頻繁に表示されないと思われます。
詳細に説明すると、switchステートメントはいつでもif/elseブロックに置き換えることができます。この観点から見ると、デフォルトのスイッチケースでのスローとスローしないのは、この例とほぼ同じであることがわかります。
if( i == 0 ) {
} else { // i > 0
}
対
if( i == 0 ) {
} else if ( i > 0 ) {
} else {
// i < 0 is now an enforced error
throw new Illegal(...)
}
2番目の例は、エラーの制約に違反したときに失敗する可能性があるため、誤った想定のもとで続行するよりも、通常はより適切であると見なされます。
代わりに必要な場合:
if( i == 0 ) {
} else { // i != 0
// we can handle both positve or negative, just not zero
}
次に、実際には、最後のケースがif/elseステートメントとして表示される可能性が高いと思います。そのため、switchステートメントは2番目のif/elseブロックに非常によく似ているため、通常、スローはベストプラクティスです。
さらに考慮すべき点がいくつかあります。-多態的なアプローチまたは列挙メソッドを検討して、switchステートメントを完全に置き換えます。例: C# の列挙内のメソッド他の回答に記載されているように、スローは最適です。ボイラープレートコードを避けるために特定の例外タイプを使用することを好みます。例:InvalidEnumArgumentException