prova / cattura violazione principio aperto / chiuso

1

Ho tre o più eccezioni personalizzate diverse che una classe può lanciare e ho bisogno di usare try / catch per scoprire quale eccezione è stata lanciata.

Dal mio punto di vista questa parte di codice viola il principio Open / Closed, perché se guardiamo la definizione vediamo:

"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"

try {
    // something that throws
} catch (const FooException& e) {
    // ...
} catch (const BarException& e) {
    // ...
} catch (const BizException& e) {
    // ...
} catch (...)
{
    // ...
}

Che cosa succede se ho bisogno di rilevare un'altra eccezione? Dovrò inserire un'altra frase di cattura, giusto?

C'è un altro modo per farlo? O il linguaggio mi costringe in qualche modo a violare il principio?

    
posta yayuj 17.11.2014 - 14:58
fonte

4 risposte

5

Dipende da come prevedi di recuperare da queste eccezioni, cioè quale codice viene eseguito nelle dichiarazioni catch. Se è lo stesso per tutte e 3 le eccezioni, puoi creare una superclasse per loro e prendere la superclasse. Se il tuo metodo deve lanciare una nuova eccezione, fallo semplicemente estendere la superclasse e verrebbe comunque catturata dal chiamante e non richiederebbe alcuna modifica nel codice client.

Generalmente difficile, se un metodo genera troppe eccezioni di solito è un suggerimento che il metodo fa molto e potresti prendere in considerazione la possibilità di rifarlo.

Se vuoi una risposta più dettagliata, sarebbe utile vedere un elenco completo del codice del tuo metodo.

    
risposta data 17.11.2014 - 15:12
fonte
3

Se vuoi solo logg ogni eccezione - in qualche modo a seconda del suo tipo specifico, puoi dividere quel problema in un logger delle eccezioni dedicato.

Per prima cosa, hai bisogno di un tipo di base - supponiamo che tu abbia appena sottoclasse std::exception . Ora, il tuo codice principale può essere solo:

try {
    // something that throws
} catch (const std::exception& e) {
    ExceptionLogger::log(e);
    throw;
}

Chiedi al metodo log di utilizzare dynamic_cast per gestire tutti i sottotipi specifici che conosce e hai solo un posto da modificare quando viene aggiunto un nuovo tipo di eccezione.

NB. Ho creato log un metodo statico qui, ma potresti ragionevolmente avere un'istanza locale (o membro dei dati, o ...) ExceptionLogger configurata in modo diverso per componente, collegata a un diverso registratore di output / destinazione, ecc. Ecc.

Esistono essenzialmente due opzioni di implementazione per il metodo log stesso:

  1. creare a mano qualcosa che conosca ogni tipo derivato e cosa fare con esso. Questo può usare dynamic_cast esplicitamente:

    void ExceptionLogger::log(std::exception const &ex) {
      if ((FooException const *e = dynamic_cast<FooException const *>(&ex))) {
        // handle foo
      } else if ((BarException const *e = dynamic_cast<BarException const *>(&ex))) {
        // handle bar
      } // else ... etc.
    }
    

    (come probabilmente hai notato, questo è in realtà più brutto del codice originale, quindi tutto ciò che abbiamo fatto è disaccoppiarlo dal codice che non vuoi modificare), oppure puoi spostare esattamente lo stesso arrangiamento try / catch fuori dalla tua funzione - basta rilanciare il blocco try.

  2. Scrivi un dispatcher generico basato su tipo, in modo da poter rimuovere il codice del tipo per eccezione sia dalla tua funzione originale che dal dispatcher. Questa è una forma di visita ad hoc (in pratica stai scrivendo un metodo visit per ciascuna sottoclasse interessante di std::exception ). Ora puoi registrare ogni nuovo gestore esternamente, senza modificare il tuo codice originale o ExceptionLogger stesso.

risposta data 17.11.2014 - 15:56
fonte
3

Se si desidera un modo conforme a OCP di gestire un elenco non specificato di eccezioni, è possibile considerare la "catena di responsabilità". Ma non è chiaro dalla tua domanda se il tuo codice è specificato per gestire "un insieme arbitrario di eccezioni, ognuna in un modo diverso, ognuna delle quali sarà specificata in futuro ed è soggetta a modifiche", o se la lista fissa di tre che attualmente gestisce è una parte fondamentale e affidabile del design.

Aggiungere un'altra eccezione all'elenco di cose che il tuo codice deve gestire non sta "estendendo" il codice che hai, lo sta "modificando". Quindi, se sei nel primo caso flessibile, allora hai una violazione OCP nelle tue mani perché dovrai modificare il codice per gestire una modifica anticipata. Se ti trovi nell'ultimo caso, forse perché Foo , Bar e Biz rappresentano le uniche tre classi di base legali per tutte le eccezioni che il tuo codice è autorizzato a gestire in modo diverso dal caso ... , allora potresti va bene.

A patto che "qualcosa che getta" abbia un'interfaccia ben definita e stabile, non è necessario aggiungere all'elenco delle eccezioni gestite. Quindi il tuo codice non è di per sé una violazione di OCP. Parte di OCP è che non si apportano modifiche retroattive alle interfacce esistenti su cui le persone fanno affidamento su tutto il codice. Lanciare una nuova eccezione è un cambiamento di interfaccia incompatibile con le versioni precedenti, è una modifica.

Se "qualcosa che getta" ha un'interfaccia mal documentata, o una materia soggetta a modifiche in futuro, in modo che possa improvvisamente iniziare a lanciare qualcosa che non ha gettato prima, allora hai una violazione di OCP: "qualcosa" lo viola essendo aperto alle modifiche. Queste violazioni tendono a diffondersi, quindi il tuo codice dovrà anche violarlo.

Se "qualcosa" è ben specificato, ma il tuo codice utilizza informazioni su "qualcosa" che va oltre la sua interfaccia, allora hai un problema con un eccesso di accoppiamento tra i componenti. Non sono sicuro se considerare che anche una violazione OCP, non sono realmente in una posizione in cui la tassonomia delle questioni oltre la prima è importante per me; -)

Si noti che in ogni caso un impegno completo per OCP è uno standard ragionevolmente alto. Potresti scoprire che "Non scriverò il codice in questo modo perché viola il Principio Aperto / Chiuso" semplicemente non è adatto a te o ai tuoi colleghi. Il che non significa necessariamente che tu debba ignorare se stai violando o meno.

    
risposta data 17.11.2014 - 21:04
fonte
1

Penso che la risposta di Phil dovrebbe essere accettata alla fine perché contiene le migliori informazioni. Ma rispondi specificatamente alla tua domanda Is there another way for doing this? : ecco un altro modo per separare la parte di gestione delle eccezioni piuttosto bene, sebbene dipenda dalla situazione esatta quanto sia utile

template< class Fun >
void TryCatch( Fun f )
{
  try {
      f()
  } catch (const FooException& e) {
      // ...
  } catch (const BarException& e) {
      // ...
  } catch (const BizException& e) {
      // ...
  } catch (...)
  {
      // ...
  }
}

void DoSomething()
{
  TryCatch( []()
    {
      //do something
    } );     
}
    
risposta data 17.11.2014 - 16:47
fonte

Leggi altre domande sui tag