再利用された例外タイプは、使い捨てのものよりも優先されるべきですか?


8

Doorが管理しているsがあるとしDoorServiceます。DoorServiceデータベースに格納されているドアを開く閉じてロックを担当しています。

public interface DoorService
{
    void open(Door door) throws DoorLockedException, DoorAlreadyOpenedException;

    void close(Door door) throws DoorAlreadyClosedException;

    /**
     * Closes the door if open
     */
    void lock(Door door) throws DoorAlreadyLockedException;
}

lockメソッドにはがありDoorAlreadyLockedException、openメソッドにはがありDoorLockedExceptionます。これはオプションですが、他にも可能なオプションがあります。

1)DoorLockedExceptionすべてに使用すると、lock()通話で例外をキャッチするときに少し面倒です

try
{
    doorService.lock(myDoor);
}
catch(DoorLockedException ex) // door ALREADY locked
{
    //error handling...
}

2)2つの例外タイプがDoorLockedExceptionあり、DoorAlreadyLockedException

3)2つの例外タイプがありますが、 DoorAlreadyLockedException extends DoorLockedException

どちらが最善の解決策であり、それはなぜですか?


1
「ベスト」をどのように定義しますか?C#では、例外もチェックしていません。これにより、開発が簡単になり、信頼性が高まると考えています。
ロバートハーヴェイ

2
AlreadyLockedとLockedの解像度はどのように異なりますか?
whatsisname 2018

@RobertHarvey例外をメソッドのロジックの一部と見なしています。最良の解決策は、最も明示的で最も論理的な解決策です。C#とは異なり、例外的なケースのキャッチを強制し、何が起こっているかを完全に理解していない人がメソッドを呼び出さないようにすることで、利便性よりも明示を促進しています。

1
これらのメソッドは、単にステータスコードを返す必要があります。返されたコードに基づいてユーザーに通知し、無料でパフォーマンスを向上させることができます(例外は高価であり、ステータスコードを返すよりも約1000〜10000倍高価です)。
ロバートハーベイ

1
@RobertHarvey「例外は高価であり、ステータスコードを返すよりも約1000〜10000倍高価です」私はこれについてJavaでいくつかの分析を行いましたが、例外のコストは非常に誇張されていることがよくあります。スタックが非常に深い場合、コストが高くなる可能性があります。IIRC、例外を心配するほど遅くするのに十分な大きさのスタックを作成できるようにするには、最大スタックサイズを大幅に増やす必要がありました。Springのような積み重ねた豚でもそれはできません。タイトなループでスローやキャッチを行わない限り、例外を回避するのは時期尚早の最適化です。
JimmyJames

回答:


21

フロー制御に例外を使用することで人々が大きな問題を起こすことは知っていますが、それがここでの最大の問題ではありません。巨大なコマンドクエリ分離違反が見つかりました。しかし、それは最悪ではありません。

いいえ、最悪はここです。

/**
 * Closes the door if open
 */

ドアの状態についてのあなたの仮定が間違っているがlock()、あなたのためにそれを修正するだけなのに、なぜ他のすべてが爆破するのですか?これはドアを開けるのをロックすることを不可能にすることを忘れてください、それは絶対に可能で、時々役に立ちます。ここでは問題はありません。間違った仮定に対処するために2つの異なる哲学を混在させていることです。それは紛らわしいです。それをしないでください。同じ命名スタイルで同じレベルの抽象化ではありません。わぁ!それらのアイデアの1つを外に持ち出してください。ドアサービスメソッドはすべて同じように動作するはずです。

コマンドクエリの分離違反に関しては、ドアが閉じているか開いているかを確認するためにドアを閉める必要はありません。私はただ尋ねることができるはずです。ドアサービスは、ドアの状態を変更せずにそれを行う方法を提供していません。これは、値を返すコマンド(CQSが何であるかについてよくある誤解)に比べて、これをはるかに悪化させます。ここでは、状態変更コマンドがクエリを実行する唯一の方法です。わぁ!

例外はステータスコードよりもコストが高いので、それは最適化の話です。十分な速さで十分です。本当の問題は、人間が典型的なケースの例外を期待しないことです。あなたが好きなすべての典型的なものについて議論することができます。私にとって大きな問題は、使用するコードをどの程度読みやすくするかです。

ensureClosed(DoorService service, Door door){
  // Need door closed and unlocked. No idea of its state. What to do?
  try {
    service.open(door)
    service.close(door)
  } 
  catch( DoorLockedException e ){
    //Have no way to unlock the door so give up and die
    log(e);
    throw new NoOneGaveMeAKeyException(e);      
  }
  catch( DoorAlreadyOpenedException e ){
    try { 
      service.close(door);
    }
    catch( DoorAlreadyClosedException e ){
      //Some multithreaded goof has been messing with our door.
      //Oh well, this is what we wanted anyway.
      //Hope they didn't lock it.
    }
  }
}

このようなコードを書かないでください。方法isLocked()など教えてくださいisClosed()。それらを使用して、自分ensureClosed()ensureUnlocked()読みやすいメソッドを作成できます。投稿条件に違反した場合にのみスローするもの。もちろん、あなたがすでにそれらを書いてテストしたことがあると思います。状態を変更できないときにスローするものと一緒に混ぜないでください。少なくとも、区別しやすい名前を付けてください。

何をするにしても、何も呼ばないでくださいtryClose()。それはひどい名前です。

用としてDoorLockedExceptionも持つ対だけでDoorAlreadyLockedException病気がこれを言う:それは使用してコードについてのすべてです。使用するコードを記述し、作成している混乱を確認せずに、このようなサービスを設計しないでください。使用するコードが少なくとも読めるようになるまで、リファクタリングと再設計を行います。実際、使用コードを最初に記述することを検討してください。

ensureClosed(DoorService service, Door door){
  if( !service.isClosed(door) ){
    try{
      service.close(door);
    }
    catch( DoorAlreadyClosedException e ){
      //Some multithreaded goof has been messing with our door.
      //Oh well, this is what we wanted anyway.
      //Hope they didn't lock it.
    }
  } else {
    //This is what you wanted, so quietly do nothing. 
    //Why are you even here? Who bothers to write empty else conditions?
  }
}

ensureUnlocked(DoorService service, Door door){
  if( service.islocked(door) ){
    throw new NoOneGaveMeAKeyException(); 
  }
}

lock()呼び出しているメソッドは、close()質問を書くときにちょうど私が怠けていたが、それを私が意識させることに感謝!実際のコードを書くとき、私はそれに落ちません。私の場合、3つのメソッドが実際に連続して呼び出されることはありません。例外はありません。例外がなければ、1つを呼び出します。それ以外の場合は、翻訳されたダイアログを表示し、場合によってはログアウトします(InvalidTokenExceptionの場合)。私はisLocked()isOpen()メソッドを持っていますが、それらはサーバーの制御フローで使用されますが、エラーダイアログを表示する必要があるかどうかを確認する方法として使用しないでください。これは例外の仕事です。

あなたの最後の点については、はい、使用するコードは少し良く見えますが、DoorAlreadyLockedExceptionツインクラスになります。

2
双子のクラスであなたが例外自体を意味する場合は、継承の私の好きな使用法を紹介させてください:public class DoorAlreadyLockedException inherits DoorLockedException {}。わかりやすい名前を付けるために、クラスを1行で定義します。継承するものを賢く選択してください。長い継承チェーンを回避するために、無意味に冗長ではないが、できる限り上位から継承することを好みます。
candied_orange

私は名前を変更するように感じるNoOneGaveMeAKeyためにNoOneGaveMeAKeyExceptionちょうどpendanticします。それ以外の場合は、処理の前にオブジェクトの状態を知る必要があるという重要な点に同意します。
Walfrat

2
悪魔の支持者-ただisOpen/ を持っているだけでisClosedは十分ではありません。ファイルシステムについて考えます。ファイルが存在する/存在しないことを確認できますが、ファイルを読み書きしようとするときに変更される可能性があり、IOException結果は次のようになります。このコンテキストでは、ドアを開閉したときに状態が無効になる可能性があります。
トムG

8

フロー制御にはさまざまな例外タイプが使用されていることを区別DoorLockedExceptionしてDoorAlreadyLockedException示唆しています。これは、すでにアンチパターンと広く見なされている手法です。

この害のないものに対して複数の例外タイプがあることは不合理です。追加の利点は、追加の複雑さを上回らない。

DoorLockedExceptionすべてに使用するだけです。

さらに良いことに、何もしないでください。ドアがすでにロックされている場合、lock()メソッドが完了してもドアは正しい状態のままです。目立たない状況では例外をスローしないでください。

それでも不快な場合は、メソッドの名前を変更してくださいensureDoorLocked()


4
「フロー制御の例外」ミームを使いすぎないようにしてください。2つの状況が何らかの理由で劇的に異なるアクションのコースを解決する必要がある場合は、異なる例外を設けることが理にかなっています。それが事実であるかどうかは、例から明らかではありません。
whatsisname 2018

1
@whatsisname:それが実際に根拠のないミームである場合は、その効果に対する独自の答えを投稿してください。
ロバートハーヴェイ

1
@RobertHarvey例外は、エラーダイアログとエラーログの「制御フロー」に使用されます。メインのロジックアプリケーションは、catch句を使用しても実行されません。私は区別しないと思いますし、それDoorLockedExceptionDoorAlreadyLockedException示唆しています。それlock()は、クラスがほとんど複製されているという犠牲を払って、メソッドに対してはるかに重要なエラーハンドラーを用意することです。

1
lock()を呼び出し、ドアがロックされている場合は、ドアがすでにロックされていることがわかります。すでに知っていることを言われる必要はありません。
ロバートハーヴェイ

2
@ewan:ああ、ffs。好きな名前を付けてください。列挙、結果、何でも。それは正しいアプローチです。
ロバートハーベイ

3

この質問の主題は、「リサイクルされた例外のタイプは、使い捨ての例外のタイプよりも優先されるべきか?」です。回答だけでなく、質問の詳細でも無視されているようです(質問の詳細に対する回答でした)。

回答者が提供したコードに対するほとんどの批判に同意します。

しかし、見出しの質問に対する答えとして、「使い捨ての例外」よりも「リサイクルされた例外」の再利用を強く好むべきだと私は言うでしょう。

例外処理のより一般的な使用では、例外が検出されてスローされる場所と、例外がキャッチされて処理される場所との間のコードに大きな距離があります。つまり、例外処理でプライベート(モジュラー)型を使用しても、通常はうまく機能しません。例外処理を備えた典型的なプログラム-例外が処理されるコード内のトップレベルの場所がいくつかあります。そして、それらはトップレベルの場所であるため、特定のモジュールの狭い側面について詳細な知識を持つことは困難です。

代わりに、いくつかの高レベルの問題に対する高レベルのハンドラーが含まれる可能性があります。

詳細なカスタム例外クラスを使用する唯一の理由は、サンプルで理にかなっているように思われます。これは(そして、ここでは他の回答者をインパクトしています)-通常の制御フローでは例外処理を使用しないでください。


1
「例外処理のより一般的な使用では、優れた距離があります」典型的なものを分析していません。私たちはそれが機能する方法を設計しています。多くの優れた設計は、コールスタックの非常に次のレベルで、非常に限定されたスコープで例外をキャッチします。問題から回復するだけの場合は、フロー制御としてそれを無視すべきではありません。最良の例外設計は、例外を定義する前に回復する方法を計画することから始まります。その場合、例外名は問題の報告に関するものである必要はありません。彼らは解決策を見つけることについてです。msg文字列を使用して問題を報告できます。
candied_orange 2018

「私たちは典型的なものを分析していません。それが機能するための方法を設計しています。」同意して、それはあなたがやっていたことです。それは私がやっていたことではありません。見出しを見ると、1つの質問がされています。メッセージの本文を見ると、3つの異なる(関連する)質問がされています。本文の質問に回答しました(ある程度、実際にはサンプルコードを批判しました)。見出しの質問に答えました。
ルイスプリングル

ところで、あなたは「多くの優れた設計は、コールスタックの非常に次のレベルで、非常に限定されたスコープで例外をキャッチします」と主張します。それについて完全に反対するかどうかはわかりません。しかし、私は40年のプログラミングの後、その主張を裏付ける例を思いつくのに苦労しています。私はそれが少しスピンオフしていることを認識しています(そして、私はこれを正しく処理しない可能性があります-stackoverflowの新機能)-そのようなローカライズされたtry / catch処理が最良のソリューションである例を1つまたは2つ提供していただけませんか?それは確かに典型的/一般的なパターンではありません。
ルイスプリングル

例え?私はをご紹介しましょうジョンスキート
candied_orange

OK、それはあなたが主張したものの例ではありません。「多くの優れたデザインは、コールスタックの非常に次のレベルで、非常に限定されたスコープで例外をキャッチします」と述べました。あなたの例は、エラーで例外を生成するだけのAPIを、代わりにセンチネル値を生成するAPIに変換するだけでした。たぶん、これは私たち2人の言葉遣いの違いにすぎません。私はそれを「良い設計」とは言いませんが、「使用しているツールで不便な設計の選択を回避するためのローカライズされたハック」です。
ルイスプリングル

0

未踏の代替品。あなたはあなたのクラスで間違ったものをモデル化しているかもしれません、代わりにこれらを持っていたらどうでしょうか?

public interface OpenDoor
{
    public ClosedDoor close();
    public LockedDoor lock();
}

public interface ClosedDoor
{
    public OpenDoor open();
    public LockedDoor lock();
}

public interface LockedDoor
{
    // ? unlock? open?
}

現在、エラーであるものを試みることは不可能です。例外は必要ありません。


ただし、データベースはいつでも変更できるため、ほとんどのアプリケーションではこれらのオブジェクトへの参照を保持できません。良いクリエイティブな努力

0

例を批判するのではなく、元の質問に答えます。

簡単に言えば、クライアントがケースごとに異なる反応をする必要があると思われる場合は、新しい例外タイプを保証します。

複数の例外タイプを使用することを決定したにもかかわらず、クライアントがそれらに共通のcatch句を作成したい場合があると予想する場合は、おそらく両方が拡張する抽象スーパークラスを作成する必要があります。


0

これは疑似コードですが、私が意味することは理解できると思います。

public interface DoorService
{
    // ensures the door is open, uses key if locked, returns false if lock without a key or key does not fit
    bool Open(Door door, optional Key key) throws DoorStuckException, HouseOnFireException;

    // closes the door if open. already closed doors stay closed. Locked status does not change.
    void Close(Door door) throws throws DoorStuckException, HouseOnFireException;

    // closes the door if open and locks it
    void CloseAndLock(Door door, Key key) throws DoorStuckException, HouseOnFireException;
}

はい、本当に例外的なものに例外を使用する場合、例外タイプの再利用は理にかなっています。本当に例外的なことは、主に分野横断的な懸念だからです。例外を基本的に制御フローに使用していたため、これらの問題が発生しました。


しかし、これらの例外の種類は実際には説明的ではありません。ドアを開く、閉じる、またはロックすることしかできないときに、ドアが「スタック」しているとはどういう意味ですか?

これは単に例外を飲み込んでいるだけで、例外はありません。これは、何が起こっているのかを知りたくない状況では適切ではありません。これは、Javaの例をC#で実行する方法に似ています。私がJavaを使用しているのには理由があります。

まあ、それは行き詰まっている。動けません。したがって、それを開いたり、閉じたりすることはできません。これは、別のメソッドを呼び出して修正する必要のある論理的な状態ではなく、ドアサービスでは解決できない例外的な状態です。
nvoigt 2018

そして、それは何も飲み込んでおらず、例外ではないプログラム状態の例外を生み出していません。妻が私にバルコニーのドアを閉めてくださいと頼んで、起きてそれがすでに閉まっているのを見つけたら、私は走り回って叫んだりパニックに陥ったりしません。私は戻って「今は閉まっている」と言います。
nvoigt

厳密には、チェックされた例外では、プログラムの最初に戻ることができない例外は、呼び出し元によってキャッチされる必要があります。呼び出しでは、例外を無視するか警告をログに記録するだけで問題ありません。また、「例外」と呼ばれるのは、RuntimeExceptionだけです。DoorStuckExceptionそしてHouseOnFireException、確認すべきではない、彼らはすべてのコールにcatchedことを意味するものではありません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.