Telegram Web
Как я провалила курс по спрингу

В декабре прошлого года я проводила адвент-календарь по java. 21 день почти тысяча человек разбирали нюансы java core. В конце я спросила: какую тему ещё разобрать, где есть непонятки? Ответ был почти единогласным — Spring.

И я поставила цель на 2023 — сделать что-нибудь полезное по спрингу. Не для начинающих, с практическими фишками и лучшими практиками.

За 8 месяцев так ничего и не сделала😞

Я не склонна к самобичеванию, и попробовала разобраться, почему так получилось. Ответ лежал на поверхности.

Все силы идут на курс по многопоточке. Чуть появляется свободное время — бегу его улучшать. Хотя давно можно остановиться. Отзывы прекрасные, доходимость высокая. Хороший баланс между пользой и нагрузкой. Аналогов до сих пор нет ни в СНГ, ни в англоязычном мире.

И кажется, это отличный момент пойти дальше.

Следующий поток будет в октябре, и это будет последний раз в текущем виде. Дальше я пройдусь по бэклогу, сделаю рефакторинг, и курс останется только в варианте без обратной связи.

А со следующего года снова займусь образовательным творчеством. Руки чешутся взяться за спринг и другие темы!

Всё это похоже на чувства от принятого оффера. Когда на старом месте всё получается, и коллеги лапочки, но есть чувство, что можешь больше. Новый проект обещает что-то интересное, и ты с надеждой устремляешься в путь.

Так что кто хотел на курс с обратной связью — откладывать больше нельзя. Через пару недель объявлю набор, и в начале октября начнём🚀
Pattern matching: зачем?

Последние годы в джаву активно добавляется группа фич на тему pattern matching.

Тагир Валеев из Intellij IDEA в этом докладе рассказывает, какие это сложные фичи, как много челленджей стояло перед командой разработки. Но не говорит, зачем он вообще нужен.

Архитектор java Brian Goetz пишет, что pattern matching — не просто синтаксический сахар, а движение в сторону data oriented programming. Но это слабо применимо к большинству корпоративных систем, где доминирует ООП и изредка встречается функциональное программирование.

Большая часть моих знакомых считает паттерн матчинг "стрелкой вместо двоеточия в свиче":)

Так что сегодня расскажу, для каких задач пригодится pattern matching. В следующем посте обсудим реализацию в java.

Формат входных данных в энтерпрайзе обычно чётко определён. Если входящее сообщение не подходит по формату — это ошибка со стороны клиента.

Но бывает, что система работает со множеством источников данных, и выделить общий формат очень сложно.

Например, вы парсите чужие сайты/документы/дампы и достаёте оттуда что-то полезное. Знаю один проект, где бизнес-данные вытаскивают из логов(!) другой системы🤯

В итоге входные данные выглядят как [Object, Object, Object].

Паттерн — схема того, что мы ищем. Паттерн для поиска координат может выглядеть так:

▫️ [Double, Double] — ищём массив из двух чисел с плавающей запятой
▫️ [(-90;90), (-180,180)] — массив с двумя числами в указанных диапазонах

Дальше идём по набору данных и проверяем их на соответствие паттерну.

Традиционно для такой задачи используется связка if + instanceOf. Вариант рабочий, но читаемость ужасная.

Вот что нужно написать, чтобы проверить, является ли координатами массив [Object, Object] data:

if (data.length == 2 && data[0] instanceOf Double && data[1] instanceOf Double) {
double n = (Double) data[0];
double e = (Double) data[1];
if (n ≥ -90 && n≤ 90 && e ≥ -180 && e ≤ 180) {
// что-то делаем
}
}

В pattern matching многие проверки убираются под капот, и код выглядит симпатичнее:

switch(data) {
case [(-90; 90), (-180, 180)]:
// что-то делаем
}

Близкий родственник паттерн матчинга — регулярные выражения. Строка — это набор символов, внутри этого набора ищутся паттерны. Можно сделать ту же работу через if, но регулярка удобнее. Плюс в строке элементы однородные (символы), а паттерн матчинг работает с разными типами данных.

Ещё пример:

double discount = switch(transaction) {
case ["vip", Long, _, _, Double sum] → sum*0,9
case _ → 0
}

Здесь ищем список из 5 элементов, где первый — строка "vip", второй и пятый — число. Если нашли — можно сразу работать с полем sum. Если не нашли — используем паттерн по умолчанию _

С if-ами эта конструкция будет гораздо объёмнее

Резюме

Pattern matching нужен, когда мы пытаемся найти что-то знакомое в слабо- или неструктурированных данных.
Большинство instanceOf и if отправляются под капот, и мы получаем более компактный код.
Разумеется, применить pattern matching можно и в других сценариях, но здесь видится наибольший профит в корпоративном царстве ООП.

В следующем посте распишу возможности pattern matching конкретно в джаве.
Спойлер: пока не впечатляет😑
Что будет напечатано в консоли? (используется Java 20)
Что будет напечатано в консоли? (используется Java 20)
Anonymous Poll
24%
Even Even
4%
Even Even Other
51%
Even Other Even
8%
Even Other Even Other
13%
Ошибка компиляции в одном из методов
Pattern matching: синтаксис

В прошлый раз мы обсудили pattern matching в вакууме. Сегодня обсудим, как он выглядит в java с учётом 21 версии, и чего не хватает в текущей реализации.

Под зонтик pattern matching относят много фич: sealed классы, records, обновление switch и instanceOf. Sealed и records в контексте pattern matching — специфичные кейсы data oriented programming, не будем о них. Cфокусируемся на более популярных сценариях:)

Итак, как pattern matching воплощается в синтаксисе:

1️⃣ Компактный instanceOf

Раньше для неизвестного типа выполнялись две операции: instanceOf и явное приведение типа:

if (obj instanceof String) {
String str = (String) obj;
// используем str
}

Теперь эти операции объединены. Рядом с instanceOf объявляем имя переменной и сразу ей пользуемся:

if (obj instanceof String str) {
// используем str
}

Тонкий момент— переменная определяется только при успешном выполнении instanceOf, поэтому есть нюанс с областью видимости.

В логическом И можно использовать переменную в том же if:
if (obj instanceof String s && s.length() > 5)

Логическое ИЛИ такого не позволяет, будет ошибка компиляции:
if (obj instanceof String s || s.length() > 5)

2️⃣ Компактный switch

У древнейшей конструкции доступен новый синтаксис:
▫️ Стрелочка вместо двоеточия
▫️ Break в конце case по умолчанию, и его можно не писать
▫️ Можно объединить несколько case в один

Старый синтаксис никуда не делся, им можно пользоваться:

switch (value) {    
case 1:
case 3: println("Odd"); break;
case 2:
case 4: println("Even"); break;
default: println("Other");
}

В "новом стиле" это будет так:

switch (value) {    
case 1,3 -> println("Odd");
case 2,4 -> println("Even");
default -> println("Other")
}

Важный момент — break добавляется по умолчанию только в вариант "со стрелочками". В "двоеточиях" break всё ещё добавляется вручную.

🔥 Ответ на вопрос перед постом: выведется Even Other Even. Чтобы работало как надо, нужно переписать на стрелочки или добавить break.

3️⃣ Присвоение элемента через switch

int value = switch(…) {…};

4️⃣ Проверка в switch по типам и сложные case

Раньше в case принимались только константы. Теперь можно проверить тип переменной:

switch (obj) {
case Integer i -> …
case Long l -> …
case Double d -> …
}

Если произошёл мэтч, для переменной сразу доступна доп. информация, и можно добавить условия в case:

switch (response) {
case String s when s.length() == 10 -> …
}

А теперь самое интересное! Обсудим, чего в текущей реализации нет:

Нет работы с массивами

Один из кейсов pattern matching — работа с набором данных, в которых мы ищем определённые признаки. Такого в java пока нет:

double discount = switch(transaction) {
case ["vip", Long, _, _, Double sum] → sum*0,9
case _ → 0
}

В других языках такой функционал есть. Например, List patterns в С# или Matching sequences в питоне.

Нет паттернов по полям класса

Под капот спрятан только instanceOf, остальные условия выглядят как в обычном if. Вся работа с полями происходит через методы:

switch (figure) {
case Square s when s.getLength() != 10 → …
}

В том же питоне запись гораздо компактнее:

match point:
case (0, 0): …
case (0, y): …
case _: …

JEP Record Patterns мог быть как раз об этом, ведь records позиционируются как лаконичные контейнеры данных. Но увы.

Резюме

В текущем виде pattern matching выглядит слабо, особенно по сравнению с другими языками. Покрыты только базовые кейсы, в таком виде область применения очень ограничена.

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

Старт: 2 октября
Длительность: 9 недель

Кто давно ждал и уже готов → https://fillthegaps.ru/mt

Теперь подробнее. У курса две основные задачи:

Научиться писать хороший многопоточный код

Разберём типовые энтерпрайзные задачи, огромное количество кейсов, лучших практик и возможных ошибок. Сравним производительность разных решений для разных ситуаций

Подготовимся к собеседованиям, где требуется concurrency. Обсудим стандартные и нестандартные вопросы, порешаем тестовые задания

Что говорят ученики:

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

Океан отзывов можно почитать тут

Для какого уровня курс?

Middle и выше

Сколько стоит?

🔸 Без обратной связи: 32000 руб.
🔸 С обратной связью: мест нет

Разницу между тарифами можно почитать тут. Этот поток — последний с обратной связью.

✔️ Есть рассрочка на 3 и 6 месяцев
✔️ Принимаются карты любых банков
✔️ Курс можно оплатить за счёт компании
✔️ Налоговый вычет 13%

Аналогов у курса нет. С каждым потоком программа становится лучше, задания интереснее, а учёба приятнее. Если хотите разобраться с многопоточкой, и вам близок мой стиль изложения — записывайтесь, будет очень полезно!

https://fillthegaps.ru/mt
Любимые подписчики, поздравляю вас с днём разработчика!

Мы все разные, с разным опытом, интересами и бэкграундом:

👦🏻 Кто-то пришёл в профессию по зову сердца, кто-то ради денег
👩 Кто-то горит работой, а кто-то уже выгорел
🧔🏻‍♀️ Кто-то работает 10 лет и видел всё. Кто-то только делает первые шаги

Но каждый прошёл огромный путь. Изучил тонны информации, научился тысяче вещей. Гибкий ум, критическое мышление, способность быстро обучаться и разбираться в сложном — наши сильные стороны.

Cегодня отличный день, чтобы признать свои заслуги и сказать себе: "я умничка". Признать, что делаешь сложную работу, и хорошо с ней справляешься.

Ребята, вы прекрасны! Вы очень умные и талантливые, каждый из вас.

Не будьте критичны к себе, почаще отдыхайте. Не стесняйтесь просить большую зарплату. Берегите своё желание учиться и покорять новые высоты❤️

С праздником🥳
Какая версия Java сейчас основная на вашем проекте?
Anonymous Poll
19%
8
1%
9 или 10
28%
11
2%
12-16
44%
17
6%
18+
Сегодня релиз Java 21🥳

Главные фичи новой версии:

🔸 Виртуальные потоки

В чём суть: для многих задач производительность ограничивается количеством потоков в ОС. Чтобы обойти это ограничение и использовать потоки на максимум, придумали множество способов — асинхронные библиотеки, реактивное программирование и тд. Вариант рабочий, но код усложняется.

Виртуальные потоки позволяют писать простой код и не упираться в ограничения ОС.

Ближайший аналог виртуальных потоков — горутины в Go. Собственно, горутины — основная причина, почему многие сервисы написаны на Go.

Более дальний аналог — корутины в Kotlin. Они имитируют виртуальные потоки на уровне библиотеки и несут большие накладные расходы. В java виртуальные потоки реализованы на уровне JVM, поэтому их производительность гораздо выше.

В теории виртуальные потоки принесут пользу большинству приложений. Посмотрим, что будет на практике.

🔸 Pattern matching

В этих постах (один, два) я подробно расписала, зачем нужен pattern matching в целом, и как он реализован в java. Вкратце: нужен не всем, реализация в джаве так себе

🔸 Sequenced collections

Новые методы в коллекциях: getFirst(), getLast(), reversed() и другие. Подробный пост тут

Интересные preview фичи:

🔹 String templates — интерполяция строк, возможность писать

String str = "Hello, ${name}!";

Подробнее в этом посте

🔹 Scoped Values — аналог ThreadLocal c ограниченной областью действия
🔹 Structured Concurrency — способ организации подзадач в многопоточной среде

Остальные фичи очень специфичные и вряд ли пригодятся большинству разработчиков:

▫️ Generational ZGC — в сборщик добавили поколения, как следует из названия. Молодые объекты будут собираться чаще, и сборщик будет работать эффективнее
▫️ Record Patterns — records можно использовать внутри case
▫️ Foreign Function & Memory API (Third Preview) — методы для работы с нативным кодом и управлению памятью за пределами JVM. Это нужно для приложений, которые хотят сами управлять размещением объектов в памяти и не зависеть от сборщика мусора.
▫️ Unnamed Patterns and Variables (Preview) — можно не указывать имя переменной, если оно не нужно. Вместо него ставить _

Например, в case:
case Point(int x, _) → …

Или при ловле исключения:
catch (IllegalStateException _)

Было бы удобно сделать такое для лямбд, но для этого есть отдельный JEP, который сделают чёрт знает когда

▫️ Deprecate the Windows 32-bit x86 Port for Removal — перестать работать с Windows 32-bit x86, в будущем удалить
▫️ Prepare to Disallow the Dynamic Click Me Load More of Agents

Агент — компонент, который изменяет классы при загрузке или меняет уже загруженные классы в JVM. Используется для мониторинга, профайлинга и других служебных целей

▫️ Key Encapsulation Mechanism API — методы для защиты ключей симметричного шифрования
▫️ Unnamed Classes and Instance Main Methods (Preview)

Видимо чтобы короче писать Hello World:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}

Вот такой релиз. Конечно, самая ожидаемая фича — это виртуальные потоки. Если планируете в ближайшее время внедрять их, отпишитесь по впечатлениям, мне очень интересно😊
Scoped Value (preview)

Неделю назад вышла java 21. Cегодня разберу интересную фичу в стадии превью — Scoped Value.

Ближайший аналог Scoped Value — ThreadLocal. Это когда мы объявляем переменную

ThreadLocal<Integer> value;

и для каждого потока будет своё независимое значение value.

В бизнес-логике это редко нужно, но фреймворки активно пользуются этим классом. Spring Security использует ThreadLocal для хранения информации о текущем пользователе. Давайте на этом кейсе посмотрим недостатки ThreadLocal, и что предлагает ScopedValue.

Как работает секьюрити:

1️⃣ Когда приходит новый запрос, Spring вытаскивает информацию о пользователе и записывает в ThreadLocal переменную:

public static ThreadLocal<Principal> PRINCIPAL = …

void serve(Request request, Response response) {

var principal = ADMIN;
PRINCIPAL.set(principal);
…}

2️⃣ Бизнес-логика. В любом месте кода можно узнать, кто выполняет запрос:

var principal = PRINCIPAL.get();

Обычно каждый запрос обрабатывается в своём потоке, поэтому данные между запросами не пересекаются.

3️⃣ В конце работы с запросом удаляем информацию из ThreadLocal переменной

Что в итоге:

Не надо передавать Principal в параметрах
Надо явно очищать значение ThreadLocal переменной в конце работы
В любом месте можно вызвать set/remove и всё сломать
Подход несовместим с виртуальными потоками

Scoped Value намерен решить проблемы выше. Как это выглядит:

public static ScopedValue<Principal> PRINCIPAL = …

void serve(Request request, Response response) {

var principal = ADMIN;
ScopedValue.where(PRINCIPAL, principal)
.run(() -> process(request, response));
…}

Переменная PRINCIPAL со значением principal будет доступна только внутри конкретного вызова метода process. Достать значение внутри process:

var principal = PRINCIPAL.get();

Кроме run есть метод call, который возвращает значение из переданной функции:

var result = ScopedValue.where(Server.PRINCIPAL, guest)
.call(() -> getResult());

Сначала кажется, что для java синтаксис Scoped Value очень необычный — как будто переменная главнее основного действия. Но такое в java уже есть, вспомните try-with-resources.

Что получаем:

Видимость переменной задаётся для конкретного вызова метода
У ScopedValue нет метода set, переменную нельзя обнулить/поменять внутри блока
Код совместим с виртуальными потоками

Что вызывает вопросы:

🤔 Сценарии использования

Неизменяемый аналог ThreadLocal, совместимый с Project Loom точно нужен, но не вижу смысла задавать область видимости настолько гранулярно

🤔 Нельзя использовать несколько ScopedValue без использования вложенности. Хотя это легко реализовать по аналогии с try-with-resources

Где использовать: пока вижу только как замену ThreadLocal при переходе на виртуальные потоки.

Фича сейчас в стадии превью, посмотрим, как она будет развиваться. Если будет, конечно:)
IDEA: как не потерять важные места в коде

В огромном проекте всегда есть места, которые хочется отметить или быстро находить.

Раньше это делали так:
🔴 Ставили неактивный брейкпойнт в нужном месте. В принципе нормально, но иногда сложно вспомнить, что где находится
⭐️ Добавляли файл в favorites. Файл добавляется целиком, что не очень удобно

Затем в IDEA убрали favorites и добавили закладки. Супер удобно, ни одна важная строчка теперь не потеряется.

▫️ Курсор на нужной строке → F11 → появляется закладка
▫️ Правый щёлк по закладке → Rename bookmark… → ввести что-то осмысленное
▫️ Посмотреть закладки: View → Tool Windows → Bookmarks (или Shift + F11)
Postgres и Kafka, часть 1

Возвращаюсь к ведению канала, и на этой неделе вас ждут 2 огненных поста🔥🔥 о взаимодействии Postgres и Kafka.

Эта пара безумно популярна на большинстве проектов👯‍♀️ База хранит данные, кафка отвечает за коммуникации между сервисами. Сегодня разберу одну проблему в их отношениях и расскажу вариант решения. А в следующем посте обсудим ещё 2 способа.

Рассмотрим проблему на простом примере.

Пользователь создал заказ, сервис принял запрос. Сервис добавил заказ в базу, и хочет рассказать другим сервисам о новом заказе. Что-то вроде такого:
public Order saveOrder(…) {
Order saved = orderRepo.save(…);
kafkaTemplate.send("orders",new OrderCreated(…));
}

Другие сервисы, подписанные на orders, получат сообщение и что-то сделают. Посчитают скидки, обновят статистику, запишут заказ в свою БД и тд.

В чём проблема?

Мы обращаемся к двум отдельным компонентам — базе данных и брокеру сообщений. Каждый из них в любой момент может отвалиться, например, пропадёт связь по сети. В зависимости от порядка строк в saveOrder возможны 2 негативных исхода:

😢 запись в базу сделали, сообщение не отправили
😢 отправили сообщение, но запись в БД не прошла

Получим несоответствие. Поэтому иногда хочется, чтобы события выполнились атомарно: либо оба успешно завершаются, либо ни одно из них.

Большинство разработчиков нетерпеливо скажут: "Что тут думать, нужен transaction outbox!!1". Но если спросить 10 человек, что они под этим понимают, получится 10 разных ответов.

В лучших традициях канала обсудим всё простыми словами:) Очень грубо все решения можно назвать так:
1️⃣ Убираем кафку
2️⃣ Убираем БД
3️⃣ Добавляем координатор

Сегодня рассмотрим первый вариант, в следующем посте — остальные два.

Вариант 1: убираем кафку

У Postgres есть механизм notify/listen, который отправляет уведомления заинтересованным лицам. И вместо отправки сообщений через кафку мы возьмём механизм подписки внутри БД.

База становится единственным компонентом и выполняет оба действия (сохранить в таблицу, уведомить заинтересованных) в одной транзакции.

Чтобы не решать проблемы с координацией двух компонентов, мы переложили всю работу на один.

Образцовая транзакция: атомарность и доставка exactly once
Минимальная задержка между сохранением в базу и уведомлением
Ограниченная функциональность уведомлений
Размытие ответственности — часть уведомлений делает Kafka, часть — Postgres
Увеличение нагрузки на БД

Последний пункт — главный ограничитель, поэтому подход "база делает всё" не очень популярен.

Реализация

Можно взять spring-integration-jdbc и для отправки сообщений, и для получения уведомлений. Документация максимально скудная, дополнительные детали есть в этой статье (под VPN)

В следующем посте обсудим ещё 2 варианта🔥
Postgres и Kafka, часть 2

В прошлом посте обсуждали, как атомарно записать данные в базу и отправить сообщение в Kafka. Чтобы на случай проблем с сетью компоненты не противоречили друг другу.

Сложность в том, что это БД и месседж брокер — разные сервисы и обеспечить их атомарную работу сложно. Есть 3 подхода к решению проблемы:
1️⃣ Убираем кафку
2️⃣ Убираем БД
3️⃣ Добавляем координатор

Первый пункт уже рассмотрели, в этом посте обсудим варианты 2 и 3.

Вариант 2: убираем БД

При сохранении сущности не делаем сразу запись в базу, только отправляем событие:
public void saveOrder(…) {
kafkaTemplate.send("orders", new OrderCreated(…));
}

Сервис прекращает быть "главным" и становится обычным потребителем событий. Получает сообщение — делает запись в БД. Другие сервисы тоже получают сообщение и что-то делают.

В случае проблем с сетью стратегия сервиса такая:
1️⃣ Отправь сообщение, пока не получится
2️⃣ Получив сообщение, пиши в БД, пока не получится

Вместо координации двух компонентов мы добиваемся исполнения гарантий на каждом шаге.

Доступны все возможности Kafka: роутинг, разные гарантии доставки, репликация сообщений
БД не становится узким местом
Самое долгое время между отправкой сообщения и сохранением в БД среди всех вариантов
Возможна неконсистентность данных и нарушение порядка изменений
Другие сервисы могут получить сообщение об изменениях раньше, чем "основной" сервис.

Если развивать вариант "убираем БД" дальше, придём к Event Sourcing. Это подход, в котором основной источник данных — события, а не БД. Но это уже другая история🧚‍♀️

Вариант 3: добавляем координатор

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

🔸 В базе данных что-то меняется
🔸 Координатор обнаруживает это изменение
🔸 Транслирует изменение в кафку

База может записывать изменения в отдельную таблицу с помощью триггера, materialized view или логической репликации. Координатор может смотреть на таблицу, а может читать логи БД.

Всё это многообразие объединяется термином Change Data Capture (CDC).

Чёткие зоны ответственности. Postgres хранит данные, Kafka шлёт сообщения, координатор координирует
Огромная гибкость, множество вариантов реализации
Возможна дупликация сообщений
Сложность системы увеличивается
Теперь у нас не 2 компонента (БД и Kafka), а гораздо больше. Больше инстансов, настроек, мониторинга и потраченных человеко-часов.

Реализация

Большинство инструментов базируются на Kafka Connect, в том числе популярная CDC система Debezium. Здесь и остановимся, пока всё просто и понятно:) Оценить, насколько глубока кроличья нора, можно в документации Debezium.

Резюме

Для слаженной работы Postgres и Kafka есть три основных подхода. Они отличаются
✍️ гарантиями доставки сообщения
✍️ нагрузкой на БД
✍️ временем между сохранением в БД и доставкой сообщения
✍️ сложностью
✍️ дополнительными возможностями

Выбираем решение по приоритетам, требованиям и нагрузке и ставим огоньки хорошему посту🔥
8 ошибок начинающих разработчиков

Рассмотрим типичную историю начинающего разработчика👶

Чтобы стать программистом, он долго учился. Прошёл много курсов, писал учебные проекты. У них маленькая кодовая база, один пользователь, а главная задача — отработать алгоритм или попробовать фреймворк.

В этих условиях у стажёра сформировался стиль написания кода.

После собеседования стажёр начинает работать в крупном проекте вместе с другими разработчиками. И здесь очень важно перестроиться: многие паттерны, нормальные в учебных проектах, уже не подходят.

В этом посте опишу 8 таких паттернов на простых примерах.

1️⃣ Процедурный стиль

Задача: получить список заказов пользователя. Новичок скорее всего напишет такой код:
List list = new ArrayList();
process(user, list);


Во входной параметр list потом запишется результат. Такой стиль часто идёт из учебных заданий по алгоритмам, где экономится каждый бит и максимально сокращается количество объектов.

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

Как лучше: использовать выходные параметры, не менять входные данные, давать осмысленные имена методам:
List orders = getOrders(user);


2️⃣ Сложные универсальные методы

Задача: по-разному считать скидку для новых пользователей и пользователей с картами лояльности.

Новички часто используют принцип Don't Repeat Yourself на максималках и пишут универсальный метод с кучей параметров и десятком if внутри:
getDiscount(user, true, true, limit, true)


Как лучше: сфокусированные методы для разных ситуаций
getDiscountNew(user);
getDiscountLoyal(user, limit)


3️⃣ Длинные методы

Сложно читать и тестировать, страшно менять.

4️⃣ Любовь к статическим методам

Как лучше: небольшие методы, связанные с конкретным классом. Связность ниже, ошибок меньше.

5️⃣ Сложное проектирование

Задача: завести три типа пользователей: новые, обычные и VIP. Новички скорее всего сделают интерфейс, 3 класса и статический класс с фабричными методами и билдером.

Как лучше: как можно проще. Например, один класс пользователя с полем Тип. Усложнять при реальной необходимости

PS Все "как лучше" не всегда лучше. Но думаю, идея понятна.

6️⃣ Нулевое или минимальное покрытие тестами

как следствие больших сложных методов и недостаточной инкапсуляции.

7️⃣ Низкий уровень ответственности

Пункт не относится к разработке, но очень актуален для начинающих. Проявляется в двух формах:

🔸 Непонятно, что происходит. Человек сидит и молчит до последнего, пока не спросишь статус задачи или про возможные трудности. Он умалчивает проблемы или переносит на других:

— Что с задачей, которую я тебе дала 3 дня назад?
— Я не понял, куда смотреть, потом меня HR позвал бумаги подписывать, потом я настраивал гит, увидел другую задачу и переключился на неё.


🔸 Код не решает проблему, а заметает симптомы:

— Приходил пустой параметр, и я выставил дефолтный. Тесты мешали сделать пул-реквест, и я их отключил.

8️⃣ Слабые коммуникативные навыки

— Как ты починил баг с расчётом ставки?
— Там через геттер фабричный метод нашёл, и потом докер с постгрёй поднял посмотреть, в логах был фильтр урезанный, я письмо отправил тебе в цц, но вроде скоуп не тот или тот, короче, запушил
— В чём была ошибка?
— Там два двоеточия вылезло
— Где?
— В дебаге
🤯


Эти ошибки ожидаемы в начале работы, я тоже их совершала🙂 Чем быстрее вы перестроитесь на командный стиль разработки, тем вероятнее пройдёте испытательный срок и быстрее вольётесь в проект.
С новым годом!

Любимые подписчики, пусть в следующем году выгорят только бенгальские огни, а работа принесёт море удовольствия и денег. Пусть жизнь за пределами работы вас вдохновляет и наполняет❤️

Я к концу года очень устала, и на канал не осталось сил. Но отдых творит чудеса, поэтому скоро вернусь в строй. Вы тоже отдохните как следует, вы это заслужили!

С наступающим новым годом!🎄
Как вредит ChatGPT

ChatGPT всё глубже проникает в жизнь, но не всегда улучшает её в лучшую сторону. За последние полгода появились две неприятные тендеции, которые больно бьют по начинающим разработчикам.

Нейросети — прекрасный инструмент, и в работе с ним есть два важных этапа:
👆 задать вопрос
✌️ провалидировать ответ

Второй пункт очень важен. ЧатЖПТ и другие нейросетки — не гигантская сеть знаний. Это искусственная модель, которая пытается угадать ответ, а задача пользователя — провалидировать его. Но бывает, что этап валидации пропускается, и мы получаем

Следствие 1: низкий уровень знаний после курсов

Иногда ученики курсов ленятся разбираться в непонятном. Тогда они спрашивают ответ у нейросети и сдают его на проверку.

Пользы от такого "обучения" нет. Лучше ошибиться и разобрать ошибку с преподавателем, чем получить формальный зачёт и забить не возможный пробел.

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

Так что скоро увидим больше выпускников курсов с формальным дипломом и поверхностными знаниями. Ситуация на рынке джунов и так печальная, а станет ещё хуже😔

✍️ Что делать:

Не используйте нейросети для обучения. Если что-то непонятно — спросите преподавателя, поищите статьи на хабре/baeldung, почитайте документацию.

Особенно это касается практики. Во многих вещах надо "набить руку", и в процессе работы всплывают все непонятные моменты. А готовый ответ выветрится из головы через секунду.

Следствие 2: низкое качество контента в "Java" каналах

Вы точно видели такие каналы — посты с рандомными фактами и обильная реклама курсов. Раньше в основе контента был копипаст из устаревших статей, переводы и куски из документации. Не очень интересно, но кому-то норм.

Сейчас чаще появляются посты, сделанные при участии ChatGPT. Качество контента не выросло, но теперь в постах проскальзывают ошибки.

Парочка примеров:

🤔 Пост о StringBuffer и противоречивые абзацы насчёт потокобезопасности:
Класс является потокобезопасным, т. е. может использоваться в многопоточных приложениях.
Методы StringBuffer не синхронизированы, поэтому для многопоточного доступа нужно вручную синхронизировать доступ с помощью synchronized блока.


🤔 Пост о методе compareAndSwap():
Такого метода в атомиках вообще нет. В описании снова находим противоречие — метод возвращает то ли результат операции, то ли текущее значение переменной.

Хорошо, если в комментариях найдётся молодец, который укажет на ошибку. Но комментарии читают не всегда, а админам безразлично, что публиковать. Создавать контент с ChatGPT быстро и дешево, в будущем его будет больше.

При использовании нейросети человек отдаёт себе отчёт, что это нейросеть, и может засомневаться в ответе. А посты в "Java" канале реже подвергаются сомнению.

✍️ Что делать:

Отписаться от таких каналов. В интернете петабайты материалов для новичков, в которых точно нет ошибок. Даже устаревший текст лучше неверной информации.

И конечно, читать каналы разработчиков, которые пишут авторский контент и делятся опытом. Люди тоже ошибаются, но качество таких постов гораздо выше. Не забывайте про лайки и обратную связь, это правда очень мотивирует писать дальше🥰
От JDBC до Spring Data: часть 1

Общение с базой данных связано с огромным количеством технологий — навскидку вспоминаем JDBC, JPA, Hibernate и Spring Data. Они тесно переплетены, и не всегда люди чётко понимают, что есть что и зачем нужно.

В ближайших постах разложу по полочкам основные технологии по работе с данными.

Почему всё так сложно? Почему нельзя сохранить всё как есть?

Приложение использует оперативную память и представляет данные в виде объектов. Можно работать с любым объектом в любой момент — чтение и запись происходят быстро. Минус оперативки — когда приложение завершается, память освобождается, и данные пропадают.

Чтобы данные пережили приложение, они записываются в постоянную память. Хранением и организацией данных занимается БД.

В оперативке данные лежат кое-как — где место нашлось, там объект и создаётся. В постоянной памяти данных много, всё упорядочено и оптимизировано. Поэтому модели данных в БД и приложении иногда отличаются.

А ещё БД — это отдельное приложение. В итоге для сохранения/извлечения данных нужна куча дополнительной работы:
🔸 открыть соединение
🔸 составить SQL запрос
🔸 передать в запросе данные из приложения
🔸 преобразовать ответ БД в java объект
🔸 закрыть соединение

JDBC

— стандартные java методы, которые выполняют все пункты выше. Все инструменты по работе с БД под капотом используют как раз JDBC.

Работа с соединениями и преобразование данных — утомительная работа. Поэтому и появляются библиотеки, облегчающие этот труд.

Spring JDBC

берёт на себя работу с соединениями. Разработчик всё так же составляет запросы, передаёт параметры и преобразует ответы в java объекты.

ORM

Object Relational Mapping — преобразование данных (mapping) из java объектов в сущности БД и обратно.

Формально, ORM — просто название процесса. В случае JDBC весь ОRМ разработчик делает вручную.

На практике под ORM подразумевают ORM библиотеку/фреймворк — какой-нибудь инструмент, который берёт на себя часть работы по запросам и преобразованию данных.

Hibernate

— самая популярная ORM библиотека. Составляет простые SQL запросы и преобразует данные. Упростил жизнь многим и заслужил их любовь❤️

В хибернейте не всё идеально:
▪️ Работа с соединениями (сессиями) остаётся на пользователе
▪️ Для корректной работы надо знать внутрянку (dirty session, как разруливаются отношения и тд).

Сложно не признать, что Hibernate великолепен. Ворвался на олимп ORM библиотек в 2001 году и до сих пор оттуда не слезает🏆

JPA

Сейчас большинство приложений базируются на спринге, но 10-15 лет назад приложения часто опирались на Java ЕЕ. В те года ORM Java ЕЕ выглядел сложно — для каждой сущности требовались несколько классов и кучка интерфейсов.

Hibernate выглядел привлекательно, но нельзя просто взять и добавить библиотеку в проект. Во вселенной Java EE всё работает через спецификации — стандартные интерфейсы.

Поэтому появилась новая спека по ORM — Java Persistence API или JPA. С небольшими отличиями почти полностью списана с хибернейта. Вскоре Hibernate подстроился под JPA и стал использоваться в Java EE.

Итого

⭐️ JDBC — базовое API по работе с БД
⭐️ ORM — преобразование данных между приложением и БД. На практике под “у нас на проекте ORM” имеют в виду, что используется ORM библиотека, например, Hibernate
⭐️ JPA — спецификация по ORM. Набор интерфейсов, аннотаций и описание, как всё должно работать. Не включает в себя конкретную реализацию
⭐️ Hibernate — популярная ORM библиотека, реализующая JPA

В следующем посте распишу вариации Spring Data, и почему материалы по хибернейт могут не соответствовать реальности.
От JDBC до Spring Data: часть 2

В этом посте расскажу про популярные модули Spring Data и подскажу важный нюанс при изучении Hibernate.

Spring Data JPA

Hibernate вызвал вау-эффект тем, что взял на себя маппинг и простейшие SQL запросы.

Spring Data JPA пошёл дальше и избавил разработчика от унылых конфигов и возни с сессиями, плюс генерирует более сложные SQL запросы.

Всё очень круто (без шуток), но есть нюанс.

Ванильный Hibernate подразумевает, что пользователь знаком с деталями реализации — умело работает с кэшами 1 и 2 уровня, различает persist/save/merge, использует нужные типы данных и тд.

А вот JPA определяет только интерфейс доступа к данным. Поэтому в Spring Data JPA многие хибернейт фичи не используются.

Пример — ленивая загрузка коллекций и кэш 1 уровня. Spring Data в общем случае при каждом обращении к репозиторию создаёт новую сессию. Кэширования в итоге нет, а при загрузке коллекций ловим эксепшн.

Кэш 2 уровня и EntityGraph поправят ситуацию, но это уже продвинутый уровень:) Недостаточно пользоваться абстракцией "репозиторий", надо знать и Hibernate, и как Spring использует Hibernate.

Практический совет — если что-то читаете по хибернейту, уточняйте, как это работает в Spring Data и работает ли вообще.

Для простых сервисов Spring Data JPA существенно упрощает жизнь. Для сложных тоже, но требует больше знаний.

Spring Data JDBC

— альтернатива Spring Data JPA. Под капотом у него JDBC без посредничества Hibernate.

Интерфейс такой же — пользователь работает с репозиторием и размечает классы аннотациями типа @Id или @Column.

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

Отдельного внимания заслуживает работа с зависимыми сущностями в DDD стиле. А в этом докладе показан наглядный пример и больше различий Spring Data JPA/JDBC.

Важный момент! Не путайте две библиотеки:
🌸 Spring JDBC упрощает работу с соединениями. Запросы, маппинг сущностей, управление транзакциями пишет разработчик
🌹 Spring Data JDBC даёт следующий уровень абстрации — репозиторий. Работа c запросами, маппингом и транзациями упрощается за счёт аннотаций

MyBatis

часто упоминается как альтернатива Hibernate. Называет себя persistence framework, а не ORM, но занимается тем же — помогает писать меньше кода по перегону данных между БД и приложением.

Основное отличие MyBatis от хибернейта — все SQL-запросы пишутся явно, и внутри можно писать if и foreach блоки.

MyBatis в целом ничего, но редко встречается. Причины просты:
Нет Spring Data модуля, только Spring Boot Starter. Писать руками нужно гораздо больше
В MyBatis есть аннотации, но документация и большинство статей используют XML. Выглядит несовременно👨‍🦳

Итого

⭐️ Spring Data * берёт на себя конфиги, работу с сессиями, генерацию некоторых запросов
⭐️ Spring Data JPA упрощает работу с Hibernate
⭐️ Spring Data JDBC предлагает похожий интерфейс, но на основе JDBC
⭐️ MyBatis для тех, кто хочет чего-то другого

Что выбрать?

Функционально Spring Data JPA/JDBC и MyBatis похожи, но со своими нюансами. Адекватных и современных бенчмарков в интернете нет. Статьи вроде "Hibernate vs MyBatis" очень поверхностные, не тратьте на них время.

На практике выбор делается почти случайно. Что затащат в проект на старте, то и используется:)
Выполнится ли до конца задача, отправленная в экзекьютор? Длительность задачи - около 500мс
Выполнится ли до конца задача, отправленная в экзекьютор? Длительность задачи - около 500мс
Anonymous Poll
25%
Да, если она начала выполнение до вызова cancel
16%
Нет
59%
Невозможно определить
2025/06/29 18:41:02
Back to Top
HTML Embed Code: