Премиум каналы
Категория Технологии IT
Подкатегория
0
0
5
11325

Java: fill the gaps

Привет, Диана! Заниматься java разработкой с 2013 года - это замечательно! Если вы ищете информацию и ресурсы для улучшения своих навыков в Java, я могу порекомендовать вам Telegram-канал "Java: fill the gaps". Этот канал предлагает материалы по Java Core и лучшим практикам программирования на Java. Удачи в вашем путешествии в мире Java разработки!

Последние публикации с канала

Паттерн Builder для продвинутых

Начав обсуждать билдер, остановиться сложно. Я и не буду:) Сегодня подсвечу пару интересных моментов и один приём, который точно пригодится на практике.

Сразу к делу:

1️⃣ Билдер может содержать сложную логику, а не просто копить будущие поля

Например:
🔸 Собирать одну информацию, отдавать объекту другую
🔸 Возвращать любые классы и объекты, даже прокси и уже созданные
🔸 Проводить операции внутри метода build

Всё это при умелом использовании создаёт симпатичное и лаконичное API.

Пример 1: класс StringBuilder

Гораздо интереснее, чем кажется:
▫️ Копит данные в изменяемом массиве, на выходе отдаёт неизменяемую строку
▫️ Методы append можно вызывать сколько угодно раз
▫️ Есть дополнительные методы вроде reverse или delete

В результате получаем удобный и функциональный класс. Ломбок такой билдер не соберёт, чатЖПТ такое не придумает:)

Пример 2: класс HttpClient

В самом простом варианте код выглядит так:

HttpClient client = HttpClient.newBuilder().build();

🤔 Зачем тут билдер? Почему просто не сделать new HttpClient()?

Потому что внутри build происходит такая магия:
SingleFacadeFactory facadeFactory = new SingleFacadeFactory();
HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);
impl.start();

return facadeFactory.facade;

Тут прячется установка работы с сетью в 200+ строк кода и механизм по завершению работы с сетью, когда объект HttpClient станет не нужен. Хотя объект работает с ресурсами, его не надо помещать в блок try-with-resources.

Мелочь, а приятно🥰

И вторая классная особенность:

2️⃣ Билдер может сделать несколько объектов на базе переданных данных

Часто помогает при написании тестов.

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

▫️ Сделать общий для всех тестов билдер, но build() не вызывать
▫️ В каждом тесте доставить нужные поля и построить новый объект

Получается так:
// общее поле c базовой информацией
Account.Builder accBuilder = Account.builder().INN(…).KPP(…)

// в тесте, где важен БИК:
Account acc = accBuilder.RCBIC(123).build();

// в тесте, где нужно название банка
Acccount acc = accBuilder.bankName("green").build();

Это короче, чем в каждом тесте создавать объект с нуля. Плюс сразу видно "главное" для теста поле.

Приём подходит не всегда, но иногда здорово улучшает читаемость.

Итого

Метод build может содержать сложную логику: от проверки параметров до шифрований и преобразований
Builder может создать несколько объектов на основе переданных данных

Возьмите эти свойства на заметку, в умелых руках паттерн Builder делает код проще и удобнее🔥

24.04.2024 / 06:04

Что лучше — Fluent API или Builder?

Продолжим разговор о Fluent API и рассмотрим частный случай — создание объекта. Сравним "классический" флуент апи с билдером и посмотрим, когда что использовать.

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

Уточним термины

▫️ Fluent API — это стиль написания кода. Грубо говоря, когда методы соединяются через точку (method chaining). С помощью Fluent API можно создать объект и проинициализировать поля.

▫️ Builder — паттерн для создания объектов, суть которого в постепенном накоплении информации. Билдер тоже использует method chaining и относится к fluent api.

В чём разница на практике?

Допустим, надо создать экземпляр класса Account с информацией о счёте. Рассмотрим 4 варианта:

1️⃣ Все параметры передаём в конструкторе:

Account acc = new Account(111, 222, 333);

Идеально для обязательных параметров. Компилятор не даст шанса пользователю что-то забыть
Плохая читаемость — передаётся набор чисел, непонятно, что есть что
Если есть необязательные параметры, нужно несколько конструкторов

2️⃣ Параметры выставляем через сеттеры:
Account acc = new Account();
acc.setINN(111);
acc.setKPP(222);
acc.setRCBIC(333);

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

3️⃣ Используем Fluent API:
Account acc = Account.new()
.INN(111)
.KPP(222)
.RCBIC(333);

Симпатичнее, чем сеттеры
Та же проблема с обязательными параметрами, никаких проверок
Не подходит для неизменяемых классов
Более сложный код. Но если у вас разрешён ломбок, аннотация @Accessors(chain = true) упрощает задачу

4️⃣ Создание объекта через билдер:
Account acc = Account.builder().
.INN(111)
.KPP(222)
.RCBIC(333)
.build();

В build() можно добавить нужные проверки
Можно использовать для неизменяемых классов
Надо написать отдельный класс Builder или использовать аннотацию @Builder из ломбока

Создание объекта через Fluent API не очень отличается от билдера по лаконичности, но проигрывает ему в функциональности. Fluent API не может проверить поля на "обязательность".

Итого

🔸 Если в классе мало полей, идём по классическому пути — обязательные параметры помещаем в конструктор, необязательные выставляем сеттерами
🔸 Если полей много или между полями сложные связи — используем билдер. Именованые методы улучшат читаемость, а метод build() проверит всё, что нужно

Fluent API обычно не даёт преимуществ в ситуациях выше, и редко используется для создания объектов. Его сильная сторона — логические блоки из нескольких операций🔥

19.04.2024 / 06:04

Fluent API: что такое и зачем нужен

Fluent API набирает популярность последние лет 5 и часто вызывает вопросы:
🤔 В чём его польза?
🤔 Чем он отличается от билдера?
🤔 Когда нужен, а когда нет?

В этом и следующем посте постараюсь ответить на эти вопросы.

Основная задача Fluent API — улучшить читаемость кода. Для этого:

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

Пример:

CompletableFuture.runAsync(…).thenRun(…).exceptionally(…);


Fluent API не всегда уместен и не всегда реализован удачно. Список формальных условий определить сложно, на практике такой формат часто выбирается интуитивно. Поэтому рассмотрим побольше примеров!

Хороший пример из AssertJ:
assertThat(str).startsWith(…).contains(…);


За раз пишем несколько проверок для строки str. Без Fluent API кода будет больше

Хороший пример из Мокито:
when(mock.method()).thenReturn(…).thenReturn(…).thenThrow(…);

Читается как одно предложение. Мне даже сложно представить, как это написать в “традиционном” стиле:)

Прекрасный пример из Spring JDBC:
List<User > users = jdbcClient.sql(…)
.param("rating", 5, Types.INTEGER)
.query(mapper)
.list();


Почему пример прекрасен? Потому что Fluent API скрывает работу с объектом PreparedStatement. Код получается не только короче, но и проще🔥

😐 Так себе пример из Spring Data:
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths(…)
.withStringMatcher(StringMatcher.ENDING);


Приставка with у методов лишняя, название последнего метода неудачное

Плохой пример из SLF4J:
logger.atInfo().log(…);


Классический logger.info(…) короче и удобнее

Плохой пример из популярного джава канала:
Person person = new Person().setName(…).setAge(…);

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

Итого

Fluent API подойдёт, когда работа с объектом проходит в несколько шагов, но единым логическим блоком. Основная цель — улучшить читаемость. Высший пилотаж — повысить с помощью Fluent API уровень инкапсуляции.

Чтобы сделать удобно и красиво, нужен опыт, насмотренность и немножко вдохновения:)

Отдельный случай — Fluent API при создании объектов. Здесь часто возникает путаница с билдером, и непонятно, что когда использовать. Этот вопрос я разберу отдельно в следующем посте🔥

17.04.2024 / 06:04

Нужно ли высшее образование для разработчика?

Встречаются 2 радикальных мнения:

🧔‍♂️ Вышка не нужна, там старая программа. Всё есть в интернете и можно выучить самому
👩‍🦰 Только с высшим образованием станешь “нормальным” разработчиком, без вышки твой потолок — это круды


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

Я отучилась в ИТМО на инженера-программиста 11 лет назад. "Информационные технологии" присутствуют в названии универа, факультета и специальности — уровень айтишности максимальный.

Какие плюсы вижу с высоты прожитых лет:

Можно попробовать разные стороны IT

Помимо разработки было море других предметов: кодирование данных, математическое моделирование, ассемблер, компьютерная графика и даже 1С:). Здорово расширяет горизонт и помогает нащупать, что интересно конкретно тебе.

Выбор самоучек ограничен энтерпрайзным меню: фронт, бэк или мобилки. Дальше человек ориентируется на требования рынка.

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

Базовые знания

Как работают операционные системы, компьютерные сети, linux/bash и тд.

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

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

Эти темы легко освоить самостоятельно, глубокого погружения не требуется. Но на практике редко у кого доходят руки до таких тем. В универе у тебя нет выбора:)

Стажировки, факультативы, конкурсы, хакатоны

от университета и известных компаний. Учёба по обмену в другой стране. Интересные люди приходят читать лекции и делиться опытом.

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

А теперь минусы:

Долго и непродуктивно

За 4-5 лет через студента проходит огромный объём информации, и большинство тем он видит в первый и последний раз. Ни ассемблер, ни теория поля мне ни разу не пригодились.

Весь объём знаний, который я использую сейчас, осваивается за полгода-год самостоятельного обучения.

Знаний из университета недостаточно

Нет специальности "Java разработчик". В университете учат на универсального инженера-программиста. Поэтому уходить вглубь пришлось самостоятельно.

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

Но не всё так страшно. Мне хватило пары месяцев, чтобы подготовиться к собеседованиям и устроиться на работу. После косинусных преобразований и электротехники освоить технологии энтерпрайза очень просто:)

Итого

Хотя мне не пригодилось 90% того, что было в университете, сама учёба мне нравилась, и это было отличное время🥰

🔸 Если вам 15-18, интересно айти и есть время на учебу, рекомендую идти в универ и брать от него максимум. Ходить на факультативы, вписываться в стажировки и доп.программы, наращивать связи

🔸 Если чувствуете серьёзный интерес к области, далёкой от энтерпрайза, поищите соответствующую магистратуру. Разработка языков, биоинформатика, квантовые вычисления, etc — всё это лучше осваивать комплексно и в кругу единомышленников

🔸 Если вы уже работаете и видите, куда и как расти, то получать дополнительно высшее образование не нужно. Полезнее прицельно изучать технологии и качать софт-скиллы

27.03.2024 / 06:03

Есть ли у вас высшее айтишное образование?

Anonymous Poll

39% - Есть. Доволен, что получил

14% - Есть, но мог бы обойтись и без

30% - Нет, но думаю, что вышка полезна

17% - Нет, и не вижу смысла

27.03.2024 / 06:03

Чего ждать от Java ближайшие 10 лет?

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

⭐️ Project Loom

Цель: добавить легковесные(виртуальные) потоки

Самая заметная фича со времён Stream API. Большинство проектов получат огромный буст от внедрения виртуальных потоков. Что важно — с минимальными изменениями в коде.

В Java 21 вышло базовое апи по работе с виртуальными потоками. Предстоит ещё много работы внутри JVM и в рамках языка, чтобы удобно управлять тысячами задач.

⭐️ ZGC / Shenandoah


Цель: сборщик мусора с минимальными паузами

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

Для большинства проектов это не актуально. Сборщик по умолчанию G1 отлично работает и становится лучше с каждым релизом.

⭐️ Project Panama

Цель: упростить работу с native кодом и памятью за пределами JVM

Проект делится на 2 направления:

🔹 Новый вариант JNI

Нужен для работы с библиотеками, которые не написаны на Java, и вряд ли когда-нибудь будут: работа с графикой, манипуляции с ОС, сетью и тд. Текущий JNI очень старый, работает медленно и не безопасен. Поэтому пишут новый:)

🔹 Работа с памятью за пределами JVM

Нужна проектам, которые хотят управлять памятью без посредничества Java. Самим делать сборку мусора, сжимать и раскладывать данные по определённым структурам.

⭐️ Project Amber

Цель: упростить язык, добавить новые конструкции

Самые "народные" фичи, которые часто попадают в обзоры и статьи: var, текстовые блоки, records, pattern matching, sealed классы, string templates и так далее.

Что-то получается хорошо, что-то не очень. Где-то много пафосных разговоров про data-oriented programming. Есть странные фичи, вроде упрощения написания Hello world.

⭐️ Project Leyden

Цель: ускорить время старта Java программ

Оптимизировать загрузку классов, линковку, перенести часть процессов на этап компиляции. На энтерпрайз повлияет мало, по сравнению с работой фреймворков ускорения на уровне JVM будут мало заметны.

⭐️ Project Valhalla

Цель: оптимизировать работу с данными

Здесь так же два направления:

🔹 Создать value types — объект с полями и методами, работа с которым идёт как с примитивом:
Передаётся по значению
Компактно лежит в памяти
Не может быть null

🔹 Создать общую схему работы с примитивами, объектами и value types, избавить разработчика от мыслей про boxing/unboxing

А когда будет готово?

Плохая новость — реализации всех проектов растягиваются на десятки лет.

10 лет — не преувеличение. Лямбда-выражения в java обсуждались с 2004 года, а увидели свет только в 2014.

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

На java пишут большие системы, которые работают десятки лет. Поэтому основательный подход абсолютно оправдан😌

15.03.2024 / 06:03

Java 22: изменения в Stream API (с плохим API)

19 марта выйдет Java 22. Там появится интересная превью фича Stream Gatherers. Коротко расскажу, что это, зачем нужно, и что с ней не так.

Начнём. Код со Stream API делится на 3 части:
▫️ источник данных
▫️ промежуточные операции: map, filter, flatMap
▫️ терминальная операция: collect, count, findAny

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

Иногда их недостаточно. Например, хочется сделать distinct по какому-то признаку и дальше работать с исходными объектами:

clients.stream()
.distinct(Client::getBonusType)
.map(Client::getId).forEach(…)


Но это невозможно, distinct работает только по equals. В итоге надо либо извращаться, либо переписывать на for.

В Java 22 в Stream API появится универсальный метод gather. Туда можно передать логику преобразований с любыми отношениями - 1:1, 1:N, N:1, N:N. Синтаксис сложный, но круто, что такая возможность появилась.

Второе нововведение. Для collect есть готовые статические методы в классе Collectors, а для gather появится Gatherers с новыми методами. Самое полезное — два метода по работе с окнами:

🪟 windowFixed — поделить стрим на подмножества заданного размера
[1,2,3,4,5,6] → windowFixed(3) → [1,2,3], [4,5,6]


🪟 windowSliding — подмножества с пересечением одного элемента
[1,2,3,4,5,6,7] → windowSliding(3) → [1,2,3], [3,4,5], [5,6,7]


Что не так c методом gather?

1️⃣ Многословность

Та же ситуация, что и с collect. Чтобы разбить список на окна, придётся написать
list.stream()
.gather(Gatherers.windowFixed(2))
.collect(Collectors.toList())


Даже со статическим импортом выглядит не очень. Хочется писать без лишних слов:
list.stream()
.windowFixed(2)
.toList()


Да, исходный код Stream API сложный, и его тяжело расширять. Да, через статические методы реализация получится проще. Но не вижу ничего невозможного, чтобы сделать новые методы лаконичными, без приставки "gather(Gatherers"

2️⃣ Странные новые методы

Начнём с оконных. windowSliding делает пересечение только по одному элементу. Зачем нужно это ограничение — непонятно. Так же непонятно, зачем делать два отдельных метода windowFixed и windowSliding.

За образец можно взять Kotlin:
list.windowed(5,2)


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

Ещё в Gatherers появятся три странных метода: fold, mapConcurrent и scan. С первого взгляда непонятно, зачем они нужны, очень уж специфичны.

В целом криминала в gather/Gatherers нет, жить можно. Но важный навык разработчика — замечать слабые места в своих и чужих решениях. Этот навык нужно развивать, для этого и нужен этот пост:)

Что ещё почитать:
🔥 Серия постов про коллекторы. Там же я рассказала, почему в стримах используется отдельный метод collect, а не просто toList()
🔥 Новые методы Stream API в Java 16
🔥 Критикую метод HashMap в Java 20. Хотя сам метод маленький, он показывает серьёзную ошибку проектирования API

06.03.2024 / 06:03

Best practice: не использовать boolean параметры в методах

На вопрос выше сходу ответят только знатоки многопоточки. Остальные наверняка попытались придумать, что означает cancel(true), и в чём разница с cancel(false).

Метод Future#cancel — наглядная иллюстрация, почему boolean параметры в public методах не ок. Потому что непонятно, что означают true и false.

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

Даже если у параметров хорошие имена, итоговые true и false выглядят несимпатично и неудобно.

С private методами ситуация другая. Внутренняя реализация должна быть читаемой и понятной, но требования всё же менее жёсткие, чем у public методов. В private методах флажки допустимы, но увлекаться не стоит:)

Как исправить метод с boolean параметром?

1️⃣ Сделать два метода

и зашить "особенность" флажка в название. Необязательно дублировать реализацию — public методы могут под капотом вызывать private метод с флажком.

Так сделано, например, в классе HashMap для методов put(k,v) и putIfAbsent(k,v). Оба в итоге вызывают private метод putVal с флажком ifAbsent

2️⃣ Создать enum с двумя значениями и передавать его вместо флажка

enum ReplicateStatus {NO_REPLICATION, REPLICATE};
...
saveRequest(req, ReplicateStatus.REPLICATE);

Такой вариант предлагается в книге Effective Java (Item 51).

Выглядит лучше, чем флажки, но на практике такой способ встречается редко. Если enum нужен только для обозначения true/false, быстрее и проще сделать 2 метода с разными именами.

🔥 Ответ на вопрос перед постом — "невозможно определить".

Мы не знаем точно, начала ли задача выполнение до вызова cancel. Даже если задача стартовала, успех cancel зависит от кода внутри задачи. Поэтому итог непредсказуем🤷‍♀️

13.02.2024 / 06:02

Выполнится ли до конца задача, отправленная в экзекьютор? Длительность задачи - около 500мс

Anonymous Poll

25% - Да, если она начала выполнение до вызова cancel

16% - Нет

59% - Невозможно определить

13.02.2024 / 06:02

Выполнится ли до конца задача, отправленная в экзекьютор? Длительность задачи - около 500мс

13.02.2024 / 06:02

От 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" очень поверхностные, не тратьте на них время.

На практике выбор делается почти случайно. Что затащат в проект на старте, то и используется:)

01.02.2024 / 06:02

От 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, и почему материалы по хибернейт могут не соответствовать реальности.

30.01.2024 / 06:01

Как вредит ChatGPT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

11.01.2024 / 06:01

С новым годом!

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

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

С наступающим новым годом!🎄

31.12.2023 / 06:12

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️⃣ Слабые коммуникативные навыки

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


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

08.11.2023 / 06:11

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 есть три основных подхода. Они отличаются
✍️ гарантиями доставки сообщения
✍️ нагрузкой на БД
✍️ временем между сохранением в БД и доставкой сообщения
✍️ сложностью
✍️ дополнительными возможностями

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

02.11.2023 / 06:11

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 варианта🔥

31.10.2023 / 06:10

IDEA: как не потерять важные места в коде

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

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

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

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

29.09.2023 / 06:09

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 при переходе на виртуальные потоки.

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

27.09.2023 / 06:09

Сегодня релиз 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 Loading 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!");
}
}

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

19.09.2023 / 06:09
Отзывы: - 0
Поделитесь вашим мнением! Оставьте отзыв:

Похожие

Избранное