web-dev-qa-db-ja.com

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

Doorによって管理されているDoorServicesがあるとします。 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_にします

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

8
Winter

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

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

_/**
 * 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(); 
  }
}
_
21
candied_orange

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

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

すべてに対してDoorLockedExceptionを使用してください。

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

それでも不快な場合は、メソッドの名前をensureDoorLocked()に変更します。

8
Robert Harvey

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

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

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

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

代わりに、いくつかの高レベルの問題のための高レベルのハンドラーを持っている可能性があります。

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

3
Lewis Pringle

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

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

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

0
Gudmundur Orn

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

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

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

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

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

0
Caleth

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

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;
}

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

0
nvoigt