Outチームは新しいプロジェクトの最前線にいます。システムの境界にあるコンポーネントの1つは、外部COMコンポーネント(通常のdllとして参照される)を介してプリンターと対話するコンポーネントです。
コマンドの実行中にエラーが発生した場合、COMコンポーネントは整数コードを返します。具体的なコードを考えてみましょう。
public class OperationException:Exception {
public int ErrorCode { get; private set; }
public string ErrorDescription { get; private set; }
public OperationException(int errorCode, string errorDescription) {
ErrorCode = errorCode;
ErrorDescription = errorDescription;
}
}
//The exception throwing way
public class Provider1:IFrProvider {
private readonly IDrvFR48 driver;
public string GetSerialNumber() {
//must read status before get SerialNumber
int resultCode = driver.ReadEcrStatus();
if (resultCode != 0) {
throw new OperationException(resultCode, driver.ErrorDescription);
}
return driver.SerialNumber;
}
}
//The way of out parameters returning.
public class Provider2 : IFrProvider
{
private readonly IDrvFR48 driver;
public string GetSerialNumber(out Result result) {
//must read status before get SerialNumber
int resultCode = driver.ReadEcrStatus();
if (resultCode != 0) {
result = new Result(resultCode, driver.ErrorDescription);
return null;
}
result = new Result(0, null);
return driver.SerialNumber;
}
}
//The way of LastResult property setting.
public class Provider3 : IFrProvider
{
private readonly IDrvFR48 driver;
public Result LastResult {
get {
return new Result(driver.ErrorCode, driver.ErrorDescription);
}
}
public string GetSerialNumber() {
//must read status before get SerialNumber
if (driver.GetECRStatus() == 0)
return driver.SerialNumber;
return null;
}
}
public class Result {
public int ResultCode { get; private set; }
public string Description { get; private set; }
public Result(int resultCode, string description) {
ResultCode = resultCode;
Description = description;
}
}
public class Caller {
public void CallProvider1() {
var provider = new Provider1();
try {
string serialNumber = provider.GetSerialNumber();
//success flow
}
catch (OperationException e) {
if (e.ErrorCode == 123) {
//handling logic
}
}
}
public void CallProvider2() {
var provider = new Provider2();
Result result;
string serialNumber = provider.GetSerialNumber(out result);
if (result.ResultCode == 123) {
//error handling
}
//success flow
}
public void CallProvider3()
{
var provider = new Provider3();
string serialNumber = provider.GetSerialNumber();
if (provider.LastResult.ResultCode == 123) {
//handling
}
//success flow
}
}
したがって、エラー処理には3つの方法があります。これらすべての中で最も具体的なことは、各操作はデバイスに依存するため、失敗する可能性があるということです。
未処理の例外伝播の恐れがあるため、私たちのチームはoutパラメータの使用を考えています。恐怖に関連するもう1つの理由は、恐れるのをやめるためにtry\catchブロックですべての呼び出しをカバーする必要があることですが、そのようなコードはかなり醜くなります。
どのような長所と短所があり、何をアドバイスできますか?
エラーコードを使用しても、例外を超えて何かが得られるとは思いません。エラーコードのチェックを見逃してしまうのと同じくらい簡単です。
また、次の例も検討してください。
public void CallProvider2() {
var provider = new Provider2();
Result result;
string serialNumber = provider.GetSerialNumber(out result);
if (result.ResultCode != 0) {
//error handling
switch(result.ResultCode)
{
case 123:
break;
//more cases
default:
//what now?
break;
}
}
//success flow
}
上記はエラーを処理する方法であり、「デフォルト」の場合は未チェックのエラーコードをキャッチし、未処理の例外と同等になります。そのケースがこれまでに到達した場合に何をすべきかを尋ねる価値があります。続行することはできませんが、そのメソッドを安全に終了する必要がありますか?スタックの最後までResultオブジェクトを返して、どこでもResultCode!= 0かどうかを確認しますか?例外に固執するかもしれません。
考慮すべき例外のその他の利点:
1)スタックトレースを自動的に取得します。これはデバッグに非常に役立ちます。
2)呼び出しスタックの上位でエラーを処理する必要がある場合、例外は自動的に上方に伝播します。それらを処理して再スローすることもできます。 ResultCodeの例では、必要に応じてそれらを渡すために追加のコードを記述する必要があります。
3).NETランタイムプロジェクトには、サブスクライブして中央集中型のエラーロギングを実行できるUnhandledExceptionイベントのバリエーションがいくつかあります。アプリがダウンしても、その理由に関する情報を取得できます。そのための配管はすでに組み込まれているので、それに接続するだけです。
4)未処理の例外によるアプリのクラッシュは望ましくありませんが、有用です。何かがうまくいかなかったと誰もが誤解することはできません。
非常に議論の余地のある質問です。プロジェクト、ワークフロー、コードが他の開発者によって使用されるかどうかなど、多くの条件によって異なります。
はい、一般的な.Netの習慣は、エラーコードを返す代わりに例外をスローすることです。それについての非常に良いコメント ここ 。
エラーコードは古いskoolであり、例外をサポートしていなかった環境であるCOMおよびCプログラミングの古き良き時代に必要でした。現在、例外を使用して、クライアントコードまたはユーザーに問題を通知しています。 .NETの例外は自己記述的であり、タイプ、メッセージ、および診断があります。
考えたところ、他の側面を念頭に置く必要があります-エラーのコード自体も(httpコード、404、503などのように)自己記述的である場合は、コードを返すことができます。そして.Netフレームワークは実際にはそのようなコードを HttpWebResponse.StatusCode で返します(ただし、列挙型でラップされています)。ただし、これらのコードが将来変更されないことが確実にわかっている場合は、そうしてください。
他の場合では、それが発生したことをコードのユーザーが知ることは、エラー#23452であるということです。さらに、将来的に変更される可能性があり、変更された数値のためにユーザーがコードをオーバーライドする原因になります。
予見可能な理由で失敗する可能性があるが、予期できないときにAPIを設計する場合、通常、呼び出し元が半予測可能な失敗に対処することを期待しているかどうかを示す手段を許可する必要があります。使用可能な唯一のメソッドが例外ではなくエラーコードを返す場合、エラーコードで実行できる唯一のことは例外をスローする場合でも、すべての呼び出しサイトは戻り値をテストする必要があります。利用可能な唯一のメソッドがすべての失敗に対して例外をスローする場合、呼び出し元が期待しているものであっても、失敗を処理する準備ができているすべての呼び出し元は、そのためにtry/catchを使用する必要があります。
呼び出し側が失敗を予期するかどうかに関係なく、メソッドを公開する一般的な方法は、名前のないメソッドのペアを使用することです(例:TryFnorble
とFnorble
)。 Thing1a
とThing1b
には両方のスタイルのメソッドがあり、Thing2a
には最初のメソッドしかなく、Thing2b
には2番目のメソッドしかないとします。最初の問題を処理するために準備されているが、2番目の問題は処理されていないコードは、次のように書くことができます。
Thing1a.Fnorble();
if (Thing1b.Fnorble())
Thing1bIsReady = true;
else
Thing1bIsReady = false;
対照的に、コードがThing2a
/Thing2b
を使用している場合は、次のように記述する必要があります。
if (!Thing2a.Fnorble())
throw new FnorbleFailureException(...);
try
{
Thing2b.Fnorble();
Thing2bIsReady = true;
}
catch (Ex as FnorbleFailureException)
{
Thing2bIsReady = False;
}
はるかにニース。
[注:戻り値をフラグに直接割り当てるのではなく、if/else
を使用してThing1bIsReady
フラグを明示的に設定/クリアします。戻り値の型とフラグが両方ともbool
であっても、意味的に異なる意味を持つ。メソッドがtrue
を1回返すとフラグがすぐに設定され、false
を1回返すとフラグがすぐにクリアされることを実装の詳細として考えます。