DMdev talks
3.24K subscribers
156 photos
13 videos
89 links
Авторский канал Дениса Матвеенко, создателя DMdev - обучение Java программированию

То, что все ищут по Java:
https://taplink.cc/denis.dmdev

P.S. Когда не программирую - я бегаю:
https://t.iss.one/dmdev_pro_run
Download Telegram
Предпочитай методы, реализующие функциональные интерфейсы, а не возвращающие функциональные интерфейсы

Довольно часто замечал, особенно среди тех, кто сильно вдохновился функциональным программированием и старается его применить где только можно, вот такой код:

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
👏3👍1
Lambdas vs Method references

С предыдущего п
оста мы уже поняли, что лямбда-выражения лучше всего использовать для небольших и простых фрагментов кода, которые занимают в идеале 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥62👏1
Генерация тестовых данных

Чтобы написать хороший тест, нужны хорошие тестовые данные, приближенные к production. Плохо подготовленные данные = плохо написанный тест.

Поэтому практически все тесты должны состоять из трех основных частей:
- given (подготовка данных и стабов для mock/spy)
- when (вызов тестируемого API)
- then (проверка результата)

Сложно написать хороший тест, полагаясь на данные, которые уже существуют в базе в момент запуска теста (за исключением справочных данных или тех, что были накатаны на production с помощью миграционных фреймворков вроде liquibase и flyway). Обычно на эти данные полагаются другие тесты, а потому часто меняются, что ломает наши тесты или делает их даже flaky.

Поэтому каждый тест должен в идеале готовить данные только для себя, на которых он планирует проверить API:

@Test
void findAll() {
// given
// Все компактно, содержит только необходимую информацию для программиста
User user1 = userDao.save(getUser("[email protected]"));
User user2 = userDao.save(getUser("[email protected]"));
User user3 = userDao.save(getUser("[email protected]"));

// when
List<User> actualResult = userDao.findAll();

// then
// Легко получить доступ к id объектов, т.к. накатывание данных было в самом тесте
assertThat(actualResult).hasSize(3);
List<Integer> userIds = actualResult.stream()
.map(User::getId)
.toList();
assertThat(userIds).contains(user1.getId(), user2.getId(), user3.getId());
}


А чтобы не испортить состояние базы во время проверки, то:
- открываем транзакцию ПЕРЕД выполнением теста (@BeforeEach)
- накатываем данные, вызываем API и проверяем результат (@Test)
- откатываем транзакцию в конце (@AfterEach)

#dmdev_best_practices
👍63🔥25❤‍🔥7
Wildcards

Параметризация является одним из самых мощных, но в то же самое время одним и самых сложных инструментов в Java. Как сейчас помню, что начал ее понимать как положено только после года работы инженер-программистом.

Поэтому не стоит усложнять свой API еще больше, когда можно обойтись, например, wildcards вместо создания нового параметризованного типа:

// Wrong
// Параметр <T> просто мешает чтению сигнатуры метода и не дает никакой пользы.
public <T extends Number> void update(Collection<T> ids) { ... }

// Right
public void update(Collection<? extends Number> ids) { ... }


Ну и еще раз напомню то, что объяснял на курсе Java Level 2 (Generics), и что является ключевым в понимании параметризации и wildcars в Java - это аббревиатура PECS.
PECS - Producer: Extends; Consumer: Super


// Метод добавляет (produce) элементы в коллекцию, поэтому Extends
void addAll(Collection<? extends E> collection) {
for (E item : c) {
add(item);
}
}

// Объект filter потребяет (consume) элемент из коллекции, чтобы отвалидировать его, поэтому Super
void removeIf(Predicate<? super E> filter) {
Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
}
}
}


PS. Более подробно про Generics также можно почитать в топ 1 книге для джавистов Effective Java (Item 31)

#dmdev_best_practices
🔥72👍28❤‍🔥4
Interruption

Остановить поток выполнения - не совсем тривиальная задача. Мы не знаем, какие ресурсы были заняты, а значит и не знаем, как их освободить. Поэтому еще во 2 версии Java метод stop был deprecated. Теперь мы можем только послать "сигнал" потоку, чтобы он увидел, что мы хотим "прервать" его выполнение. Тогда он сможет спокойно завершиться и почистить все необходимые ресурсы.

Когда в принципе может понадобится это прерывание? На самом деле основных вариантов немного (но встречаются очень часто в приложениях):
- graceful shutdown (всего приложения или его части)
- cancel (пользователь хочет отменить выполнение задачи)
- timeouts (задача/запрос выполняется дольше, чем отведено на это время)
- остановить ненужные параллельные задачи

Раньше я пытался реализовывать свой механизм отмены выполнения задачи, чтобы не мучиться с InterruptedException. Но в итоге код сводился к тому, что мне приходилось и проверять свой велосипед, и стандартный механизм interrupt, потому что он никуда не уходит.

Поэтому я выработал для себя несколько основных правил, которым всегда следую:
1. Когда это только возможно в логике (чаще всего перед каждой итерацией цикла) - проверяй Thread.isInterrupted()
2. Если ты увидел, что нужно прервать выполнение - то делаешь весь необходимый cleanup и пробрасываешь дальше InterruptedException
3. Если пробросить InterruptedException не получается (например, нужно свое кастомное исключение), то обязательно вызови Thread.currentThread().interrupt(), чтобы восстановить interrupted status


try {
logicThatCanBeTimeOutOrCancelled();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore the interrupted status
throw new MyCheckedException("describe what task was interrupted", e);
}




for (Path file : files) {
if (Thread.isInterrupted()) { // Check interruption before each iteration
// do cleanup and throw an InterruptedException
throw new InterruptedException();
}
doExpensiveTask(file);
}


#dmdev_best_practices
🔥72👍23❤‍🔥3🎉3
🥰4
Избегай длительного выполнения задач

Довольно часто на практике необходимо реализовывать задачи или scheduled jobs, которые должны долго выполнять какую-то работу. Например:
- рассылка недельных отчетов (статистика) для миллионов пользователей в системе
- обновление статуса всех expired подписок
- обработка файлов/фото/видео

Если такие задачи решать в лоб, то можно просто создать отдельный поток, который, например, начинает идти по всем пользователям в базе данных и рассылать им email.

Но какие проблемы мы получаем в таком случае?
1️⃣ Мы даже примерно не знаем, когда закончит выполнение задача. А в это время мы хотели бы рестартовать приложение, выкатывать новые версии его и т.д. - что означает прерывание задачи (что мы писали в предыдущем посте) и последующий запуск с того момента, на котором мы остановились.
2️⃣ Сложно распределить нагрузку между несколькими инстансами сервиса. Чаще всего такие задачи можно запустить только на одном, иначе возникает конфликт (который еще нужно избежать, потратив время на соответствующую реализацию!).
3️⃣ Трудоемкие долго живущие задачи могут замедлять оставшийся функционал сервиса, что негативно скажется на пользователях, а мы не сможем даже это исправить.
4️⃣ Такие задачи сложны в поддержке и обслуживании (как остановить ее? как поставить на паузу? как перезапустить? на все эти операции писать отдельный API?). Они более подвержены ошибкам и требуют больше усилий для исправления и оптимизации.

Поэтому предпочтительнее разбивать сложные задачи на более мелкие, более управляемые подзадачи
Например:
- запускать задачу гораздо чаще и обрабатывать только batch пользователей (например, по 1000)
- ограничивать по времени (например, отсылать emails не более минуты - после чего завершать работу).


Это помогает распределить нагрузку на систему, упростить отладку и обслуживание, а также обеспечить более быструю реакцию на изменения и улучшить общую производительность. Кроме того, такой подход позволяет более гибко и эффективно управлять ресурсами и временем разработки.

#dmdev_best_practices
🔥68👍19❤‍🔥4💯1
👍3
Не следует сохранять лямбда-выражения в переменных/константах

Возможно, кто-то как и я встречался с кодом, где лямбда-выражения выносятся в переменные внутри метода или даже константы, чтобы якобы улучшить производительность приложения. Выглядит это примерно так:

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🔥227❤‍🔥2
Заключительная заметка best practices от DMdev 🏁
Надеюсь, эта рубрика была полезной и интересной!

Ставь реакцию 💯, если хочешь оставить ее (естественно не на ежедневной основе, ибо чайник закипает мой уже 🤯).

Оставляй комментарий с предложениями о новых рубриках или изменении уже существующих - буду очень рад что-нибудь реализовать.

Всех с Наступающим Новым Годом!!! 🎄

P.S. А кто хочет применить на практике все best practices - приглашаю ко мне на менторство 2 ступени (осталось 4 места). Будет отличным новогодним подарком для себя!
Please open Telegram to view this post
VIEW IN TELEGRAM
💯226🔥6👍43
Используй 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
🔥56👍14❤‍🔥3🥰21
На YouTube вышла презентация проекта "Magic: The Gathering" по окончании 2-ой ступени менторства DMdev

С такими навыками каждого будут рады видеть в достойной компании на позиции сильного Junior Java Developer.

💻 В проекте использовались:
- Apache Maven для автоматизированной сборки
- JUnit 5 для покрытия кода unit и integration тестами
- Hibernate как основной ORM framework
- Liquibase для миграции SQL скриптов
- Spring Boot с Embedded Tomcat
- Множество различных Spring Starters (data-jpa, web, security, validation, thymeleaf, test, etc)

Более детальное описание можно найти в техническом задании

Смотри видео, задавай вопросы по проекту в комментариях.

А для тех, кто хочет стать участником менторства 2 ступени
👉 ссылка для записи с подробной информацией.

- старт 22 января - осталось 3 места (ко мне)

P.S. Дай знать 🔥, если видео было интересным
🔥40👍11❤‍🔥52😁2🤯1
#15 Мой путь

Осенний призыв в армию 2014 года. Последние пару месяцев мне довольно часто приходилось ездить из Минска в военкомат своего родного города Мстиславль, чтобы относить различные справки, документы и проходить медкомиссии. Я даже встретился с тем самым офицером, у которого я забирал документы на поступление на военный факультет в далеком 2009 году. Он так и сказал: “я ведь говорил, что мы тебя все равно дождемся”.

Настал момент распределения по воинским частям и вынесения решения о твоей службе. К счастью, тот полученный от заказчика документ сработал, и меня направили обучаться артиллерийскому мастерству и проходить резервную службу в Печах, что находится под городом Борисов. Сказать, что я был рад - значит ничего не сказать!

В день Х я снова приехал в Мстиславль, взяв оплачиваемый отпуск на целый месяц на работе - именно столько был первый сбор. Постригся впервые налысо и собрал необходимые вещи, что были прописаны в напутствующей бумажке от военкомата. Хотя добираться до войсковой части Печи из Минска всего 70 км, но пришлось ехать в Мстиславль, т.к. был прописал там. Оттуда же меня увезли на автобусе в город Могилев на еще одно медобследование перед тем, как уже другой автобус тронулся дальше в Печи. Честно говоря, мне прям не терпелось побыстрее оказаться в казарме, чтобы чисто ради любопытства понять, какова жизнь солдата и что она в принципе из себя представляет.

Я понял одно - что одного месяца такой службы мне более чем хватило, чтобы осознать всю суть вещей и полюбить с двойной силой мою настоящую жизнь программистом. По возвращении я даже получал удовольствие от того, что могу сидеть в нормальном туалете, принимать душ когда захочу и сколько захочу. Да какой там душ… в принципе мыться чаще раза в неделю - это просто нечто невероятное. А удобная обувь чего стоит? Мои ноги только месяц приходили в порядок и заживали мозоли, что натерли пятки до кости.

Наверное, описывать службу в армии нет смысла, потому что она оказалась в действительности такой, какой ее мы все слышали из уст других людей: унижение нижестоящего по званию, грубость и маты, уборка луж с помощью лопат для снега (декабрь же на дворе!), 8-часовой сон с 10 вечера до 6 утра в одном помещении с 70-тью другими храпящими ребятами, еда вообще оставляла желать лучшего (я похудел до 55 кг).

Но у всего есть как положительные, так и отрицательные стороны. Что действительно полезное я вынес оттуда, и что помогает мне по сей день добиваться всего, чего я только захочу - это дисциплина. И не только это… Думаю, что это был отличный опыт, который я запомню на всю жизнь. Именно такой опыт заставляет задумываться о том, что ты имеешь сейчас, и поднимает твою мотивацию в чем-либо до небес. Ибо практически что угодно лучше службы в армии.

#my_little_story
👍70🔥21❤‍🔥10😁32
Для тех, кто ждет «последний звонок» как в театре 🎭
Это он!

Присоединиться:

Первая ступень менторства:
https://dmdev.tilda.ws/first-level

Вторая ступень менторства:
https://dmdev.tilda.ws/second-level
🥰65🤔5🔥3😱2🤯1