Che cos’è una traccia dello stack e come posso utilizzarla per eseguire il debug degli errori dell’applicazione?

A volte, quando eseguo la mia applicazione, mi viene visualizzato un errore simile al seguente:

Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 

Le persone hanno definito questo come una “traccia dello stack”. Cos’è una traccia di stack? Cosa può dirmi dell’errore che sta accadendo nel mio programma?


Riguardo a questa domanda – molto spesso vedo una domanda che arriva dove un programmatore inesperto sta “ottenendo un errore”, e semplicemente incollano la loro traccia di stack e qualche blocco casuale di codice senza capire quale sia la traccia dello stack o come possano usarla. Questa domanda è intesa come riferimento per i programmatori principianti che potrebbero aver bisogno di aiuto per comprendere il valore di una traccia dello stack.

In termini semplici, una traccia dello stack è un elenco delle chiamate al metodo in cui si trovava l’applicazione nel mezzo di quando è stata lanciata un’eccezione.

Semplice esempio

Con l’esempio fornito nella domanda, possiamo determinare esattamente dove è stata generata l’eccezione nell’applicazione. Diamo un’occhiata alla traccia dello stack:

 Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 

Questa è una traccia dello stack molto semplice. Se iniziamo dall’inizio della lista di “a …”, possiamo dire dove è successo il nostro errore. Quello che stiamo cercando è la chiamata al metodo più in alto che fa parte della nostra applicazione. In questo caso, è:

 at com.example.myproject.Book.getTitle(Book.java:16) 

Per eseguire il debug di questo, possiamo aprire Book.java e guardare la riga 16 , che è:

 15 public String getTitle() { 16 System.out.println(title.toString()); 17 return title; 18 } 

Ciò indicherebbe che qualcosa (probabilmente il title ) è null nel codice precedente.

Esempio con una catena di eccezioni

A volte le applicazioni catturano un’eccezione e la rilanciano come causa di un’altra eccezione. Questo in genere assomiglia a:

 34 public void getBookIds(int id) { 35 try { 36 book.getId(id); // this method it throws a NullPointerException on line 22 37 } catch (NullPointerException e) { 38 throw new IllegalStateException("A book has a null property", e) 39 } 40 } 

Questo potrebbe darti una traccia di stack che assomiglia a:

 Exception in thread "main" java.lang.IllegalStateException: A book has a null property at com.example.myproject.Author.getBookIds(Author.java:38) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) Caused by: java.lang.NullPointerException at com.example.myproject.Book.getId(Book.java:22) at com.example.myproject.Author.getBookIds(Author.java:36) ... 1 more 

Ciò che è diverso in questo è il “Causato da”. A volte le eccezioni avranno più sezioni “Causate da”. Per questi, in genere si desidera trovare la “causa principale”, che sarà una delle sezioni “Caused by” più basse nella traccia dello stack. Nel nostro caso, è:

 Caused by: java.lang.NullPointerException < -- root cause at com.example.myproject.Book.getId(Book.java:22) <-- important line 

Ancora una volta, con questa eccezione vorremmo esaminare la riga 22 di Book.java per vedere cosa potrebbe causare qui NullPointerException .

Esempio più scoraggiante con codice libreria

Di solito le tracce dello stack sono molto più complesse rispetto ai due esempi sopra. Ecco un esempio (è molto lungo, ma mostra diversi livelli di eccezioni concatenate):

 javax.servlet.ServletException: Something bad happened at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) at org.mortbay.jetty.Server.handle(Server.java:326) at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542) at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943) at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756) at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218) at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404) at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228) at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582) Caused by: com.example.myproject.MyProjectServletException at com.example.myproject.MyServlet.doPost(MyServlet.java:169) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166) at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30) ... 27 more Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity] at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329) at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822) at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268) at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321) at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210) at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195) at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693) at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689) at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344) at $Proxy19.save(Unknown Source) at com.example.myproject.MyEntityService.save(MyEntityService.java:59) < -- relevant call (see notes below) at com.example.myproject.MyServlet.doPost(MyServlet.java:164) ... 32 more Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...] at org.hsqldb.jdbc.Util.throwError(Unknown Source) at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source) at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105) at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57) ... 54 more 

In questo esempio, c'è molto di più. Ciò di cui ci preoccupiamo maggiormente è la ricerca di metodi che com.example.myproject dal nostro codice , che sarebbe qualsiasi cosa nel pacchetto com.example.myproject . Dal secondo esempio (sopra), dovremmo prima cercare la causa principale, ovvero:

 Caused by: java.sql.SQLException 

Tuttavia, tutte le chiamate al metodo sotto sono codice di libreria. Quindi passeremo al "Caused by" sopra di esso e cercheremo la prima chiamata al metodo proveniente dal nostro codice, che è:

 at com.example.myproject.MyEntityService.save(MyEntityService.java:59) 

Come negli esempi precedenti, dovremmo guardare MyEntityService.java alla riga 59 , perché è da lì che è MyEntityService.java questo errore (questo è un po 'ovvio che cosa è andato storto, dal momento che SQLException afferma l'errore, ma la procedura di debug è ciò che stiamo cercando) .

Sto postando questa risposta in modo che la risposta più alta (quando ordinata per attività) non sia semplicemente sbagliata.

Cos’è uno Stacktrace?

Uno stacktrace è uno strumento di debug molto utile. Mostra lo stack di chiamate (ovvero la pila di funzioni richiamate fino a quel momento) nel momento in cui è stata generata un’eccezione non rilevata (o il tempo in cui lo stacktrace è stato generato manualmente). Questo è molto utile perché non mostra solo dove si è verificato l’errore, ma anche come il programma è finito in quel punto del codice. Questo porta alla domanda successiva:

Cos’è un’eccezione?

Un’eccezione è ciò che l’ambiente runtime utilizza per dirti che si è verificato un errore. Esempi popolari sono NullPointerException, IndexOutOfBoundsException o ArithmeticException. Ognuno di questi è causato quando si tenta di fare qualcosa che non è ansible. Ad esempio, verrà generata una NullPointerException quando si tenta di trasferire un object Null:

 Object a = null; a.toString(); //this line throws a NullPointerException Object[] b = new Object[5]; System.out.println(b[10]); //this line throws an IndexOutOfBoundsException, //because b is only 5 elements long int ia = 5; int ib = 0; ia = ia/ib; //this line throws an ArithmeticException with the //message "/ by 0", because you are trying to //divide by 0, which is not possible. 

Come devo gestire Stacktraces / Exceptions?

Inizialmente, scopri cosa sta causando l’eccezione. Prova a cercare su google il nome dell’eccezione per scoprire qual è la causa di tale eccezione. Il più delle volte sarà causato da un codice errato. Negli esempi sopra riportati, tutte le eccezioni sono causate da codice errato. Quindi, per l’esempio NullPointerException puoi assicurarti che a non sia mai nullo in quel momento. Ad esempio, è ansible inizializzare o includere un controllo come questo:

 if (a!=null) { a.toString(); } 

In questo modo, la riga incriminata non viene eseguita se a==null . Lo stesso vale per gli altri esempi.

A volte non puoi assicurarti di non ottenere un’eccezione. Ad esempio, se si utilizza una connessione di rete nel programma, non è ansible impedire al computer di perdere la connessione Internet (ad esempio, non è ansible impedire all’utente di disconnettere la connessione di rete del computer). In questo caso la libreria di rete probabilmente genererà un’eccezione. Ora dovresti prendere l’eccezione e gestirla . Ciò significa, nell’esempio con la connessione di rete, provare a riaprire la connessione o informare l’utente o qualcosa del genere. Inoltre, ogni volta che usi il catch, prendi sempre solo l’eccezione che vuoi catturare, non usare le istruzioni di cattura generale come catch (Exception e) che catturerebbero tutte le eccezioni. Questo è molto importante, perché altrimenti potresti accidentalmente catturare l’eccezione sbagliata e reactjs nel modo sbagliato.

 try { Socket x = new Socket("1.1.1.1", 6789); x.getInputStream().read() } catch (IOException e) { System.err.println("Connection could not be established, please try again later!") } 

Perché non dovrei usare il catch (Exception e) ?

Usiamo un piccolo esempio per mostrare perché non dovresti solo catturare tutte le eccezioni:

 int mult(Integer a,Integer b) { try { int result = a/b return result; } catch (Exception e) { System.err.println("Error: Division by zero!"); return 0; } } 

Ciò che questo codice sta tentando di fare è catturare l’ ArithmeticException causata da una ansible divisione di 0. Ma NullPointerException anche una ansible NullPointerException che viene lanciata se a o b sono null . Ciò significa che potresti ottenere una NullPointerException ma la tratterai come una ArithmeticException e probabilmente farai la cosa sbagliata. Nel migliore dei casi ti manca ancora una NullPointerException. Cose del genere rendono il debug molto più difficile, quindi non farlo.

TLDR

  1. Scopri qual è la causa dell’eccezione e risolvila, in modo che non generi affatto l’eccezione.
  2. Se 1. non è ansible, prendi l’eccezione specifica e gestiscila.

    • Non aggiungere mai un try / catch e quindi ignorare l’eccezione! Non farlo!
    • Non utilizzare mai la catch (Exception e) , prendere sempre eccezioni specifiche. Questo ti farà risparmiare un sacco di mal di testa.

Per aggiungere a ciò che Rob ha menzionato. L’impostazione dei punti di interruzione nell’applicazione consente l’elaborazione passo-passo dello stack. Ciò consente allo sviluppatore di utilizzare il debugger per vedere in quale punto esatto il metodo sta facendo qualcosa che non era previsto.

Poiché Rob ha utilizzato NullPointerException (NPE) per illustrare qualcosa di comune, possiamo aiutare a rimuovere questo problema nel modo seguente:

se abbiamo un metodo che prende parametri come: void (String firstName)

Nel nostro codice vorremmo valutare che firstName contenga un valore, faremmo così: if(firstName == null || firstName.equals("")) return;

Quanto sopra ci impedisce di utilizzare firstName come parametro non sicuro. Pertanto, eseguendo controlli nulli prima dell’elaborazione, possiamo contribuire a garantire che il nostro codice funzioni correttamente. Per espandere un esempio che utilizza un object con metodi possiamo guardare qui:

if(dog == null || dog.firstName == null) return;

Quanto sopra è l’ordine corretto per verificare i valori nulli, iniziamo con l’object base, cane in questo caso, e quindi iniziamo a camminare lungo l’albero delle possibilità per assicurarci che tutto sia valido prima dell’elaborazione. Se l’ordine fosse stato annullato, un NPE potrebbe essere potenzialmente gettato e il nostro programma si schianterebbe.

Vi è un’altra funzionalità di stacktrace offerta dalla famiglia Throwable: la possibilità di manipolare le informazioni di traccia dello stack.

Comportamento standard:

 package test.stack.trace; public class SomeClass { public void methodA() { methodB(); } public void methodB() { methodC(); } public void methodC() { throw new RuntimeException(); } public static void main(String[] args) { new SomeClass().methodA(); } } 

Traccia dello stack:

 Exception in thread "main" java.lang.RuntimeException at test.stack.trace.SomeClass.methodC(SomeClass.java:18) at test.stack.trace.SomeClass.methodB(SomeClass.java:13) at test.stack.trace.SomeClass.methodA(SomeClass.java:9) at test.stack.trace.SomeClass.main(SomeClass.java:27) 

Traccia stack manipolata:

 package test.stack.trace; public class SomeClass { ... public void methodC() { RuntimeException e = new RuntimeException(); e.setStackTrace(new StackTraceElement[]{ new StackTraceElement("OtherClass", "methodX", "String.java", 99), new StackTraceElement("OtherClass", "methodY", "String.java", 55) }); throw e; } public static void main(String[] args) { new SomeClass().methodA(); } } 

Traccia dello stack:

 Exception in thread "main" java.lang.RuntimeException at OtherClass.methodX(String.java:99) at OtherClass.methodY(String.java:55) 

Per capire il nome : Una traccia dello stack è una lista di Eccezioni (o si può dire un elenco di “Cause per”), dall’eccezione più superficiale (ad es. Eccezione livello servizio) alla più profonda (ad es. Eccezione database). Proprio come la ragione per cui lo chiamiamo ‘stack’ è perché lo stack è First in Last out (FILO), l’eccezione più profonda è avvenuta proprio all’inizio, quindi una catena di eccezioni ha generato una serie di conseguenze, l’eccezione di superficie è stata l’ultima uno è successo in tempo, ma lo vediamo in primo luogo.

Chiave 1 : una cosa difficile e importante qui deve essere la seguente: la causa più profonda potrebbe non essere la “causa principale”, perché se si scrive un “codice errato”, potrebbe causare qualche eccezione al di sotto della quale è più profonda del suo livello. Ad esempio, una query sql non valida può causare il reset della connessione SQLServerException nel bottem anziché l’errore syndax, che può essere solo nel mezzo dello stack.

-> Individua la causa principale nel mezzo è il tuo lavoro. inserisci la descrizione dell'immagine qui

Chiave 2 : Un’altra cosa difficile ma importante è all’interno di ogni blocco “Cause per”, la prima linea era il livello più profondo e si verificava il primo posto per questo blocco. Per esempio,

 Exception in thread "main" java.lang.NullPointerException at com.example.myproject.Book.getTitle(Book.java:16) at com.example.myproject.Author.getBookTitles(Author.java:25) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) 

Book.java:16 è stato chiamato da Auther.java:25 che è stato chiamato da Bootstrap.java:14, Book.java:16 era la causa principale. Qui allega un diagramma per ordinare la pila di tracce in ordine cronologico. inserisci la descrizione dell'immagine qui

Solo per aggiungere agli altri esempi, ci sono classi interne (nidificate) che appaiono con il segno $ . Per esempio:

 public class Test { private static void privateMethod() { throw new RuntimeException(); } public static void main(String[] args) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { privateMethod(); } }; runnable.run(); } } 

Risulterà in questa traccia dello stack:

 Exception in thread "main" java.lang.RuntimeException at Test.privateMethod(Test.java:4) at Test.access$000(Test.java:1) at Test$1.run(Test.java:10) at Test.main(Test.java:13) 

Gli altri post descrivono cosa è una traccia di stack, ma può essere ancora difficile lavorarci.

Se si ottiene una traccia di stack e si desidera tracciare la causa dell’eccezione, un buon punto di partenza per la sua comprensione è l’utilizzo di Java Stack Trace Console in Eclipse . Se usi un altro IDE, potrebbe esserci una funzionalità simile, ma questa risposta riguarda Eclipse.

Innanzitutto, assicurati di avere tutte le tue sorgenti Java accessibili in un progetto Eclipse.

Quindi, nella prospettiva Java , fare clic sulla scheda Console (in genere nella parte inferiore). Se la vista Console non è visibile, vai all’opzione di menu Finestra -> Mostra vista e seleziona Console .

Quindi nella finestra della console, fare clic sul seguente pulsante (sulla destra)

Pulsante delle console

quindi selezionare Java Stack Trace Console dall’elenco a discesa.

Incolla la traccia dello stack nella console. Fornirà quindi un elenco di collegamenti nel codice sorgente e qualsiasi altro codice sorgente disponibile.

Questo è ciò che potresti vedere (immagine dalla documentazione di Eclipse):

Diagramma dalla documentazione di Eclipse

La chiamata al metodo più recente effettuata sarà la parte superiore dello stack, ovvero la riga superiore (escluso il testo del messaggio). Scendendo lo stack va indietro nel tempo. La seconda linea è il metodo che chiama la prima riga, ecc.

Se si utilizza un software open source, potrebbe essere necessario scaricare e albind al progetto le origini se si desidera esaminare. Scarica i jar di origine, nel tuo progetto, apri la cartella Biblioteche di riferimento per trovare il tuo jar per il tuo modulo open source (quello con i file di class), quindi fai clic con il pulsante destro del mouse, seleziona Proprietà e collega il jar di origine.