Как избавиться от NullPointerException

1656513372 kak izbavitsya ot

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

0*Qhtt3vCy6mUF-z2X
Monster NullPointerException OverOps

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

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

Итак, откуда берутся все эти NULL?

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

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

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

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

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

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

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

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

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

Найти функции

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

Какие проблемы с использованием null?

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

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

1*2ULzFy6tmPqxYQKpuwWc3A
Сейчас я счастлив, но со временем взорваюсь.

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

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

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

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

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

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

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

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

Отсутствуют типы ссылок, не имеющие значения NULL, в C# и Java

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

Потому что трудно узнать, возвращается ли функция null или нет (если это не задокументировано), разработчики или вставляют null проверяет, когда не нужно, или не проверяет nulls когда это нужно — и да, иногда отношение нулевых проверок, когда нужно?

Этот плохой выбор дизайна влечет за собой проблемы, которые я описал ранее в разделе «Скрытые ошибки», и многое NullPointerException ошибки, конечно. Ситуация проигрыш-проигрыш. ?

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

Корпорация Майкрософт использует тот же подход, вводя в C#8 типы ссылок, допускающих значение Null.

Итак, что нам делать?

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

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

0*HGcUEEZNp9mTmK5w

Но как?

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

Использование типа опции

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

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

Разбиение функции на две части

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

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

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

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

Распространено использование этого шаблона int.Parse и int.TryParse.

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

Разбиение интерфейса

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

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

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

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

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

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

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

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован.