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

Как избавиться от исключения NullPointerException

Исключение NullPointerException — это удивляет меня так же, как удивляет вас израильская компания OverOps… С тегами csharp, java, качество кода, программирование.

Overlaps, израильская компания, которая помогает разработчикам понять, что происходит в производстве, провела исследование о том, какие основные исключения Java в производстве. Хотите угадать, кто из них находится на 1-м месте? Исключение NullPointerException .

Почему это исключение встречается так часто? Я утверждаю (как и дядя Боб в своей книге “Чистый код”), что это не , потому что разработчики забывают добавлять нулевые проверки.

Причина: разработчики слишком часто используют нули.

Так откуда же берутся все эти нули?

В C# и Java все ссылочные типы могут указывать на null. Мы можем получить ссылку, указывающую на ноль, следующими способами:

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

  • члены класса неинициализированного ссылочного типа.

  • явное присвоение значения null или возврат значения null из функции.

Вот некоторые закономерности, которые я заметил в функциях, возвращающих null:

Обработка ошибок

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

Отсутствуют дополнительные данные для сущностей

Свойство объекта может быть необязательным. Если для необязательного свойства нет данных, оно возвращает значение null.

Иерархические модели

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

Поиск функций

Когда мы хотим найти объект по критериям в коллекции, мы возвращаем значение null, чтобы сказать, что объект не был найден.

В чем проблемы с использованием нулей?

Он взорвется. В конце концов…

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

В следующем примере кода есть ошибка, где-то в классе A, вызывающая сущность должна быть нулевой. Но исключение NullPointerException возникает внутри функции класса B. Реальный код может быть намного сложнее.

Скрытые ошибки

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

  • “Я знаю, что должен проверить, нет ли нуля но я не знаю, что это значит, когда функция возвращает null и я не знаю, что с этим делать”, или

  • ” Я думаю, что это не может быть нулем, но просто чтобы убедиться, я не хочу, чтобы это взорвалось в процессе производства “

Обычно это выглядит так:

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

Представьте, что вы покупаете билет на шоу онлайн. Вы получили сообщение об успехе! Наконец-то настал день шоу, вы уходите с работы пораньше, нанимаете няню и идете смотреть шоу. Когда вы приезжаете, вы обнаруживаете, что у вас нет билетов! и свободных мест нет. Вы возвращаетесь домой расстроенный и растерянный 😠 . Можете ли вы понять, как такого рода нулевая проверка может привести к такой ситуации?

Это также делает код разветвленным и уродливым 😢

Отсутствующие ненулевые ссылочные типы в C# и Java

В C# и Java ссылочные типы всегда могут указывать на null . Это приводит к ситуации, когда мы не можем знать, глядя на сигнатуру функции, является ли null допустимым входом или выходом. Я считаю, что большинство функций не возвращают и не принимают значение null.

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

Этот неудачный выбор дизайна вызывает проблемы, которые я описал ранее в разделе “Скрытые ошибки”, и, конечно, множество ошибок NullPointerException. Проигрыш- проигрышная ситуация. 😢

Существуют такие языки, как Kotlin , которые направлены на устранение ошибок исключения NullPointerException путем проведения различия между обнуляемыми ссылками и ненулевыми ссылками. Это позволяет перехватывать значение null, присвоенное ненулевым ссылкам, и убедиться, что разработчики проверяют значение null перед разыменованием нулевых ссылок, все во время компиляции .

Корпорация Майкрософт применяет тот же подход, вводя Обнуляемые ссылочные типы в C#8.

Так что же нам делать?

Послушай дядю Боба

Роберт К. Мартин , широко известный как “Дядя Боб”, написал одну из самых известных книг о чистом коде под названием (удивительно) “Чистый код” . В этой книге дядя Боб утверждает, что мы не должны возвращать нули и не должны передавать null функции.

Но как?

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

Используя тип параметра

Тип параметра |/- это другой способ представления необязательного значения. Этот тип запрашивает, существует ли значение, и, если да, получает доступ к значению. При попытке получить доступ к несуществующему значению возникает исключение . Это решает проблему исключения NullPointerException, возникающего в областях кода, удаленных от ошибки. В Java есть Необязательный класс . В C# (до C# 7 ) существует Обнуляемый тип , который предназначен только для типов значений, но вы можете создать свой собственный или использовать библиотеку .

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

Разделение функции на две

Каждая функция, возвращающая значение null, будет преобразована в две функции. Одна функция с той же сигнатурой, которая выдает исключение вместо возврата null. Вторая функция возвращает логическое значение, представляющее, допустимо или нет вызывать первую функцию. Давайте посмотрим на пример:

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

Давайте посмотрим на другой пример:

Логика Содержит сотрудника по идентификатору совпадает с Найти сотрудника по идентификатору но без возврата сотрудника. Теперь давайте предположим, что эти функции достигают базы данных, у нас здесь проблема с производительностью. Давайте представим похожий, но другой шаблон: логическая функция при возврате true также вернет данные, которые мы ищем. Это выглядит так:

Обычно этот шаблон используется как int. Разобрать и int. Попробуйте разобрать .

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

Разделение интерфейса

Практическое руководство, которое мы можем вывести из принципа Лискова , состоит в том, что класс должен реализовывать все функции интерфейса, который он реализует. Возврат null или исключение – это способы не реализовывать функцию. Так что возврат null – это запах кода для нарушения принципа Лискова.

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

Теперь вместо того, чтобы спрашивать сотрудника. HasManager — что мы будем делать, если бы использовали первый подход ” Разделение функции на две” — мы спрашиваем, является ли сотрудник IManagedEmployee .

Я работаю не один и не над новым проектом. Что теперь?

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

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

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

Я бы хотел знать, что вы думаете. Собираетесь ли вы принять эту конвенцию? А если нет, то почему? Что тебя сдерживает?

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

Спасибо Марку Казакову за забавный мем, Алексу Житницкому из OverOps за ответы на мои вопросы, Баоту за организацию отличного писательского мероприятия для новых блоггеров, Ицику Сабану , Амитаю Хорвицу и Максу Офиусу за предоставленную мне обратную связь.

Оригинал: “https://dev.to/shanif/how-to-get-rid-of-nullpointerexception-35k”