I tipi di eccezioni riutilizzati dovrebbero essere preferiti rispetto a quelli monouso?

8

Diciamo che ho Door s che sono gestiti da DoorService . Il DoorService ha il compito di aprire, chiudere e bloccare le porte che sono memorizzate nel database.

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

Per il metodo di blocco, c'è un DoorAlreadyLockedException e per il metodo aperto c'è un DoorLockedException . Questa è un'opzione ma ci sono altre opzioni possibili:

1) Usa DoorLockedException per tutto, è un po 'imbarazzante quando rilevi l'eccezione su una chiamata lock()

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

2) Hanno i 2 tipi di eccezioni DoorLockedException e DoorAlreadyLockedException

3) Avere i due tipi di eccezioni ma lasciare DoorAlreadyLockedException extends DoorLockedException

Qual è la soluzione migliore e perché?

    
posta Winter 17.07.2018 - 03:34
fonte

6 risposte

21

So che le persone fanno un grosso problema usando le eccezioni per il controllo del flusso ma questo non è il problema più grande qui. Vedo una grande violazione della separazione di comandi . Ma anche questo non è il peggiore.

No, il peggio è proprio qui:

/**
 * Closes the door if open
 */

Perché diavolo fa tutto il resto se la tua ipotesi sullo stato delle porte è sbagliata, ma lock() lo aggiusta per te? Dimentica che ciò rende impossibile bloccare la porta aperta, il che è assolutamente possibile e occasionalmente utile. No, il problema qui è che hai mischiato due filosofie diverse per affrontare ipotesi errate. Questo è confuso. Non farlo. Non allo stesso livello di astrazione con lo stesso stile di denominazione. Ahi! Porta fuori una di quelle idee. I metodi di servizio delle porte dovrebbero funzionare tutti allo stesso modo.

Per quanto riguarda la violazione del comando Query Separation, non dovrei provare a chiudere una porta per scoprire se è chiusa o aperta. Dovrei riuscire a chiedere. Il servizio porta non fornisce un modo per farlo senza modificare lo stato della porta. Ciò rende molto più grave dei comandi che capita anche di restituire valori (un comune fraintendimento su cosa sia CQS). Qui, i comandi che cambiano lo stato sono l'unico modo per fare query! Ahi!

Poiché le eccezioni sono più costose dei codici di stato, si parla di ottimizzazione. Abbastanza veloce è abbastanza veloce. No, il vero problema è che gli umani non si aspettano eccezioni per casi tipici. Puoi discutere su ciò che è tipico tutto ciò che ti piace. Per me la grande domanda è quanto leggibile stai usando il codice.

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

Per favore non farmi scrivere codice come questo. Per favore forniscici metodi isLocked() e isClosed() . Con quelli posso scrivere i miei metodi ensureClosed() e ensureUnlocked() che sono facili da leggere. Quelli che si lanciano solo se le loro condizioni postali sono violate. Preferirei solo che tu abbia già scritto e testato, naturalmente. Basta non mescolarli con quelli che si lanciano quando non possono cambiare lo stato. Perlomeno dai loro nomi distintivi.

Qualunque cosa tu faccia, per favore non chiamare nulla tryClose() . È un nome terribile.

Per quanto riguarda DoorLockedException solo vs anche avendo DoorAlreadyLockedException ammesso questo: si tratta di utilizzare il codice. Si prega di non progettare servizi come questo senza scrivere il codice usando e guardando il casino che stai creando. Refactoring e riprogettazione fino a quando il codice using è almeno leggibile. In effetti, considera prima di scrivere il codice usando.

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(); 
  }
}
    
risposta data 17.07.2018 - 12:35
fonte
8

Fare una distinzione tra DoorLockedException e DoorAlreadyLockedException suggerisce che vengono usati diversi tipi di eccezioni per il controllo del flusso, una pratica che è già ampiamente considerata un antipattern.

Avere più tipi di eccezioni per qualcosa di benigno è gratuito. La complessità aggiuntiva non è controbilanciata da ulteriori vantaggi.

Usa solo DoorLockedException per tutto.

Ancora meglio, semplicemente non fare nulla. Se la porta è già bloccata, sarà ancora nello stato corretto al termine del metodo lock() . Non lanciare eccezioni per condizioni che non sono degni di nota.

Se ti senti ancora a disagio, rinomina il tuo metodo ensureDoorLocked() .

    
risposta data 17.07.2018 - 04:04
fonte
3

L'oggetto di questa domanda "I tipi di eccezioni riciclati dovrebbero essere preferiti rispetto a quelli monouso?" sembra essere stato ignorato, non solo nelle risposte, ma nei dettagli per le domande (le risposte erano ai dettagli della domanda).

Sono d'accordo con la maggior parte delle critiche al codice che gli intervistati hanno offerto.

Ma come risposta alla domanda principale, direi che dovresti strongmente preferire il riutilizzo delle "eccezioni riciclate" rispetto a quelle "monouso".

Nell'uso più tipico della gestione delle eccezioni, si ha una grande DISTANZA nel codice tra il punto in cui l'eccezione viene rilevata e generata e dove viene rilevata e gestita l'eccezione. Ciò significa che l'utilizzo di tipi privati (modulari) nella gestione delle eccezioni in genere non funzionerà correttamente. Un tipico programma con gestione delle eccezioni - avrà una manciata di posizioni di primo livello nel codice in cui vengono gestite le eccezioni. E poiché questi sono luoghi di alto livello, è difficile per loro avere una conoscenza dettagliata degli aspetti ristretti di particolari moduli.

Invece, probabilmente avranno un gestore di alto livello per alcuni problemi di alto livello.

L'unica ragione per cui le classi di eccezioni personalizzate dettagliate sembrano avere senso nell'esempio, è perché (e qui sto pappagliando gli altri rispondenti) - non dovresti usare la gestione delle eccezioni per il normale flusso di controllo.

    
risposta data 17.07.2018 - 19:31
fonte
0

Un'alternativa inesplorata. Potresti modellare la cosa sbagliata con le tue lezioni, e se invece avessi queste?

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

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

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

Ora è impossibile tentare qualsiasi cosa che sia un errore. Non hai bisogno di eccezioni.

    
risposta data 18.07.2018 - 18:09
fonte
0

Risponderò alla domanda originale, piuttosto che criticando l'esempio.

In parole povere, se ti aspetti che il cliente debba reagire diversamente per ogni caso, garantisce un nuovo tipo di eccezione.

Se decidi di utilizzare più tipi di eccezione e ti aspetti comunque che il client possa in alcuni casi voler creare una clausola di catch comune per loro, dovresti probabilmente creare una superclasse astratta che entrambi estendono.

    
risposta data 18.07.2018 - 17:15
fonte
0

Questo è pseudocodice, ma penso che otterrai ciò che intendo:

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

Sì, il riutilizzo del tipo di eccezione ha senso, se si utilizzano eccezioni per cose veramente eccezionali . Perché le cose veramente eccezionali sono per lo più preoccupazioni trasversali. Stavi usando le eccezioni fondamentalmente per i flussi di controllo e questo porta a quei problemi.

    
risposta data 19.07.2018 - 14:55
fonte

Leggi altre domande sui tag