Не следует сохранять лямбда-выражения в переменных/константах
Возможно, кто-то как и я встречался с кодом, где лямбда-выражения выносятся в переменные внутри метода или даже константы, чтобы якобы улучшить производительность приложения. Выглядит это примерно так:
На самом деле, тут не все так плохо, но все-таки лучше рассматривать другой более читабельный вариант, где лямбда-выражения остаются внутри Stream API/Optional или вообще выносятся в отдельные методы:
Здесь также есть другие преимущества:
- код будет разрастаться, и естественнее это будет выглядеть в методах, а не переменной
- намного проще именовать методы, чем переменные
- более естественно тестировать в последующем методы, а не переменные
- если поведение не совсем очевидно и хитро, то проще будет написать javadoc для метода
И последнее:
#dmdev_best_practices
Возможно, кто-то как и я встречался с кодом, где лямбда-выражения выносятся в переменные внутри метода или даже константы, чтобы якобы улучшить производительность приложения. Выглядит это примерно так:
Function<String, CsvRow> CONVERT_TO_CSV_ROW_FUNCTION =
line -> new CsvRow(line.split(","));
Predicate<CsvRow> VALIDATE_CSV_ROW_PREDICATE =
csvRow -> !csvRow.hasEmptyColumns();
return lines.stream()
.map(CONVERT_TO_CSV_ROW_FUNCTION)
.filter(VALIDATE_CSV_ROW_PREDICATE)
.toList();
На самом деле, тут не все так плохо, но все-таки лучше рассматривать другой более читабельный вариант, где лямбда-выражения остаются внутри Stream API/Optional или вообще выносятся в отдельные методы:
return lines.stream()
.map(line -> new CsvRow(line.split(",")))
.filter(csvRow -> !csvRow.hasEmptyColumns())
.toList();
Здесь также есть другие преимущества:
- код будет разрастаться, и естественнее это будет выглядеть в методах, а не переменной
- намного проще именовать методы, чем переменные
- более естественно тестировать в последующем методы, а не переменные
- если поведение не совсем очевидно и хитро, то проще будет написать javadoc для метода
И последнее:
Нет необходимости сохранять лямбда-выражения в константы, чтобы "улучшить производительность" приложения. JVM оптимизирует те участки кода, где это необходимо. И делает это очень даже умно! Лучше обратить свое внимание на IO операции: там 90%+ проблемных по перфомансу мест.
#dmdev_best_practices
👍72🔥22❤7❤🔥2
Используй deadline propagation
Обычно, каждый RPC запрос должен выполняться с установленным deadline, который означает максимальное количество времени, отведенное на выполнение этого запроса. Если время превышает максимально допустимое - прерывается выполнение запроса и пробрасывается timout exception. Более того, сервер может пробросить это исключение сразу же, если видит, что deadline короче, чем требуется для выполнения запроса.
Но зачем в принципе использовать deadlines?
- чтобы не допускать запросов, которые выполняются бесконечно (особенно критично для user-facing)
- чтобы не тратить ресурсы сервера на поддержание долгих/бесконечных запросов
Зачем тогда deadline propagation?
Дело в том, что зачастую сервер не обрабатывает запрос полностью сам, а делает дополнительно другие downstream RPC запросы. И тогда становится вопрос - какой deadline устанавливать для них? Устанавливать какой-то дефолтный - плохо, он может быть меньше или больше необходимого. Поэтому deadline высчитывается из оставшегося времени, которое отвел на выполнение инициатор запроса (например, frondend).
Например, как на картинке:
- Client (инициатор) отправляет запрос с deadline = 10 секундам
- Server A обрабатывает запрос сам за 5 секунд и делает 2 асинхронных RPCs в Server B и D, установив каждому deadline = 5 секунд (10 - 5)
- Server B обрабывает запрос сам за 3 секунды и делает 1 RPC запрос в Server C, установив deadline = 2 секунды (5 - 3)
Как это работает?
- в случае синхронных серверов, вроде Apache Tomcat, то можно просто хранить значение deadline в ThreadLocal и при выполнении любой IO операции - получать ее оттуда (иначе использовать дефолтный или не ограничивать вовсе)
- в случае асинхронных серверов, вроде Netty, использовать аналог ThreadLocal - это Local Context.
#dmdev_best_practices
Обычно, каждый RPC запрос должен выполняться с установленным deadline, который означает максимальное количество времени, отведенное на выполнение этого запроса. Если время превышает максимально допустимое - прерывается выполнение запроса и пробрасывается timout exception. Более того, сервер может пробросить это исключение сразу же, если видит, что deadline короче, чем требуется для выполнения запроса.
Но зачем в принципе использовать deadlines?
- чтобы не допускать запросов, которые выполняются бесконечно (особенно критично для user-facing)
- чтобы не тратить ресурсы сервера на поддержание долгих/бесконечных запросов
Зачем тогда deadline propagation?
Дело в том, что зачастую сервер не обрабатывает запрос полностью сам, а делает дополнительно другие downstream RPC запросы. И тогда становится вопрос - какой deadline устанавливать для них? Устанавливать какой-то дефолтный - плохо, он может быть меньше или больше необходимого. Поэтому deadline высчитывается из оставшегося времени, которое отвел на выполнение инициатор запроса (например, frondend).
Например, как на картинке:
- Client (инициатор) отправляет запрос с deadline = 10 секундам
- Server A обрабатывает запрос сам за 5 секунд и делает 2 асинхронных RPCs в Server B и D, установив каждому deadline = 5 секунд (10 - 5)
- Server B обрабывает запрос сам за 3 секунды и делает 1 RPC запрос в Server C, установив deadline = 2 секунды (5 - 3)
Как это работает?
- в случае синхронных серверов, вроде Apache Tomcat, то можно просто хранить значение deadline в ThreadLocal и при выполнении любой IO операции - получать ее оттуда (иначе использовать дефолтный или не ограничивать вовсе)
- в случае асинхронных серверов, вроде Netty, использовать аналог ThreadLocal - это Local Context.
#dmdev_best_practices
🔥56👍14❤🔥3🥰2❤1
JUnit assertions
Во фреймворке JUnit есть свои классические методы для проверки ожидаемого (expected) и актуального (actual) значений. Но как показывает практика, довольно часто программисты делают ошибки, путая местами параметры actual и expected. Из-за этого логи могут вводишь в замешательство, почему, например, актуальное значение не верно, хотя на самом деле - оно просто попутано местами.
Именно поэтому я предпочитаю использовать дополнительные библиотеки, такие как AssertJ или Truth. Потому что я всегда знаю, что на вход каждый метод assertThat принимает именно актуальное значение. Кстати, в своем курсе JUnit 5 я более подробно рассказывал про это.
Есть только несколько исключений, когда я могу воспользоваться классическими методами из JUnit 5, потому что они короче и проще/лучше читаются:
В остальных случаях все-таки не следует мешать в одном тесте сразу несколько библиотек, а предпочитать одну!
#dmdev_best_practices
Во фреймворке JUnit есть свои классические методы для проверки ожидаемого (expected) и актуального (actual) значений. Но как показывает практика, довольно часто программисты делают ошибки, путая местами параметры actual и expected. Из-за этого логи могут вводишь в замешательство, почему, например, актуальное значение не верно, хотя на самом деле - оно просто попутано местами.
// Только заглянув в название параметров, можно понять где что
public static void assertEquals(Object expected, Object actual) {
AssertEquals.assertEquals(expected, actual);
}
Именно поэтому я предпочитаю использовать дополнительные библиотеки, такие как AssertJ или Truth. Потому что я всегда знаю, что на вход каждый метод assertThat принимает именно актуальное значение. Кстати, в своем курсе JUnit 5 я более подробно рассказывал про это.
// Пример метода assertThat из библиотеки AssertJ
public static <T> ObjectAssert<T> assertThat(T actual) {
return AssertionsForClassTypes.assertThat(actual);
}
Есть только несколько исключений, когда я могу воспользоваться классическими методами из JUnit 5, потому что они короче и проще/лучше читаются:
assertNull(Object actual);
assertNotNull(Object actual);
assertTrue(boolean condition);
assertFalse(boolean condition);
В остальных случаях все-таки не следует мешать в одном тесте сразу несколько библиотек, а предпочитать одну!
#dmdev_best_practices
👍46❤15🔥8
Не отправляй stale notifications пользователю
На практике довольно часто приходится отправлять различные уведомления пользователю (email, in-app notifications), когда происходит какое-то событие. Например:
- у пользователя заканчивается срок действия подписки
- он пробежал свой первый марафон и нужно вручить особенный badge
- необходимо поставить электронную подпись на только что отправленные документы
- пришел счет на оплату электроэнергии
- и т.д.
И также очень часто такие уведомление отправляются асинхронно от основной логики приложения, потому что если что-то пойдет не так с отправкой - это не должно останавливать или повторять заново основную логику. Да и время не хочется тратить на ожидание того, когда закончится отправка уведомления.
Поэтому их часто складывают в какую-нибудь очередь в базе данных, или в Message Brokers вроде Kafka.
И казалось бы, что может пойти не так - но на деле сообщения из таких источников могут приходить в места их обратки с запозданием (а порой и с запозданием в несколько дней/недель, если что-то пошло не так). Получить badge об успешном прохождении марафонской дистанции через пару дней - хоть и не приятно, но еще не критично. Но вот поставить электронную подпись на документы, которые нужно было подписать еще несколько недель назад - уже более критично. Не говоря про оплату элекроэнергии за прошлый месяц, когда уже пора платить за новый и с пеней.
- действительно ли дата предполагаемой отправки не outdated?
- действительно ли событие (истекла подписка) произошло совсем недавно, а не далеко в прошлом?
- действительно ли состояние пользователя в базе данных сооветствует отправляемому уведомлению?
- и т.д.
Только благодаря таким double checks получится избежать отправки stale notifications и не вводить пользователя в заблуждение!
#dmdev_best_practices
На практике довольно часто приходится отправлять различные уведомления пользователю (email, in-app notifications), когда происходит какое-то событие. Например:
- у пользователя заканчивается срок действия подписки
- он пробежал свой первый марафон и нужно вручить особенный badge
- необходимо поставить электронную подпись на только что отправленные документы
- пришел счет на оплату электроэнергии
- и т.д.
И также очень часто такие уведомление отправляются асинхронно от основной логики приложения, потому что если что-то пойдет не так с отправкой - это не должно останавливать или повторять заново основную логику. Да и время не хочется тратить на ожидание того, когда закончится отправка уведомления.
Поэтому их часто складывают в какую-нибудь очередь в базе данных, или в Message Brokers вроде Kafka.
И казалось бы, что может пойти не так - но на деле сообщения из таких источников могут приходить в места их обратки с запозданием (а порой и с запозданием в несколько дней/недель, если что-то пошло не так). Получить badge об успешном прохождении марафонской дистанции через пару дней - хоть и не приятно, но еще не критично. Но вот поставить электронную подпись на документы, которые нужно было подписать еще несколько недель назад - уже более критично. Не говоря про оплату элекроэнергии за прошлый месяц, когда уже пора платить за новый и с пеней.
Поэтому прямо перед самой отправкой уведомлений нужно всегда проверять еще раз актуальность этой отправки
- действительно ли дата предполагаемой отправки не outdated?
- действительно ли событие (истекла подписка) произошло совсем недавно, а не далеко в прошлом?
- действительно ли состояние пользователя в базе данных сооветствует отправляемому уведомлению?
- и т.д.
Только благодаря таким double checks получится избежать отправки stale notifications и не вводить пользователя в заблуждение!
#dmdev_best_practices
❤🔥34👍29🔥9❤1