English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Le eccezioni sono uno strumento di debug potente perché rispondono a tre domande principali:
1. Cosa è andato storto?
2. Dove è andato storto?
3. Perché è andato storto?
In caso di utilizzo efficace delle eccezioni, il tipo di eccezione risponde a 'cosa' è stato lanciato, il tracciamento delle eccezioni risponde a 'dove' è stato lanciato, e il messaggio di eccezione risponde a 'perché' è stato lanciato. Se le tue eccezioni non rispondono a tutte queste domande, potrebbe essere che non le stai utilizzando correttamente.
Tre principi possono aiutarti a sfruttare al massimo le eccezioni durante il debug, questi principi sono:
1. Chiara e specifica
2. Lancio anticipato
3. Cattura ritardata
Per illustrare questi tre principi di gestione efficace delle eccezioni, questo articolo discute il simulatore di gestione personale JCheckbook, utilizzato per registrare e tracciare attività di conto bancario come depositi e prelievi, emissioni di fatture.
Chiara e specifica
Java definisce una gerarchia di classi di eccezioni, che inizia con Throwable, si estende a Error e Exception, e Exception si estende a RuntimeException. Come mostrato in Figura 1.
Queste classi sono generiche e non forniscono molte informazioni di errore, anche se l'istanziazione di queste classi è grammaticalmente valida (ad esempio: new Throwable()), è meglio trattarle come classi base virtuali e utilizzare le loro sottoclassi più specializzate. Java ha fornito un gran numero di sottoclassi di eccezione, e se necessario, puoi anche definire la tua eccezione personalizzata.
Ad esempio: il pacchetto java.io definisce la sottoclasse Exception IOException, che è più specializzata rispetto a FileNotFoundException, EOFException e ObjectStreamException, che sono sottoclassi di IOException. Ogni una descrive un tipo specifico di errore I/O: perdita di file, fine anomala del file e flusso di oggetti serializzati errati. Più specifica è l'eccezione, meglio la nostra applicazione può rispondere alla domanda 'cosa è andato storto'.
E' molto importante essere chiari quando si cattura un'eccezione. Ad esempio: JCheckbook può gestire FileNotFoundException chiedendo nuovamente il nome del file all'utente, per EOFException può continuare a eseguire in base alle informazioni lette prima dell'eccezione, se viene lanciata ObjectStreamException, il programma dovrebbe avvisare l'utente che il file è danneggiato e dovrebbe utilizzare un file di backup o un altro file.
Java rende facile catturare eccezioni chiare, poiché possiamo definire più blocc catch all'interno dello stesso blocco try, affinché possano essere gestite in modo appropriato ogni eccezione.
File prefsFile = new File(prefsFilename); try{ readPreferences(prefsFile); } catch (FileNotFoundException e){} // avvisa l'utente che il file specificato // non esiste } catch (EOFException e){ // avvisa l'utente che è stato raggiunto la fine del file // è stato raggiunto } catch (ObjectStreamException e){ // avvisa l'utente che il file è corrotto } catch (IOException e){ // avvisa l'utente che c'è stato un altro I/O // è verificato un errore }
JCheckbook utilizza più blocchi catch per fornire all'utente informazioni chiare sulle eccezioni catturate. Ad esempio: se viene catturato FileNotFoundException, può suggerire all'utente di specificare un altro file. In alcuni casi, il lavoro aggiuntivo di codifica necessario per più blocchi catch potrebbe essere un onere non necessario, ma in questo esempio, il codice aggiuntivo aiuta effettivamente il programma a fornire una risposta più amichevole all'utente.
Oltre alle eccezioni gestite dai primi tre blocchi catch, l'ultimo blocco catch fornisce all'utente informazioni di errore più generali quando viene lanciata IOException. In questo modo, il programma può fornire informazioni il più possibile specifiche, ma anche avere la capacità di gestire altre eccezioni impreviste.
A volte gli sviluppatori catturano eccezioni generiche e mostrano il nome della classe dell'eccezione o stampano le informazioni di stack per ottenere "specificità". Non fate così! Gli utenti vedono java.io.EOFException o le informazioni di stack e solo avranno mal di testa, non riceveranno aiuto. Dovrebbe catturare l'eccezione specifica e fornire informazioni precise all'utente in un linguaggio comprensibile. Tuttavia, è possibile stampare le informazioni di stack nel file di log. Ricordate, le eccezioni e le informazioni di stack sono per aiutare gli sviluppatori, non gli utenti.
Infine, è importante notare che JCheckbook non cattura l'eccezione in readPreferences(), ma lascia che l'utente faccia la cattura e il trattamento dell'eccezione nella parte dell'interfaccia utente, in modo che l'utente possa essere notificato tramite finestre di dialogo o altri metodi. Questo viene chiamato "cattura ritardata", che sarà trattato nel testo seguente.
Estrarre in anticipo
Le informazioni di stack dell'eccezione forniscono l'ordine esatto della catena di chiamate che ha causato l'eccezione, inclusi il nome della classe, il nome del metodo, il nome del file di codice e persino il numero di riga, per localizzare con precisione il luogo in cui è stata rilevata l'eccezione.
java.lang.NullPointerException at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.open(Metodo Nativo) at java.io.FileInputStream.<init>(FileInputStream.java:103) at jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
Come sopra mostrato, il metodo open() della classe FileInputStream lancia NullPointerException. Tuttavia, notare che FileInputStream.close() fa parte della libreria standard Java e potrebbe causare questo problema non a causa dell'API Java, ma del nostro codice. Quindi il problema potrebbe essere presente in uno dei metodi precedenti, fortunatamente è anche stampato nelle informazioni di stack.
Sfortunatamente, la NullPointerException è l'eccezione con meno informazioni in Java (ma è anche quella più comune e più frustrante). Non menziona affatto la cosa che ci interessa di più: dove è null. Quindi dobbiamo fare un passo indietro per trovare dove è successo l'errore.
public void readPreferences(String filename) lancia IllegalArgumentException{ if (filename == null){ throw new IllegalArgumentException("filename is null"); } //if //...eseguire altre operazioni... InputStream in = new FileInputStream(filename); //...leggere il file delle preferenze... }
Tramite l'anticipazione dell'eccezione (noto anche come 'fallimento rapido'), l'eccezione diventa chiara e precisa. Le informazioni di stack immediatamente riflettono cosa è andato storto (fornito un valore di parametro non valido), perché è successo (il nome del file non può essere un valore nullo) e dove è successo (la parte iniziale di readPreferences()). Così le nostre informazioni di stack possono fornire fedelmente:
java.lang.IllegalArgumentException: nome del file è nullo at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207) at jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
Inoltre, le informazioni di eccezione incluse ("Il nome del file è vuoto") rendono l'informazione fornita dall'eccezione più ricca, rispondendo chiaramente a quale sia il valore nullo, che è una risposta che non poteva essere fornita dal NullPointerException che abbiamo lanciato nel codice precedente.
Implementare un fallimento rapido quando si rileva un errore può evitare la costruzione inutilizzata di oggetti o l'occupazione di risorse come file o connessioni di rete. Allo stesso modo, possono essere evitate le operazioni di pulizia necessarie per aprire queste risorse.
Cattura differita
Un errore comune che sia i neofiti che gli esperti possono commettere è di catturare un'eccezione prima che il programma abbia la capacità di gestirla. Il compilatore Java incoraggia indirettamente questo comportamento richiedendo che tutte le eccezioni trovate siano catturate o throw. L'approccio naturale è di avvolgere il codice in un blocco try e usare un blocco catch per catturare l'eccezione per evitare di ricevere un errore di compilazione.
Il problema è che cosa fare dopo aver catturato l'eccezione? Il peggior errore è non fare nulla. Un blocco catch vuoto è come gettare l'eccezione in un buco nero, perdendo tutte le informazioni utili su quando, dove e perché è avvenuto l'errore. Scrivere l'eccezione nei log è un po' meglio, almeno ci sono delle tracce da seguire. Ma non possiamo aspettarci che l'utente legga o capisca i file di log e le informazioni di eccezione. Mostrare un dialogo di errore tramite readPreferences() non è una buona scelta, perché sebbene JCheckbook sia attualmente un'applicazione desktop, abbiamo in programma di trasformarla in una web application basata su HTML. In questo caso, mostrare un dialogo di errore non è una buona opzione. Inoltre, sia nella versione HTML che nella versione C/S, le informazioni di configurazione vengono lette sul server, mentre le informazioni di errore devono essere visualizzate nel browser web o nel programma client. readPreferences() dovrebbe considerare queste future esigenze già durante la progettazione. Separare correttamente il codice dell'interfaccia utente e la logica del programma può migliorare la riutilizzabilità del nostro codice.
Catturare un'eccezione troppo presto prima di gestirla può portare a errori più gravi e altre eccezioni. Ad esempio, se il metodo readPreferences() del precedente esempio cattura e registra immediatamente una possibile FileNotFoundException chiamando il costruttore di FileInputStream, il codice diventa così:
public void readPreferences(String filename){ //... InputStream in = null; // NON FARE QUESTO!!! try{ in = new FileInputStream(filename); } catch (FileNotFoundException e){} logger.log(e); } in.read(...); //... }
The above code caught the FileNotFoundException when it was completely unable to recover from it. If the file cannot be found, the following method is obviously unable to read it. What will happen if readPreferences() is required to read a non-existent file? Of course, FileNotFoundException will be recorded, and if we look at the log file at that time, we will know. However, when the program tries to read data from the file, what will happen? Since the file does not exist, the variable in is empty, and a NullPointerException will be thrown.
When debugging the program, instinct tells us to look at the information at the end of the log. It will be a NullPointerException, and it is very annoying that this exception is very unspecific. The error message not only misleads us about what went wrong (the real error is FileNotFoundException, not NullPointerException), but also misleads us about the source of the error. The real problem is several lines outside the place where NullPointerException is thrown, among which there may be several method calls and class destructions. Our attention is drawn away from the real error by this small fish until we look back at the log to find the source of the problem.
Since the real job of readPreferences() is not to catch these exceptions, what should it be? It may seem a bit contrary to common sense, but in fact, the most appropriate action is to do nothing, do not catch the exception immediately. Pass the responsibility to the caller of readPreferences(), letting it study the appropriate method to handle the missing configuration file, it may prompt the user to specify another file, or use the default value, and if it is really not possible, maybe warn the user and exit the program.
The method of passing the responsibility of exception handling to the upstream of the call chain is to declare the exception in the throws clause of the method. When declaring the possible exceptions to be thrown, it is better to be as specific as possible. This is used to identify the exception types that the program calling your method needs to know and be prepared to handle. For example, the 'deferred catch' version of readPreferences() might look like this:
public void readPreferences(String filename) throws IllegalArgumentException, FileNotFoundException, IOException{ if (filename == null){ throw new IllegalArgumentException("filename is null"); } //if //... InputStream in = new FileInputStream(filename); //... }
Tecnicamente, l'unica eccezione che dobbiamo dichiarare è IOException, ma abbiamo chiarito che il metodo potrebbe lanciare FileNotFoundException. IllegalArgumentException non è necessariamente dichiarata, poiché è un'eccezione non controllata (cioè una sottoclasse di RuntimeException). Tuttavia, la dichiarazione è fatta per documentare il nostro codice (queste eccezioni dovrebbero anche essere indicate nei JavaDocs del metodo).
Naturalmente, alla fine il tuo programma deve catturare le eccezioni, altrimenti terminerà inaspettatamente. Tuttavia, l'abilità qui è catturare le eccezioni al livello appropriato, in modo che il tuo programma possa recuperare significativamente dalle eccezioni e continuare senza causare errori più profondi; o fornire informazioni chiare agli utenti, inclusi suggerimenti su come riprendersi dagli errori. Se il tuo metodo non è in grado di gestire le eccezioni, non trattarle, ma lasciale catturare e gestite in un livello appropriato.
Conclusione
Sia i programmatori esperti sanno che la maggiore difficoltà nella debug di un programma non è nella risoluzione dei difetti, ma nel trovare dove si nascondono i difetti tra un'enorme quantità di codice. Seguendo i tre principi di questo articolo, è possibile far sì che le eccezioni ti assistano nel tracciare e eliminare i difetti, rendendo il tuo programma più robusto e più amichevole per gli utenti. Questo è tutto il contenuto dell'articolo, speriamo che possa essere di aiuto per la tua apprendimento o lavoro.
Dichiarazione: il contenuto di questo articolo è stato prelevato da Internet, il copyright spetta ai rispettivi autori, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene i diritti di proprietà, non è stato editato manualmente e non assume alcuna responsabilità legale. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, questo sito rimuoverà immediatamente il contenuto sospetto di violazione del copyright.