2009/09/19(Sat) 10:59:13 編集(投稿者)
コードを投稿する場合は、[投稿モード]を通常モードから図表モードに切り替えてください。
(本文を記述する欄のすぐ上に、投稿モードのラジオボタンがあります)
■No41389 (simano さん) に返信
>> // エラー通知イベントでの捕捉
>> obj.OnError += delegate { MessageBox.Show("失敗"); }; // エラー理由はイベント引数で
>> MyData result = obj.GetFromServer(0);
先のこのパターンは、他と少し毛色が違いますが、非同期的に通知させる事を意図したものです。
BackgroundWorker の RunWorkerCompleted イベントや、
ADODB.Connection の InfoMessage イベントのように、
イベント引数で例外情報を伝えるという状況を想定しています。
# ……もはや参照渡し/値渡しとは関係無い話になってしまいましたが。(^^;
> 魔界の仮面弁士さんの挙げた例のうち、個人的には「出力引数」が一番良いと思いました。
> サーバの返すエラーの種類をアプリケーション側で細かく把握できたほうが、
> 融通が利くためです。
クラスの利用側にエラー内容を細かく把握させること自体は、出力引数以外の手法でも
実装することができると思いますよ(そもそも、例外処理はその一例でしょうし)。
たとえば戻り値で実装するなら、RunWorkerCompletedEventArgs クラスのプロパティのように、
詳細なエラー情報をデータと共に併せ持つといった方法もあるかと思います。
result = obj.GetFromServer(0);
if(result.Error == null) {
// OK 処理
MessageBox.Show(result.Result.ToString());
} else {
// NG 処理
if(result.Error is MyServerException) {
Recovery((MyServerException)result.Error);
} else {
throw new SimanoAppException("GetFromServer が予期せぬエラーを返しました。", result.Error);
}
}
> 個人的には「出力引数」が一番良いと思いました。
出力引数を使うにしても、その実装パターンは幾つか考えられるかと思います。
resultStatus = obj.Foo(inputData, out outputData);
outputData = obj.Foo(inputData, out resultStatus);
resultStatus = obj.Foo(ref inputOutputData);
上記はいずれも、「結果データ」と「エラーの有無」を戻り値と引数とで受け取るようになっています。
得られる結果だけみれば、同じ処理目的のために使えますが、それぞれが指し示すメソッドの「意図」は
微妙に異なるため、それぞれの実装を単純に同列に扱うわけにもいきませんよね。
> その際、以下のように、ErrorDefinitionクラスを作成し、エラーの種類を定数で持たせたいと考えました。
const int を切るかわりに、System.Net.WebExceptionStatus や
System.Net.HttpStatusCode などのように、enum にするという手もあります。
この実装方法は、エラーの種類を事前に網羅できる場合に有効かと思います。
たとえば HttpStatusCode は、HTTP/1.1 仕様書に定義されたステータスと等価ですね。
http://www.studyinghttp.net/status_code
> そうすれば、ビルドしたときにライブラリの定数名が変更されれば、
そのライブラリが DLL 参照では無く、プロジェクト参照としてソリューションに組み込まれているならば、
定数名が変更されれば、それを利用する側も機械的にリファクタリングできるかと思います。
ところで private ならばともかく、外部公開されている定数名を後から変える理由は何なのでしょうか?
その理由次第では、何か対応策があるかも知れません。
たとえば当方では、あるメンバにスペルミスが発覚したため、その綴りを変更したい…という状況が
発生した事があったのですが、そのときはスペルを直接修正するのではなく、同じ意味を持つ
正しいスペルの項目を新たに追加し、旧項目に Obsolete 属性を付与させるという手法をとりました。
こうすると、旧項目を使っている場所があれば、リコンパイル時に警告/エラーが通知されますので、
利用側で、完全に置き換え修正が完了した事を確認した上で、その次のバージョンのリリース時に
そのスペルミス項目を削除するようにしました。
> つまり「dllの変更時にthrowされた例外を取りこぼす」等のうっかりミスが減りそうだと考えます。
どの方法にしても、利点もあれば欠点もあると思います。
たとえば、if(ret == ErrorDefinition.SERVER_FILE_NOT_FOUND) の手法だと、
新たな定数コードが増えたときに、呼び出し側の取りこぼしが起こりえそうです。
すべての組みあわせに対して、else if が網羅されていれば、
最後の else にヒットして検出できるかも知れませんけれどね。
> 魔界の仮面弁士さんはどのような例外処理を行っていますか?
少々答えにくい質問ですが… MSDN のガイドラインを参考にしてはいます。
http://msdn.microsoft.com/ja-jp/library/cc433258.aspx
http://msdn.microsoft.com/ja-jp/library/dd296858.aspx
http://msdn.microsoft.com/ja-jp/library/ms229014.aspx
> >「例外の種類を変える」とは、どういう意味でしょうか?
> 以下の意味でした。
> ・MyServerException の例外が、サーバーエラー以外に対しても throw されるようになった。
だとしたら、それは設計ミスだと思いますよ。
例外クラス名と実際の例外の内容が一致しませんし。
> ・MyServerException を廃止して、別の例外が throw されるように実装し直した。
まず、廃止という点について、
(1)GetFromServer メソッドが MyServerException を throw しなくなった。
(2)MyServerException クラスに、Obsolete 属性を付与した。
(3)DLL 内から、MyServerException クラス自体の定義を削除した。
といった 3 つのパターンを想像してみました。
(1) については、呼び出し元の catch 句が埋め殺しになるだけであって、
通常処理系に対しては、影響は少ないような気がします。
(通常処理から外れるがゆえの「例外」なわけですし)
(2) についてはコンパイル段階で発覚しますから、該当箇所の列挙は難しくないでしょう。
(3) に至ってはそもそもコンパイルが通らなくなるので、やはり発見は容易かと。
それから新たな例外の新設という点についてですが、それは確かに
既存の catch(MyServerException ex) では拾われないと思います。しかし
その新設した例外(あるいはその例外の継承元クラス)を catch していない場合、
それはさらにその呼び出し元に伝わっていくわけですよね。ならばテスト段階で
StackTrace を追跡すれば、問題箇所を検出することもできるのでは無いかと。
まぁ、例外を意図的に握りつぶしている箇所の再精査は必要ですが、
そのような再精査は、仕組みが違うにしろ、他の手法(戻り値や出力引数等)でも
同じように全チェックしなければならない事柄でしょうし。