Лучший способ обработать нули в Java? [закрыто]

21

У меня есть код, который терпит неудачу из-за NullPointerException. Метод вызывается на объекте, где объект не существует.

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

о чем ты думаешь?

Шон Ф
источник
5
Почему бы вам не "исправить причину обнуления"? Можете ли вы привести пример того, почему это не единственный разумный выбор? Очевидно, что-то должно отталкивать вас от очевидного. Что это такое? Почему не исправить причину ваш первый выбор?
S.Lott
Исправление «основной причины» проблемы может помочь в краткосрочной перспективе, но если код по-прежнему не выполняет защитную проверку на наличие нулей и повторно используется другим процессом, который может вводить нулевое значение, мне придется исправить это снова.
Шон Ф
2
@Shaun F: если код не работает, он не работает. Я не понимаю, как код может выдавать неожиданные нули и не исправляться. Очевидно, что происходит какое-то «тупое управление» или «политические» вещи. Или что-то, что позволяет ошибочному коду быть как-то приемлемым. Можете ли вы объяснить, как ошибочный код не исправляется? Какой политический фон побуждает вас задать этот вопрос?
S.Lott
1
«уязвимы для последующего создания данных, в которых есть нули» Что это вообще значит? Если «я могу исправить процедуру создания данных», дальнейшая уязвимость отсутствует. Я не могу понять, что может означать это «последующее создание данных, в котором были нули»? Глючит софт?
С.Лотт
1
@ S.Lott, инкапсуляция и единая ответственность. Весь ваш код не «знает», что делает весь ваш другой код. Если класс принимает Froobinators, он делает как можно меньше предположений о том, кто и как создал Froobinator. Он сделан из «внешнего» кода или просто из другой части базы кода. Я не говорю, что вы не должны исправлять ошибочный код; но ищите «оборонительное программирование», и вы поймете, на что ссылается ОП.
Пол Дрэйпер

Ответы:

45

Если для вашего метода приемлемый входной параметр, исправьте метод. Если нет, исправьте звонящего. «Разумный» - это гибкий термин, поэтому я предлагаю следующий тест: Как метод должен обрабатывать нулевой ввод? Если вы найдете более одного возможного ответа, то null не является разумным вводом.

user281377
источник
1
Это действительно так просто.
biziclop
3
В Guava есть несколько очень хороших вспомогательных методов, делающих проверку нуля столь же простой, как и Precondition.checkNotNull(...). См stackoverflow.com/questions/3022319/...
Мое правило состоит в том, чтобы инициализировать все к разумным значениям по умолчанию, если возможно, и потом беспокоиться о нулевых исключениях.
davidk01
Thorbjørn: То есть он заменяет случайное исключение NullPointerException намеренным исключением NullPointerException? В некоторых случаях ранняя проверка может быть полезной или даже необходимой; но я боюсь множества бессмысленных пустых проверок, которые мало что добавляют и просто делают программу больше.
user281377
6
Если null не является приемлемым входным параметром для вашего метода, но вызовы метода могут случайно передать значение null (особенно, если метод общедоступен), вы также можете захотеть, чтобы ваш метод выдавал значение IllegalArgumentExceptionnull. Это сигнализирует вызывающим методам, что ошибка в их коде (а не в самом методе).
Брайан
20

Не используйте ноль, используйте Необязательный

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

Невозможно сказать, что может быть, nullа что нет.

Java 8 предоставляет гораздо лучше картина: Optional.

И пример из Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Если каждый из них может или не может вернуть успешное значение, вы можете изменить API на Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

Благодаря явному кодированию опциональности в типе ваши интерфейсы будут намного лучше, а ваш код - более чистым.

Если вы не используете Java 8, вы можете посмотреть com.google.common.base.Optionalв Google Guava.

Хорошее объяснение от команды Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Более общее объяснение недостатков, связанных с нулем, с примерами на нескольких языках: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nonnull, @Nullable

Java 8 добавляет эти аннотации, чтобы помочь инструментам проверки кода, таким как IDE, выявлять проблемы. Они довольно ограничены в своей эффективности.


Проверьте, когда это имеет смысл

Не пишите 50% кода, проверяющего ноль, особенно если нет ничего разумного, что ваш код может сделать со nullзначением.

С другой стороны, если это nullможно использовать и что-то значить, обязательно используйте его.


В конечном счете, вы, очевидно, не можете удалить nullиз Java. Я настоятельно рекомендую заменять Optionalабстракцию всякий раз, когда это возможно, и проверять в nullдругих случаях, что вы можете сделать что-то разумное с этим.

Пол Дрэйпер
источник
2
Обычно ответы на четырехлетние вопросы в конечном итоге удаляются из-за низкого качества. Хорошая работа, рассказывающая о том, как Java 8 (которой тогда не было) может решить проблему.
но не имеет отношения к первоначальному вопросу, конечно. И что сказать, аргумент действительно необязателен?
jwenting
5
@jwenting Это полностью соответствует оригинальному вопросу. Ответ «Какой лучший способ забить гвоздь с помощью отвертки» - «Используйте молоток вместо отвертки».
Дениф
@jwenting, я думаю, что я охватил это, но для ясности: аргумент не является обязательным, я бы обычно не проверял на ноль. У вас будет 100 строк проверки на ноль и 40 строк вещества. Проверьте на null в более открытых интерфейсах или когда фактически имеет значение null (то есть аргумент является необязательным).
Пол Дрейпер
4
@ J-Boss, как, на Java, вы бы не проверяли NullPointerException? A NullPointerExceptionможет происходить буквально каждый раз, когда вы вызываете метод экземпляра в Java. Вы бы имели throws NullPointerExceptionпочти в каждом методе когда-либо.
Пол Дрейпер
8

Есть несколько способов справиться с этим, перетаскивание вашего кода if (obj != null) {}не идеально, это грязно, это добавляет шум при чтении кода позже во время цикла обслуживания и подвержено ошибкам, так как это легко забыть сделать оберткой.

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

Что такое ноль

В каждом определении и случае Null представляет абсолютный недостаток данных. Нули в базах данных представляют отсутствие значения для этого столбца. null String- это не то же самое, что пустое String, null int- это не то же самое, что ZERO, в теории. На практике «это зависит». Empty Stringможет сделать хорошую Null Objectреализацию для класса String, поскольку Integerэто зависит от бизнес-логики.

Альтернативы:

  1. Null ObjectУзор. Создайте экземпляр вашего объекта, который представляет nullсостояние, и инициализируйте все ссылки на этот тип со ссылкой на Nullреализацию. Это полезно для простых объектов типа значения, которые не имеют большого количества ссылок на другие объекты, которые также могут быть nullи, как ожидается, будут nullв качестве допустимого состояния.

  2. Используйте ориентированные на аспект инструменты, чтобы связать методы с Null Checkerаспектом, который предотвращает нулевые параметры. Это для случаев, когда nullесть ошибка.

  3. Используйте assert()не намного лучше, if (obj != null){}но меньше шума.

  4. Используйте инструмент исполнения контрактов, такой как Contracts For Java . Тот же сценарий использования, что и в AspectJ, но более новый и использует аннотации вместо внешних файлов конфигурации. Лучшие из работ Аспектов и Актеров.

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

2, 3 и 4 - просто удобные альтернативные генераторы исключений, которые можно заменить NullPointerExceptionна что-то более информативное, что всегда и является улучшением.

В конце

nullв Java почти во всех случаях логическая ошибка. Вы должны всегда стремиться устранить первопричину NullPointerExceptions. Вы должны стремиться не использовать nullусловия в качестве бизнес-логики. if (x == null) { i = someDefault; }просто сделайте начальное присвоение этому экземпляру объекта по умолчанию.


источник
Если это возможно, для шаблона нулевого объекта это самый изящный способ обработки нулевого регистра. Редко, когда вы хотите, чтобы ноль вызывал взрыв материала в производстве!
Ричард Мискин
@Richard: если это nullне неожиданно, то это истинная ошибка, тогда все должно полностью остановиться.
Нуль в базах данных и в программном коде обрабатывается по-разному в одном важном аспекте: в программировании null == null(даже в PHP), но в базах данных, null != nullпоскольку в базах данных он представляет собой неизвестное значение, а не «ничто». Два неизвестных не обязательно равны, а два ничто не равны.
Саймон Форсберг,
8

Добавление нулевых проверок может сделать тестирование проблематичным. Посмотрите эту прекрасную лекцию ...

Ознакомьтесь с лекцией Google Tech: «Чистые переговоры по коду - не ищите!» он говорит об этом около минуты 24

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

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

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

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

это мешает вам создать Дом, так как вы будете выдавать исключение. Предположим, что ваши тестовые сценарии создают фиктивные объекты для тестирования чего-то другого, кроме Door, ну, вы не можете сделать это, потому что door требуется

Те, кто пострадал от ада создания Насмешек, хорошо знают об этих типах раздражения.

Таким образом, ваш набор тестов должен быть достаточно надежным, чтобы тестировать двери, дома, крыши или что-то еще, не будучи параноиком по этому поводу. Seroiusly, как трудно добавить тест нулевой проверки для конкретных объектов в вашем тестировании :)

Вы всегда должны отдавать предпочтение приложениям, которые работают, потому что у вас есть несколько тестов, которые ДОКАЗЫВАЮТ, что они работают, а не НАДЕЖДА, что это работает просто потому, что у вас есть целая куча предварительных проверок нуля повсюду

Constantin
источник
4

tl; dr - ХОРОШО проверять наличие неожиданностей, nullно ПЛОХО для приложения, чтобы попытаться сделать их хорошими.

Детали

Ясно, что существуют ситуации, когда nullдопустимый ввод или вывод метода, и другие, где это не так.

Правило № 1:

Javadoc для метода, который разрешает nullпараметр или возвращает nullзначение, должен четко документировать это и объяснять, что это nullзначит.

Правило № 2:

Метод API не следует указывать как принимающий или возвращающий, nullесли для этого нет веских причин.

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

Правило № 3:

Приложение не должно пытаться «исправлять» ошибки программирования.

Если метод обнаруживает null что этого не должно быть, он не должен пытаться решить проблему, превращая его во что-то другое. Это просто скрывает проблему от программиста. Вместо этого он должен позволить NPE произойти и вызвать сбой, чтобы программист мог выяснить, что является основной причиной, и устранить ее. Надеемся, что сбой будет замечен во время тестирования. Если нет, то это говорит о вашей методологии тестирования.

Правило № 4:

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

Если в вашем коде есть ошибки, которые приводят к большому количеству NPE, самое сложное - выяснить, откуда взялись nullзначения. Один из способов облегчить диагностику - это написать свой код, чтобы nullон был обнаружен как можно скорее. Часто вы можете сделать это в сочетании с другими проверками; например

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

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

Стивен С
источник
3

Я бы порекомендовал исправить метод для защиты. Например:

String go(String s){  
    return s.toString();  
}

Должно быть больше в соответствии с этим:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

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

Woot4Moo
источник
10
Это имеет неприятный побочный эффект в том, что вызывающая программа (программист, которая кодирует этот API) может выработать привычку передавать значение null в качестве параметра, чтобы получить поведение по умолчанию.
Горан Йович
2
Если это Java, вы не должны использовать new String()вообще.
Бизиклоп
1
@biziclop на самом деле это гораздо важнее, чем многие думают.
Woot4Moo
1
По моему мнению, имеет больше смысла помещать утверждение и выдавать исключение, если аргумент нулевой, поскольку это явно ошибка вызывающей стороны. Не молча «исправлять» ошибки вызывающего абонента; вместо этого, чтобы они знали о них.
Андрес Ф.
1
@ Woot4Moo Так что напишите свой собственный код, который выдает исключение. Важно сообщить вызывающей стороне, что переданные им аргументы неверны, и сообщить им как можно скорее. Молчаливая коррекция nulls - худший из возможных вариантов, хуже, чем бросание NPE.
Андрес Ф.
2

Следующие общие правила о NULL очень помогли мне до сих пор:

  1. Если данные поступают извне, систематически проверяйте их на наличие нулей и действуйте соответствующим образом. Это означает, что вы можете сгенерировать исключение, которое имеет смысл для функции (проверено или не отмечено, просто убедитесь, что имя исключения точно говорит вам, что происходит.). Но НИКОГДА не теряйте ценность в вашей системе, которая потенциально может принести сюрпризы.

  2. Если значение Null находится в области соответствующих значений для вашей модели данных, действуйте соответствующим образом.

  3. При возврате значений старайтесь по возможности не возвращать нули. Всегда предпочитайте пустые списки, пустые строки, шаблоны нулевых объектов. Сохраняйте пустые значения в качестве возвращаемых значений, когда это наилучшее возможное представление данных для данного варианта использования.

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

Это имеет тенденцию немного параноидально относиться к нулям, часто приводящим к фасадам и прокси, которые соединяют системы с внешним миром и строго контролируют значения внутри с избыточной избыточностью. Внешний мир здесь означает почти все, что я сам не кодировал. Это требует затрат времени выполнения, но до сих пор мне редко приходилось оптимизировать это, создавая «нулевые безопасные разделы» кода. Я должен сказать, однако, что я в основном создаю долго работающие системы для здравоохранения, и последнее, что мне нужно, это интерфейсная подсистема, переносящая аллергию на йод на сканер КТ из-за неожиданного нулевого указателя, потому что кто-то в другой системе никогда не осознавал, что имена могут содержать апострофы или символы, такие как 但 耒耨。

в любом случае .... мои 2 цента

Newtopian
источник
1

Я бы предложил использовать шаблон Option / Some / None из функциональных языков. Я не специалист по Java, но я интенсивно использую собственную реализацию этого шаблона в своем проекте C #, и я уверен, что он может быть преобразован в мир Java.

Идея этого шаблона заключается в следующем: если логически возникает ситуация, когда существует вероятность отсутствия значения (например, при получении из базы данных по идентификатору), вы предоставляете объект типа Option [T], где T - возможное значение. , Я возвращал случай отсутствия объекта-значения класса None [T], если возвращалось существование объекта-объекта Some [T], содержащего значение.

В этом случае вы должны обрабатывать возможность отсутствия значения, и если вы делаете обзор кода, вы легко можете найти место неправильной обработки. Чтобы получить вдохновение от реализации языка C #, обратитесь к моему репозиторию bitbucket. https://bitbucket.org/mikegirkin/optionsomenone

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

Hedin
источник
0

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

Как обычно, это зависит от случая, как и с большинством вещей в жизни :)

jonezy
источник
0

В lang- библиотеках Apache Commons есть способ обработки нулевых значений.

метод defaultIfNull в классе ObjectUtils позволяет вам возвращать значение по умолчанию, если переданный объект является нулевым

Махмуд Хоссам
источник
0
  1. Не используйте тип Optional, если он действительно не является обязательным, часто выход лучше обрабатывать как исключение, если вы действительно не ожидали null в качестве опции, а не потому, что вы регулярно пишете глючный код.

  2. Как указывает статья Google, проблема не в нулевом типе, который он использует. Проблема в том, что значения NULL должны проверяться и обрабатываться, часто их можно корректно завершить.

  3. Существует ряд нулевых случаев, которые представляют недопустимые условия в областях, выходящих за рамки обычной работы программы (недопустимый ввод данных пользователем, проблемы с базой данных, сбой сети, отсутствующие файлы, поврежденные данные), для чего предназначены проверенные исключения, их обработка, даже если это просто для регистрации.

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

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

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

J-Босс
источник