C'è una particolare circostanza che lanciando eccezioni alla superclasse di root è una buona pratica?

2

Mi è stato insegnato che le eccezioni dovrebbero avere significati concisi e dovrebbero contenere un messaggio che spieghi al cliente qual è la situazione eccezionale. Mi chiedo, visto che ho trovato un pezzo di codice che contiene un paio di righe in un blocco catch try e alla fine cattura un'eccezione generica con il messaggio: An error occurred performing the task

Questo è scritto in una libreria e in qualche modo faccio scattare questa eccezione. Come posso riparare ciò che è rotto se non so cosa è rotto? In genere so che questa non è una buona pratica. Ci sono situazioni in cui lanciare / catturare eccezioni generiche è una buona pratica?

    
posta George D 01.12.2015 - 18:08
fonte

3 risposte

4

Un'eccezione dovrebbe essere descrittiva. Questa è una combinazione di tre cose:

  • L'identità (tipo) dell'eccezione.
  • Il messaggio contenuto nell'eccezione.
  • Altri metadati contenuti nell'eccezione, ad esempio un'eccezione concatenata o un codice di errore.

Si noti che lingue e runtime diversi hanno semantica delle eccezioni leggermente diversa, ma queste tre idee sono abbastanza universali. Per lo meno, un'eccezione ha un tipo e di solito ha un messaggio, ma i dati aggiuntivi non sono sempre pertinenti. Nota anche che alcuni linguaggi come C ++ non hanno un tipo di eccezione "root": mentre C ++ ha std::exception , uno può lanciare qualsiasi incluse le primitive.

Scegliendo Java, ci sono quattro tipi di core che formano la dorsale della gerarchia delle eccezioni: Throwable , Error , Exception e RuntimeException . Mentre nessuno è astratto, nessuno deve essere gettato direttamente: si dovrebbe usare un tipo più specifico. Detto questo, il fatto che nessuno sia astratto significa che è possibile istanziarlo e lanciarli direttamente.

Ritorno al punto originale: generalmente, si dovrebbero generare sottotipi di eccezioni specifici perché rende più facile che determinate eccezioni ingigantiscano lo stack di chiamate per essere catturato al livello appropriato. Considera il seguente esempio Java:

public void doSomething() throws Exception {
  doOneThing(); // Throws Exception
  doAnotherThing(); // Throws IOException
}

Se doSomething() non è un luogo appropriato per gestire l'eccezione, il metodo che lo chiama deve sapere quali eccezioni prendere. Anche l'ordine delle clausole catch è importante: devono passare dallo specifico al generale. La cattura di Exception prima ingoierà tutte le eccezioni tutte , incluso IOException , nello stesso blocco. Questo diventa un po 'più difficile da capire cosa catturare e cosa fare con l'eccezione una volta catturati. Considera invece questo codice:

public void doSomething() throws SQLException, IOException {
  doOneThing(); // Throws SQLException
  doAnotherThing(); // Throws IOException
}

Ora è molto meno ambiguo quali siano gli errori possibili e non vi è alcun accoppiamento in termini di ordine in cui vengono catturati. Invece di questo:

try {
  doSomething();
}
// Order matters!
catch (IOException e) {
  // Obviously some sort of file or socket I/O error.
}
catch (Exception e) {
  // What are we dealing with here?
}

Abbiamo questo:

try {
  doSomething();
}
catch (IOException e) {
  // Obviously some sort of file or socket I/O error.
}
catch (SQLException e) {
  // Now we can get the error code and do something meaningful.
}

Puoi anche consentire ad alcune eccezioni di passare attraverso lo stack di chiamate, e altre a essere gestite in quel blocco try / catch. Ad esempio, il primo esempio renderebbe difficile consentire a SQLException di superare lo stack di chiamate durante la gestione di IOException, perché non è dichiarato correttamente. Anche se possibile, richiederebbe il casting, il controllo dei tipi e sarebbe un codice fragile e non pulito.

Il secondo esempio è più chiaro in termini di intenzioni e consente di eseguire operazioni più utili utilizzando l'oggetto eccezione.

Si noti che gli esempi precedenti fanno sì che si desideri lasciare passare alcune eccezioni, oppure si potrebbe voler gestire eccezioni diverse in modo diverso (ad esempio mostrare un messaggio di errore per uno, ritentare l'operazione per un altro). Alcune lingue consentono di catturare più tipi di eccezioni senza catturare eccezioni tutte . Questo può essere un buon compromesso tra un blocco catch-all e un blocco molto verboso "catch this, then this, then ...".

Ad esempio, considera il seguente blocco:

try {
  // open a file, read some XML, and instantiate
  // some classes referenced therein using reflection.
  initializeModule();
}
catch (IOException e) {
  // Do something
}
catch (ReflectiveOperationException e) {
  // Do something
}
catch (XMLStreamException e) {
  // Do something
}

Java 8 consente invece quanto segue:

try {
  // open a file, read some XML, and instantiate
  // some classes referenced therein using reflection.
  initializeModule();
}
catch (IOException | ReflectiveOperationException | XMLStreamException e) {
  // Do something
}

Questo può essere un buon compromesso perché cattura eccezioni specifiche in un singolo blocco senza che cattura troppo, il che potrebbe non essere un progetto pulito o il posto giusto per catturare altre eccezioni. Ad esempio, il codice precedente consente alle classi RuntimeException (ad esempio, i puntatori nulli) di superare lo stack di chiamate. Questo può essere fastidioso da fare in Java perché RuntimeException eredita da Exception. L'eccezione di cattura rileverà anche RuntimeExceptions, che non è sempre ciò che si vuole fare.

L'unica volta in cui trovo che tutte le eccezioni sono buone pratiche è l'ultima risorsa prima di morire:

public static void main(String[] args) {
  try {
    // Invoke program logic here.
  }
  catch (Throwable t) {
    // Last-ditch logging before letting the program die.
  }
}
    
risposta data 01.12.2015 - 21:28
fonte
2

L'utilizzo di gestori di eccezioni catch-all è importante in un paio di casi:

  • Il tuo software deve registrare tutte le eccezioni. Pertanto, catturerai tutte le eccezioni al livello più esterno, le registrerai e le ripeterai.

  • Il tuo software è un demone o server a esecuzione prolungata che non deve andare giù quando alcune attività non possono essere completate. Quando si genera un'attività, verranno catturate le eccezioni generate da tale attività e proseguiranno indipendentemente dall'errore di tale attività. Per esempio. un server Web HTTP non dovrebbe andare giù se qualsiasi richiesta causa un'eccezione. Invece, risponde con un errore del server interno 500 e continua a servire le altre richieste. I dettagli dell'errore originale sono disponibili in un registro e non vengono passati.

  • Il tuo software è una libreria o un livello. Quando si verifica un'eccezione, il wrapping di qualsiasi eccezione in un'eccezione specifica del dominio può fornire più informazioni di una traccia di stack nuda. Per esempio. un FileNotFoundException("ad83jfw8.rsa could not be opened") è irrilevante per un utente esterno se confrontato con un

    IllegalConfigurationValue(
      key="crypto.secretKey",
      value="ad83jfw8.rsa",
      file="myApp.cfg",
      line=13,
      message="file could not be opened",
      cause=FileNotFoundException(…),
    )
    

    Il punto importante quando si sostituiscono gli errori con le eccezioni personalizzate è che il problema originale non viene inghiottito ma spostato! L'eccezione originale dovrebbe essere passata come un campo e probabilmente stampata oltre a una traccia dello stack. Per esempio. in Java, le classi di eccezioni standard possono richiedere un Throwable cause in aggiunta a un messaggio. Questo design dovrebbe assolutamente essere copiato in classi di eccezioni personalizzate!

In altri casi, non ci sono scuse per ingoiare un errore. Al minimo, l'errore deve essere incapsulato o registrato.

    
risposta data 01.12.2015 - 21:20
fonte
1

a couple of lines into a try catch block and in the end catches a generic exception with the message: An error occurred performing the task

Intendi qualcosa come:

try {
    thingWhichThrowsDescriptiveException();
} catch (Throwable t) {
    throw new Exception("An error occurred");
}

Se è così, allora la mia risposta è No. Hai diritto a qualsiasi indignazione giusta che senti quando vedi questo. Semplicemente getta via i dettagli dell'eccezione e li sostituisce con garbage.

Se non vuoi un'eccezione nella firma del metodo, fallo invece: Soluzione alternativa per Java controllate le eccezioni

    
risposta data 02.12.2015 - 17:41
fonte

Leggi altre domande sui tag