Forwarded from Dmitry Alekhine
Денис, в энтерпрайз проге существует куда более масштабная проблема, я бы хотел услышать твое мнение по поводу нее:
Существует тонна библиотек и фреймворков, каждый(ая) решает проблему на все случаи жизни
Увы, наблюдаю, что в самом большом софте, будь то даже blink engine (движок хрома) - челы не используют stdlib, другие либы - они пишут свои собственные реализации корутин, виртуальных тредов, пулов и так далее.
С чем это скорее всего связано:
> Многоуровневый оберточный пи*дец "для удобства"
> Нежелание разобраться с библиотекой: "я свою реализацию быстрее напишу" - именно это больше всего и влияет на observability проекта и на его overbloat'ность
С другой стороны есть такие гении, которые ради одной функции импортируют целый фреймворк. Открываешь POMник - 500+ строк
Как находить баланс при разработке проекта, чтобы проект медленнее скатывался в оверблоат, но и не был тяжеловесным говном благодаря ленивым и глупым васянам, которые не в силах помимо чейнинга фремворочков и жсончиков придумать свою реализацию
Существует тонна библиотек и фреймворков, каждый(ая) решает проблему на все случаи жизни
Увы, наблюдаю, что в самом большом софте, будь то даже blink engine (движок хрома) - челы не используют stdlib, другие либы - они пишут свои собственные реализации корутин, виртуальных тредов, пулов и так далее.
С чем это скорее всего связано:
> Многоуровневый оберточный пи*дец "для удобства"
> Нежелание разобраться с библиотекой: "я свою реализацию быстрее напишу" - именно это больше всего и влияет на observability проекта и на его overbloat'ность
С другой стороны есть такие гении, которые ради одной функции импортируют целый фреймворк. Открываешь POMник - 500+ строк
Как находить баланс при разработке проекта, чтобы проект медленнее скатывался в оверблоат, но и не был тяжеловесным говном благодаря ленивым и глупым васянам, которые не в силах помимо чейнинга фремворочков и жсончиков придумать свою реализацию
🔥22👍5❤🔥3❤1
Ответ на вопрос 👆
При решении любой более менее сложной задачи - всегда нужно одним из первых шагов сделать поиск уже готовых решений. Я уже писал в посте про "велосипед", что практически невозможно написать сразу свое решение без багов и учесть все нюансы. Потому что только со временем полируется любое решение, когда им начинают пользоваться другие люди.
Далее идет поддержка самописного функционала, ибо оно определенно точно будет претерпевать изменения и никто кроме создателя не будет также хорошо знать его. А это все время, которое можно было потратить на более приоритетные вещи. Но, к сожалению, придется тратить его на поддержку, отвечать на вопросы и помогать другим программистам, кто столкнулся с трудностями в его использовании и т.д.
Далее идет использование инструмента другими разработчиками и их онбординг в проект/компанию. Если это общеизвестная библиотека или фреймворк вроде Spring, Hibernate, которые еще и обновляются “сами” - то с очень большой вероятностью человек будет знать его, что сэкономит время, а значит и деньги для компании.
Далее привнесение новых библиотек/фреймворков. В любом более менее работающем в продакшене приложении уже имеется опробованный стэк технологий. Привносить что-то новое и НЕ опробованное обычно чревато последствиями, как минимум очень сильно вырастает шанс того, что что-то пойдет не так. Поэтому в здравом уме никто не идет на такие риски, либо эти риски должны быть оправданы.
Пример с хромом и другими продуктами от гигантов вроде Google, Amazon и т.д. не стоит брать во внимание, потому что у них штат сотрудников в десятки и сотни тысяч человек. У них практически все используется свое самописное, потому что для них так проще. У них есть достаточно денежных и человеческий ресурсов, чтобы не только создавать такие инструменты, но и поддерживать их и даже выкладывать в open source. И онбординг в 3-6 месяцев для них ничего не стоит.
При решении любой более менее сложной задачи - всегда нужно одним из первых шагов сделать поиск уже готовых решений. Я уже писал в посте про "велосипед", что практически невозможно написать сразу свое решение без багов и учесть все нюансы. Потому что только со временем полируется любое решение, когда им начинают пользоваться другие люди.
Далее идет поддержка самописного функционала, ибо оно определенно точно будет претерпевать изменения и никто кроме создателя не будет также хорошо знать его. А это все время, которое можно было потратить на более приоритетные вещи. Но, к сожалению, придется тратить его на поддержку, отвечать на вопросы и помогать другим программистам, кто столкнулся с трудностями в его использовании и т.д.
Далее идет использование инструмента другими разработчиками и их онбординг в проект/компанию. Если это общеизвестная библиотека или фреймворк вроде Spring, Hibernate, которые еще и обновляются “сами” - то с очень большой вероятностью человек будет знать его, что сэкономит время, а значит и деньги для компании.
Далее привнесение новых библиотек/фреймворков. В любом более менее работающем в продакшене приложении уже имеется опробованный стэк технологий. Привносить что-то новое и НЕ опробованное обычно чревато последствиями, как минимум очень сильно вырастает шанс того, что что-то пойдет не так. Поэтому в здравом уме никто не идет на такие риски, либо эти риски должны быть оправданы.
Пример с хромом и другими продуктами от гигантов вроде Google, Amazon и т.д. не стоит брать во внимание, потому что у них штат сотрудников в десятки и сотни тысяч человек. У них практически все используется свое самописное, потому что для них так проще. У них есть достаточно денежных и человеческий ресурсов, чтобы не только создавать такие инструменты, но и поддерживать их и даже выкладывать в open source. И онбординг в 3-6 месяцев для них ничего не стоит.
👍64🔥20❤🔥2❤1👏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
Невозможное условие
Невозможным является такое условие, которое никогда не может возникнуть. За исключением случаев, когда наши самые глубокие предположения были не верны, или код не был значительно изменен в будущем. Поскольку условие невозможно, то и нет смысла его проверять и писать тест, если только это не требуется компилятором.
Первое, что стоит попробовать - это постараться исправить код так, чтобы невозможное условие никогда не возникало!
Если код исправить не получилось, то лучше обрабатывать такие невозможные условия, вызывая AssertionError. Обычно сообщение об исключении не требуется. Но если же ты все-таки начинаешь долго думать над ним, то это признак того, что условие скорее всего не так уж и невозможно!
Кстати, если невозможное условие является исключением, полезно назвать его impossible (как в примере ниже).
#dmdev_best_practices
Невозможным является такое условие, которое никогда не может возникнуть. За исключением случаев, когда наши самые глубокие предположения были не верны, или код не был значительно изменен в будущем. Поскольку условие невозможно, то и нет смысла его проверять и писать тест, если только это не требуется компилятором.
Первое, что стоит попробовать - это постараться исправить код так, чтобы невозможное условие никогда не возникало!
Если код исправить не получилось, то лучше обрабатывать такие невозможные условия, вызывая AssertionError. Обычно сообщение об исключении не требуется. Но если же ты все-таки начинаешь долго думать над ним, то это признак того, что условие скорее всего не так уж и невозможно!
Кстати, если невозможное условие является исключением, полезно назвать его impossible (как в примере ниже).
// Компилятор требует обработки IOException, но мы знаем, что IO исключение невозможно при конкатенации строк.
// Кстати, это отличный вариант для использования Lombok аннотации @SneakyThrows.
Appendable a = new StringBuilder();
try {
a.append("hello");
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
// Строка, которую невозможно достичь, только если не изменить логику кода в будущем
switch (array.length % 2) {
case 0:
return handleEven(array);
case 1:
return handleOdd(array);
default:
throw new AssertionError("array length is not even or odd: " + array.length);
}
#dmdev_best_practices
🔥60👍25❤🔥7❤1
Избегай громоздких списков параметров
Думаю, программистам интуитивно понятно, что сигнатуры методов (в первую очередь public) - должны быть хорошо продуманы, чтобы в последующем легко читались и использовались в коде. И особенно это касается количества параметров методов. Например, Джошуа Блох (Item 51) рекомендует не более 4, и я с ним практически согласен.
Хотя в идеале метод вообще должен состоять ровно из одного значения, которое передается на вход, и одного значения на выходе (return). А все, что происходит внутри - не должно менять каким-то образом состояние этого параметра.
Кроме количества также важен и тип параметров, ибо очень неприятно использовать методы, например, с 4 типами int. В одном из практическом видео на курсе Java Level 2 (доступно спонсорам Junior level) я демонстрировал домашнее задание с прямоугольником, в котором нужно было указать две точки для его определения:
Второй отличный вариант избегать большого количества параметров (валидно только для конструкторов) - это конечно же паттер Builder, про который мы уже обсуждали в одном из предыдущем best practice.
Третий - объединять несколько параметров в один объект Dto/Value.
#dmdev_best_practices
Думаю, программистам интуитивно понятно, что сигнатуры методов (в первую очередь public) - должны быть хорошо продуманы, чтобы в последующем легко читались и использовались в коде. И особенно это касается количества параметров методов. Например, Джошуа Блох (Item 51) рекомендует не более 4, и я с ним практически согласен.
Хотя в идеале метод вообще должен состоять ровно из одного значения, которое передается на вход, и одного значения на выходе (return). А все, что происходит внутри - не должно менять каким-то образом состояние этого параметра.
Кроме количества также важен и тип параметров, ибо очень неприятно использовать методы, например, с 4 типами int. В одном из практическом видео на курсе Java Level 2 (доступно спонсорам Junior level) я демонстрировал домашнее задание с прямоугольником, в котором нужно было указать две точки для его определения:
// Вот так неудобно использовать
public Rectangle(int top, int bottom, int left, int right) {}
// А вот так и удобнее, и гораздо понятнее. Еще и запутаться сложно
public Rectangle(Point upperLeft, Point lowerRight) {}
Второй отличный вариант избегать большого количества параметров (валидно только для конструкторов) - это конечно же паттер Builder, про который мы уже обсуждали в одном из предыдущем best practice.
Третий - объединять несколько параметров в один объект Dto/Value.
#dmdev_best_practices
🔥89👍15❤7💯3❤🔥2
Предпочитай методы, реализующие функциональные интерфейсы, а не возвращающие функциональные интерфейсы
Довольно часто замечал, особенно среди тех, кто сильно вдохновился функциональным программированием и старается его применить где только можно, вот такой код:
Но гораздо приятнее и более читабельнее будет преобразовать метод так, чтобы он не возвращал функциональный интерфейс
Также хотелось бы добавить, что две парадигмы программирования, функциональное и объектно-ориентированное, не исключают друг друга. Каждая имеет свои плюсы и минусы: какие-то задачи лучше решать с помощью функционального стиля, какие-то с помощью объектно-ориентированного.
#dmdev_best_practices
Довольно часто замечал, особенно среди тех, кто сильно вдохновился функциональным программированием и старается его применить где только можно, вот такой код:
csvRows.stream()
.filter(csvRowValidatorPredicate(fileContext))
.toList();
Predicate<CsvRow> csvRowValidatorPredicate(FileContext context) {
return csvRow -> context.findSuitableValidator(csvRow).isValid();
}
Но гораздо приятнее и более читабельнее будет преобразовать метод так, чтобы он не возвращал функциональный интерфейс
csvRows.stream()
.filter(row -> isCsvRowValid(row, fileContext))
.toList();
boolean isCsvRowValid(CsvRow row, FileContext context) {
return context.findSuitableValidator(row).isValid();
}
Также хотелось бы добавить, что две парадигмы программирования, функциональное и объектно-ориентированное, не исключают друг друга. Каждая имеет свои плюсы и минусы: какие-то задачи лучше решать с помощью функционального стиля, какие-то с помощью объектно-ориентированного.
Но как обычно - правда где-то по середине. Поэтому нужно комбинировать плюсы обоих подходов для получение наболее эффективного и читабельного кода, а не наоборот (как в примере выше).
#dmdev_best_practices
👍70🔥19❤🔥9💯3🤔1
Lambdas vs Method references
С предыдущего поста мы уже поняли, что лямбда-выражения лучше всего использовать для небольших и простых фрагментов кода, которые занимают в идеале 1 строчку, а максимум субъективен для каждого, но все-таки не должен превышать 3-5 строк.
С другой стороны в Java есть ссылки на метод (method reference) - это альтернативный синтаксис лямбда-выражения, который, по сути, передает параметры лямбда-выражения именованному методу.
Но когда/что лучше использовать?
В принципе, нужно склоняться в пользу method reference когда только это возможно. Ссылки на методы столь же эффективны, а иногда даже более эффективны, чем лямбда-выражения (правильнее даже сказать, что под-капотом лямбда-выражения преобразуются в ссылки на методы, чем наоборот). Особенно если лямбда-выражение становится слишком длинным - просто перенеси его тело в метод и вместо этого используй ссылку на метод.
К сожалению, при определенных обстоятельствах, например, когда приходится указывать название класса, а оно длинное - ссылки на методы лучше не использовать в угоду читаемости кода:
#dmdev_best_practices
С предыдущего поста мы уже поняли, что лямбда-выражения лучше всего использовать для небольших и простых фрагментов кода, которые занимают в идеале 1 строчку, а максимум субъективен для каждого, но все-таки не должен превышать 3-5 строк.
С другой стороны в Java есть ссылки на метод (method reference) - это альтернативный синтаксис лямбда-выражения, который, по сути, передает параметры лямбда-выражения именованному методу.
Но когда/что лучше использовать?
В принципе, нужно склоняться в пользу method reference когда только это возможно. Ссылки на методы столь же эффективны, а иногда даже более эффективны, чем лямбда-выражения (правильнее даже сказать, что под-капотом лямбда-выражения преобразуются в ссылки на методы, чем наоборот). Особенно если лямбда-выражение становится слишком длинным - просто перенеси его тело в метод и вместо этого используй ссылку на метод.
К сожалению, при определенных обстоятельствах, например, когда приходится указывать название класса, а оно длинное - ссылки на методы лучше не использовать в угоду читаемости кода:
// Lambda expression выглядит приятнее
.map(it -> splitToColumns(it))
// Чем аналогичный method reference
.map(UserDataCsvFileConvertorUtils::splitToColumns)
Тем не менее, в целом ссылки на методы обычно более компактны, чем лямбда-выражения, и им следует отдавать предпочтение, даже если они немного длиннее.
#dmdev_best_practices
🔥50👍23❤🔥5
This media is not supported in your browser
VIEW IN TELEGRAM
Менторство DMdev как дополнительный уровень в игре, только вместо нового босса тебя ждет опытный ментор, я и крутые знания.
➡️ Стартуем уже через месяц!
Осталось всего 2 места на первую ступень и 5 мест на вторую.
Выбирай свой уровень, записывайся и побеждай в мире IT:
🧩 Первая ступень менторства
🧩 Вторая ступень менторства
Если тебя все еще гложат сомнения или
важные вопросы остались не отвеченными —> просто напиши
@karina_matveyenka
Осталось всего 2 места на первую ступень и 5 мест на вторую.
Выбирай свой уровень, записывайся и побеждай в мире IT:
🧩 Первая ступень менторства
🧩 Вторая ступень менторства
Если тебя все еще гложат сомнения или
важные вопросы остались не отвеченными —> просто напиши
@karina_matveyenka
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥6❤2👏1