Вопрос-ответ

What is a stack trace, and how can I use it to debug my application errors?

Что такое трассировка стека и как я могу использовать ее для отладки ошибок моего приложения?

Иногда, когда я запускаю свое приложение, оно выдает ошибку, которая выглядит как:

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)

Люди называют это "трассировкой стека". Что такое трассировка стека? Что это может сказать мне об ошибке, которая происходит в моей программе?


По поводу этого вопроса - довольно часто я вижу вопрос, когда начинающий программист "получает ошибку", и они просто вставляют свою трассировку стека и какой-то случайный блок кода, не понимая, что такое трассировка стека или как они могут ее использовать. Этот вопрос предназначен в качестве справочного материала для начинающих программистов, которым может понадобиться помощь в понимании значения трассировки стека.

Переведено автоматически
Ответ 1

Проще говоря, трассировка стека - это список вызовов методов, которые приложение выполняло в середине, когда было сгенерировано исключение.

Простой пример

На примере, приведенном в вопросе, мы можем точно определить, где в приложении было сгенерировано исключение. Давайте посмотрим на трассировку стека:

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)

Это очень простая трассировка стека. Если мы начнем с начала списка "at ...", мы сможем определить, где произошла наша ошибка. То, что мы ищем, - это самый верхний вызов метода, который является частью нашего приложения. В данном случае это:

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

Чтобы отладить это, мы можем открыть Book.java и посмотреть на строку 16, которая:

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

Это будет указывать на то, что что-то (вероятно title) есть null в приведенном выше коде.

Пример с цепочкой исключений

Иногда приложения перехватывают исключение и повторно выдают его как причину другого исключения. Обычно это выглядит следующим образом:

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 }

Это может дать вам трассировку стека, которая выглядит следующим образом:

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

Что отличается от этого, так это "Вызвано". Иногда исключения будут содержать несколько разделов "Вызвано". Для них обычно требуется найти "основную причину", которая будет одним из самых низких разделов "Вызвано" в трассировке стека. В нашем случае это:

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

Опять же, за этим исключением мы хотели бы взглянуть на строку 22 of Book.java , чтобы увидеть, что может вызвать NullPointerException здесь.

Более сложный пример с библиотечным кодом

Обычно трассировки стека намного сложнее, чем два примера выше. Вот пример (он длинный, но демонстрирует несколько уровней связанных исключений):

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

В этом примере гораздо больше. Больше всего нас беспокоит поиск методов из нашего кода, которые могли бы быть любыми в com.example.myproject пакете. Из второго примера (выше) мы сначала хотели бы найти основную причину, которая:

Caused by: java.sql.SQLException

Однако все вызовы методов в рамках этого являются библиотечным кодом. Итак, мы перейдем к блоку "Вызвано" выше, и в этом блоке "Вызвано" поищем первый вызов метода, исходящий из нашего кода, который:

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

Как и в предыдущих примерах, мы должны посмотреть на MyEntityService.java on line 59, потому что именно там возникла эта ошибка (здесь немного очевидно, что пошло не так, поскольку исключение SQLException указывает на ошибку, но процедура отладки - это то, что нам нужно).

Ответ 2

Что такое трассировка стека?

stacktrace - очень полезный инструмент отладки. Он показывает стек вызовов (то есть стек функций, которые вызывались до этого момента) на момент возникновения неперехваченного исключения (или время, когда трассировка стека была сгенерирована вручную). Это очень полезно, потому что не только показывает вам, где произошла ошибка, но и как программа оказалась в этом месте кода. Это приводит к следующему вопросу.:

Что такое исключение?

Исключение - это то, что среда выполнения использует, чтобы сообщить вам о возникновении ошибки. Популярными примерами являются NullPointerException, IndexOutOfBoundsException или ArithmeticException. Каждая из них возникает, когда вы пытаетесь сделать что-то невозможное. Например, исключение NullPointerException будет вызвано при попытке разыменования нулевого объекта:

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.

Как я должен работать с трассировками стека / исключениями?

Сначала выясните, что вызывает исключение. Попробуйте погуглить название исключения, чтобы выяснить причину этого исключения. В большинстве случаев это будет вызвано неправильным кодом. В приведенных выше примерах все исключения вызваны неправильным кодом. Итак, для примера NullPointerException вы могли бы убедиться, что a никогда не имеет значения null в то время. Вы могли бы, например, инициализировать a или включить проверку, подобную этой:

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

Таким образом, строка-нарушитель не выполняется, если a==null. То же самое относится и к другим примерам.

Иногда вы не можете быть уверены, что не получите исключение. Например, если вы используете сетевое подключение в своей программе, вы не можете помешать компьютеру потерять подключение к Интернету (например, вы не можете помешать пользователю отключить сетевое подключение компьютера). В этом случае сетевая библиотека, вероятно, выдаст исключение. Теперь вы должны перехватить исключение и обработать его. Это означает, что в примере с сетевым подключением вы должны попытаться повторно открыть соединение или уведомить пользователя или что-то в этом роде. Кроме того, всякий раз, когда вы используете catch, всегда перехватывайте только то исключение, которое вы хотите перехватить, не используйте расширенные операторы catch, такие как catch (Exception e) которые перехватывали бы все исключения. Это очень важно, потому что в противном случае вы можете случайно поймать неправильное исключение и отреагировать неправильным образом.

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!")
}

Почему я не должен использовать catch (Exception e)?

Давайте воспользуемся небольшим примером, чтобы показать, почему вы не должны просто перехватывать все исключения:

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;
}
}

Что пытается сделать этот код, так это перехватить ArithmeticException , вызванный возможным делением на 0. Но он также улавливает возможное , NullPointerException которое выдается, если a или b есть null. Это означает, что вы можете получить NullPointerException но вы будете рассматривать это как ArithmeticException и, вероятно, сделаете что-то неправильно. В лучшем случае вы все равно пропустите, что было исключение NullPointerException . Подобные вещи значительно усложняют отладку, поэтому не делайте этого.

TLDR


  1. Выясните, в чем причина исключения, и исправьте это, чтобы оно вообще не вызывало исключения.

  2. Если 1. невозможно, перехватите конкретное исключение и обработайте его.

    • Никогда просто не добавляйте try / catch, а затем просто игнорируйте исключение! Не делайте этого!

    • Никогда не используйте catch (Exception e), всегда перехватывайте определенные исключения. Это избавит вас от множества головных болей.



Ответ 3

В дополнение к тому, что упомянул Роб. Установка точек останова в вашем приложении позволяет выполнять пошаговую обработку стека. Это позволяет разработчику использовать отладчик, чтобы увидеть, в какой именно момент метод выполняет что-то непредвиденное.

Поскольку Роб использовал NullPointerException (NPE) для иллюстрации чего-то общего, мы можем помочь устранить эту проблему следующим образом:

если у нас есть метод , который принимает такие параметры, как: void (String firstName)

В нашем коде мы хотели бы оценить, что firstName содержит значение, мы бы сделали это следующим образом: if(firstName == null || firstName.equals("")) return;

Вышесказанное не позволяет нам использовать firstName в качестве небезопасного параметра. Следовательно, выполняя проверки null перед обработкой, мы можем гарантировать, что наш код будет выполняться должным образом. Чтобы подробнее рассмотреть пример , в котором используется объект с методами , мы можем посмотреть здесь:

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

Приведенный выше порядок проверки на наличие нулей является правильным, мы начинаем с базового объекта, в данном случае dog, а затем начинаем спускаться по дереву возможностей, чтобы убедиться, что все правильно перед обработкой. Если бы порядок был изменен на противоположный, потенциально мог бы возникнуть NPE и наша программа завершилась бы сбоем.

Ответ 4

Чтобы понять название: трассировка стека - это список исключений (или вы можете сказать список "Причин"), от самого поверхностного исключения (например, исключение уровня сервиса) до самого глубокого (например, исключение базы данных). Точно так же, как причина, по которой мы называем это "стеком", заключается в том, что stack является первым в Last out (FILO), самое глубокое исключение произошло в самом начале, затем была сгенерирована цепочка исключений с серией последствий, поверхностное исключение было последним по времени, но мы видим его в первую очередь.

Ключ 1: здесь нужно понять сложную и важную вещь: глубинная причина может быть не "основной причиной", потому что, если вы напишете какой-нибудь "плохой код", это может вызвать некоторое исключение, которое находится глубже, чем его слой. Например, неправильный sql-запрос может вызвать сброс подключения SQLServerException в нижней части вместо ошибки syndax, которая может находиться только в середине стека.

-> Найти первопричину посередине - это ваша работа.
введите описание изображения здесь

Ключ 2: Еще одна сложная, но важная вещь находится внутри каждого блока "Причина по", первая строка была самым глубоким слоем и занимала первое место для этого блока. Например,

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 был вызван Auther.java:25, который был вызван Bootstrap.java: 14, Book.java: 16 был основной причиной.
Здесь прилагается схема сортировки стека трассировки в хронологическом порядке.
введите описание изображения здесь

java