В блоге на Hacker Noon бывший разработчик LinkedIn Стивен Хейдел собрал 4 рекомендации, которые ему приходилось давать наиболее часто при выполнении проверок кода в компании. dev.by публикует перевод статьи.
Рекомендация 1: выбрасывать исключения, когда что-то идёт не так
Вот типичный паттерн, который я наблюдал:
Этот паттерн приводил к сбоям в одном из мобильных приложений, с которым я работал. Поиск на стороне сервера, который мы использовали, начал бросать исключения. Но в серверном API приложения был код, подобный тому, что приведён выше. Поэтому приложение получало ответ об успешном выполнении «200», а пользователю выдавало пустой список по всем до единого поисковым запросам.
Если бы вместо этого API выбросил исключение, наши системы мониторинга мгновенно обнаружили бы его и исправили.
Очень часто возникает желание просто вернуть пустой объект, когда приложение поймало исключение. Примеры пустых объектов в Java — Optional.empty(), null и пустой список, а сделать это больше всего хочется при парсинге URL. Если парсить URL-адрес из строки не получается, то следует не возвращать null, а задаться вопросом: почему URL неверен и не имеет ли место проблема с данными, которую нужно исправлять на более высоком уровне.
В таких случаях пустые объекты — не лучшее средство. Если ситуация исключительная, нужно выбрасывать исключение.
Рекомендация 2: использовать самый специфический тип из возможных
Этот совет — противоположность антипаттерна «строковой типизации». Зачастую я вижу вот такие примеры кода:
Использование наиболее специфического типа позволяет избежать целого класса багов, и это мощный аргумент в пользу сильно типизированных языков вроде Java.
Вопрос: почему программисты с самыми добрыми намерениями пишут плохой код со строковой типизацией? Ответ: потому что внешний мир не сильно типизирован. Есть много разных мест, где можно найти строки:
- параметры запроса и пути в URL;
- JSON;
- базы данных, не поддерживающие перечисление;
- плохо написанные библиотеки.
Чтобы избежать такой проблемы во всех примерах выше, нужно придерживаться следующей стратегии: парсинг и сериализацию строки нужно делать в начале и в конце программы.
Это даёт ряд преимуществ, например позволяет сразу найти проблемные данные, а в случае ошибки приложение упадёт на ранних этапах. А также не понадобится отлавливать исключения по всему приложению, если данные заранее проверены. Кроме того, сильная типизация обеспечивает более наглядные сигнатуры методов, поэтому не нужно писать такое количество javadoc-ов по каждому методу.
Рекомендация 3: использовать Optional вместо null
Одна из самых лучших особенностей 8 версии Java — класс Optional
. Он представляет собой объект, который может существовать или не существовать.
Вопрос: какое единственное исключение имеет свою аббревиатуру? Ответ: NPE, или Null Pointer Exception. Это самое популярное исключение в Java, которое обходится в миллиарды долларов.
Optional
позволяет полностью избавиться от NPE в программе. Однако этот класс нужно применять корректно. Вот несколько замечаний:
- Не следует просто вызывать
.get()
каждый раз, когда естьOptional
, чтобы использовать его. Вместо этого нужно хорошо подумать о том случае, когдаOptional
отсутствует, и придумать оптимальное значение по умолчанию. - Если такого оптимального значения ещё нет, методы
.map()
и.flatMap()
дадут возможность сделать выбор позже. - Если внешняя библиотека возвращает
null
, нужно сразу обернуть значение, используяOptional.ofNullable()
. Такое обычно возникает внутри программ, поэтому лучше предотвратить это в самом начале. - Следует использовать
Optional
в типах возвращаемого значения методов, потому что тогда не нужно читать javadoc, чтобы разобраться, допустимо ли отсутствующее значение.
Рекомендация 4: «unlift» методов при возможности
Лучше избегать методов, которые выглядят вот так:
У всех этих методов есть одно сходство: они используют контейнеры, например Optional, List или Task, в качестве параметров метода. Ещё хуже, если тип возвращаемого значения является тем же контейнером (то есть метод с одним параметром берёт Optional и возвращает Optional). Потому что
1) Promise<A> method(Promise<B> param)
менее гибко, чем просто
2) A method(B param)
.
Когда есть Promise<B>
, можно использовать первый вариант или же второй, «подняв» (lifting) функцию с помощью .map
(то есть promise.map(method)
).
Однако если есть только В, можно запросто использовать второй способ, а первый — нельзя, поэтому второй способ более универсален.
Я называю этот способ «unlifting», потому что он противоположен распространённому служебному методу lift. Эти корректировки сделают методы более гибкими и простыми в использовании при вызове.
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.