Argomenti contro la soppressione degli errori

35

Ho trovato un pezzo di codice come questo in uno dei nostri progetti:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

Per quanto ho capito, sopprimere errori come questo è una cattiva pratica, in quanto distrugge informazioni utili dall'eccezione del server originale e rende il codice continua quando in realtà dovrebbe terminare.

Quando è opportuno sopprimere completamente tutti gli errori come questo?

    
posta user626528 23.02.2016 - 11:22
fonte

7 risposte

52

Immagina il codice con migliaia di file usando un mucchio di librerie. Immagina che tutti siano codificati in questo modo.

Immagina, per esempio, che un aggiornamento del tuo server causi la scomparsa di un file di configurazione; e ora tutto quello che hai è una traccia dello stack è un'eccezione di puntatore nullo quando provi ad usare quella classe: come la risolveresti? Potrebbero essere necessarie ore, in cui almeno la registrazione della traccia dello stack non elaborata del file non trovato [percorso file] potrebbe consentire di risolverlo momentaneamente.

O ancora peggio: un errore in una delle librerie che usi dopo un aggiornamento che causa il crash del tuo codice in seguito. Come puoi rintracciare questo in biblioteca?

Anche senza una solida gestione degli errori, solo facendo

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

o

LOGGER.error("[method name]/[arguments] should not be there")

potrebbe farti risparmiare ore di tempo.

Ma ci sono alcuni casi in cui potresti davvero voler ignorare l'eccezione e restituire null come questo (o non fare nulla). Soprattutto se ci si integra con codice legacy mal progettato e questa eccezione è prevista come caso normale.

In effetti, quando lo fai, dovresti semplicemente chiedermi se stai davvero ignorando l'eccezione o "gestendo correttamente" le tue esigenze. Se restituire null è "gestire correttamente" la tua eccezione in quel caso, allora fallo. E aggiungi un commento perché questa è la cosa giusta da fare.

Nella maggior parte dei casi le best practice sono le cose da seguire, forse l'80%, forse il 99%, ma troverai sempre un caso limite in cui non si applicano. In tal caso, lascia un commento sul perché non stai seguendo la pratica per gli altri (o anche per te stesso) che leggeranno il tuo codice mesi dopo.

    
risposta data 23.02.2016 - 11:37
fonte
14

Ci sono casi in cui questo schema è utile - ma sono tipicamente usati quando l'eccezione generata non dovrebbe mai essere stata (cioè quando le eccezioni sono usate per il comportamento normale).

Ad esempio, immagina di avere una classe che apre un file, lo memorizza nell'oggetto che viene restituito. Se il file non esiste, si può considerare che non sia un caso di errore, si restituisce null e si consente all'utente di creare un nuovo file. Il metodo file-open può lanciare un'eccezione qui per indicare che nessun file è presente, quindi catturarlo in modo invisibile può essere una situazione valida.

Tuttavia, non è frequente che tu voglia fare questo, e se il codice è cosparso di un tale schema, vorresti affrontarlo ora. Per lo meno mi aspetterei che una presa così silenziosa possa scrivere una riga di log dicendo che questo è quello che è successo (hai tracciato, giusto) e un commento per spiegare questo comportamento.

    
risposta data 23.02.2016 - 12:05
fonte
7

Questo è al 100% dipendente dal contesto. Se il chiamante di tale funzione ha l'obbligo di visualizzare o registrare gli errori da qualche parte, allora è ovviamente senza senso. Se anche il chiamante ha ignorato tutti gli errori o i messaggi di eccezione, non avrebbe molto senso restituire l'eccezione o il suo messaggio. Quando il requisito è di far terminare il codice solo senza mostrare alcun motivo, allora una chiamata

   if(QueryServer(args)==null)
      TerminateProgram();

probabilmente sarebbe sufficiente. Bisogna considerare se questo renderà difficile trovare la causa dell'errore da parte dell'utente - se questo è il caso, questa è la forma sbagliata di gestione degli errori. Tuttavia, se il codice chiamante è simile a questo

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

allora dovresti avere un argomento con lo sviluppatore durante la revisione del codice. Se qualcuno implementa una tale forma di gestione senza errori solo perché è troppo pigro per scoprire i requisiti corretti, allora questo codice non dovrebbe entrare in produzione e l'autore del codice dovrebbe essere istruito sulle possibili conseguenze di tale pigrizia .

    
risposta data 23.02.2016 - 11:37
fonte
5

Negli anni in cui ho speso la programmazione e lo sviluppo di sistemi, ci sono solo due situazioni in cui ho trovato utile il pattern in questione (in entrambi i casi la supression conteneva anche la registrazione dell'eccezione generata, non considero il catch semplice e null return come buona pratica).

Le due situazioni sono le seguenti:

1. Quando l'eccezione non è stata considerata uno stato eccezionale

Questo è il momento in cui esegui un'operazione su alcuni dati, che possono essere lanciati, sai che potrebbero essere lanciati ma vuoi comunque che l'applicazione continui a funzionare, perché non hai bisogno dei dati elaborati. Se li ricevi, è buono, se non lo fai, è anche buono.

Potrebbero venire in mente alcuni attributi opzionali di una classe.

2. Quando stai fornendo una nuova implementazione (migliore, più veloce?) Di una libreria utilizzando un'interfaccia già utilizzata in un'applicazione

Immagina di avere un'applicazione che utilizza una sorta di vecchia libreria, che non genera eccezioni ma restituisce null in caso di errore. Pertanto, hai creato un adattatore per questa libreria, praticamente copiando l'API originale della libreria e stai utilizzando questa nuova interfaccia (ancora non a lancio) nell'applicazione e gestendoti i controlli null .

Viene una nuova versione della libreria, o forse una libreria completamente diversa che offre la stessa funzionalità, che, invece di restituire null s, genera eccezioni e si desidera utilizzarla.

Non vuoi far trapelare le eccezioni all'applicazione principale, quindi le annulli e le registra nell'adattatore che hai creato per avvolgere questa nuova dipendenza.

Il primo caso non è un problema, è il comportamento desiderato del codice. Nella seconda situazione, tuttavia, se dappertutto il valore di ritorno null dell'adattatore della libreria significa davvero un errore, refactoring dell'API per generare un'eccezione e rilevarlo invece di cercare null può essere (e in genere il codice è di solito ) una buona idea.

Personalmente uso la supressione delle eccezioni solo per il primo caso. L'ho usato solo per il secondo caso, quando non avevamo il budget per far funzionare il resto dell'applicazione con le eccezioni invece di null s.

    
risposta data 23.02.2016 - 13:02
fonte
-1

Anche se sembra logico affermare che i programmi dovrebbero rilevare solo eccezioni che sanno come gestire e che non possono sapere come gestire le eccezioni non previste dal programmatore, tale affermazione ignora il fatto che molte operazioni possono fallire in un numero quasi illimitato di modi che non hanno effetti collaterali, e che in molti casi la corretta gestione della stragrande maggioranza di tali fallimenti sarà identica; i dettagli esatti del fallimento saranno irrilevanti e di conseguenza non dovrebbe importare se il programmatore li ha anticipati.

Se, ad esempio, lo scopo di una funzione è leggere un file di documento in un oggetto adatto e creare una nuova finestra di documento per mostrare quell'oggetto o segnalare all'utente che il file non può essere letto, un tentativo caricare un file di documento non valido non dovrebbe mandare in crash l'applicazione - dovrebbe invece mostrare un messaggio che indica il problema ma lasciare che il resto dell'applicazione continui a funzionare normalmente a meno che per qualche ragione il tentativo di caricare il documento abbia danneggiato lo stato di qualcos'altro nel sistema.

Fondamentalmente, la corretta gestione di un'eccezione dipende spesso meno sul tipo di eccezione rispetto alla posizione in cui è stato lanciato; se una la risorsa è protetta da un blocco di lettura-scrittura e viene generata un'eccezione un metodo che ha acquisito il blocco per la lettura, dovrebbe essere il comportamento corretto in genere è necessario rilasciare il blocco poiché il metodo non può aver fatto nulla alla risorsa. Se viene lanciata un'eccezione durante l'acquisizione del blocco scrivendo, il blocco dovrebbe essere invalidato, poiché la risorsa protetta potrebbe trovarsi in uno stato non valido; se la primitiva di blocco non ha uno stato "invalidato", si dovrebbe aggiungere un flag per tracciare tale invalidazione. Rilasciare il blocco senza invalidarlo non è corretto perché altri codici potrebbero visualizzare l'oggetto protetto in uno stato non valido. Lasciare la serratura penzolante, tuttavia, non è una soluzione adeguata neanche. La soluzione corretta è di invalidare il blocco in modo che eventuali tentativi in sospeso o futuri di acquisizione falliscano immediatamente.

Se si scopre che una risorsa invalida viene abbandonata prima che si tenti di usarla, non c'è motivo per cui debba far cadere l'applicazione. Se una risorsa invalidata è fondamentale per l'operazione continua di un'applicazione, è necessario che l'applicazione venga arrestata, ma è probabile che l'invalidazione della risorsa si verifichi. Il codice che ha ricevuto l'eccezione originale spesso non ha modo di sapere quale situazione si applica, ma se invalida la risorsa può garantire che l'azione corretta finirà per essere presa in entrambi i casi.

    
risposta data 24.02.2016 - 08:56
fonte
-2

L'inghiottimento di questo tipo di errore non è particolarmente utile a nessuno. Sì, il chiamante originale potrebbe non interessarsi dell'eccezione, ma qualcun altro potrebbe .

Allora cosa fanno? Aggiungeranno il codice per gestire l'eccezione per vedere quale sapore di eccezione stanno tornando. Grande. Ma se il gestore delle eccezioni viene lasciato in, il chiamante originale non restituisce più nulla e qualcosa si rompe altrove.

Anche se sei consapevole che un codice a monte potrebbe generare un errore e quindi restituire null, sarebbe seriamente inerte da parte tua non tentare almeno di eliminare l'eccezione nel codice chiamante IMHO.

    
risposta data 23.02.2016 - 12:41
fonte
-3

Ho visto esempi in cui le librerie di terze parti disponevano di metodi potenzialmente utili, tranne per il fatto che in alcuni casi generano eccezioni che dovrebbero funzionare per me. Cosa posso fare?

  • Realizza esattamente ciò di cui ho bisogno. Può essere difficile a una scadenza.
  • Modifica il codice di terze parti. Ma poi devo cercare conflitti di fusione con ogni aggiornamento.
  • Scrivi un metodo wrapper per trasformare l'eccezione in un normale puntatore nullo, un falso booleano o qualsiasi altra cosa.

Ad esempio, il metodo di libreria

public foo findFoo() {...} 

restituisce il primo pippo, ma se non ce n'è nessuno lancia un'eccezione. Ma quello di cui ho bisogno è un metodo per restituire il primo foo o un null. Quindi scrivo questo:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Non pulito. Ma a volte la soluzione pragmatica.

    
risposta data 23.02.2016 - 20:38
fonte