4 рекомендации бывшего разработчика LinkedIn, который сделал 1000 код-ревью

разработка
8 декабря 2018, 12:09

В блоге на 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. Эти корректировки сделают методы более гибкими и простыми в использовании при вызове.

Обсуждение