中の技術日誌ブログ

C#とC++/CLIと
VBと.NETとWindowsで戯れる
 

目次

Blog 利用状況

ニュース

自己紹介

東京でソフトウェアエンジニアをやっています。
お仕事大募集中です。
記事執筆や、講師依頼とかでも何でもどうぞ(*^_^*)
似顔絵 MSMVPロゴ
MSMVP Visual C# Since 2004/04-2013/03

記事カテゴリ

書庫

日記カテゴリ

00-整理

01-MSMVP

もうちょっと具体的に

問題提起シリーズ?なのかな。

なぜわからないのかがわかった。。。かな?

捨てられない戻り値

さてサンプルで説明する際などに戻り値は不適切な場合が多いっていうのを書きました。

すこし具体的にコンテキストについて考えてみましょう。

int 登録処理(int 得意先ID, string "伝票名称")

この戻り値は登録した結果のたとえば伝票IDがIDENTITY値として戻ってくるとしましょう。

利用する側はこんな感じになるでしょうか。

int 伝票ID = 登録処理( 100, "新発売のWindows Vistaについて" );
DataRow dr = 取得処理(伝票id);

特に問題があるとは思えませんよね?

伝票IDがこのタイミングで不要な場合には読まずに捨てればいいのです。その場合のサンプルがこちら

登録処理( 100, "新発売のWindows Vistaについて" );

ただ登録処理の伝票名称の重複は不可となっている場合にはどうしましょう。

この重複不可エラーはもちろん業務的なエラーですから、アプリケーションを落としてはいけません。

方法としては、例外、クラス化などがあるでしょう。

int 登録処理(int 得意先ID, string "伝票名称") {
  try {
    return (int)com.execute();
  } catch (SQLException ex){
    if ( ex.Number = 2726 ) {
      throw new KeyDuplicationException();
    } else {
      throw ex;
    }
  }
}
int 登録伝票ID;
エラーコード _error;
bool 登録処理(int 得意先ID, string "伝票名称") {
  try {
    登録伝票ID = com.execute();
    return true;
  } catch (SQLException ex){
    if ( ex.Number = 2726 ) {
      this._error = エラーコード.KeyDuplicate;
      return false;
    } else {
      throw ex;
    }
  }
}

#あくまでイメージなので、微細なところに突っ込むのはなしで。

さて、上の案と、下の案どっちがより優れているでしょうか。

上の案は現状の流行であるコンテキストを重視したタイプ。

下の案は一昔前の成功失敗を確実に追い求めるタイプ。

利用する側のコードも一緒に考えてみてください。記述するサンプルも一緒に考えてみてください。コーディング量も考えてみてください。

#それだけで飯を食っているわけではないけど、ライターな自分への自戒なども込めて。

投稿日時 : 2007年1月14日 11:14

コメントを追加

# re: もうちょっと具体的に 2007/01/14 13:35 えムナウ

コーディング量としても実行スピードとしても下でしょうが、私は上をとります。
拡張性を考えて SQLException を握りつぶさずに innerException を使いますが。

# re: もうちょっと具体的に 2007/01/14 14:23 黒龍

下をとりますね。論拠としては弱いのですが登録処理とあるのでひとまず成否を取れればよいと思うので。

# re: もうちょっと具体的に 2007/01/14 14:24 えムナウ

理由を書き忘れていました。
このメソッドの主たる仕事は何でそのResultは何かという観点からです。

# re: もうちょっと具体的に 2007/01/14 16:09 2リットル

はじめまして、いつもわんくま楽しく見ています。

「伝票名称の重複」は登録処理()で解釈していいだろうと思うので下かな。でも実際には、他の似たような処理との兼ね合いできめます。




# re: もうちょっと具体的に 2007/01/14 20:21 ひろえむ

私はえムナウさんと同じで上かな。

まま、クラスにもよりますが、今回は登録処理ということですから、登録を行うのが主業務でエラーの内容による判定はあくまで呼び出し側じゃないかなと。

また、例外が発生した場合の判定は内部で握りつぶしたりするものではないと思うワケで、握りつぶしてしまうのは、バグの温床のような気がします(^^;

あくまで登録業務以外のことは例外としてとらえるほうが自然な気がしますねー(^^;

まま、2リットルさんと同様で実際は関連する業務と足並み揃えるほうが優先順位としては高いですが・・・(^^;;

# re: もうちょっと具体的に 2007/01/14 20:35 ひろえむ

あ、書き忘れましたが、登録処理のリカバリまでの責務をメソッドが負うのであれば、話は違ってきますが(^^;

でも、今回の場合は下のケースですとエラーコードの設定ということであれば、これは呼び出し側の責務のような気がします(^^;

じゃなければ、同じエラーコードを作成するならば、Exceptionの内容を判定するクラスを作るかなぁ(^^;

まま、いずれもミニマムコードなのであまり風呂敷を広げない方向で考えるならば、前者かなと(^^;

# re: もうちょっと具体的に 2007/01/14 21:15 シャノン

このケースであれば、悔しいけれど後者。
このケースってのは、どちらにせよ、「実際に登録を試行してみるまで、成功するかどうかわからない」という前提で設計が為されている。
つまり、「登録できなかった」ということは予期した結果のひとつ。ならば例外を投げてはならない。
ただ、個人的には、(DBには疎いので現実的かどうかわからないのだけれど)「登録が成功するという保証がある状況下でしか登録しない」という設計にはできないものか、と思う。

# re: もうちょっと具体的に 2007/01/14 21:38 シャノン

COMを扱う予定でいるのでこういう言い方をするけれど、もしも今回の例をCOMで書いたとするならば、登録失敗はS_FALSEを返すケースであるように思える。
そのような設計が妥当であるかどうかはまた別問題として、そのような設計である以上は後者、と。

# re: もうちょっと具体的に 2007/01/14 21:39 シャノン

↑ごめんこれ撤回。うわー、削除してぇ。

# re: もうちょっと具体的に 2007/01/14 21:43 渋木宏明(ひどり)

throw ex はおかしい。

# re: もうちょっと具体的に 2007/01/14 21:49 ひろえむ

そうかーなるほど。

確かに言われてみれば、コードが示しているのは「登録処理」を行うということと、登録処理から発生する例外を捕獲するという2点ですね。

で、重複が発生する可能性があることは事前条件として示されている訳だから、想定内動作ということになるのか。

なるほどー。

ただ、クラスの責務にもよりますが、メソッド内でその判定をするところまで及ぶとかなりガチガチな処理依存メソッドになりそうでちょっと考えちゃいますが(^^;

重複が発生する可能性がある判定ができるのはビジネスロジックでデータアクセスですべきかどうかは少々疑問が残りますねー(^^;

# re: もうちょっと具体的に 2007/01/14 21:50 渋木宏明(ひどり)

ちなみに僕は上。
登録失敗がどれだけ致命的かは、このスコープだけで語ることは出来ません。
なので、メソッドの責務である「登録」に失敗したら、それは例外で表現して問題ないと思います。
それをどう扱うかは呼び出し側の判断、ということで。

# re: もうちょっと具体的に 2007/01/14 22:24 がんふぃーるど

私はなんとなく後者派ですね。理由はシャノンさんの最初の説明と同じ。
想定内の/ただのエラー処理扱いです。

業務的に伝票名称が重複する可能性があるなら、重複時は想定内のエラー処理扱いにすると思います。

#ただ、フレームワークのDAO部を作るなんて話ならFW側はKeyDuplicationExceptionを投げて、
#業務側は例外をキャッチさせて例外ではなく、想定内のエラー処理扱いとします。
#どっちにしろ業務としてはエラー処理扱いだったり…

# re: もうちょっと具体的に 2007/01/15 11:57 シャノン

ちょっと、自分なりの「例外」の定義を変更した。
けど、このケースでは相変わらず後者。
どう変わったのかは、近々マイブログで。

「登録失敗はメソッドの責務が果たせなかったことになる」とされる方々は、キーの重複の場合だけ特別な処理をしている理由は何であると考えますか?

# re: もうちょっと具体的に 2007/01/15 11:59 とっちゃん

想定内の動きでも、処理失敗を例外にするという方が多いです。
なので、基本は前者の例外を投げるほうですね。
#例外を変更することはあまりやらないけど

# re: もうちょっと具体的に 2007/01/15 19:23 渋木宏明(ひどり)

例外の読み替えをここで行うかどうかは「仕様」です。
必ずしもここで行わなくてもいいと思います。
何を以ってエラーとするのかは(さらにい言えば捕捉するのかスルーするのかも)呼び出し側の責務なので。
例外は「失敗」の伝達機構と考えればOKと思います。
成功したら戻り値が返るか、何も戻らない。
COM の場合「成功」「失敗」を例外の「ある」「なし」の代わりに HRESULT 値で通知しているだけで、本質的には同じことだと思います。
戻り値がある場合は out retval 属性付きの引数を使うわけで、決して HRESULT に「戻り値」の役割を負わせたりはしません。

# re: もうちょっと具体的に 2007/01/15 19:59 シャノン

> 例外の読み替えをここで行うかどうかは「仕様」です。
> 必ずしもここで行わなくてもいいと思います。

ここで行うという仕様だからこういうコードになっているのではないですか?

登録処理の責務は登録をすることで、理由が何であれ登録ができなかったのは失敗だから例外を投げる。そこまではいい。
どうして、キー重複の場合だけ特別視するという仕様なのか。「理由が何であれ失敗は失敗」ではないのか。

別にここで読み替えを行わなくてよい=例外処理は行わず、起きた例外はそのまま呼び出し元に上げてよいというのであれば、俺も上のコード(から例外処理を省いたバージョン)を支持しましょう。

# re: もうちょっと具体的に 2007/01/15 22:17 Jitta

トラックバックされてない?
http://blogs.wankuma.com/jitta/archive/2007/01/15/56387.aspx

# re: もうちょっと具体的に 2007/01/16 11:35 黒龍

えっと個人的な感触だけ補足。
そもそも上と下のメソッドでは責務の範囲が若干違うように思います。上の場合は登録処理の中の永続化処理をメソッドとして切り出したもの、下の場合はメソッド内に登録処理を押し込もうという思想のように受け取れました。(戻り値の違いによる)
なので上のサンプルで例外を投げる&判定が必要というのはある意味当然のようにも思えますし下の例で想定内エラーとして読み替えを行っているのもある意味当然だと思います。
どちらも正解ではあるのですが上は業務例外への読み替えがないので永続化層での処理が上位側まで波及する点と登録処理という名称ながら処理が部分的(登録処理内の1処理)なのでメソッド名称が良くないとの理由で下がよいと考えました。
このように上、下それぞれコードを補完していって完成させる場合の方向性があるように思うのです。皆さんの受け取り方は違うのかなぁ?

# re: もうちょっと具体的に 2007/01/16 15:43 菊池

ちなみに、この2つのコードは何を目的としたサンプルなの?

DALの公開メソッドだとしたら、DALとしては最も良くない例でしょう。
IDENTITYなんてのはDALの中でしか意味が無いデータなのだからIDENTITYをクライアントに露出する前提がまず駄目。

DALの内部メソッドだとしたら、DALの公開メソッドがラップしてるはずで、そこがクライアントとの責務関係を調整しているはずだから、ここで例外の利用可否とかそんな事を考えるのが無駄(決め次第だということ)、よって

>例外の読み替えをここで行うかどうかは「仕様」です。

渋木さんの言うとおり

>ただ登録処理の伝票名称の重複は不可となっている場合にはどうしましょう。

 重複「指定」が不可なのであれば、重複「指定」して呼び出したクライアントが悪い、投げるべきは事前条件エラーであり ArgumentException。ただし、その伝票名称の使用可否を調べる為の別メソッドを公開する義務が発生する。

 重複「登録」が不可なのであれば、重複「登録」が発生、検出されるのはDB側、SQLExceptionになるのも致し方無いが、SQLExceptionをそのまま投げるべきかは疑問。
 DALを用意した以上はクライアントに System.Data 名前空間を使わせたらDALの存在目的である「DBのカプセル化」に失敗してると言っても良いので、クライアントに「 catch( SQLException sqlExcep )」というコードが書かれる事を期待するのはDALを作った人にプライドが有ればあり得ない。
 パフォーマンス上その他の理由で正規化崩しを行うのと同様の結果としてSystem.Dataを使わせるしかない事になるかもしれないが、それを考えるのは最後の最後にするべきだと思う。

タイトル
名前
URL
コメント