- ぼのぼの
- ぬし
- 会議室デビュー日: 2004/09/16
- 投稿数: 544
|
投稿日時: 2005-05-07 00:36
ども、ぼのぼのです。
既にまとまったところを蒸し返すようで申し訳ないのですが、
誰も挙げてないので…
引用: |
|
k-nakさんの書き込み (2005-05-03 04:46) より:
ここらへんの話、私なりに整理するとこんな感じですかね。
コード: |
| 【仕様レベル】
+----------+------------+-----------+
| 正常応答 | エラー応答 | 異常応答 |
| | (*1) | (*2) |
+----------+------------+-----------+
| 正常系 | 異常系 |
+-----------------------+-----------+
*1 … 入力エラーや権限不足エラーなど
*2 … システムエラー、アプリケーションエラー
【DotNET】
+----------+------------+-----------+
| 戻り値(ref/out含む)| Exception |
+-----------------------+-----------+
【Java】
+----------+------------+-----------+
| 戻り値 | Exception | Error |
| | (検査例外) | |
+----------+------------+-----------+
|
>> Jittaさん
未記入さんが言われているのは、「DotNETの例外はJavaの検査例外とは別物だから、Javaの検査例外と同じような感じで『投げちゃいけない』、『キャッチしちゃいけない』のですね」ってことなんだと思います。
ここらへん私も前々から気になっておりまして、DotNETのデザインガイドに厳密に従うと、エラー応答処理があちこちに分散するか、もしくはあちこちのメソッドでエラー情報を返すoutパラメータだらけになるかと思うのですが……、実際問題、どのような設計をされてますか?
私はデザインガイド違反を承知でJavaっぽくやっちゃってます^^;
|
DotNETのデザインガイド(ルール?)とか、デザインパターンとかに関してはまだまだ全然素人な私ですが、
Javaの検査例外⇒自分で作る
JavaのError⇒.NETクラスライブラリにもとからある例外を使う
という設計って、あまり一般的ではないのでしょうか?
簡単に例をあげると、こんな感じです。
コード: |
|
int ret;
string errMsg;
try {
ret = TestClass.myFucntion(value1);
} catch (ChkException ex) {
errMsg = ex.ToString();
}
public class TestClass
{
public static int myFucntion (int value1)
{
if (value1 < -9999 || value1 > 9999) throw new ChkOverflowException();
return value1 * 10000;
}
}
public class ChkException : Exception
{
private const string defaultMessage = "検査例外が発生しました。";
public ChkException() : base(defaultMessage) {}
public ChkException(string message) : base(message) {}
}
public class ChkOverflowException : ChkException
{
private const string defaultMessage = "入力値が範囲を越えています。";
public ChkOverflowException() : base(defaultMessage) {}
public ChkOverflowException(string message) : base(message) {}
}
|
|
- Jitta
- ぬし
- 会議室デビュー日: 2002/07/05
- 投稿数: 6267
- お住まい・勤務地: 兵庫県・海手
|
投稿日時: 2005-05-07 05:54
南部さん>
ちょっと例を変えさせてください。
商品クラスがあります。このクラスには、定価と売り出し時の割引率が定義されています。たいていの場合、原価に利益率を乗せて定価を出しますが、ここでは売り場で割引率が設定でき、割引率を入力する画面とします。
ここで、割引率の設定についてですが、テキストボックスに、0〜100を設定できるとします。100%を超える値はもちろん設定できません。また、売価が原価を下回るような設定をすると損が出ますから、そのような設定ができるのもまずいでしょう。
まず、原価、定価、割引率、売価を次のように宣言したとします。
これに対して、割引率を設定するメソッドを用意します。
今のところ、私はセットアクセスメソッドにて、例外を送出するようにコーディングしています。
コード: |
|
class Goods {
private decimal costPrise; // 原価
private decimal prise; // 定価
private decimal discountRate; // 割引率
public decimal SellingPrise { // 売価
get {
return Math.Floor(prise * discountRate);
}
}
public decimal DiscountRate {
get {
return discountRate * 100D;
}
set {
decimal b = discountRate;
discountRate = value / 100D;
if (value > 100D || SellingPrise < costPrise) {
discountRate = b;
// とりあえず、定義済みをスロー
throw new ArgumentOutOfRangeException(...);
}
}
}
}
呼び出し側
try {
goods.DiscountRate = dcimal.Parse(TextBox1.Text);
} catch (ArgumentOutOfRangeException ex) {
// 入力値が規定を逸脱
....
} catch {
// 入力値が異常(パースできない/Decimalの範囲を超える)
....
}
|
これを、デザインルールに従うようにすると、次のようになる???
または、とりあえず定義済みを投げていますが、アプリケーションで定義した例外を投げるようにすればいい???
コード: |
|
...宣言省略...
public bool isDiscountRateValid(decimal value) {
if (value > 100D || Math.Ceiling(price * value) < costPrise)
return false;
else
return true;
}
public decimal DiscountRate {
get {
return discountRate * 100D;
}
set {
discountRate = value / 100D;
}
}
...呼び出し...
if (!Regex.IsMatch(TextBox1.Text, "^\d{1,3}$", RegexOptions.None)) {
// 入力値が異常(数字3桁以下ではない)
return;
}
decimal val = decimal.Parse(TextBox1.Text);
if (goods.isDiscountRateValid(val) == false) {
// 入力値が規定を逸脱
...
} else {
// 正常処理
goods.DiscountRate = val;
}
|
それで、MSDNを「例外 デザイン」で検索してみましたが、[エラーの発生および処理のガイドライン]に記載されていることが根拠でしょうか。“ガイドライン”といいながら、“規則”とも書いたあるところが、何とも。。。
引用: |
|
次の規則は、エラーの発生および処理のガイドラインを示しています。
- 例外を発生させる結果となるすべてのコード パスには、例外をスローすることなく正常に処理を続行できるかどうかを確認するためのメソッドを用意する必要があります。たとえば、FileNotFoundException を避けるには File.Exists を呼び出します。これは、常に可能な処置ではありませんが、通常の実行時に例外がスローされることをなくすことを目的としています。
- ほとんどの場合は、定義済みの例外の型を使用します。新しい型の例外を定義するのは、クラス ライブラリを使用する開発者がその新しい例外型をキャッチし、その型自体に基づいてプログラムによるアクションを実行することが想定される場合だけに限ります。この方法は、例外文字列を解析する代わりに使用します。例外文字列を解析すると、パフォーマンスと保守性に悪影響を与えることになります。
たとえば、開発者が存在しないファイルを作成できるように、FileNotFoundException を定義しておくことは意味のあることです。ただし、FileIOException は、通常はコードで特別に処理される例外ではありません。
- 通常のエラー、予期されるエラー、または正常な制御フローに対して例外を使用しないでください。
- 特に、一般的なエラーが発生した場合は、null を返すようにします。たとえば、File.Open コマンドは、ファイルが見つからない場合に null 参照を返しますが、ファイルがロックされている場合は例外をスローします。
- 通常に使用した場合には例外がスローされないようにクラスをデザインします。次のコード例の FileStream クラスは、開発者がファイルの末尾を超えて読み取ろうとしたときにスローされる例外を回避できるように、ファイルの末尾に達したかどうかを判断するための別の方法を公開しています。
- エラー コードや HRESULT を返す代わりに、例外をスローします。
- できるだけ固有の例外をスローします。
|
_________________
|
- nanbu
- 大ベテラン
- 会議室デビュー日: 2004/08/19
- 投稿数: 178
|
投稿日時: 2005-05-07 18:41
南部です。
引用: |
|
Jittaさんの書き込み (2005-05-07 05:54) より:
それで、MSDNを「例外 デザイン」で検索してみましたが、[エラーの発生および処理のガイドライン]に記載されていることが根拠でしょうか。“ガイドライン”といいながら、“規則”とも書いたあるところが、何とも。。。
|
何度も読みましたが、、、、、理解できた自信はありません。
なので、本スレッドの回答は、
「.NET エンタープライズ Web アプリケーション大全 Vol.3」
の4.4で言及されている内容にとどめていました。
こちらは、自分なりに理解できたと思っています。
ここからは、私の解釈に拠ります。
Jittaさんの例で、元のコードと変更後コードの違いは、
1.割引率の正当性チェックするメソッドを追加
2.割引率プロパティ内でのチェック及び例外スローを廃止
※呼び出しメソッド内は省略
この変更において適用された規則は
1・例外を発生させる結果となるすべてのコード パスには、例外をスローすることなく正常に処理を続行できるかどうかを確認するためのメソッドを用意する必要があります。(略)
2.通常に使用した場合には例外がスローされないようにクラスをデザインします。(略)
だと思います。
確かに、文字だけ見ると規則どおりの変更なのですが、、、、
1の場合、MSDNの例だと例外(FileNotFoundException)が
スローされることをなくすためFile.Existsを呼び出すとあります。
これはファイルが無い場合の正常なフローが設計時に想定されている場合
(なければ作成とか、上書きするか確認するとか)
つまり、ほぼ業務エラーとして扱われ、正常な業務ルートに戻されるであろう
例外をスローするパスに対し事前に確認できるメソッドを用意すると解釈しました。
2の場合、FileStreamのReadByteを例に挙げています。
これは。明らかに例外ではなく、呼び出し元が業務エラーかどうか判断する必要が
ないものをスローしないと解釈しました。
なので、Jittaさんの変更後のコードは
1.割引率の正当性チェックするメソッドを追加
⇒あってもいいし、なくてもいい
2.割引率プロパティ内でのチェック及び例外スローを廃止
⇒元のコードのまま
あくまで私の解釈でしかありません。
|
- 猫山みやお
- 大ベテラン
- 会議室デビュー日: 2004/09/09
- 投稿数: 119
|
投稿日時: 2005-05-07 19:24
なかなか盛り上がってますね
要するに、C#とJavaは「違う物」
ってことです
|
- 未記入
- ぬし
- 会議室デビュー日: 2004/09/17
- 投稿数: 667
|
投稿日時: 2005-05-07 19:51
引用: |
| 要するに、C#とJavaは「違う物」ってことです
|
そんなことは、みんな分かった上で話をしているわけで・・・。
Java よりも .NET にかなり精通しているであろう Jitta さんが Java っぽい例外の使い方をするのは興味深いですね。Java 屋が例外を使えと言って、.NET 屋が例外をなるべく使うなと言う構図なら なんとも思わないんだけど。.NET に関してかなり詳しいであろう Jitta さんが例外を推奨しているところが、このスレの醍醐味かな。Jitta さんもデザインルールとやらには詳しくなかったようだし。たとえ catch が強制されない .NET でも例外を積極的に使っていくという文化は生まれるかもしれない。そもそも、C# って Java コミュニティの開発者流入を期待しているんだよね? Java 経験者が流れ込んでいったら 例外の使い方のスタンダードだって変わるかもしれないよ?
|
- Jitta
- ぬし
- 会議室デビュー日: 2002/07/05
- 投稿数: 6267
- お住まい・勤務地: 兵庫県・海手
|
投稿日時: 2005-05-07 21:37
事故レス。。。これだと、例外が発生することを想定しているのだから、業務フロー上のエラーなのか。。。
えっと、ユーザに入力してもらう数値のエラーを、2つ考えました。1つは「Decimalとして理解できない入力」、もう1つは「割引率として設定してはいけない入力」です。後者を、セットアクセスメソッドで、ArgumentOutOfRangeExceptionにて通知し、前者はDecimal.Parseが送出するであろう例外で検知しよう、としています。
ここで、「割引率として…」は、業務ルールとして設定しているのだから当然検査しなければなりません。しかし、「Decimalとして…」は、ここで検査すべきかどうかは微妙です。
例えば、TextBoxを拡張して、数字しか受け付けない、桁数をプロパティで設定できる…ああ!その為のValidatorコントロールか!!
例外のデザインルール…ガイドラインに従うなら、入力が0〜100の整数の範囲に収まるように、Validatorコントロールでチェックして、「入力値と定価の積が、原価より大きいかをチェックする」のもValidatorであらかじめチェックするべし、というところでしょうか。(。.ヾ
_________________
|
- Jitta
- ぬし
- 会議室デビュー日: 2002/07/05
- 投稿数: 6267
- お住まい・勤務地: 兵庫県・海手
|
投稿日時: 2005-05-07 22:16
引用: |
|
未記入さんの書き込み (2005-05-07 19:51) より:
|
私のは付け焼き刃です。メッキはすぐにはがれる、、、って、おい
MSDNのガイドライン記述には一通り目を通していますが、「読んだ」=「理解した」ではありませんから(^-^;
っつうか、Javaは知らない、C++でもほとんど例外は使わなかった、という前提で、例外のガイドラインを読んで、検査メソッドと例外使用の落とし所を捕まえきれなかった、というところです。
[投稿日時: 2005-05-07 05:54]の変更後コードで、斜字体にしているところ、バグです。検査メソッドを多用すると、このような、ちょっとしたバグを作り込む可能性があります。
# いや、メソッド事態、慌てて修正したところがあって、いい例ではないですけど
経験的に、検査部分、特にユーザ入力の検査は修正が入ることが多いです。そのため、検査する箇所はできるだけ少なくしたい。そのためには深いところで検査して、例外で一気に通知する方が楽です。
[投稿日時: 2005-05-07 21:37]ではValidatorコントロールについて書きましたが、入力がUIだけでなく、ファイルからもあるなら、クラスのセットメソッドで検査して、両方に同じ形式で通知できる方が楽、と考えました。
また、メソッドの使用者に、「先にこのメソッドで検査してから、このメソッドを呼び出してください」というより、「このメソッドを呼び出してください。何か不都合があれば例外がスローされますので、キャッチしてください」の方が、伝える側も使う側も楽でしょう。
_________________
|
- unibon
- ぬし
- 会議室デビュー日: 2002/08/22
- 投稿数: 1532
- お住まい・勤務地: 美人谷 良回答(20pt)
|
投稿日時: 2005-05-07 22:34
unibon です。こんにちわ。
#私はできるなら例外は使わないほうが良いと考える主義です。
私の考えを書きますと、setter の戻り値を bool にして、引数が規定外の値だったら false を返し、そうでなければ true を返す、という良くあるスタイルにしたほうが良いと考えます。
以下は、Jittaさんの Goods クラスを例にしたコードです。(すぐに書けるのが Java なので Java になっていますが、C# でも理屈は同じはずです。)
コード: |
|
public class Goods {
private int costPrice; // 原価
private int price; // 定価
private double discountRate; // 割引率
public Goods(int aCostPrice, int aPrice) {
costPrice = aCostPrice;
price = aPrice;
discountRate = 1.0;
}
public int getSellingPrice(double aDiscountRate) { // 売価
return (int) Math.floor(price * aDiscountRate);
}
public int getSellingPrice() { // 売価
return getSellingPrice(discountRate);
}
public double getDiscountRate() {
return discountRate;
}
public boolean setDiscountRate(double aDiscountRate) {
if (aDiscountRate > 1.0 || getSellingPrice(aDiscountRate) < costPrice) {
return false;
} else {
discountRate = aDiscountRate;
return true;
}
}
public static void main(String[] args) {
Goods g = new Goods(80, 100);
boolean b = g.setDiscountRate(0.7);
if (!b) {
System.out.println("割引率を設定できません。");
return;
}
int sellingPrice = g.getSellingPrice();
System.out.println("売値は " + sellingPrice + " です。");
}
}
|
なお、これとは違って、set する前に検査する、というやり方も考えられますが、原子性(Atomicity)を考えると、set に検査を含めてしまうほうが良いと思います。
set ではありませんが、たとえば、ファイルをオープンするような処理の場合、ファイルのオープン(open)前にファイルの存在(exists)を検査し、大丈夫だった場合にオープンする、というやりかただと、exists を検査した後、かつ、 open する前、にそのファイルが削除されてしまったりすると、結局は open 時にエラーになってしまうのでわざわざ exists で検査する意味がなくなってしまいます。
|