Рубрики
Без рубрики

Как прочитать и понять трассировку стека Java

Когда что-то идет не так в запущенном приложении Java, часто первым признаком того, что вы увидите, является печать строк… Помечено как java, новички, ошибки.

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

Exception in thread "main" java.lang.RuntimeException: Something has gone wrong, aborting!
  at com.myproject.module.MyProject.badMethod(MyProject.java:22)
  at com.myproject.module.MyProject.oneMoreMethod(MyProject.java:18)
  at com.myproject.module.MyProject.anotherMethod(MyProject.java:14)
  at com.myproject.module.MyProject.someMethod(MyProject.java:10)
  at com.myproject.module.MyProject.main(MyProject.java:6)

Это Stacktrace , и в этом посте я объясню, что это такое, как они сделаны и как их читать и понимать. Если это кажется вам болезненным, тогда читайте дальше…

Анатомия трассировки стека

Обычно трассировка стека отображается, когда Исключение неправильно обрабатывается в коде. Это может быть один из встроенных типов исключений или пользовательское исключение , созданное программой или библиотекой.

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

Давайте проанализируем эту трассировку стека. Первая строка сообщает нам подробности исключения:

Это хорошее начало. Строка 2 показывает, какой код был запущен, когда это произошло:

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

Что пошло не так?

То, что вызывает исключение, обычно является явным оператором throw . Используя имя файла и номер строки, вы можете точно проверить, какой код вызвал исключение. Вероятно, это будет выглядеть примерно так:

  throw new RuntimeException("Something has gone wrong, aborting!");

Это отличное место для начала поиска основной проблемы: существуют ли какие-либо утверждения “если” вокруг этого? Что делает этот код? Откуда берутся данные, используемые в этом методе?

Код также может выдавать исключение без явного оператора throw , например, вы можете получить:

  • Исключение NullPointerException если obj является нулевым в коде, который вызывает obj.someMethod()
  • Арифметическое исключение, если деление на ноль происходит в целочисленной арифметике, т. е. 1/0 – любопытно, что нет исключения, если это вычисление с плавающей запятой, хотя, 1.0/0.0 возвращает бесконечность просто отлично!
  • Исключение NullPointerException, если нуль Целое число распаковывается в int в коде, подобном этому: Целое число; a++;
  • В Спецификации языка Java есть несколько других примеров, поэтому важно знать, что исключения могут возникать без явного вызова.

Работа с исключениями, создаваемыми библиотеками

Одной из самых сильных сторон Java является огромное количество доступных библиотек. Любая популярная библиотека будет хорошо протестирована, поэтому, как правило, при возникновении исключения из библиотеки лучше сначала проверить, вызвана ли ошибка тем, как ее использует наш код.

Например, если мы используем класс Fraction из Apache Commons Lang и передаем ему некоторые входные данные следующим образом:

  Fraction.getFraction(numberOfFoos, numberOfBars);

Если Количество панелей равно нулю, то трассировка стека будет выглядеть следующим образом:

Exception in thread "main" java.lang.ArithmeticException: The denominator must not be zero
  at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)   
  at com.project.module.MyProject.anotherMethod(MyProject.java:17)
  at com.project.module.MyProject.someMethod(MyProject.java:13)
  at com.project.module.MyProject.main(MyProject.java:9)

Многие хорошие библиотеки предоставляют Javadoc который содержит информацию о том, какие исключения могут возникать и почему. В этом случае Fraction.getfraction задокументировал выдаст исключение ArithmeticException, если дробь имеет нулевой знаменатель . Здесь это также ясно из сообщения, но в более сложных или неоднозначных ситуациях документы могут оказать большую помощь.

Чтобы прочитать эту трассировку стека, начните сверху с типа исключения – ArithmeticException и сообщения Знаменатель не должен быть равен нулю . Это дает представление о том, что пошло не так, но чтобы выяснить, какой код вызвал исключение, пропустите трассировку стека в поисках чего-то в пакете com.мой проект (это на 3-й строке здесь), затем отсканируйте до конца строки, чтобы увидеть, где находится код ( MyProject.java:17 ). Эта строка будет содержать некоторый код, который вызывает Fraction.getFraction . Это отправная точка для исследования: что передается в getFraction ? Откуда это взялось?

В больших проектах с большим количеством библиотек трассировки стека могут занимать сотни строк поэтому, если вы видите большую трассировку стека, потренируйтесь сканировать список at ... at ... at ... поиск собственного кода – это полезный навык для развития.

Наилучшая практика: Улавливание и Выбрасывание исключений

Допустим, мы работаем над большим проектом, который имеет дело с вымышленными Foobar , и наш код будет использоваться другими. Мы могли бы решить перехватить исключение ArithmeticException из Дроби и переопределить его как нечто специфичное для проекта, которое выглядит следующим образом:

  try {
    ....
    Fraction.getFraction(x,y);
    ....
  } catch ( ArithmeticException e ){ 
    throw new MyProjectFooBarException("The number of FooBars cannot be zero", e);
  }

Поймать исключение ArithmeticException и выбросить его имеет несколько преимуществ:

  • Наши пользователи защищены от необходимости заботиться об исключении ArithmeticException , что дает нам гибкость в изменении способа использования commons-lang.
  • Можно добавить больше контекста, например, указав, что проблема в количестве фообаров .
  • Это также может облегчить чтение трассировок стека, как мы увидим ниже.

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

Обратите внимание, что конструктор для Исключения Foo Bar моего проекта принимает 2 аргумента: сообщение и Исключение, которое его вызвало. Каждое исключение в Java имеет поле причина , и при выполнении перехвата и повторной обработки , как это, вы должны всегда устанавливать это, чтобы помочь людям отлаживать ошибки. Трассировка стека теперь может выглядеть примерно так:

Exception in thread "main" com.myproject.module.MyProjectFooBarException: The number of FooBars cannot be zero
  at com.myproject.module.MyProject.anotherMethod(MyProject.java:19)
  at com.myproject.module.MyProject.someMethod(MyProject.java:12)
  at com.myproject.module.MyProject.main(MyProject.java:8)
Caused by: java.lang.ArithmeticException: The denominator must not be zero
  at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)
  at com.myproject.module.MyProject.anotherMethod(MyProject.java:17)
  ... 2 more

Последнее созданное исключение находится в первой строке, а место, где оно было создано, все еще находится в строке 2. Однако этот тип трассировки стека может вызвать путаницу, поскольку catch-and-retrow изменил порядок вызовов методов по сравнению с трассировками стека, которые мы видели ранее. Основной метод больше не находится внизу, а код, который первым выдал исключение, больше не находится вверху. Когда у вас есть несколько этапов перехвата и повторного броска, он становится больше, но схема одна и та же:

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

Библиотеки против фреймворков

Разница между библиотекой и фреймворком в Java заключается в следующем:

  • Ваш код вызывает методы в библиотеке
  • Ваш код вызывается методами в фреймворке

Распространенным типом фреймворка является сервер веб-приложений, такой как Spark Java или Spring Boot . Используя Spark Java и Commons-Lang с нашим кодом, мы могли бы увидеть трассировку стека, подобную этой:

com.framework.FrameworkException: Error in web request
    at com.framework.ApplicationStarter.lambda$start$0(ApplicationStarter.java:15)
    at spark.RouteImpl$1.handle(RouteImpl.java:72)
    at spark.http.matching.Routes.execute(Routes.java:61)
    at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:134)
    at spark.embeddedserver.jetty.JettyHandler.doHandle(JettyHandler.java:50)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1568)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
    at org.eclipse.jetty.server.Server.handle(Server.java:503)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.project.module.MyProjectFooBarException: The number of FooBars cannot be zero
    at com.project.module.MyProject.anotherMethod(MyProject.java:20)
    at com.project.module.MyProject.someMethod(MyProject.java:12)
    at com.framework.ApplicationStarter.lambda$start$0(ApplicationStarter.java:13)
    ... 16 more
Caused by: java.lang.ArithmeticException: The denominator must not be zero
    at org.apache.commons.lang3.math.Fraction.getFraction(Fraction.java:143)
    at com.project.module.MyProject.anotherMethod(MyProject.java:18)
    ... 18 more

Хорошо, это уже довольно долго. Как и прежде, мы должны сначала заподозрить наш собственный код, но становится все труднее и труднее найти где это . Вверху находится исключение фреймворка, внизу – Библиотека, а прямо посередине – наш собственный код. Фу!

Сложный фреймворк и библиотека могут создать дюжину или более Вызвано разделами: , поэтому хорошей стратегией будет перейти к тем, кто ищет ваш собственный код: Вызвано: com.myproject... Затем прочтите этот раздел подробно, чтобы определить проблему.

Вкратце

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

Если у вас есть какие-либо советы или рекомендации по работе с Java Stacktraces, я бы хотел услышать о них, так что свяжитесь со мной и давайте поделимся тем, что мы знаем.

mgilliard@twilio.com @@Максимальная загрузка

Оригинал: “https://dev.to/twilio/how-to-read-and-understand-a-java-stacktrace-3796”