Комментарии 17
"В “Своем Банк” мы стремимся не создавать такие поля, даже если оно планируется использоваться когда-то в будущем, следуем принципу YAGNI («You aren't gonna need it»)"
На этой фразе у Годзиллы случился сердечный приступ и он умер
Вы боретесь не причиной, а со следствием (NullPointerException), тогда как причина возникновения этого исключения - нарушение логики работы приложения, но с ней вы так ничего и не делаете при явных проверках. Ведь в последнем случае правильнее был бы примерно вот такой код:
if (name != null) {
int length = name.length();
} else {
throw new NameEmptyException();
}
То есть от исключения вы все равно никуда деваетесь.
Но если вы обрабатываете ошибки логики с помощью кодов возврата, вот тогда любые исключения будут мешаться. Но в этом случае проблема опять же не в самих NullPointerException, а в том, что у вас перемешиваются способы обработки ошибок (с помощью исключений и кодов возврата). И тогда да, первые два способа Non-null по умолчанию и Null wrapper частично закрывают данную проблему, но к сожалению не избавляют от исключений полностью. Поэтому даже в этих случаях все равно приходится ловить исключения, хоть и в меньшем объеме, чем с nullable объектами.
Все зависит от реализуемой логики. Явные проверки необходимы, если объект nullable и это ожидаемое поведение. Вы привели только лишь один случай, когда null - это ошибка:
Если
name
не должен бытьnull
и это ошибка, то несомнено нужно бросить исключение, но читаемое и понятное.
if (name != null) {
return name.length();
} else {
throw new ViolationException('Expected non-null value on field [name].');
}
А вот несколько примеров, когда null
- это ожидаемое значение
private static int DEFAULT_LENGTH = 10;
..
if (name != null) {
return name.length();
} else {
return DEFAULT_LENGTH;
}
if (name != null) {
return Optional.of(name.length());
} else {
return Optional.empty();
}
Разработчики в “Своем Банке” стремятся не возвращать null "by design"
А что делать если null - валидное значение. Так-то null вполне естественный способ вернуть ответ "того, что ты ищешь, у меня нет"
Optional же. Запросил, получил Optional, дальше - по обстоятельствам. В целом Optional спасает и от NPE в цеплочке вида country.getCity().getDistrict().getStreet().getHouse().getApartment() благодаря методу map.
Но всё равно не идеально... Думаю, если бы была идеальная серебряная пуля от NPE, её бы уже все использовали и не задавали вопросов.
На мой вкус Optional ужасно раздувает и делает плохочитаемым код. А уж если у вас больше одной nullable переменной, то замена строки if (a != null && b != null) {}
на альтернативу с Optional
будет выглядеть как небольшая программа на Лиспе
Мой вопрос не к тому, что нужно искать какую-то другую "серебряную пулю". Я скорее к тому, что не стоит так уж демонизировать null. Вполне нормальная практика его возвращать, когда это уместно, как по мне. Аннотации @Nullable/@NotNull
и ворнинги в IDE решают почти все потенциальные проблемы с использованием null.
Optional спасает и от NPE в цеплочке вида country.getCity().getDistrict().getStreet().getHouse().getApartment()
В такого рода вызовах Optional - то, что надо, да.
Согласен.
Есть еще способ - это default или null object.
Пример реализации null object можно подглядеть в jackson: https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/node/NullNode.html
Поле в объекте может быть null и это может быть валидное значение.
Но при этом getter поля может возвратить Optional или Null\Default object.
Например,
class .. {
private String name;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
}
или
class .. {
private static final String DEFAULT_NAME = 'UNKNOWN';
private String name;
public String getName() {
return name == null ? DEFAULT_NAME : name;
}
}
Не вспомню случая, когда возвратить из метода null
значение - это было бы хорошо. Разве что для обратной совместимости или при использовании библиотек, где возвращение null
- это часть контракта
А чем Null-Object удобнее в обработке, чем простой null? Если каждый раз писать проверку вида
if (value != NULL_VALUE) ...
то разницы вроде и никакой. Только этот NULL_VALUE надо еще для каждого класса задефайнить и где-то в глобальной переменной держать. А null из коробки есть. И инспекции в IDE опять же с null подскажут, что я проверку упустил, а с NULL_VALUE - уже нет.
А ежели проверки не писать, то это будет источником багов, которые отлавливаются куда хуже, чем NPE. NPE хотя бы fail fast.
Optional да, лучше. Но выше уже отвечал, что он уж больно код раздувает. Хотя это вкусовщина, возможно
Null-object не нужно проверять на то, что он "пустой". С ним работают так же как с обычным классом, но у него поведение "пустоты".
Только этот NULL_VALUE надо еще для каждого класса задефайнить и где-то в глобальной переменной держать.
Конечно для каждого класса нужно описать Null-object, но это часть логики. Не во всех задачах подойдет такое решение.
А null из коробки есть.
null - есть и коробки, но его появление, как правило становиться неожиданностью, если не использовать аннотации или Optional.
А чем Null-Object удобнее в обработке, чем простой null? Если каждый раз писать проверку вида
Далее приведу простые примеры реализации null-object, которые дадут понять, что не нужно писать проверки:
строка содержащая что то:
String someString = "I'm not empty";
И пустая строка, которая по сути null-object:
String nullObjectString = "";
И с someString
и nullObjectString
работать можно не думая о проверке на null
.
Более сложный пример с пользовательским классом:
interface Friend {
void sendEmail();
String getName();
}
class BestFriend extends Friend {
private final String name = "m_chrom";
public void sendEmail() {
// send email
}
public String getName() {
return name;
}
}
class NullFriend extends Friend {
private static final String name = 'Unknown';
public void sendEmail() {
//do nothing
}
String getName() {
return name;
}
}
"более сложный" пример как раз хорошо объясняет то, что я имел в виду. Если не использовать проверки, то в каком-то интерфейсе вашего продукта у вас всплывет фейковый друг с именем "Unknown" и кнопкой "Отправить письмо", которая ничего не делает. Хотя логичнее было бы увидеть сообщение "Друг не задан, выберите друга".
Я не говорю, что Null-object антипаттерн. Он полезен в специфических случаях. Но как универсальную замену null-ов я бы его не советовал. Правило "всегда использовать использовать аннотации @Nullable
там, где может прилететь null" выглядит проще и безопаснее
то в каком-то интерфейсе вашего продукта у вас всплывет фейковый друг с именем "Unknown" и кнопкой "Отправить письмо", которая ничего не делает.
Это вопрос требований и реализации логики. Где то уместно вывести имя "Unknown" и ничего не делать при "Отправить письмо" , а где то - нет.
Правило "всегда использовать использовать аннотации
@Nullable
там, где может прилететь null"
Есть разные инструменты работы с nullable объектами, каждый инструмент подходит под свою задачу. О них подробнее напишу в следующей статье.
Nullable
подойдет, чтобы пометить поле в объекте.
Optional
и null-object подойдут для того, чтобы возвратить значение из метода.
Nullable
работает только в compile time.
Optional
и null-object работают в runtime.
Это вопрос требований и реализации логики. Где то уместно вывести имя "Unknown" и ничего не делать при "Отправить письмо" , а где то - нет.
Со всем уважением, но я с трудом представляю требование показывать пользователю технический объект-заглушку там, где на самом деле объекта нет.
Nullable
подойдет, чтобы пометить поле в объекте.
А чем плохо помечать Nullable
метод?
Nullable
работает только в compile time.
Так если мы говорим о способе избегать NPE, то есть по сути о борьбе с человеческими ошибками, то compile time вполне достаточно. Ну Optional
будет более строгим, да. Но ценой читабельности кода.
Еще раз уточню с чем я спорю. Я согласен с посылом, что можно использовать Optional
для возвращения потенциально null
значений (если не пугает объем кода). Я также не вижу проблемы в использовании Null-object для определенных кейсов. Мне показалось излишне строгим предложение никогда не возвращать null
из методов. Потому что я не вижу в этом ничего плохого, если при этом не забывать помечать метод соответствующей аннотацией.
Мне показалось излишне строгим предложение никогда не возвращать
null
из методов.
Допустим нам необходимо получить книгу getBook(String id)
и получить количество страниц book.getTotalPages()
.
Рассмотрим 3 случая
Nullable
//Реализация метода:
@Nullable
Book getBook(String id) {
Book book = // ищем где то книгу, если не найдем вернется null
return book;
}
//Реализация получения количества страниц,
//которая может привести к NullPointerException:
totalPages = getBook("Clean code").getTotalPages()
//Если обратить внимание на подсветку от среды разработки
// или на алерты от статического анализа
// или заглянуть в описание метода,
// то пишем безопасный код:
int totalPages = 0;
Book book = getBook("Clean code");
if (book != null) {
totalPages = book.getTotalPages()
}
Optional
//Реализация метода:
Optional<Book> getBook(String id) {
Book book = // ищем где то книгу, если не найдем вернется null
return Optional.ofNullable(book);
}
//Нативная реализация получения количества страниц,
//которая вынуждает работать безопасно:
totalPages = getBook("Clean code")
.map(book -> book.getTotalPages())
.orElse(0)
Null-object
class Book {
public static Book emptyBook() {
return //создаем пустую книгу, без страниц
// getTotalPages() будет возвращать 0
}
}
//Реализация метода:
@Nonnull
Book getBook(String id) {
Book book = // ищем где то книгу, если не найдем вернется null
if (book == null) {
return Book.emptyBook();
} else {
return book;
}
}
//Реализация получения количества страниц,
//которая работает безопасно:
totalPages = getBook("Clean code");
Итого при реализации getBook
с Nullable
мы даем возможность ошибиться разработчику при его использовании
с Optional
мы обязываем обработать отсутствие значение
с Null-object
мы снимаем с разработчика дополнительную логику и реализовываем сами
Не нужно вам возвращать null
из метода, уж поверьте. Не мне так популярным фреймворкам, где это применяют.
с
Null-object
мы снимаем с разработчика дополнительную логику и реализовываем сами
Боюсь, эта абстракция очень быстро начинает подтекать в реальных юзкейсах. Например, нам дают List<String>
из айдишников книг и просят показать список из этих книг, отсортированный по тому же количеству станиц. Или, скажем, сгрупированный по автору. И вот мне уже нужно знать, что книга из метода getBook()
прилетает не всегда настоящая.
Не нужно вам возвращать
null
из метода, уж поверьте. Не мне так популярным фреймворкам, где это применяют.
Ну уж слишком-то сильно не обобщайте, только ситхи всё возводят в абсолют ))))
навскидку надергал примеров из Hibernate:
https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/boot/ResourceLocator.html#locateResource(java.lang.String)
https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/SharedSessionContract.html#getTenantIdentifier()
https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/SharedSessionContract.html#getEntityGraph(java.lang.String)
из RxJava:
https://reactivex.io/RxJava/javadoc/
и Jackson:
https://www.javadoc.io/static/com.fasterxml.jackson.core/jackson-core/2.18.0/com/fasterxml/jackson/core/io/CharacterEscapes.html#getEscapeSequence-int-
Весьма популярные.И вполне возвращают, там где это уместно. Философии фреймворков они разные бывают )))
Кстати, спасибо за развернутые ответы с примерами! Понимаю, что на это нужно много времени, и очень ценю )))
del
Null-безопасность в Java: когда нули тоже имеют значение