Как отследить исключение NullPointerException в цепочке получателей - PullRequest
24 голосов
/ 04 января 2009

Если я получаю исключение NullPointerException при таком вызове:

someObject.getSomething().getSomethingElse().
    getAnotherThing().getYetAnotherObject().getValue();

Я получаю довольно бесполезный текст исключения:

Exception in thread "main" java.lang.NullPointerException
at package.SomeClass.someMethod(SomeClass.java:12)

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

Foo ret1 = someObject.getSomething();
Bar ret2 = ret1.getSomethingElse();
Baz ret3 = ret2.getAnotherThing();
Bam ret4 = ret3.getYetAnotherOject();
int ret5 = ret4.getValue();

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

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

Подсказка: я использую eclipse и знаю, что такое отладчик, но не могу понять, как применить его к проблеме.

Мой вывод по ответам:
В некоторых ответах говорилось, что я не должен цеплять получатели один за другим, в некоторых ответах показано, как отлаживать код, если я не послушался этого совета.

Я принял ответ, который научил меня, когда нужно цеплять добытчиков:

  • Если они не могут вернуть ноль, соедините их так долго, как хотите. Не нужно проверять! = Null, не нужно беспокоиться об исключениях NullPointerExceptions ( предупреждаем, что цепочка все еще изменяет закон деметры, но я могу жить с этим )
  • Если они могут возвращать ноль, никогда, никогда, никогда не связывать их в цепочку и выполнять проверку на нулевые значения для каждого из них, которые могут возвращать ноль

Это делает любые полезные советы по фактической отладке бесполезными.

Ответы [ 11 ]

12 голосов
/ 05 января 2009

NPE - самое бесполезное исключение в Java, точка. Кажется, что он всегда лениво реализован и никогда не говорит точно, что его вызвало, даже если простое выражение «class x.y.Z is null» очень поможет в отладке таких случаев.

В любом случае, единственный хороший способ найти метателя NPE в этих случаях - это следующий вид рефакторинга:

someObject.getSomething()
          .getSomethingElse()
          .getAnotherThing()
          .getYetAnotherObject()
          .getValue();

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

9 голосов
/ 04 января 2009

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

Таким образом, будет легко найти источник вашего NPE.

Я бы предположил, что вы можете сделать что-то подобное в netbeans или eclipse.

EDIT: Здесь - это объяснение того, как добавить точку прерывания исключения в eclipse

9 голосов
/ 04 января 2009

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

7 голосов
/ 04 января 2009

Если вы часто пишете:

a.getB().getC().getD().getE();

это, вероятно, запах кода, и его следует избегать. Например, вы можете выполнить рефакторинг в a.getE(), который вызывает b.getE(), который вызывает c.getE(), который вызывает d.getE(). (Этот пример может не иметь смысла для вашего конкретного случая использования, но это один шаблон для исправления этого запаха кода.)

См. Также Закон Деметры , который гласит:

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

Следовательно, не должно быть цепочки сообщений, например a.getB().getC().doSomething(). Следование этому «закону» имеет гораздо больше преимуществ, кроме упрощения отладки исключений NullPointerException.

3 голосов
/ 04 января 2009

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

Если вы работаете внутри вашего ide, вы можете просто установить точку останова и последовательно использовать функциональность "я оценить выражение "вашего ide для каждого элемента.

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

Между тем мы можем мечтать о надежном навигационном операторе Groovy

2 голосов
/ 04 января 2009

Вот как найти ошибку, используя Eclipse.

Сначала установите точку останова на линии:

someObject.getSomething().getSomethingElse().
getAnotherThing().getYetAnotherObject().getValue();

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

Теперь выделите «someObject» и нажмите CTRL + SHIFT + I (или щелкните правой кнопкой мыши и произнесите «Inspect»).

Это ноль? Вы нашли свой NPE. Это ненулевое значение? Затем выделите someObject.getSomething () (включая скобки) и проверьте его. Это ноль? И т.д. Продолжайте идти по цепочке, чтобы выяснить, где происходит ваш NPE, без необходимости менять код.

2 голосов
/ 04 января 2009

Ранний отказ также возможен.

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

public Foo getSomething()
{
  Foo result;
  ...
  if (result == null) {
    throw new IllegalStateException("Something is missing");
  }
  return result;
}
1 голос
/ 04 января 2009

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

Если у вас есть метод или конструктор, который принимает параметр объекта, и рассматриваемый объект / метод не может разумно иметь дело с тем, что этот параметр имеет значение null, тогда просто проверьте и сгенерируйте NullPointerException тут же.

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

1 голос
/ 04 января 2009

Возможно, вы захотите обратиться к на этот вопрос о том, как избежать! = Null .

Как правило, если null является действительным ответом, вы должны проверить его. Если нет, утверждайте это (если можете). Но что бы вы ни делали, постарайтесь свести к минимуму случаи, когда null является действительным ответом по этой причине, среди прочих.

0 голосов
/ 04 января 2009

Разместите каждый геттер на отдельной строке и отладьте. Перейдите (F6) к каждому методу, чтобы найти, какой вызов возвращает null

...