Названия тестов - очень важны!
Название теста должно описывать поведение (test case). Когда программист просто читает название теста - этого уже должно быть достаточно, чтобы понять, что тест пытается проверить и какой ожидаемый результат.
Другими словами говоря, название теста служит для своего рода самодокументации.
На своем примере:
Когда я хочу посмотреть возможные варианты использования какого-то API, то при хорошо написанных тестах - мне достаточно просто взглянуть на них. Хорошие тестовые имена очень здорово описывают поведение API при самых разных условиях/входных параметрах, которые ты, порой, даже не ожидал. Ибо у того, кто писал этот API, как правило было больше контекста, чем у тебя, и он больше понимал его логику и его нюансы.
Для интереса можно сравнить два примера одного и того же test case:
Лишь взглянув на второй вариант, мне будет достаточно понять его суть.
Поэтому я нашел для себя этот паттерн именования тестов наилучшим:
#dmdev_best_practices
Название теста должно описывать поведение (test case). Когда программист просто читает название теста - этого уже должно быть достаточно, чтобы понять, что тест пытается проверить и какой ожидаемый результат.
Другими словами говоря, название теста служит для своего рода самодокументации.
На своем примере:
Когда я хочу посмотреть возможные варианты использования какого-то API, то при хорошо написанных тестах - мне достаточно просто взглянуть на них. Хорошие тестовые имена очень здорово описывают поведение API при самых разных условиях/входных параметрах, которые ты, порой, даже не ожидал. Ибо у того, кто писал этот API, как правило было больше контекста, чем у тебя, и он больше понимал его логику и его нюансы.
Для интереса можно сравнить два примера одного и того же test case:
@Test
void processMessage_failed()
@Test
void processMessage_missingMessageId_throwsValidationException()
Лишь взглянув на второй вариант, мне будет достаточно понять его суть.
Поэтому я нашел для себя этот паттерн именования тестов наилучшим:
apiUnderTest_behavior_expectedResult
#dmdev_best_practices
🔥108👍38❤5❤🔥5
Избегай boolean параметров
Если это возможно, то лучше избегать boolean параметров в public методах (открытый API). Все потому, что такие структуры очень плохо и читаются в коде, и пишутся, да и тестировать тоже неприятно. А создавать целые переменные только для того, чтобы описывать true/false флаг, программисты попросту не будут (да и не поможет это).
Например, глядя на этот код, можно только гадать, что значит true, и что будет, если в метод передать false:
Как можно этого избежать?
1️⃣ Джошуа Блох в своей книге Effective Java (Item 51) советует создавать enum с двумя значениями. Что и легко читается, легко документируется, и в случае чего - можно добавлять третье/четвертое значение и т.д.
2️⃣ Заменить один метод на два других с более подходящими названиями.
3️⃣ Воспользоваться техникой уменьшения количества параметров в методах, т.е. просто создать объект, который будет содержать все параметры:
4️⃣ Если по каким-то стечениям обстоятельств не помогли три предыдущие пункта, то нужно хотя бы оставить комментарий, что означает boolean параметр:
#dmdev_best_practices
Если это возможно, то лучше избегать boolean параметров в public методах (открытый API). Все потому, что такие структуры очень плохо и читаются в коде, и пишутся, да и тестировать тоже неприятно. А создавать целые переменные только для того, чтобы описывать true/false флаг, программисты попросту не будут (да и не поможет это).
Например, глядя на этот код, можно только гадать, что значит true, и что будет, если в метод передать false:
Result result = processMessage(message, true);
Как можно этого избежать?
1️⃣ Джошуа Блох в своей книге Effective Java (Item 51) советует создавать enum с двумя значениями. Что и легко читается, легко документируется, и в случае чего - можно добавлять третье/четвертое значение и т.д.
Result result = processMessage(message, ProcessingType.DRY_RUN);
2️⃣ Заменить один метод на два других с более подходящими названиями.
Result result = processMessage(message);
Result result = processMessageDryRun(message);
3️⃣ Воспользоваться техникой уменьшения количества параметров в методах, т.е. просто создать объект, который будет содержать все параметры:
ProcessOptions options = ProcessOptions.builder().message(message).dryRun(true).build();
Result result = process(options);
4️⃣ Если по каким-то стечениям обстоятельств не помогли три предыдущие пункта, то нужно хотя бы оставить комментарий, что означает boolean параметр:
Result result = processMessage(message, /* dryRun= */ true);
#dmdev_best_practices
👍108🔥50❤🔥6
Используй Builders
Builders - это короткоживущие mutable объекты, которые имеют одну единственную задачу: собрать все необходимые данные, чтобы создать другой объект, который в свою очередь обычно immutable и долгоживующий.
На примере представлено классическое использование этого паттерна:
- объект builder короткоживующий (можно сразу его забыть, потому что даже не пришлось создавать переменную для его хранения)
- используется лаконичная цепочка вызовов методов (fluent API)
- установка всех параметров самодокументируется и сложно спутать один с другим
Когда следует использовать паттерн Builder?
В большинстве случаев для замены статических фабричных методов или конструкторов.
Ибо эти варианты подходят только когда класс очень простой и выражение получается компактным. Но в других ситациях это становится неприятным:
- очень часто параметры могут быть optional или иметь дефолтные значения
- параметры имеют свойство разрастаться
- легко попутать похожие параметры одного и того же типа, как на примере firstname, lastname
Более подробную информацию можно найти также в книге Effective Java (Item 2)
#dmdev_best_practices
Builders - это короткоживущие mutable объекты, которые имеют одну единственную задачу: собрать все необходимые данные, чтобы создать другой объект, который в свою очередь обычно immutable и долгоживующий.
User user = User.builder()
.firstname("Ivan")
.lastname("Ivanov")
.age(35)
.build();
На примере представлено классическое использование этого паттерна:
- объект builder короткоживующий (можно сразу его забыть, потому что даже не пришлось создавать переменную для его хранения)
- используется лаконичная цепочка вызовов методов (fluent API)
- установка всех параметров самодокументируется и сложно спутать один с другим
Когда следует использовать паттерн Builder?
В большинстве случаев для замены статических фабричных методов или конструкторов.
User user = User.of("Ivan", "Ivanov", 35);
User user = new User("Ivan", "Ivanov", 35);
Ибо эти варианты подходят только когда класс очень простой и выражение получается компактным. Но в других ситациях это становится неприятным:
- очень часто параметры могут быть optional или иметь дефолтные значения
- параметры имеют свойство разрастаться
- легко попутать похожие параметры одного и того же типа, как на примере firstname, lastname
Более подробную информацию можно найти также в книге Effective Java (Item 2)
#dmdev_best_practices
👍82🔥35❤🔥6
Kонструктивная и деструктивная критика в Code Review
Если простыми словами, то критика - это указание на недостатки (реже достоинства) чего-то, что сделал другой человек. Она бывает конструктивной и деструктивной.
Конструктивная критика не причиняет вреда человеку. Она лишь аккуратно указывает на ошибки и через их исправление подталкивает к улучшению и развитию. Конструктивная критика объективна, аргументированна и уважительна.
Деструктивная критика, наоборот, бьет по самооценке. Она обвиняет человека и нападает на него. Деструктивная критика необъективна, не аргументирована и зачастую звучит унизительно.
Критику мы наблюдаем во всех сферах жизнедеятельности человека, особенно деструктивную в комментариях под моими видео 😁
Но где чаще всего мы ее встречаем в программировании? Правильно - это Code Review, где мы оцениваем код другого человека и оставляем свои комменатрии. И конечно же, нужно использовать только конструктивную критику во время Code Review.
Другими словами говоря, важно вести себя вежливо и уважительно, а также быть предельно ясным и полезным для разработчика, код которого ты просматриваешь. Один из способов сделать это - всегда комментируете ТОЛЬКО код и никогда не комментировать разработчика. Например:
❌Плохо:
✅Хорошо:
Хорошие комментарии помогают разработчику понять, почему ты их оставил (содержат объяснение), а также довольно часто направляют в сторону принятия более правильного решения (как в данном случае - использовать однопоточный код). А это как раз-таки и есть конструктивная критика 🙂
#dmdev_best_practices
Если простыми словами, то критика - это указание на недостатки (реже достоинства) чего-то, что сделал другой человек. Она бывает конструктивной и деструктивной.
Конструктивная критика не причиняет вреда человеку. Она лишь аккуратно указывает на ошибки и через их исправление подталкивает к улучшению и развитию. Конструктивная критика объективна, аргументированна и уважительна.
Деструктивная критика, наоборот, бьет по самооценке. Она обвиняет человека и нападает на него. Деструктивная критика необъективна, не аргументирована и зачастую звучит унизительно.
Критику мы наблюдаем во всех сферах жизнедеятельности человека, особенно деструктивную в комментариях под моими видео 😁
Но где чаще всего мы ее встречаем в программировании? Правильно - это Code Review, где мы оцениваем код другого человека и оставляем свои комменатрии. И конечно же, нужно использовать только конструктивную критику во время Code Review.
Другими словами говоря, важно вести себя вежливо и уважительно, а также быть предельно ясным и полезным для разработчика, код которого ты просматриваешь. Один из способов сделать это - всегда комментируете ТОЛЬКО код и никогда не комментировать разработчика. Например:
❌Плохо:
Зачем ты использовал здесь многопоточность, когда очевидно, что от параллелизма нет никакой пользы?
✅Хорошо:
Параллелизм здесь усложняет систему без какого-либо реального выигрыша в производительности. Поскольку никакого выигрыша в производительности нет, я бы сделал этот код однопоточным. Так его и читать, и тестировать, и поддерживать гораздо проще.
Хорошие комментарии помогают разработчику понять, почему ты их оставил (содержат объяснение), а также довольно часто направляют в сторону принятия более правильного решения (как в данном случае - использовать однопоточный код). А это как раз-таки и есть конструктивная критика 🙂
#dmdev_best_practices
👍104🔥37❤🔥6👏3💯1
Параллелизм - это сложно
Поэтому где это только возможно - нужно полагаться на уже готовые библиотеки и фреймворки вместо того, чтобы создавать своивелосипеды собственные решения. Даже если текущая реализация работает правильно и эффективно решает поставленную задачу, то будущие изменения в коде могут привнести плачевные последствия, которые даже не очевидно будет как решить.
Например, если бы мне нужно было решить проблему многопоточности, то я бы следовал следующим шагам:
1. Сначала глянул на существуюющий фреймворк, может ли он мне помочь с проблемой. Это самый безопасный вариант, который не только помогает избегать багов, но еще и предлагает кучу метрик из коробки.
2. Если нужно решить low-level проблему, то конечно же я открыл бы пакет java.util.concurrent
3. Если все еще недостаточно, то я прибегнул бы к synchronized блокам или volatile (для неблокирующей синхронизации)
4. Только здесь я бы начал думать о своем собственном решении, если ничего другого не помогло, предварительно глянув решения в других проектах (возможно, кто-то уже реализовал)
В любом случае, я уже советовал книгу "Java Concurrency in Practice" в своих курсах (Java Level 2), если кто-то хочет действительно разобраться в многопоточности. Ну и конечно же нужно очень хорошо понимать Java Memory Model.
#dmdev_best_practices
Поэтому где это только возможно - нужно полагаться на уже готовые библиотеки и фреймворки вместо того, чтобы создавать свои
Например, если бы мне нужно было решить проблему многопоточности, то я бы следовал следующим шагам:
1. Сначала глянул на существуюющий фреймворк, может ли он мне помочь с проблемой. Это самый безопасный вариант, который не только помогает избегать багов, но еще и предлагает кучу метрик из коробки.
2. Если нужно решить low-level проблему, то конечно же я открыл бы пакет java.util.concurrent
3. Если все еще недостаточно, то я прибегнул бы к synchronized блокам или volatile (для неблокирующей синхронизации)
4. Только здесь я бы начал думать о своем собственном решении, если ничего другого не помогло, предварительно глянув решения в других проектах (возможно, кто-то уже реализовал)
В любом случае, я уже советовал книгу "Java Concurrency in Practice" в своих курсах (Java Level 2), если кто-то хочет действительно разобраться в многопоточности. Ну и конечно же нужно очень хорошо понимать Java Memory Model.
#dmdev_best_practices
👍75🔥25❤5❤🔥3
Избегай какой-либо логики в конструкторах
Самый простой и общепринятый вид конструкторов - это присваивание параметров полям объекта, которые имеют те же названия и типы.
Если ваш конструктор выглядит не так, то подумайте, возможно, он все-таки должен быть таким:
Чем больше логики конструктор включает, тем сложнее тестировать такие классы. И особенно писать тесты на те классы, которые транзитивно зависят от них. Частично эту проблему решает Mockito, который позволяет создавать mock объект не вызывая конструктор. Но здесь есть несколько неприятных моментов:
- такой класс не может быть final
- лучше использовать настоящие объекты в тестах, вместо mock
Если конструктор вызывает внутри себя другой не статический метод, то в нем можно наблюдать частично проинициализированный объект. Особенно эта проблема чаще наблюдается при использовании таких классов в наследовании.
Еще хуже выполнять в конструкторах IO операции, особенно сторонние вызовы в другие сервисы! Если это будет какой-нибудь Spring bean и произойдет исключение (например, сервис не отвечает), то контекст попросту не сможет подняться, что чревато в принципе неработающим приложением в продакшене. Такие ситуации я уже не раз встречал на своем опыте, и это действительно печально, когда даже откатиться на предыдущие версию не поможет восстановить работосопособность приложения.
Поэтому, вместо того, чтобы добавлять логику в конструктор, лучше перенести ее в какие-нибудь фабричные методы/классы, или вообще попросить у Dependency Injection framework нужные тебе параметры.
#dmdev_best_practices
Самый простой и общепринятый вид конструкторов - это присваивание параметров полям объекта, которые имеют те же названия и типы.
Если ваш конструктор выглядит не так, то подумайте, возможно, он все-таки должен быть таким:
User(Long id, String username, LocalDate birthDate) {
this.id = id;
this.username = username;
this.birthDate = birthDate;
}
Чем больше логики конструктор включает, тем сложнее тестировать такие классы. И особенно писать тесты на те классы, которые транзитивно зависят от них. Частично эту проблему решает Mockito, который позволяет создавать mock объект не вызывая конструктор. Но здесь есть несколько неприятных моментов:
- такой класс не может быть final
- лучше использовать настоящие объекты в тестах, вместо mock
Если конструктор вызывает внутри себя другой не статический метод, то в нем можно наблюдать частично проинициализированный объект. Особенно эта проблема чаще наблюдается при использовании таких классов в наследовании.
Еще хуже выполнять в конструкторах IO операции, особенно сторонние вызовы в другие сервисы! Если это будет какой-нибудь Spring bean и произойдет исключение (например, сервис не отвечает), то контекст попросту не сможет подняться, что чревато в принципе неработающим приложением в продакшене. Такие ситуации я уже не раз встречал на своем опыте, и это действительно печально, когда даже откатиться на предыдущие версию не поможет восстановить работосопособность приложения.
Поэтому, вместо того, чтобы добавлять логику в конструктор, лучше перенести ее в какие-нибудь фабричные методы/классы, или вообще попросить у Dependency Injection framework нужные тебе параметры.
#dmdev_best_practices
👍88🔥26❤13
Когда литералы выносить в константы
Литералы, такие как строки или числа (более подробно рассказывал про них в курсе Computer Science) можно либо просто оставлять в коде как есть, либо выносить их в константы и потом уже использовать именно константы.
В принципе, предпочтение нужно отдавать созданию констант, особенно если:
1. Одно и то же значение используется в нескольких местах, ибо это важно иметь one source of truth (иначе легко изменить значение в одном месте и забыть поменять в другом, из-за чего один и тот же функционал будет работать по-разному)
2. Значение требует пояснения, и гораздо лучше создать константы с хорошим названием и объяснением над ней, нежели оставлять неуклюжие комментарии прямо в месте использования литерала.
Например:
С другой стороны, если литерал используется лишь раз, а его значение легко понятно из контекста, то лучше все-таки не выносить его в константы. Все потому что довольно часто литералы улучшают читабильность кода, и не нужно делать лишних телодвижений, чтобы посмотреть значение константы.
Самые часто распространенные примеры, когда лучше оставлять литералы:
- true, false
- 0, 1
- SQL queries
#dmdev_best_practices
Литералы, такие как строки или числа (более подробно рассказывал про них в курсе Computer Science) можно либо просто оставлять в коде как есть, либо выносить их в константы и потом уже использовать именно константы.
В принципе, предпочтение нужно отдавать созданию констант, особенно если:
1. Одно и то же значение используется в нескольких местах, ибо это важно иметь one source of truth (иначе легко изменить значение в одном месте и забыть поменять в другом, из-за чего один и тот же функционал будет работать по-разному)
2. Значение требует пояснения, и гораздо лучше создать константы с хорошим названием и объяснением над ней, нежели оставлять неуклюжие комментарии прямо в месте использования литерала.
Например:
static final int GRACE_PERIOD_HOURS = 12;
scheduleSubscriptionRenewal(validUntil + GRACE_PERIOD_HOURS);
// или для сравнения:
scheduleSubscriptionRenewal(validUntil + 12);
С другой стороны, если литерал используется лишь раз, а его значение легко понятно из контекста, то лучше все-таки не выносить его в константы. Все потому что довольно часто литералы улучшают читабильность кода, и не нужно делать лишних телодвижений, чтобы посмотреть значение константы.
Самые часто распространенные примеры, когда лучше оставлять литералы:
- true, false
- 0, 1
- SQL queries
#dmdev_best_practices
👍53🔥25❤🔥4❤3👏1
Операции должны быть идемпотентны
Идемпотентность означает, что выполнение одного и того же запроса несколько раз подряд приведет к тому же результату, что и выполнение этого запроса в первый раз. Сюда относятся любые операции и по любым протоколам, которые использует сервис для обработки внешних запросов. Например, REST API, gRPC, Thrift, Message Brokers (Kafka, Cloud Pub/Sub) и т.д.
Почему это так важно?
1️⃣ Идемпотентные запросы могут предотвратить случайное выполнение повторных операций, что может привести к ошибкам или нежелательным изменениям в системе. Обычно из приведенных протоколов общения - никто не гарантирует, даже Message Brokers, что одно и то же сообщение будет доставлено ровно один раз!
2️⃣ Идемпотентные запросы обеспечивают надежность операций, позволяя клиентам повторять запросы в случае неудачи или потери связи.
3️⃣ Идемпотентные запросы облегчают работу с API, так как клиенты могут повторять запросы без необходимости дополнительной логики для управления состоянием. Другими словами говоря, клиенту не стоит переживать и усложнять свою систему для отправки запросов сервису.
Если брать идемпотентность в контексте CRUD операций, про которые я довольно много рассказывал в курсе Spring:
- Read операции (HTTP GET) по умолчанию должны быть идемпотентными. Ибо никаких изменений на сервере не должно происходить при обработки запросов на чтение данных.
- Update операции (HTTP PUT) точно также легко сделать идемпотентными. Можно сколько угодно обновлять один и тот же объект с теми же самыми полями в запросе.
- Delete операции (HTTP DELETE) чуть сложнее, но удаление одного и того же ресурса не должно вызывать ошибки на сервере, если такого ресурса не оказалось во втором и последующих запросах
- Create операции (HTTP POST) сложнее всего добиться идемпотентности, поскольку их выполнение приводит к созданию нового ресурса с уникальным идентификатором. Повторный запрос на создание ресурса с теми же параметрами может привести к созданию дубликатов или ошибкам. Но все-таки способы есть, про которые я расскажу в отдельном посте!
#dmdev_best_practices
Идемпотентность означает, что выполнение одного и того же запроса несколько раз подряд приведет к тому же результату, что и выполнение этого запроса в первый раз. Сюда относятся любые операции и по любым протоколам, которые использует сервис для обработки внешних запросов. Например, REST API, gRPC, Thrift, Message Brokers (Kafka, Cloud Pub/Sub) и т.д.
Почему это так важно?
1️⃣ Идемпотентные запросы могут предотвратить случайное выполнение повторных операций, что может привести к ошибкам или нежелательным изменениям в системе. Обычно из приведенных протоколов общения - никто не гарантирует, даже Message Brokers, что одно и то же сообщение будет доставлено ровно один раз!
2️⃣ Идемпотентные запросы обеспечивают надежность операций, позволяя клиентам повторять запросы в случае неудачи или потери связи.
3️⃣ Идемпотентные запросы облегчают работу с API, так как клиенты могут повторять запросы без необходимости дополнительной логики для управления состоянием. Другими словами говоря, клиенту не стоит переживать и усложнять свою систему для отправки запросов сервису.
Если брать идемпотентность в контексте CRUD операций, про которые я довольно много рассказывал в курсе Spring:
- Read операции (HTTP GET) по умолчанию должны быть идемпотентными. Ибо никаких изменений на сервере не должно происходить при обработки запросов на чтение данных.
- Update операции (HTTP PUT) точно также легко сделать идемпотентными. Можно сколько угодно обновлять один и тот же объект с теми же самыми полями в запросе.
- Delete операции (HTTP DELETE) чуть сложнее, но удаление одного и того же ресурса не должно вызывать ошибки на сервере, если такого ресурса не оказалось во втором и последующих запросах
- Create операции (HTTP POST) сложнее всего добиться идемпотентности, поскольку их выполнение приводит к созданию нового ресурса с уникальным идентификатором. Повторный запрос на создание ресурса с теми же параметрами может привести к созданию дубликатов или ошибкам. Но все-таки способы есть, про которые я расскажу в отдельном посте!
#dmdev_best_practices
👍76🔥27❤🔥7❤2
Обеспечение идемпотентности в Create операциях
Я думаю, что важность идемпотентности в Create операциях особенно высока в различных платежных системах, вроде Stripe, где повторные запросы на изменение баланса могут привести к таким плачевным ошибкам как дублирование платежей. Кстати, даже я на своем опыте встречался с этой проблемой - мне два раза перевели 10.000$ вместо одного (к сожалению, пришлось вернуть).
Почему так часто повторяются одни и те же операции?
- проблемы на стороне клиента, например, пользователю не понятно - была нажата кнопка "отправить" или нет, поэтому нажимает кнопку вновь
- запросы идут по сети, которая ненадежна
- соединение может оборваться в момент отправки сообщения, тогда клиент получит ошибку и будет пробовать переотправить сообщение вновь (retry mechanism)
- запрос может успеть дойти и обработаться сервером, но клиент об этом не узнает, потому что соединение оборвалось в момент возвращение ответа
Так какие же существуют способы для того, чтобы добиться идемпотентности в Create операциях?
1️⃣ Клиент может генерировать уникальный идентификатор Request Id (обычный UUID) для каждого запроса Create и включать его в теле запроса или заголовке. Далее сервер проверять этот идентификатор у себя в базе (можно даже кэш использовать с небольшим временем жизни) и игнорировать повторные запросы с тем же идентификатором.
2️⃣ Чем-то похож на первый вариант, но вместо создания Request id - клиент (или даже сам сервер) может генерировать уникальный идентификатор на основании выбранных полей в теле запроса. И точно также дополнительно хранить этот идентификатор у себя в базе (или кэше).
Вариантов не много (кстати, первый является более предпочтительным), потому что само по себе создание нового пораждает уникальный идентификатор, и сложно определить (а потом еще и реализовать!) было идентичное уже создано на сервере или нет.
Примеры API, как это мы обычно решаем в Google, можно глянуть в спецификации AIP-155
Еще больше про идемпотентный API можно почитать тут:
Stripe: Designing robust and predictable APIs with idempotency
Optimistic Locking in a REST API
The Amazon Builders' Library: Making retries safe with idempotent APIs
#dmdev_best_practices
Я думаю, что важность идемпотентности в Create операциях особенно высока в различных платежных системах, вроде Stripe, где повторные запросы на изменение баланса могут привести к таким плачевным ошибкам как дублирование платежей. Кстати, даже я на своем опыте встречался с этой проблемой - мне два раза перевели 10.000$ вместо одного (к сожалению, пришлось вернуть).
Почему так часто повторяются одни и те же операции?
- проблемы на стороне клиента, например, пользователю не понятно - была нажата кнопка "отправить" или нет, поэтому нажимает кнопку вновь
- запросы идут по сети, которая ненадежна
- соединение может оборваться в момент отправки сообщения, тогда клиент получит ошибку и будет пробовать переотправить сообщение вновь (retry mechanism)
- запрос может успеть дойти и обработаться сервером, но клиент об этом не узнает, потому что соединение оборвалось в момент возвращение ответа
Так какие же существуют способы для того, чтобы добиться идемпотентности в Create операциях?
1️⃣ Клиент может генерировать уникальный идентификатор Request Id (обычный UUID) для каждого запроса Create и включать его в теле запроса или заголовке. Далее сервер проверять этот идентификатор у себя в базе (можно даже кэш использовать с небольшим временем жизни) и игнорировать повторные запросы с тем же идентификатором.
2️⃣ Чем-то похож на первый вариант, но вместо создания Request id - клиент (или даже сам сервер) может генерировать уникальный идентификатор на основании выбранных полей в теле запроса. И точно также дополнительно хранить этот идентификатор у себя в базе (или кэше).
Вариантов не много (кстати, первый является более предпочтительным), потому что само по себе создание нового пораждает уникальный идентификатор, и сложно определить (а потом еще и реализовать!) было идентичное уже создано на сервере или нет.
Примеры API, как это мы обычно решаем в Google, можно глянуть в спецификации AIP-155
Еще больше про идемпотентный API можно почитать тут:
Stripe: Designing robust and predictable APIs with idempotency
Optimistic Locking in a REST API
The Amazon Builders' Library: Making retries safe with idempotent APIs
#dmdev_best_practices
👍73🔥33❤🔥10❤4
Нужно закрывать ресурсы
Самый лучший вариант не забыть закрыть ресурсы после их использования - это использовать библиотеки или встроенный в язык функционал, который работает с ресурсами на уровень выше.
Другими словами говоря, если ты пишешь код так, чтобы не давать доступ пользователю к открытому ресурсу, то он попросто не сможет забыть его закрыть. Поэтому большинстве IO операций могут быть решены при помощи таких классов как ByteSource, CharSink (guava), и другие, но общий принцип таков:
Где FileSource берет на себя работу по инициализации resource, вызывает пользовательскую функцию doWork, и после всего закрывает сам resource.
Второй отличный вариант - это конечно же блок try-with-resources (начиная с Java 7), в который можно поместить любой объект (или даже несколько!), реализующий интерфейс AutoCloseable:
И опять же в методе doWork программисту не нужно думать о закрытии ресурсов, потому что они будут закрыты автоматически на уровень выше в обратном от их инициализации порядке, т.е. сначала закроется объект out, потом in. Более того, даже если произайдет ошибка при открытии потока OutputStream, первый InputStream все равно будет закрыт.
Кстати, в Java 9 добавили функционал по закрытию ресурсов, которые НЕ БЫЛИ проинициализированы в блоке try-with-resources. Тем самым дав возможность программистам закрывать ресурсы, которые были созданы на уровень выше. Поэтому советую прибегать к этому функционалу только в случае необходимости 🙂
#dmdev_best_practices
Самый лучший вариант не забыть закрыть ресурсы после их использования - это использовать библиотеки или встроенный в язык функционал, который работает с ресурсами на уровень выше.
Другими словами говоря, если ты пишешь код так, чтобы не давать доступ пользователю к открытому ресурсу, то он попросто не сможет забыть его закрыть. Поэтому большинстве IO операций могут быть решены при помощи таких классов как ByteSource, CharSink (guava), и другие, но общий принцип таков:
File file = new File("/path/to/file");
FileSource.of(file).use(resource -> doWork(resource));
Где FileSource берет на себя работу по инициализации resource, вызывает пользовательскую функцию doWork, и после всего закрывает сам resource.
Второй отличный вариант - это конечно же блок try-with-resources (начиная с Java 7), в который можно поместить любой объект (или даже несколько!), реализующий интерфейс AutoCloseable:
try (InputStream in = Files.newInputStream(sourcePath);
OutputStream out = Files.newOutputStream(destinationPath)) {
doWork(in, out);
}
И опять же в методе doWork программисту не нужно думать о закрытии ресурсов, потому что они будут закрыты автоматически на уровень выше в обратном от их инициализации порядке, т.е. сначала закроется объект out, потом in. Более того, даже если произайдет ошибка при открытии потока OutputStream, первый InputStream все равно будет закрыт.
Кстати, в Java 9 добавили функционал по закрытию ресурсов, которые НЕ БЫЛИ проинициализированы в блоке try-with-resources. Тем самым дав возможность программистам закрывать ресурсы, которые были созданы на уровень выше. Поэтому советую прибегать к этому функционалу только в случае необходимости 🙂
#dmdev_best_practices
👍67🔥27❤🔥8
Не создавай singleton - используй Dependency Injection Framework
Для того, чтобы создать singleton в Java - есть два основных варианта:
1. Самый общеизвестный и простой (и который я показываю в своих курсах), требующий создать static final поле, хранящее единственный экземпляр класса, и private конструктор, чтобы никто не создал объект этого класса
Но здесь появляются проблемы с десериализацией (и другие обходные пути создания более одного объекта), сложность управления зависимостями, а значит и затруднения при тестировании из-за тесной связи singleton с другими компонентами приложения.
2. Второй вариант решает часть из этих проблем - это enum. Его также советовал Джошуа Блох (Item 3), но в то же самое время добавляет другие ограничения и чувствуется "не есественность" в таком подходе. Поэтому программисты обходят его стороной.
Тем не менее, лучше избегать создания обоих вариантов синглтонов, если есть возможность использовать Dependency Injection Framework, такого как Spring или Guice. Фреймворк управляет созданием и предоставлением зависимостей для объектов в приложении, что делает код более гибким, легко поддающимся изменениям и сильно упрощается тестирование.
#dmdev_best_practices
Для того, чтобы создать singleton в Java - есть два основных варианта:
1. Самый общеизвестный и простой (и который я показываю в своих курсах), требующий создать static final поле, хранящее единственный экземпляр класса, и private конструктор, чтобы никто не создал объект этого класса
class UserRepository {
private static final UserRepository INSTANCE = new UserRepository();
private UserRepository() {}
public static UserRepository getInstance() {return INSTANCE;}
}
Но здесь появляются проблемы с десериализацией (и другие обходные пути создания более одного объекта), сложность управления зависимостями, а значит и затруднения при тестировании из-за тесной связи singleton с другими компонентами приложения.
2. Второй вариант решает часть из этих проблем - это enum. Его также советовал Джошуа Блох (Item 3), но в то же самое время добавляет другие ограничения и чувствуется "не есественность" в таком подходе. Поэтому программисты обходят его стороной.
enum UserRepository {
INSTANCE;
}
Тем не менее, лучше избегать создания обоих вариантов синглтонов, если есть возможность использовать Dependency Injection Framework, такого как Spring или Guice. Фреймворк управляет созданием и предоставлением зависимостей для объектов в приложении, что делает код более гибким, легко поддающимся изменениям и сильно упрощается тестирование.
#dmdev_best_practices
🔥72👍24❤🔥5❤3🥰1