- PR -

C# 自作関数の例外処理について

投稿者投稿内容
nodera
大ベテラン
会議室デビュー日: 2003/09/08
投稿数: 200
投稿日時: 2005-01-19 14:34
引用:

ただ、何でも例外を投げればいいってわけでもないですよね。
今回のように「外部から入力した文字列を日付として解釈したい」という話であれば、


あ、それはもちろんそうです。
文字列入力された文字の値が日付として妥当かチェックするだけであれば、メソッド定義としては、bool IsDate(string date)でいいんでないでしょうか。このメソッドは例外を発生させず、単純に日付として妥当だったかどうかだけbool値として返せばよいと思います。

引用:

問題は戻り値がint型やDateTimeの場合は 例外なら何を返したらいいか?


自分が気になったのはこちらに対する、ぼのぼのさんの結果コードによる拡張性についてです。
にしざき
ぬし
会議室デビュー日: 2003/06/30
投稿数: 304
投稿日時: 2005-01-19 14:48
引用:

noderaさんの書き込み (2005-01-19 14:34) より:
引用:

問題は戻り値がint型やDateTimeの場合は 例外なら何を返したらいいか?


自分が気になったのはこちらに対する、ぼのぼのさんの結果コードによる拡張性についてです。

了解です。
確かに、成功/失敗の区分と成功したときの値を別々に返すのは危険かもしれません。
ちなみに、これを返す例としては、思いつくもので Regex.Match(string, int, int) がありますね。
ぼのぼの
ぬし
会議室デビュー日: 2004/09/16
投稿数: 544
投稿日時: 2005-01-19 15:16
ぼのぼのです。

>自分が気になったのはこちらに対する、
>ぼのぼのさんの結果コードによる拡張性についてです。

説明が不足していたようで申し訳ない。

これは、例え拡張するにしても必ず「エラーは負の値」「正常ケースは0以上の値」にして、エラー判定は0以上かどうかで行う、というローカルルールが前提でした。

>だったら列挙型かなぁ

これもごもっともですが、あえてintにしたのは>=0だけでエラー判定が行える簡単さを求めた結果です。

自作の関数なんてのは所詮道具です。道具を作るときに考えなければならないことは2つあります。1つは「誰が使っても変な動きをしないこと」で、もう一つは「道具としての使い勝手が良いこと」です。この2つにそれぞれ点数をつけるとして、必ずしも両方に100点をつけられるものが作れるとは限りません。私は基本的に後者を優先します。例えそこに何らかのルールが発生してしまい、そのルールを守ることが前提になってしまったとしてもです。当然ながらあらゆる局面に適用できる考え方ではありませんが、この道具を最初に使うのは自分なので、とりあえず「自分にとって最大限に使いやすい道具であること」を私はとても重要視しています。
ナキヲ
常連さん
会議室デビュー日: 2003/08/22
投稿数: 32
お住まい・勤務地: 京都・自宅から勤務地まで自転車で40分
投稿日時: 2005-01-19 16:57
bool IsDate(string date)
だったら、data==nullの時に
ArgumentNullExceptionくらいのチェックは入れたくなります。
(チェックしなくてもdate引数はnullで無い文字列であるとしてIsDate内では扱い、
nullが来たらそこで例外が勝手に出るか…)
nullならfalseを返すとか、
bool IsDate(string date, bool nullIsInvallide)
として、使用者に明示的に選択させる、という案もあるでしょう。
判断が難しいところですね。

それから、
「誰が使っても変な動きをしないこと」と
「道具としての使い勝手が良いこと」は両立させる方が
良いかもしれません。

引用:
例えそこに何らかのルールが発生してしまい、そのルールを守ることが前提になってしまったとしてもです


作った本人でさえルールを破ってしまう恐れがあるので、
(「誰が使っても変な動きをしないこと」の中には作者も含まれる)
例外は有益だと考えます。


[ メッセージ編集済み 編集者: ナキヲ 編集日時 2005-01-19 16:58 ]

[ メッセージ編集済み 編集者: ナキヲ 編集日時 2005-01-19 17:04 ]
ぼのぼの
ぬし
会議室デビュー日: 2004/09/16
投稿数: 544
投稿日時: 2005-01-19 17:58
うーん、例外を使うことにも「誰が使っても変な動きをしないこと」と 「道具としての使い勝手が良いこと」を両立させることにも全く反対はしてないつもりなんですが…日本語って難しい
要はケースバイケースだということを言いたいわけで。ごく簡単な例をあげますね。

<やりたいこと>
・複数のテキストボックスに任意の形式で入力された日付をDBに格納
・テキストボックスが未入力ならNULLを格納
・テキストボックスが不正日付かどうかのチェックは一括で行う

この場合、個々のテキストボックスに対する入力チェックとデータ変換の流れはだいたい以下のようになります。

コード:

if (str == null || str.Length == 0) {
    obj = DBNull.Value;
} else {
    try {
        obj = DateTime.Parse(str);
    } catch(Exception) {
        //エラー情報追加(省略)
    }
}



この処理は繰り返し使われるので、自然な流れとして、この処理だけ抜き出して、例えばConvertDateという名前の関数にしよう、ということになります。
エラーチェックは複数項目に対して一括で行いたいので、この関数が例外を投げる実装にしてしまうと、使う側は関数を呼び出す度に小さなtryブロックで囲んでやらなければならなくなります。この場合は関数が「決して例外を投げない仕様」になっている方が「道具としての使い勝手が良い」し、逆に例外を投げることで「一括エラーチェック」という目的の元では「変な動き」をすることになります。
そういう意味で例外もケースバイケースだと言いたいんです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2005-01-19 21:12
引用:

jsさんの書き込み(2005-01-18 23:53)より:

コード:
public bool fnIsDate(object objDay) {
	try {
		string strDay = (string) objDay;
		DateTime.Parse(strDay);
		return true;

	} catch (Exception ER) {
		fnERLOG("fnIsDate",ER.Message);
		return false;
	} 
}




 指摘がありませんが、これ、危ないです。fnIsDate(ちなみに、fnはfunctionの意味だと思いますが、.NET Frameworkではそういう名前の付け方は推奨されていません)の引数は、objectクラスのインスタンスが1つですね。objectクラスはすべてのクラスの派生元なので、この場合、どんなクラスであっても受け付けることになります。

 ちょっと脇道。Excelのセルに"1"(数字の一)を書き込んで、そのセルの[書式設定]から[表示形式]を"日付"にすると、どうなるかご存じですか?"1900/1/1"になります。これは、Excelでは1899/12/31を0とする通算日で日付を管理できるからです。

 同じように、.NET Frameworkでも、0001/1/1(実際にはそんな日はありません)を0とする通算日(正確には小数点以下に時間を表現)で日付を表現できます。
 ということは、fnIsDate(1234)は、1234という数値は#5/19/0004#という日付も表すので、trueが返ってこないといけないのです!!でも、このコードだとfalseになると思います。メッセージは「1234は有効な日付ではありません」というようなものだと思います。これは1234という数字を"1234"という文字列に変換したため、通算日扱いにならないためです。
# 数値は値型なので、派生とかいうのは正しくないのですが

 また、引数がstringクラスにキャストできない場合もfalseになります。オブジェクトが文字列を経由して日付に変更できるか、オブジェクトが直接日付に変更できるか、という違いが出てきます。

 どういうことかというと、実際の仕様とメソッドの定義が一致していないということです。単にこの例だけではそれほど大きな問題ではないかもしれません。しかし、チームで開発する場合は十分に気をつけないといけないところです。


 そういうわけで、このメソッドの引数はstringクラスのインスタンスを1つに変更するのが無難でしょう。または、引数がDateクラスのインスタンス(実際にはDateは値型なのですが)であればtrueとするか。
コード:
1.
public bool IsValidDateString(string DateString) {
	if (System.Text.System.Text.RegularExpressions.Regex.IsMatch(
			DateString, "^\d{1,4}/\d{1,2}/\d{1,2}$")) {
		try {
			DateTime.Parse(DateString);
			return true;
		} catch (Exception ex) {
			fnERLOG("IsValidDateString", er.Message);
			return false;
		}
	} else {
		// yyyy/M/dの形式ではないので、
		// パースするまでもなくfalse
		// もっとも、Dec.24 2004 という形式もあるのだが
		fnERLOG("IsValidDateString", "形式が無効");
		return false;
	}
}

2.
public bool IsDate(object obj) {
	if obj is Date then
		return true;
	else
		return false;
}



_________________
vincent
大ベテラン
会議室デビュー日: 2004/07/09
投稿数: 142
投稿日時: 2005-01-20 00:08
例外をどう扱うか、というのはけっこう「文化の違い」が
あるのではないかと思っています。

Cなどの手続き型言語が主流だった時代は、正常値として0以上の値を
返す関数は異常が発生した場合エラーコードとして-1を返す、
ポインタ関連ならnullを返すというやり方が(わりと)一般的でした。
しかし、C#などのオブジェクト指向言語では、正常に処理されない
ケースは例外をthrowするというのが基本であるとされています。

また、戻り値のとりうる範囲によって「複数の意味」を持たせては
いけないという議論もあります。たとえば、ゼロ以上だと
正常な「値」として使われるのに、-1のときは「値」ではなく
「エラーを意味する」ということになるのは一貫性がない、
というような話です。

(以下、なんとなくイメージ)
C言語の頃からやってきた人の考え方:
「例外なんか作ったらメモリを確保したりして重くなるじゃないか!
 まったく最近のプログラマはやたらとインスタンスばかり作るし、
 エラーチェックもろくにしないし…なっとらんわ!」

オブジェクト指向でやっている人の考え方:
「いつ変わるかわからないエラーコードのチェックなんて
 呼び出し元でいちいち書きたくないんだよ。そっちの責任だろ。
 あとバカの一つ覚えで-1返されたって、何のことだかわかんないんだよ!」
nanbu
大ベテラン
会議室デビュー日: 2004/08/19
投稿数: 178
投稿日時: 2005-01-20 19:07
南部です。

最近では、検査例外と非検査例外(実行時例外)の
区別はあまりないんでしょうか。 > 仕事でやっている方々

私は、検査例外が必要なところでは、戻り値で判断。
そうでないところは、例外をスローという認識でした。

検査例外が必要なところって、try〜catchを強制させるところですが、
.NETには検査例外はないので、戻り値になるわけです。
つまり、「戻り値があるメソッドの戻り値はチェックすべし」
という意識があって成り立つことではありますが、、、



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