È mai ok avere una dichiarazione di cattura vuota?


58

Ci ho pensato e non ho potuto fare un esempio. Perché qualcuno dovrebbe voler prendere un'eccezione e non fare nulla al riguardo? Puoi fare un esempio? Forse è solo qualcosa che non dovrebbe mai essere fatto.


E finalmente?
Chris,

2
a proposito - questo è stato chiesto a SO l'anno scorso: stackoverflow.com/questions/1234343/…
warren

17
dovrebbe almeno contenere un commento sul perché è vuoto.
Peter

Risposte:


77

Lo faccio sempre con cose come errori di conversione in D:

import std.conv, std.stdio, std.exception;

void main(string[] args) {  
    enforce(args.length > 1, "Usage:  foo.exe filename");

    double[] nums;

    // Process a text file with one number per line into an array of doubles,
    // ignoring any malformed lines.
    foreach(line; File(args[1]).byLine()) {
        try {
            nums ~= to!double(line);
        } catch(ConvError) {
            // Ignore malformed lines.
        }
    }

    // Do stuff with nums.
}

Detto questo, penso che tutti i blocchi di cattura dovrebbero avere qualcosa in loro, anche se quel qualcosa è solo un commento che spiega perché stai ignorando l'eccezione.

Modifica: voglio anche sottolineare che, se hai intenzione di fare qualcosa del genere, dovresti fare attenzione a cogliere solo l'eccezione specifica che vuoi ignorare. Fare un vecchio semplice catch {}è quasi sempre una brutta cosa.


15
+1 per il commento esplicativo.
Michael K,

Alcuni IDE consentono di specificare che un nome particolare per l'eccezione (di solito "ignora") indica che lo stai ignorando di proposito, il che sopprime l'avvertimento che altrimenti mostrerebbe; questo prende il posto del commento.
Alex Feinman,

Non ho familiarità con D, ma suppongo che sia possibile fare qualcosa come bool TryParse (stringa line, out double valToParse), che potrebbe essere più pulito.
ysolik,

4
Wow, qualcuno che effettivamente usa D! :-P
James McNellis il

4
Come dice @Michael - non è del tutto vuoto poiché hai il commento lì; dovrebbe essere obbligatorio aggiungere un commento "questa cattura lasciata intenzionalmente in bianco" se non c'è una dichiarazione
STW

50

Un esempio in cui penso sia giusto ingoiare l'eccezione senza fare nulla, anche registrando l'eccezione, è all'interno del codice di registrazione stesso.

Se hai provato a registrare qualcosa e hai ottenuto un'eccezione, non c'è molto che puoi fare al riguardo:

  • non è possibile registrarlo ovviamente;
  • potresti essere in grado di ricorrere a un meccanismo di registrazione delle riserve, ma la maggior parte delle applicazioni non è così sofisticata e per quelle che sono abbastanza sofisticate lo stesso problema di progettazione sorgerà con il meccanismo di registrazione delle riserve;
  • fallimento rapido: sarà una soluzione troppo radicale per la maggior parte delle applicazioni;
  • vomitare un'eccezione farà poco bene in quanto finirà in una dichiarazione catch nello stack che quasi sicuramente proverà a registrarlo ...

Ciò ti lascia con l'opzione "meno del male" di ingoiarlo in silenzio, consentendo all'applicazione di continuare a svolgere il suo lavoro principale, ma compromettendo la capacità di risolverlo.


10
ottimo punto. il debug del codice di debug è sempre una sfida.
DevSolo

7
Questo, un milione di volte. Soprattutto se si considera che se si accede, è possibile che si trovi in ​​uno stato terribile; potresti essere senza memoria, potrebbe andare in crash, potrebbe essere qualsiasi cosa. A questo punto, quando si registra un errore e QUELLO non riesce, l'unica cosa responsabile da fare è nulla; non hai alcuna garanzia che qualsiasi cosa tu provi non lo farà peggiorare.
GWLlosa,

Ottimo punto Nel mio codice di registrazione, mi piace anche aggiungere InnerMessage per l'eccezione. Molte volte questo è nullo, quindi provare ad aggiungerlo farebbe saltare la registrazione stessa.
Tangurena,

@Tangurena Se è spesso nullo, gestiscilo, non soffiare. In C # spesso guardo queste cose di ?? "";
Loren Pechtel,

8

Se viene generata un'eccezione da un'operazione facoltativa, potrebbe non esserci nulla da fare. Potrebbe non essere utile registrarlo. In tal caso, potresti semplicemente voler prenderlo e non fare nulla.

Se lo stai facendo, includi un commento. Commenta sempre tutto ciò che sembra un bug (descrizione dettagliata di una switchdichiarazione, catchblocco vuoto , assegnazione in una condizione).

Potresti sostenere che questo non è un uso corretto delle eccezioni, ma è per un'altra domanda.


6

I catchblocchi vuoti sono un odore di codice nella maggior parte delle lingue. L'idea principale è usare le eccezioni per situazioni eccezionali e non usarle per il controllo logico. Tutte le eccezioni devono essere gestite da qualche parte.

Come conseguenza di questo approccio, quando si programma un particolare livello di applicazione, sono disponibili diverse opzioni:

  • non fare nulla per l'eccezione. Sarà catturato e gestito da un livello diverso
  • prenderlo ed eseguire l'azione correttiva.
  • prenderlo, fare qualcosa, ma ri-lanciarlo per un altro livello da gestire

Questo non lascia spazio a catchblocchi vuoti senza nulla .

MODIFICATO PER AGGIUNGERE : supponiamo che tu stia programmando in un linguaggio in cui lanciare eccezioni è il modo normale di controllare la logica del programma (una delle alternative a goto). Quindi un catchblocco vuoto in un programma scritto in questa lingua è molto simile a un elseblocco vuoto in una lingua tradizionale (o nessun elseblocco). Tuttavia, credo che questo non sia lo stile di programmazione raccomandato dalle comunità di sviluppo C #, Java o C ++.


Penso che usare le eccezioni come controllo logico sia diventato abbastanza comune, quindi mi trovo abbastanza spesso con blocchi di cattura vuoti o quasi vuoti.
whatsisname

+1 per riconoscere che un blocco di cattura vuoto potrebbe indicare l'uso di eccezioni per il controllo del flusso.
John M Gant,

L'uso delle eccezioni per la logica di programmazione è generalmente considerato una cattiva pratica. Ma, sorprendentemente (la mia argomentazione originale contro le eccezioni), le eccezioni non comportano un notevole successo prestazionale. Vedi codeproject.com/KB/exception/ExceptionPerformance.aspx .
Evan Plaice,

6

In alcuni casi, Java richiede di gestire un'eccezione che, in nessun caso, può mai accadere. Considera questo codice:

try {
   bytes[] hw = "Hello World".getBytes("UTF-8");
}
catch(UnsupportedCodingException e) {
}

Ogni implementazione della piattaforma Java è necessaria per supportare i seguenti set di caratteri standard.

...

UTF-8

Senza rompere la tua piattaforma Java, non c'è semplicemente alcun modo per causare questa eccezione. Allora perché preoccuparsi di gestirlo? Ma non puoi semplicemente aggiungere la clausola try-catch.


1
In casi come questo sarei tentato di aggiungere throw new Error(e)solo per essere assolutamente sicuro di non aver perso nulla (o di segnalare piattaforme effettivamente rotte).
Halle Knast,

@HalleKnast Sì. Questo è stato discusso in dettaglio qui: softwareengineering.stackexchange.com/questions/122233/…
user281377

5

Come regola generale, no. O vuoi gettare l'eccezione nello stack o registrare ciò che è successo.

Tuttavia, lo faccio spesso quando analizzo le stringhe e la conversione in numeri (specialmente nella mappatura di progetti che sono di uso una volta e butto via la varietà).

boolean isNonZeroNumber = false;

try {
   if(StringUtils.isNotBlank(s) && new BigDecimal(s).compareTo(BigDecimal.ZERO) != 0) {
     b = true;
   }
} catch(NumberFormatException e) {
//swallow this exception; b stays false.
}

return b;

3

In alcuni casi, l'eccezione non è affatto eccezionale; in effetti, è previsto. Considera questo esempio:

    /* Check if the process is still alive; exitValue() throws an exception if it is */
    try {
        p.exitValue();
        processPool.remove(p);
    }
    catch (IllegalThreadStateException e) { /* expected behaviour */ }

Poiché non esiste un metodo isAlive () in java.lang.Process (), l'unico modo per verificare se è ancora attivo è chiamare exitValue (), che genera un oggetto IllegalThreadStateException se il processo è ancora in esecuzione.


2

Nella mia umile esperienza, raramente è una cosa positiva. Il chilometraggio può variare però.

Quasi ogni volta che l'ho visto nella nostra base di codice, è stato perché ho rintracciato un bug che l'eccezione mangiata ha nascosto. Per dirla in altro modo, non fornendo alcun codice, l'applicazione non ha modo di indicare un errore o eseguire un'altra azione.

A volte, ad esempio nei livelli API, potresti non voler o essere in grado di far passare le eccezioni, quindi in quel caso, tendo a cercare di catturare il più generico, quindi registrarle. Ancora una volta, per dare almeno una possibilità a una sessione di debug / diagnosi.

In genere, se deve essere vuoto, il minimo che faccio è un'asserzione di qualche tipo, quindi almeno qualcosa accade durante una sessione di debug. Detto questo, se non riesco a gestirlo, tendo a ometterli completamente.


2

IMO c'è un posto dove le dichiarazioni di cattura vuote sono OK. Nei casi di test in cui ti aspetti un'eccezione:

try
{
   DoSomething(); //This should throw exception if everything works well
   Assert.Fail('Expected exception was not thrown');
}
catch(<whatever exception>)
{
    //Expected to get here
}

+1, odio farlo, ma funziona a JUnit
sal

@sal: che dire di @Test (previsti = Exception.class)?
ysolik,

@ysolik, cattiva abitudine
sal

1
@sal: Perché è una cattiva abitudine?
Adam Lear

ummmm. ci sono modi per farlo con i framework di unit test. Ex. NUnit. Ci sono alcuni casi in cui può essere utile verificare che qualcosa di prevedibilmente fallito. Anche se, non lo farei mai nel codice di produzione.
Evan Plaice,

2

Credo che ci siano alcuni casi in cui è giustificato avere una clausola catch vuota - questi sono casi in cui si desidera che il codice continui a funzionare se una determinata operazione fallisce. Tuttavia, penso che questo modello possa essere facilmente abusato, quindi ogni utilizzo dovrebbe essere attentamente esaminato per assicurarsi che non ci sia un modo migliore per gestirlo. In particolare, ciò non dovrebbe mai essere fatto se lascia il programma in uno stato incoerente o se potrebbe portare a qualche altra parte del codice che fallisce in seguito.


1

Non credo di non avere un certo livello di allerta quando si verifica un'eccezione: uno degli obiettivi della programmazione non dovrebbe essere quello di migliorare il codice? Se un'eccezione si verifica il 10% delle volte e non si è mai a conoscenza di essa, allora l'eccezione sarà sempre così grave o peggiore.

Tuttavia, vedo l'altro lato, che se l'eccezione non fa alcun danno reale nell'elaborazione del codice, perché esporre l'utente ad esso. La soluzione che in genere implemento è averlo registrato dalla mia classe di logger (che è in ogni singola classe che io abbia mai scritto al lavoro).

Se è un'eccezione benigna, allora faccio una chiamata al metodo .Debug () del mio Logger; in caso contrario, Logger.Error () (e (forse) lancio).

try
{
   doWork();
}
catch(BenignException x)
{
  _log.Debug("Error doing work: " + x.Message);
}

A proposito, nella mia implementazione del mio logger, effettuare una chiamata al metodo .Debug () lo registrerà solo se il mio file App.Config specifica che il livello del registro di esecuzione del programma è in modalità Debug.


Cosa succede se _log.Debug genera un'eccezione (per qualsiasi motivo)?
Giovanni

Quindi .Debug () mangia l'eccezione e non lo so mai. Ma mi sento molto più fiducioso nel mio logger che nel codice che spesso genera eccezioni. Se fossi preoccupato per questo, suppongo che potrei implementare una sorta di modello che controlla l'ingresso nel blocco di cattura e lo salva per dopo. In ogni caso - punto preso
Tim Claason il

1

Il controllo dell'esistenza è un buon caso d'uso:

// If an exception happens, it doesn't matter. Log the initial value or the new value

function logId(person) {
    let id = 'No ID';
    try {
        id = person.data.id;
    } catch {}
    console.log(id);
}

Un altro caso è quando finallyviene utilizzata una clausola:

 function getter(obj){}

 function objChecker()
   {
   try
     {
     /* Check argument length and constructor type */
     if (getter.length === 1 && getter.constructor === Function)
       {
       console.log("Correct implementation");
       return true;
       }
     else
       {
       console.log("Wrong implementation");
       return false;
       }
     }
   catch(e)
     {
     }
   finally
     {
     console.log(JSON.stringify(getter))
     }
   }

 // Test the override of the getter method 
 getter = getter;
 objChecker();
 getter = [];
 objChecker();
 getter = {};
 objChecker();
 getter = RegExp;
 objChecker();
 getter = Function;
 objChecker();

Esiste una proposta per consentire l' omissione del vincolo di cattura in ECMAScript che riguarda questo e altri casi d'uso simili.

Riferimenti


Un altro esempio: in nodejs, il metodo fs.exists (che restituisce vero o falso) è deprecato ( nodejs.org/dist/latest-v10.x/docs/api/… ) a favore di fs.access che genera un'eccezione in nel caso in cui non esista un file. Se si vuole fare qualcosa solo se il file esiste, la clausola catch è totalmente superflua.
systemovich

0

L'unica volta in cui ho davvero avuto bisogno di fare qualcosa del genere era con una classe di registrazione per un'app console. Esisteva un gestore di cattura globale di livello superiore che emetteva l'errore su STDOUT, registrava l'errore su un file, quindi chiudeva l'app con un codice di errore> 0.

Il problema qui era che a volte la registrazione nel file non riusciva. I permessi della directory ti hanno impedito di scrivere il file, il file era bloccato esclusivamente, qualunque cosa. In tal caso, non potrei gestire l'eccezione nello stesso modo che avevo altrove (cattura, registra i dettagli rilevanti , avvolgi in un tipo di eccezione migliore, ecc.) Perché la registrazione dei dettagli dell'eccezione ha fatto sì che il logger eseguisse le stesse azioni ( tentando di scrivere su un file) che non era già riuscito. Quando hanno fallito di nuovo ... Vedi dove sto andando. Entra in un ciclo infinito.

Se elimini alcuni dei blocchi try {}, puoi finire con l'eccezione che appare in una finestra di messaggio (non quello che vuoi per un'app di configurazione silenziosa). Quindi in quel metodo che scrive nel file di registro, ho un gestore di cattura vuoto. Questo non seppellisce l'errore poiché ottieni ancora il codice di errore> 0, interrompe semplicemente il ciclo infinito e consente all'app di uscire con grazia.

C'è ovviamente un commento, che spiega perché è vuoto.

(Avevo intenzione di postare questo in precedenza, avevo solo paura di farmi infiammare nell'oblio. Dato che non sono l'unico, però ...)


0

Va bene in una situazione in cui è necessario eseguire una certa sequenza, indipendentemente dal pezzo più critico posto alla fine.

Per esempio. In motion control comunicazione tra host e controller. In situazioni di emergenza vi sono una serie di passaggi di preparazione per interrompere il movimento, arrestare la generazione della traiettoria, rallentare i motori, abilitare le interruzioni, disabilitare l'amplificatore e successivamente è necessario interrompere l'alimentazione del bus. Tutto deve avvenire in sequenza e se uno dei passaggi fallisce il resto dei passaggi deve essere eseguito comunque anche con il rischio accettato di danneggiare l'hardware. Dì anche se tutto quanto sopra fallisce, la potenza del bus di uccisione deve avvenire comunque.


Ciò non significa che non fai nulla, solo che ciò che fai non interrompe il flusso. Cattura le tue eccezioni, salvale in memoria (nessun ritardo di scrittura sul disco), quindi interrompi la potenza in un'istruzione finally. Quindi fai quello che vuoi con le informazioni salvate.
Loren Pechtel,

0

Chiudere le risorse di sistema con garbo per recuperare da un errore

Per esempio. Se si sta eseguendo un servizio in background che non fornisce il feedback per l'utente, non si può decidere di spingere l'indicazione della errore per l'utente, ma si fare necessità di chiudere le risorse in uso in modo grazioso.

try
{
    // execution code here
}
catch(Exception e)
{
    // do nothing here
}
finally
{
    // close db connection
    // close file io resource
    // close memory stream
    // etc...
}

Idealmente, dovresti utilizzare la cattura in modalità debug per rilevare errori e stamparli sulla console o su un file di registro per il test. In un ambiente di produzione, i servizi in background generalmente non devono interrompere l'utente quando viene generato un errore, pertanto è necessario eliminare l'output dell'errore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.