- PR -

C# 戻り値の型を動的に変更することは可能ですか?

投稿者投稿内容
ぼのぼの
ぬし
会議室デビュー日: 2004/09/16
投稿数: 544
投稿日時: 2005-05-10 20:44
ぼのぼのです。

引用:

tosiroさんの書き込み (2005-05-10 18:05) より:
例外の話は9ページにわたって議論しなければならないほど
分かりづらいのですね。

最終的にどなたか箇条書きでまとめて頂けませんか?
素人でもわかるように...。
(箇条書きで書けるようなら9ページにわたって討論していないのでしょうか???)


私の読んだ限り、論点を集約すると.NETのエラー通知には例外を使うべきか否かってことになるかと思うのですが、9ページにわたる議論の末、まだ結論は出ていないんじゃないでしょうか?

「想定の範囲」は結局アプリケーション(or 人?)に依存する、という話が出てましたが、例えばクラスライブラリの場合は一つのアプリケーションのことだけ考えればいい、ということにもなりません。

例えば、アプリケーションAから使用されるクラスライブラリAがあり、この中に数値形式の文字列をカンマ埋めする関数AddComma()があったとします。で、アプリケーションAでは「数値として認識できない文字列は0として扱う」という業務ルールがあったとします。すると、呼ばれる側、呼ぶ側のロジックは単純に考えて以下の3パターンになるかと思います。

コード:

'呼ばれる側@A
Public Shared Function AddComma(ByVal str As String) As String
Return Format(CDec(str), "#,##0")
End Function

'呼ぶ側@
Try
str = AddComma(str)
Catch ex As Exception
str = "0"
End Try

'呼ぶ側A
If IsNumeric(str) Then
str = AddComma(str)
Else
str = "0"
End If

'呼ばれる側B
Public Shared Function AddComma(ByVal str As String) As String
If IsNumeric(str) Then
Return Format(CDec(str), "#,##0")
Else
Return "0"
End If
End Function

'呼ぶ側B
str = AddComma(str)


さて、どれが正しいのでしょうか?

@は想定範囲内のエラーの通知にも例外を使用する、という設計方針なのでAddComma()で入力が数値形式でない場合は例外をthrowします。Aの場合、AddComma()は入力文字列が数値形式であることを前提としているので、入力が数値形式でない場合は想定範囲外ということになり、@と同じく例外をthrowします。設計方針は違っても結果として呼ばれる側のソースコードは同じになります。
Bは「数値として認識できない文字列は0として扱う」という業務ルールをクラスライブラリ側で吸収しています。

私なら、このクラスライブラリがアプリケーションAのみ、もしくは同じ業務ルールが適用されるアプリケーションのみから使用されることが前提ならBにするでしょう。使う側のコードが一行ですみますから。しかし、このクラスライブラリが将来的に異なる業務ルールのアプリケーションからも使用される可能性があるなら@Aのどちらかにするでしょう。

[ メッセージ編集済み 編集者: ぼのぼの 編集日時 2005-05-10 22:12 ]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2005-05-10 22:50
引用:

データベースを使用するWebアプリケーションの業務フローでの
「データベースに接続できない」と
データベースを監視するアプリケーションでの
「データベースに接続できない」は違います。


 確かに、違うものだと思います。
 ただ、それを、どこで実装するか。または、『切り替える責任を持つ』ものを実装するのか、と考えると、アプリケーションに依存するものではない、と思います。


 例えば、1つのソリューションとして、データベースを使用するアプリケーションと、データベースを管理するアプリケーションがあるとします。このソリューションに、「選択されたデータベースに接続し、接続オブジェクトを返す」メソッド、‘DB接続メソッド’を作ります。

 南部さんのおっしゃることは・・・

※データベースを使用するアプリケーション
 →そのまま‘DB接続メソッド’を使う
 →メソッドからの例外はキャッチしない

※データベースを監視するアプリケーション
 →「選択されたデータベースに接続できるか検査し、booleanを返す」メソッド、‘接続検査メソッド’を追加する
 →‘接続検査メソッド’で事前に検査することで、‘DB接続メソッド’の例外を避ける

というような作りになると思います。

 そうすると、同じソリューションの中で、「データベースに接続する」という同じことを実行するために、別々の実装をしなければなりません。これは不便ですし、混乱します。デザインガイドやルールは、混乱を起こすものではないと思います。
 例外によって通知するなら、使用するアプリケーションも監視するアプリケーションも同じ形で呼び出しができ、キャッチブロックの中の実装によって動作を変更するだけで済みます。

 業務的に考えると、監視するアプリケーションでは、データベースに接続できないことは、とてもよくあり得ることだと思います。
 使用するアプリケーションで、接続できないことは確かに致命的な例外、エラーでしょう。しかし、想定されない事態ではないと思います。同じマシンにインストールされているならともかく、違うマシンにインストールされているなら、経路が様々な要因で切れることは想定されていなければならないと思います。

 例えば、1画面に10の項目があって、ユーザがこれらの項目に入力を行い、「登録」ボタンをクリックしました。しかし、データベースに接続できませんでした。ここで、「データベースに接続できないことは、アプリケーションとしては想定範囲外」として落ちてしまう(例外をキャッチしない)と、ユーザの入力は失われてしまいます。
# 一部、「継続」を選べる例外もありますけど
# 逆にユーザは、「終了」と「継続」、どちらを選べばよいのか迷うと思う


引用:

経験に関係なくビギナーもベテランも従えるものがガイドラインやルールではないでしょうか?


ということで


# 「ガイドラインがよくわからんぞ」と、MSDNをフィードバックするか?


まとめて失礼
甕星さん>
引用:

入力された値が仕様上定められた範囲内か否かは、当然値が入力された直後に行われているものと思います。少なくとも中堅以上のエンジニアなら、入力値をチェックする処理ぐらい入れてくれると思っています。


えっと、その「入力値をチェックする処理」が、例外を返すのか、チェックメソッドとしてあらかじめ検査するのか、ということだと思うのですが???

_________________
nodera
大ベテラン
会議室デビュー日: 2003/09/08
投稿数: 200
投稿日時: 2005-05-11 10:06
こんにちは。

引用:

ぼのぼのさんの書き込み (2005-05-10 20:44) より:
<略>
さて、どれが正しいのでしょうか?



正しいかどうかは分かりませんが、自分だったら「呼ばれる側(1)(2)」と「呼ぶ側(2)」を選びます。なぜかというと「呼ばれる側(1)(2)」「呼ばれる側(3)」にしても、「数値として認識できない」という条件と同時に、次の暗黙の条件が存在しているからです。
・引数はnullであってはならない
・引数はDecimalの範囲を超えてはならない
この条件が満たされなければ、どちらの関数にしても例外が発生します。
(VB.NETを良く知らないですが、CDec()ってDecimal.Parce()と同じですよね?)

自分であれば、「数値形式の文字列をカンマ埋めする関数」という仕様をもったメソッドを作ったときに上記件を考慮し、ライブラリのドキュメントには引数で渡す文字列はDecimalに変換できる形でなければいけない(というニュアンス)の一文を加えておきます。
もっと良いのは、無効な引数が渡された場合は、Decimalに変換する際に発生する3つの例外もドキュメントにあわせて記述しておきます。

そうすれば使う側は、呼び出す前に事前検査をおこなうでしょう。
自分は、提供側は前提が破られた場合は例外によって通知する方が確実であり、逆に要求側は例外が発生する要因が事前に分かるのであれば、事前検査すべきだと思っています。
(IsNumericが上記3つの例外発生要因を検査できるものなのかよくわかりませんが)

また、今はライブラリの中(コード)が明らかになっていますが、完全にブラックボックスだった場合、上記件がドキュメントに記載されていなければ、「数値形式の文字列」という部分しか想定できないですが、やはり「呼ぶ側(2)」で実装です。

想定外の例外(未処理の例外)が発生した場合、そのままにしておくとアプリケーションが落ちてしまうということになりますが、未処理の例外に対しては、かならずUnhandledExceptionEventHandlerやThreadExceptionEventHandlerで受け取り(WinAplの場合)、ログを出力するなり、最終的にどうするか(続行するのか、終了するのか)といった判断をユーザーに促すようにしています(ここもアプリケーションの仕様によるけど)。
nanbu
大ベテラン
会議室デビュー日: 2004/08/19
投稿数: 178
投稿日時: 2005-05-12 07:07
南部です。

引用:

Jittaさんの書き込み (2005-05-10 22:50) より:
引用:

データベースを使用するWebアプリケーションの業務フローでの
「データベースに接続できない」と
データベースを監視するアプリケーションでの
「データベースに接続できない」は違います。


 確かに、違うものだと思います。
 ただ、それを、どこで実装するか。または、『切り替える責任を持つ』ものを実装するのか、と考えると、アプリケーションに依存するものではない、と思います。


業務エラーは、業務フローに対してのエラールートを想定する以上、
アプリケーションに依存すると思うのですが、、、

引用:

 そうすると、同じソリューションの中で、「データベースに接続する」という同じことを実行するために、別々の実装をしなければなりません。これは不便ですし、混乱します。デザインガイドやルールは、混乱を起こすものではないと思います。
 例外によって通知するなら、使用するアプリケーションも監視するアプリケーションも同じ形で呼び出しができ、キャッチブロックの中の実装によって動作を変更するだけで済みます。


同じソリューション内でも、ある機能を持つクラスを継承なり、委譲なりで機能を追加することってありませんか?

> 例外によって通知するなら
たしかに、業務エラーを「例外」で表すことは、コーディング上は可能です。
業務エラーを「例外」で表現すべきでない〜に関しては、既に出てますね。

引用:

 業務的に考えると、監視するアプリケーションでは、データベースに接続できないことは、とてもよくあり得ることだと思います。
 使用するアプリケーションで、接続できないことは確かに致命的な例外、エラーでしょう。しかし、想定されない事態ではないと思います。同じマシンにインストールされているならともかく、違うマシンにインストールされているなら、経路が様々な要因で切れることは想定されていなければならないと思います。

 例えば、1画面に10の項目があって、ユーザがこれらの項目に入力を行い、「登録」ボタンをクリックしました。しかし、データベースに接続できませんでした。ここで、「データベースに接続できないことは、アプリケーションとしては想定範囲外」として落ちてしまう(例外をキャッチしない)と、ユーザの入力は失われてしまいます。


まず、「投げっぱなし」「例外をキャッチしない」は、
まったく最後までキャッチしないとは言っていません。
「業務フロー内で」と明記しているはずです。

「ユーザが入力したデータをデータベースに登録する」という業務フロー内で、
私は、データベースに接続できなかった場合の業務エラールートは設計しません。
(想定できたとしても)
それは、データベースに接続できることが前提だからです。
接続できなかった場合、例外がスローされ、
「ユーザが入力したデータをデータベースに登録する」という業務フローは終了です。
(業務フロー内ではキャッチしませんから)
この業務フローはそれ以上の余計なことはしません。

後は、この「例外」受け取り、処理すべきところで処理すればよいです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2005-05-13 19:33
南部さん>
 何度も読み返して、1つわからないことがあります。わからないというか、認識がずれているのがここかな、と。

 “業務フロー内で”と書かれているのはわかっているのですが、では、“業務フロー外”とはどこでしょう?

 私の中では、プログラム全体(アプリケーションの操作手順)が業務フローです。ですから、「業務フローの外で受ける」というと、プログラムのコーディング中では受けない、と解釈していました。CLRでは、そういう例外を受けて、「続行しますか?終了しますか?」と聞いてくれますから。その為、業務フローを続けることは、ユーザがアプリケーションの操作を続けられる状態を保つことと、ほぼ同じでした。
 しかし、厳密に考えると、“業務{代行|支援}フロー”ですから、この中から本当の“業務フロー”である部分を切り分けるということですね?
未記入
ぬし
会議室デビュー日: 2004/09/17
投稿数: 667
投稿日時: 2005-05-14 02:12
引用:
“業務フロー内で”と書かれているのはわかっているのですが、では、“業務フロー外”とはどこでしょう? 私の中では、プログラム全体(アプリケーションの操作手順)が業務フローです。


あ、私も同じ疑問を持っていました。

引用:
「ユーザが入力したデータをデータベースに登録する」という業務フロー


このような表現が出てくるので、プログラムは複数の業務フローから構成される? ということなのかと考え直してしまったり。

私の場合は、業務フローという言葉をもっと広い範囲を指すコトバとして使います。業務フローってプログラム(つまりコンピュータシステム化された範囲)だけじゃなくて、人間、伝票回付、物品の入出庫など(コンピュータシステム化されていない部分も含め)業務全体の流れを業務フローって言いませんか? うちの会社だけかな。

--
タグが閉じてなかった・・・orz

[ メッセージ編集済み 編集者: 未記入 編集日時 2005-05-14 14:44 ]
nanbu
大ベテラン
会議室デビュー日: 2004/08/19
投稿数: 178
投稿日時: 2005-05-15 03:13
南部です。

引用:

Jittaさんの書き込み (2005-05-13 19:33) より:

 “業務フロー内で”と書かれているのはわかっているのですが、では、“業務フロー外”とはどこでしょう?

 私の中では、プログラム全体(アプリケーションの操作手順)が業務フローです。ですから、「業務フローの外で受ける」というと、プログラムのコーディング中では受けない、と解釈していました。CLRでは、そういう例外を受けて、「続行しますか?終了しますか?」と聞いてくれますから。その為、業務フローを続けることは、ユーザがアプリケーションの操作を続けられる状態を保つことと、ほぼ同じでした。
 しかし、厳密に考えると、“業務{代行|支援}フロー”ですから、この中から本当の“業務フロー”である部分を切り分けるということですね?



引用:

未記入さんの書き込み (2005-05-14 02:12) より:

私の場合は、業務フローという言葉をもっと広い範囲を指すコトバとして使います。業務フローってプログラム(つまりコンピュータシステム化された範囲)だけじゃなくて、人間、伝票回付、物品の入出庫など(コンピュータシステム化されていない部分も含め)業務全体の流れを業務フローって言いませんか? うちの会社だけかな。


!!!たしかに曖昧でした。
というか、私が使用を誤っているのかな?
私の認識、どういう意味で使用していたかを記述します。
#い、いまさら、、ですが、、、ごめんなさい。

「業務フロー」は、Jittaさん、未記入さんが
おっしゃるとおり、業務全体の流れを指します。
「業務における始点から終点までの流れ」
が的確でしょうか。
そして「ビジネスメソッド」は「業務フロー」を実現する手段、行動です。
アプリケーションはこの「業務フロー」をプログラムによってシステム化
したものですが、システム化する「業務フロー」の規模によっては、
1つのメソッドや1つのアプリケーションで完結するものや、
たくさんのアプリケーションによって実現されるものあるでしょうし、
アプリケーションの処理が連続である場合もあるし断続であることも
あるでしょう。

「業務フロー内」は、
ある業務を実現するビジネスメソッドにより構成されるフロー内であり
アプリケーション内で業務フローの一部を表している(実装している)部分、
簡単な例ですが、、、
コード:
private void button1_Click(object sender, System.EventArgs e)
{
    業務フロー開始();
}

public void 業務フロー開始() 
{
    処理1();
    if(処理状況.異常 == 処理2()) 
    {
        //業務エラールート
        処理2_1();
        return;
    }
    処理3();
}

protected virtual 処理状況 処理2() 
{
    try 
    {
        //処理

        return 処理状況.完了
    } catch (適切なエラーキャッチ)
    {
        //必要であればエラーコードで分岐
        if(〜)
            return 処理状況.異常;

        //業務フローを抜けるため例外をスロー
        throw new MyApplicationException("説明");
    } finally 
    {
        //後処理
    }
}


のような実装の場合、「業務フロー内」は
業務フロー開始メソッド内です。

Jittaさん、未記入さんのご指摘のとおり、実際はユーザが業務を
開始しようとアプリ(マシン?)を起動したときから既に業務フローは
始まっていますが、アプリ的には自分が実現している業務の実装部分、
つまり、ユーザのアクションなどによって要求される「業務的な処理」を
実装している「業務フロー開始メソッド内」に対して「業務フロー内」
という言葉を使用しました。

ボタン1をクリックし、ボタン2をクリックするという操作である
業務フローの場合、業務フローはそれぞれのハンドラで断続的に実行される
業務的な処理のフローということになります。

例外発生時(MyApplicationException)は、通常、集約エラーハンドラで、
処理されると思いますが、ボタン1クリック時の例外発生時に特有の処理を
させたい場合、button1_Click内でMyApplicationExceptionをキャッチするのも
「私は」ありだと思っています。

「業務フロー開始メソッド」 がデータベースに接続されていることを
前提としていた場合(処理2でデータベースアクセス)、たしかに、
「データベースに接続できないという例外」は想定できます。
が、この場合、この業務フロー内でキャッチすべきではありません。
言い換えれば、この業務フロー内で「データベースに接続できない」に
対する業務エラールートを作成すべきではありません。
(データベースに接続されていることを前提としていた場合ですので)

この業務フローをWebアプリケーションとして実行する場合、エラーログを
出力し、ユーザに適切なメッセージを表示するでしょう。
Windowsアプリケーションの場合、「接続できない例外」に対する処理として
「業務フロー開始メソッド」の外でキャッチし、接続設定ダイアログを
表示するというのもアリじゃないかと、、、。

#語句の使用が不適切だったら教えてください。
#そもそも、認識が誤りという可能性は大いにありますが。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2005-05-23 21:39
 『.NET エンタープライズ Web アプリケーション開発技術大全 Vol.3 ASP.NET 応用編』は買っていなかったので、買ってきました(間違ってVol.2を2冊買ってしまった(;_;))。で、4章を何度か読んだのですが。。。

 この中で、“業務ロジック”は、南部さんがおっしゃるような形で定義されているようです(図4-11など)。もしくは、“UI層”、“ビジネス層”、“データアクセス層”をきちんと分けた場合、「データベースにアクセスできない」というケースは“データアクセス層”としては想定されていないので、例外として通知する、という形(図4-14など)でしょうか。ユーザ入力の妥当性については、UI層で検査し尽くしておき、ビジネス層へは妥当なデータしか渡すな、という考え方でしょう。入力として妥当なはずのデータなので、それが設定できないようなケースはアプリケーションエラーである、と。


 しかし、とってもとっても混沌としているのが、P259でしょうか。
引用:

(.NETとJavaの)どちらを利用して実装する場合でも、以下の2点にはまったく違いがない。

  • 設計時に「業務エラー」と「アプリケーション/システムエラー」の分類を的確に行い、その上で実装しなければならない。
  • 状況によって、「業務エラー」を「アプリケーション/システムエラー」に切り替えたり、「アプリケーション/システムエラー」を「業務エラー」に切り替えるコードが必要になる場合がある。

 しかし、提供されているクラスライブラリレベルのインタフェースの設計(特にエラールート)に関しては、.NETベースクラスライブラリとJavaクラスライブラリとで大きく異なっている。


 結局、『分類を的確に行』うのは、経験や勘に頼るしかないのでしょうか。頼るというか、それによって左右されるというか。
 これについては、P253に書いてありました。
引用:

 …「業務エラー」と「例外」に関しては、「どこまでを業務エラーとみなすのか」について、ある程度の恣意性がある。
 …「設計の想定範囲内か否か」というだけでは、業務エラーと例外を綺麗に切り分けることは難しく、またシステムの特性や設計ポリシー、あるいは設計者によってもある程度のブレが生じてしまう…
 このような迷いが生じた場合には、そのエラーが「業務的なエラーなのか否か」を考えて頂きたい。


 いやだから、そのエラーが「業務的か否か」の基準が欲しいんだって(^^;。データベースが動いていて当然と考えるのか、「マシントラブルやメンテナンス、その他なんやかんやで接続できない状況もあり得るが、数秒後あるいは簡単に行える行為によって回復可能かもしれない」と考えるかは、その設計者が出会ったトラブルの量や種別によって違ってくると思うんだけどなぁ。。。

 または、業務設計を行う際に「できて当然」と考えるかどうか、という切り分け方をしています。そういうきり分け方をすると、「ユーザの入力は正しくて当然」になってきます。そうなってくると、入力値の組み合わせによるエラー以外はすべて例外として実装、みたいな考え方になってくるんですけど。極端な言い方をすると、スクリプトインジェクションや、クロスサイトスクリプトなどの入力も、例外としてはじく、と。で、これは“業務フロー”の中で受けてはいけない、と。もっとも、『「アプリケーション/システムエラー」を「業務エラー」に切り替えるコード』によって、業務エラーとするべきなのでしょうが、だとすると受けてはいけない例外を受けろ、と言っているわけで。ああ、「受けなかったことにする」のか。

 う〜ん、難しいです。余計に混乱しました。。。
 まぁ、Javaをやっている人にとっては、「検査例外は戻り値の一部で返せ。実行時例外だけを例外で返せ。」と言えばいいのかもしれないけれど。


 何はともあれ、結果的にすべての例外を補足し、メッセージを変更しなければならないのは、P264あたりで遠回しに書いてあります。
引用:

Messageプロパティは、事後的な障害解析に必要な、詳細情報を保存しておくために利用される。
Messageプロパティをそのままエンドユーザへ見せるべきではない。



_________________

スキルアップ/キャリアアップ(JOB@IT)