Java: fill the gaps
12.9K subscribers
7 photos
215 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Switch: успеть до 30-ти

Сегодняшняя тема - история успеха оператора switch. Он появился в java 1.0, и с тех пор оставался в неизменном виде. В 2018 разработчики JDK смахнули пыль с кодовой базы switch, и теперь над ним идёт активная работа.

Почему о нём вспомнили, и как меняется switch? Рассмотрим по порядку.

1️⃣ Часть 1. Эпоха ООП

Java работает с объектами уже 25 лет. В этих условиях switch редко встречается в коде и часто считается плохой практикой.

Почему? Всё дело в сценарии использования:
switch (user.getState()) {
case NEW: …
case CONFIRMED: …
case BANNED: … }

Switch - это не просто набор нескольких if. У объекта user 3 статуса. Список чётко определен, статусы не пересекаются между собой.

Почему switch так себе вариант?
Сложный код. Работа со всеми состояниями в одной куче.
Дублирование кода. Если поле проверяется несколько раз, то менять такой код неудобно и легко ошибиться.

Для объекта с понятным набором состояний switch лучше заменить на полиморфные методы. Это несложный рефакторинг - пример1, пример2.

Цель ООП - смоделировать реальный мир через объекты. Главное здесь - объекты взаимодействуют и меняют состояние друг друга. В таких условиях switch проигрывает полиморфным методам и редко используется.

2️⃣ Часть 2. Эра функциональности

Сегодня для бизнеса недостаточно простой автоматизации. Основной задачей становится работа с данными.

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

Поэтому внедряются подходы из фунциональных языков. Один из них - pattern matching, а switch идеально подходит для его реализации. Паттерн - некоторое условие для переменной:
▫️Равна заданной константе
▫️Имеет определённый тип
▫️Подходит под регулярное выражение

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

switch (animal) {
case Cat c → c.putToBox();
case Dog d → d.train(); }

Итак, в чём разница между switch в 2000 и switch в 2021?

Switch 2000 работает с объектами, у которых меняется состояние. Вокруг этого строится бизнес-логика.

Switch 2021 работает с неизменными данными и помогает найти среди них подходящие. В следующем году выйдет java 17, и switch будет появляться в коде чаще.

Вот так один непопулярный оператор в JDK нашёл своё место в мире спустя 23 года⭐️
👍2
Спасибо за этот год!

Завтра новый год, это отличный повод сказать нечто важное.

Ребята, вы супер! Спасибо, что читаете мои нудные посты без картинок, помогаете найти ошибки и задаёте интересные вопросы. Благодаря вам блог ещё жив❤️

В 2020 году на канале вышло 85 постов, которые в сумме набрали 475к просмотров! Я в шоке и постараюсь в 2021 не сбавлять обороты.

Желаю всем в следующем году +1 грейд, интересные проекты и яркую жизнь вне работы🔥
Гороскоп на 2021

Всем известно, что астрология играет важную роль в IT. Двухнедельный спринт - это ровно половина лунного цикла. Идеальный размер команды равен количеству планет солнечной системы. Премии рассчитываются по астральным коэффициентам.

Вот что говорят звёзды про 2021 год:

♈️ Овен
Металлический Бык симпатизирует Овнам, поэтому все инициативы будут удачны, особенно зимой, весной, летом и осенью. Будьте активны на ретро, предлагайте новые фичи и подходы, возьмите под наставничество стажёров. В сентябре ожидайте наплыв писем от HR.

♉️ Телец
В год Быка Тельцы нацелены на быстрый карьерный взлёт. Подтяните пробелы и обсудите с тимлидом возможности роста. В этом году звёзды раскрутили ваш потенциал до максимума. В августе будьте осторожнее с git push --force.

♊️ Близнецы
Год будет спокойным и приятным. В прошлом году вы много работали, в 2021 выделяйте больше времени на отдых. Качество жизни и работы только улучшится. Самое время взяться за большие и фундаментальные книги, которые вы долго откладывали.

♋️ Рак
Откажитесь от лишней эмоциональности, она может помешать вашему развитию. Подтяните DevOps, в этом году он вам пригодится. Сложные задачи ждут вас в середине лета, но они дадут нужный стимул для дальнейшего роста.

♌️ Лев
В этом году удача не на вашей стороне, придётся много работать. Хотите успеха — начните сейчас, чтобы уже весной видеть первые результаты. Смотрите на вещи шире. Почитайте книжки по архитектуре, посмотрите видео с конференций HighLoad и ArchDays. В апреле высокий риск простудиться, одевайтесь теплее.

♍️ Дева
Год будет богат на свежие идеи и начинания. Начните то, что давно откладывали. Интересные идеи, вопросы и решения придут в самый неожиданный момент. Сохраняйте их сразу - запишите в блокнот, голосовое сообщение, как угодно, а то улетят. Обязательно делайте бэкапы и резервные копии.

♎️ Весы
В этом году у Весов будет шанс попробовать себя в руководящей роли. Готовьтесь заранее - почитайте статьи по управлению людьми и проектами. Подтяните тайм-менеджмент, иначе времени на хобби и внерабочие активности совсем не останется.

♏️ Скорпион
В этом году вы будете на переднем фронте. Вас ждут горячие фиксы и спасение команды перед дедлайном. Будет сложно, но Сатурн вам поможет. Помните об отдыхе и набирайтесь сил в спокойное время.

♐️ Стрелец
Пересмотрите приоритеты в жизни, попробуйте смежные IT направления. Возможно позиция тимлида, менеджера или аналитика раскроют вас с новой стороны. В этом году особую важность приобретут межличностные отношения. Октябрь станет самым прибыльным месяцем в году.

♑️ Козерог
Для вас 2021 год — это борьба со своими слабостями. Уделяйте больше внимания тестированию и самопроверке. Разберитесь с NoSQL: книга для начинающих, для продолжающих. Хорошей идеей будет сходить на каток и покататься на ватрушках.

♒️ Водолей
Лучшее время для решительных шагов — начало весны. Много возможностей принесёт нетворкинг - поддерживайте тёплые отношения с коллегами, участвуйте в конференциях, митапах и корпоративных мероприятиях. Идите в ногу со временем - освойте Kotlin и Cloud computing.

♓️ Рыбы
Наступает период, когда пора применить все накопленные знания. А возможности для этого обязательно будут. Меркурий помешает сделать важные задачи в срок, поэтому закладывайте на выполнение в 2 раза больше времени. Лето подкинет массу интересных вариантов для отдыха.

Дружите со звёздами, они плохого не посоветуют💫
👍2
У аннотации Test определены все возможные Target. В какой строке будет ошибка компиляции (если будет)?
В какой строке будет ошибка компиляции (если будет)?
Anonymous Poll
16%
1
17%
2
4%
3
11%
4
35%
5
18%
Всё отлично скомпилируется
Аннотации, часть 1: обзор

Аннотации - дополнительная информация к исходному коду. @Override, @Deprecated, @SuppressWarnings - вот это всё.

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

Создать аннотацию легко:
public @interface MyAnnotation {}

Почему ключевое слово @интерфейс, а не @аннотейшн?

Во времена java 4 аннотаций не было и для дополнительной информации классу добавляли интерфейс-маркер. В интерфейсах Cloneable, Serializable, Remote нет методов, они используются только как дополнительный признак класса.

Подход рабочий, но похож на костыль. Цель интерфейса - показать контракт класса, поэтому для маркировки кода в java 5 ввели аннотации.

Вернёмся в наши дни. Посмотрим исходный код @Deprecated:

@Retention(RUNTIME)
@Target(value={FIELD,…})
public @interface Deprecated {

String since() default "";
boolean forRemoval() default false;

}

На этом примере видно, из чего состоит аннотация:

🔸Поля
Содержат доп. информацию. Если указать значение по умолчанию, поле становится необязательным:

@Deprecated(since="14") 
// forRemoval по умолчанию false

🔸Список внутри @Target показывает элементы, для которых работает аннотация.
В java 7 аннотации доступны для классов, методов, параметров, полей и переменных.

В java 8 аннотации действуют везде, где указан тип. Можно писать даже такое:

▫️new @Test Account()
▫️throws @Test IOException
▫️implements @Test Comparable<@Test
T>

Такие аннотации называются type annotations и используются в IDE и компиляторах для анализа и строгого контроля типов.

Аннотации нельзя ставить для имён переменных. Правильный ответ на вопрос перед постом - ошибка в 5 строке:
@Test String doubled
String @Test out

🔸@Retention определяет, когда доступна аннотация и как её можно использовать. Все виды подробно рассмотрим во второй части.

@Target и @Retention- это мета аннотации, то есть аннотации для аннотаций.

Какие ещё бывают мета-аннотации:

🔸@Documented - аннотация появится в JavaDoc
🔸@Inherited - наследуется подклассами
🔸@Repeatable (Java 8) - можно использовать несколько раз для одного элемента. Иногда такое приятнее читать, чем один массив:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")

Создать аннотацию легко, правильно применить - уже сложнее. С этим вопросом разберёмся во второй части.
1
Аннотации, часть 2: как использовать

Продолжим вчерашнюю тему. Рассмотрим Retention, и когда пригодится самодельная аннотация.

@Retention определяет, на каком этапе доступна аннотация:
🔹 SOURCE - аннотация видна только во время компиляции
🔹 CLASS - доступна также в байт-коде
🔹 RUNTIME - видна всегда, даже во время работы программы

Доступность выбирается исходя из цели. Поэтому перейдём к кейсам.

Что можно сделать через аннотации?

1️⃣ Объединить уже существующие.
Самый популярный и простой случай. Если в проекте несколько аннотаций часто идут вместе, объедините их в одну. Если у всех компонентов есть обработчики, то больше ничего делать не надо, всё заработает само.

Пример: @SpringBootApplication - это комбинация @Configuration, @EnableAutoConfiguration и @ComponentScan.

2️⃣ Генерация кода и файлов.
Происходит на этапе компиляции:
🔸 Отмечаем код аннотацией
🔸 Создаём класс-наследник от AbstractProcessor. Определяем, на какие аннотации реагировать
🔸 Вытаскиваем дополнительную информацию через .class

Deprecated d = Account.class. getAnnotation(Deprecated.class)

🔸 Делаем что-то полезное
🔸 Включаем процессор в компиляцию. В maven-compiler-plugin это секция annotationProcessors.

Для обработки подойдут аннотации с любой RetentionPolicy.

Что получаем:
Долгая компиляция
Специфичное тестирование. Пример
😐 Сложный и запутанный код
Не тратится время на старте приложений

Этот подход используется в библиотеке Lombok, микрофреймворках Quarkus и Micronaut, в Android фреймворке Dagger.
Библиотека Dekorate на основе аннотаций создаёт манифесты для Kubernetes и OpenShift.

3️⃣ Статический анализ.
Алгоритм такой же - создать наследник от AbstractProcessor, добавить в процесс компиляции.

Примеры: библиотека Google Error Prone ищет в коде ошибки, Hibernate Validator проверяет, что аннотации Hibernate корректно расставлены.

4️⃣ Работа с байт-кодом.
Редкий случай. В байт-коде остаются аннотации с RetentionPolicy.CLASS или RUNTIME.

5️⃣ Создать объекты и прокси-классы.
Доступно для аннотаций с RetentionPolicy.RUNTIME.

Основа работы многих фреймворков: Spring, Hibernate, Java EE. Под работу с аннотациями в рантайме заточены многие библиотеки: Reflections, Spring. Работать с ними удобно и приятно.

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

Теперь рассмотрим анти-кейсы, для чего аннотации НЕ нужны:

1️⃣ Отметить код для себя или команды.
Поставить аннотацию @RefactorASAP легко, но без дальнейших действий это бесполезно. Аннотации нужно обрабатывать, и делать это автоматически.

Чтобы запомнить место в коде, используйте TODO комментарии в Intellij IDEA.

2️⃣ Для бизнес-логики.
Аннотации легко добавить в обход ООП и основной логики. Так можно быстро решить проблему, но долгосрочно это неудачный вариант:

Нельзя контролировать процесс целиком
Сложно писать тесты
Сложно дебажить
Внезапные сайд-эффекты

Частая ситуация на Spring проектах: на старте запускаются десятки @PostConstruct. Если в процессе возникает ошибка, то найти и исправить её непросто.

Но вообще, чем меньше вы полагаетесь на аннотации, тем лучше.
👍31
Stream API: новые методы в Java 16

16 марта вышла java 16. Новые фичи входят во вторую превью стадию перед главным релизом 2021 - java 17 LTS.

Существующие классы тоже развиваются. В java 16 в Stream API появилось 4 новых метода, которые мы и рассмотрим в этом посте.

1️⃣ toList()

С 25 по 27 ноября была серия постов о коллекторах (часть 1, часть 2, часть 3). Там я писала, что вместо

collect(Collectors.toList())
было бы удобно писать просто
toList()

30 ноября разработчик Oracle добавил метод toList() в класс Stream. Вряд ли он читает этот канал, но совпадение интересное🙂

Новый метод не совсем равнозначный:
▫️Collectors.toList() возвращает экземпляр ArrayList.
▫️Новый метод toList() возвращает неизменяемый список.

2️⃣ mapMulti

Это оптизированный flatMap. Объясню суть на примере. Заказ - класс Order, товар - класс Item. Заказ состоит из нескольких товаров. Из списка заказов хотим получить список всех товаров.

orders.stream()
.flatMap(order->order.getItems().stream())
.toList();

flatMap переводит "список списков" в один список. Товары для каждого заказа превращаются в Stream, а метод flatMap объединяет эти стримы в один.

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

mapMulti устраняет этот недостаток:

orders.stream()
.<Item>mapMulti((order, consumer) ->
order.getItems().forEach(item -> consumer.accept(item))
).toList();

Что происходит:
mapMulti принимает на вход (order, consumer):
▪️order - элемент стрима, в нашем случае - заказ
▪️consumer - следующий этап в стриме. Наша задача - передать этому этапу все будущие элементы. Берём у заказа товары и для каждого вызываем consumer.accept(item).

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

<Item>mapMulti(…)

Когда использовать mapMulti?

Небольшое количество элементов в списках.
Например, много заказов с 1-2 товарами

Элементы легко получить без Stream API
Основная фишка mapMulti - нет промежуточных стримов. Если внутри метода создаётся стрим, то вся выгода сходит на нет.
order.getItems().stream()…

Есть три вариации метода:
🔸IntStream mapMultiToInt
🔸LongStream mapMultiToLong
🔸DoubleStream mapMultiToDouble

Для них выходной тип не указывается.
1
Intellij IDEA: как выучить шорткаты

В IDEA сотни горячих клавиш. С ними удобно работать, не надо тащить курсор через 2 монитора и бродить по контекстному меню. Есть только одна проблема - шорткаты сложно запомнить сразу.

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

1️⃣ Выучить топ-15

Статистика использования IDE находится в Help → Productivity Guide. Сортируем по колонке Used, получаем часто используемые команды. В описании указаны шорткаты. Запоминаем горячие клавиши для 10-15 действий, и продуктивность заметно растёт.

2️⃣ Плагин Key Promoter X

Установка: File → Settings → Plugins → Key Promoter X.

При действиях с мышкой в углу всплывает подсказка-шорткат.

Здесь работает правило 80/20: шорткаты для навигации, поиска и рефакторинга покрывают большинство ежедневных задач. Их легко выучить с помощью этих двух инструментов.
Intellij IDEA: Database View

Продолжая тему с БД, расскажу как можно работать с базой через Intellij IDEA.

Окошко открывается так: View → Tool Windows → Database

Подключить базу просто, самые необходимые функции есть:
▫️ Информация о таблицах, столбцах, индексах и т.д
▫️ Выполнить запрос
▫️ Выгрузить данные или метадату

Какие базы доступны:
Реляционные: Oracle, PostgreSQL, MySQL
Многие NoSQL: Mongo, Cassandra, Hive, ClickHouse, Vertica
Экзотичные: Exasol, Greenplum и Snowflake
Redis, Couchbase, HBase, CouchDB, Neo4j

Полный список баз на сегодня:
Лучшие посты за 3 месяца

Если что-то пропустили, рекомендую прочитать:

Java Core:
Исключения: checked и unchecked
Default методы: неудачный кейс
Будущее java: ближайшие 5-7 лет

Лонгрид про сборщики мусора
Лонгрид про коллекторы в Stream API

Intellij IDEA:
Как быстро редактировать код

Прочее:
Как найти работу без HeadHunter
Spring: статистика использования

Cпасибо, что читаете, ставите лайки и даёте обратную связь❤️
В прошлый вторник вышла java 16, последний пробный шар перед главным релизом года - java 17. Чего-то совсем нового там нет, поэтому на этой неделе поговорим о сериализации. Лонгрид будет полезен начинающим разработчикам.

⭐️ Часть 1: что такое сериализация, зачем она нужна, как сериализуются и десериализуются объекты
⭐️ Часть 2: serialVersionUID и проблемы сериализации в Java
⭐️ Часть 3: сериализация на практике
Вопрос
Есть класс Parent с полем parentValue. У него есть наследник - класс Child с полем childValue. Класс Child помечен интерфейсом Serializable

Какими будут поля экземпляра Child после десереализации?
(щёлкните на картинку, чтобы открыть код полностью)
👍2
Какими будут поля экземпляра Child после десереализации?
Anonymous Poll
10%
parentValue: 1 childValue: 50
16%
parentValue: 2 childValue: 50
19%
parentValue: 3 childValue: 55
52%
parentValue: 4 childValue: 50
4%
parentValue: 4 childValue: 55
Сериализация, часть 1: обзор

Сервисы редко существуют сами по себе, они активно обмениваются данными с окружающим миром и другими сервисами.

Сериализация превращает Java объект в набор байтов, который можно передать по сети или куда-нибудь записать. Также встречается под именами marshalling или encoding.

Десериализация восстанавливает Java объект из полученных байтов. Где-то этот процесс называется unmarshalling или decoding.

Пожелания:
1️⃣ Чтобы набор байтов занимал поменьше места. Чем короче сообщение, тем быстрее оно передаётся
2️⃣Минимум усилий со стороны программиста

Сериализация появилась в первой версии Java, и по сравнению с другими языками это была фантастика. JVM брала большую часть работы на себя. Байтовые сообщения получались компактными и быстро стали частью EJB, JMX, JMS и т.д

С тех пор механизм сериализации в java не менялся. Классы, экземпляры которых покидают JVM, должны реализовать интерфейс Serializable:

class UserRequest implements Serializable

У него нет обязательных методов, это интерфейс-маркер.

Интерфейс Externalizable даёт полный контроль над итоговым набором байтов. Хотите записать java объект в PDF или зашифровать данные - реализуйте методы Externalizable.

Как происходит сериализация Serializable классов:

1️⃣ Проверка полей
static и transient поля не участвуют в сериализации. Остальные поля должны быть либо Serializable, либо примитивами. Иначе разработчик получит NotSerializableException.

2️⃣ Объект превращается в байты
Для передачи данных обычно используется ObjectOutputStream, но часто он скрыт за фреймворком или библиотекой. Что туда пишет JVM:

🔸 Поля-заголовки
🔸 Информация о классе:
▪️ Имя класса
▪️ serialVersionUID
▪️ Количество полей
▪️ Информация по каждому полю:
▫️ Тип (имя класса или примитив)
▫️ Длина
▫️ Имя переменной
🔸 Информация про Serializable родительские классы в таком же формате
🔸 Значения переменных Serializable родительских классов
🔸 Значения переменных текущего класса. Если переменная - не примитив, то схема повторяется - записывается информация про класс и значения полей.

В примере перед постом класс Parent не реализует Serializable, поэтому parentValue не записывается в итоговый стрим, только childValue.

3️⃣ Набор байтов готов, можно отправлять.

Десериализация по шагам

Посмотрим на примере класса Parent и Child из примера выше.

1️⃣ Читаем из полученных байтов информацию о классе и о всех ближайших Serializable родителях.

2️⃣ Ищем ближайший НЕ Serializable родитель. В примере это класс Parent

3️⃣ Вызываем у класса Parent конструктор без параметров.
Тут проставляется parentValue = 2

4️⃣ Получаем экземпляр. Конструктор Child не используется, остальные поля проставляются внутренними механизмами JVM.

5️⃣ Чтение полей из потока байтов. В нашем примере передано только childValue. Записываем: childValue = 50;

Итого: в консоль выведется 2 и 50. Хотя изначально мы создавали объект с parentValue = 4, это поле не передаётся при сериализации, поэтому используется значение из конструктора Parent().

Как исправить ситуацию? Есть два варианта:
💊 Добавить классу Parent интерфейс Serializable
💊 Переопределить в классе Child методы writeObject и readObject. Они не определены в Serializable, но JVM найдёт их в процессе сериализации.

В writeObject задаётся, какие поля и в каком порядке запишутся в итоговый объект:

private void writeObject(…out) {
out.writeInt(parentValue);
out.writeInt(childValue);
}

В readObject указывается, какие поля и в каком порядке читать из байтового стрима:

private void readObject(…in){
int parentValue=in.readInt();
setParentValue(parentValue);
this.childValue=in.readInt();
}

В любом из вариантов десериализованный объект напечатает 4 и 50.

В следующем посте поговорим, зачем в сообщении нужен serialVersionUID, когда его задавать напрямую и менять, а также про недостатки Java сериализации.
👍4
Сериализация, часть 2: serialVersionUID

При использовании сериализации в класс рекомендуют добавить такое поле:

private static final long serialVersionUID = 27507467L;

В этом посте разберёмся, зачем это нужно, когда прописывать serialVersionUID и когда менять. В конце поговорим про недостатки сериализации в java.

Итак, в процессе сериализации 2 участника: отправитель и получатель. У каждого из них есть код класса Х. Отправитель сериализует экземпляр Х и отправляет по сети.

Из прошлого поста вы знаете, что в этом массиве байтов есть serialVersionUID. Получатель читает имя класса и первым делом сравнивает serialVersionUID из сообщения с serialVersionUID своего класса.

▫️ Если совпадают - начинается десериализация
▫️ Если нет - выбрасывается InvalidClassException

Когда serialVersionUID не указан в классе явно, JVM вычисляет его в рантайме на основе имени класса, интерфейсов, полей и методов. Добавили новый метод - serialVersionUID изменился. Поэтому рекомендуется зафиксировать serialVersionUID, даже если поля класса не меняются.

Другой вариант - когда класс эволюционирует и передаёт другой набор данных. Сервисы не всегда обновляются одновременно, поэтому в переходный период возникают две проблемы:

🔸 Как новому коду читать данные, созданные старым кодом? (backward compatibility)
🔸 Как старому коду читать данные, созданные новым кодом? (forward compatibility)

Приходится мириться с наличием старых версий и писать код соответственно:

1️⃣ Задать в классе serialVersionUID. Никогда не менять
2️⃣ Добавить методы readObject и writeObject и прописать порядок записи и чтения полей
3️⃣ Писать тесты на совместимость версий

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

Пара "имя класса-serialVersionUID" работает как фильтр - можно десериализовать набор байтов или нет. Когда serialVersionUID не задан в классе, он генерируется JVM. Для прямой и обратной совместимости serialVersionUID может быть любым, но постоянным. Методы writeObject и readObject задают чёткий порядок чтения/записи, но сильно разгуляться не получится.

Уже отсюда понятно, что на практике с сериализацией море проблем:

▪️ Разработка усложняется: всегда нужно иметь в виду forward/backward совместимость, набор доступных изменений сильно ограничен.

▪️ Нарушается инкапсуляция, так как private поля передаются по сети.

▪️ Ограниченные сценарии использования. Получатель и отправитель должны быть на java.

▪️ Небезопасно. Десериализация - сладкий пирожок для разных типов атак. В 2016 их было так много, что тот год на конференциях называли Java deserialization apocalypse year. В 2021 году уязвимости на основе сериализации встречаются даже в Intellij IDEA и Kubernetes.

Что с этим делать и как сериализация выглядит на практике - поговорим в третьей части.
👍3
Сериализация, часть 3: практика

На моей первой работе в проекте активно использовалась JVM сериализация. Java объекты передавались между сервисами и записывались в БД. Было сложно, зато теперь я вижу проблемы совместимости на раз-два. В прошлом посте поговорили о недостатках JVM сериализации. В этом посте обсудим, как их избежать.

Что говорят авторитетные источники:

Effective Java: нет никаких причин использовать java сериализацию в новых системах.

Brian Goetz, архитектор Java: когда меня спрашивают, о какой фиче я сожалею больше всего, то ответ прост - сериализация.

Java cериализация была отличным решением для условий 1996 года:
▫️ Медленный интернет
▫️ Небольшая память
▫️ Скудный набор языков и платформ

Сегодня это уже не актуально, и в 2021 ситуация такая:
▪️ Данные быстро меняются
▪️ Данных много
▪️ В системе одновременно существуют несколько версий данных
▪️ Быстрый интернет
▪️ Сервисы пишутся на разных языках и платформах

Данные передаются по сети в виде байтов, но используется стандартная структура сообщения, а не та, которую задаёт JVM. Такой подход снимает 90% проблем сериализации. Популярные форматы делятся на две группы:

1️⃣ Текстовые: JSON, CSV, XML

Легко читаются человеком
Библиотеки для любых платформ и языков
Избыточность. В JSON и XML названия полей занимают половину сообщения, а в CSV миллион запятых
Плохая поддержка типов. Поля классов, большие числа, даты - всё передаётся как строки

2️⃣ Бинарные

Данные пишутся максимально компактно. К этой группе относятся protobuf, Thrift, Avro, Parquet. Для каждого типа сообщений создаётся схема. В ней перечисляются поля, их размер, иногда порядковый номер. Для protobuf схема выглядит так:

message OrderRequest {
required int64 user_id = 1;
optional string address= 2;
repeated int64 item_id = 3;
}

Отправитель создаёт массив байтов опираясь на эту схему. Получатель считает данные по той же схеме.

Короткие сообщения. Вместо имён полей используются порядковые номера(protobuf, Thrift), либо данные просто идут подряд(Avro, Parquet).

Schema Registry

И для текстовых, и для бинарных форматов остаётся проблема прямой и обратной совместимости. Менять схему можно, но в ограниченных пределах. Если формат данных меняется часто, то поможет паттерн Schema Registry.

Это отдельный компонент, который хранит все версии схем данных и сопутствующую информацию:
🔹 ID схемы: id = 15
🔹 Название: subject = "orderRequest"
🔹 Версия: version = 3
🔹 Сама схема: schema = …

Отправитель формирует сообщение и передаёт его вместе с ID схемы. Получатель берёт схему из Schema registry и читает данные. 100% совместимости это не гарантирует, но заметно упрощает работу.

Резюме:

Сериализация в java была отличным решением в своё время. Я не стала подробно описывать методы и лучшие практики Serializable/Externalizable, т.к такая сериализация осталась только в дремучих легаси проектах. Даже на собеседованиях её редко спрашивают.

Сейчас чаще используются форматы, не привязанные к конкретному языку и платформе. Но проблемы совместимости не исчезают:
🔸 Backward compatibility: чтение старых данных на новых серверах
🔸 Forward compatibility: чтение новых данных на старых серверах

Эти проблемы решаются двумя способами:
🔹 Адаптировать лучшие практики из Serializable. Подход рабочий, но набор доступных изменений сильно ограничен.
🔹 Использовать схемы данных. Они доступны для JSON, XML, SOAP, protobuf, Avro и т.д. Для упрощения работы со схемами поможет паттерн Schema Registry.
👍7
Phaser: конференции и реальная жизнь

На конференции Joker 2020 было 3 доклада по теме многопоточности. Один из них - Thread Safety with Phaser, StampedLock and VarHandle. На примере Phaser хорошо виден разрыв между теорией и практикой. В этом посте расскажу, почему.

Паттерн Барьер помогает координировать потоки. Он блокирует один или несколько потоков, пока не наступит какое-то событие. JDK предлагает три реализации:
1️⃣ CountDownLatch
2️⃣ CyclicBarrier
3️⃣ Phaser

Последний - самый продвинутый:
🔸 Несколько сценариев работы
🔸 Методы мониторинга
🔸 Можно строить иерархичные структуры из нескольких Phaser
🔸 Иногда работает быстрее, чем CountDownLatch и CyclicBarrier
🔸 Обработка исключений

Класс Phaser часто встречается на воркшопах и advanced java курсах. Можно долго рассказывать про методы, рисовать схемы и многопоточно перемножать двумерные массивы.

Часто автор статьи или доклада держит фокус на инструменте:
Рассказываю про Phaser → Подбираю пример

На практике последовательность другая:
Вижу проблему → Ищу варианты → Выбираю подходящий

В такой цепочке у Phaser нет шансов. За пределами конференций и статей этот класс не используется.

Зачем тогда о нём говорить?

Чётко понимать, почему что-то НЕ работает, так же полезно, как и знать лучшие практики. Причиной может быть:
🔹 Плохой дизайн и неудобные методы
🔹 Неудачная реализация и проблемы производительности
🔹 Более подходящие инструменты

Чем плох Phaser?

Все реализации паттерна Барьер блокируют потоки, поэтому редко используются в нагруженных системах. Есть всего пара ситуаций, когда барьер - лучшее решение, но для их реализации достаточно CountDownLatch или CyclicBarrier. Phaser неплохо спроектирован, но слишком оторван от практических задач, это наглядный пример over engineering.
👍3
Как развиваться и строить свою карьеру

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

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

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

Советы джуниору

Ты устроился на первую работу. Молодец, до этого этапа не доходят большинство начинающих!

Твоя задача - влиться в текущий проект и адаптироваться к его технологиям. Даже если это чудовищное легаси.
🔸 Закрывай пробелы по java core
🔸 Учись писать тесты, работать с CI и взаимодействовать с командой
🔸 Повторяй за другими, пиши код по аналогии
🔸 Если что-то непонятно - сначала поищи ответ пару часов, если не нашёл - спроси
🔸 Записывай ответы, не спрашивай одно и то же

Сейчас сложно, потом будет легче!

Советы мидлу

Сейчас самое приятное время для работы и обучения. Ты хорошо справляешься с задачами проекта. Ответственности пока мало, и рядом всегда есть старшие товарищи. Зарплата выше среднего.

Но не задирай нос. Скорее всего большинство задач ты решаешь по аналогии с теми, что уже были. Чтобы развиваться, задай себе ряд вопросов:
Что будет, если я останусь на этом месте ещё год? А два?
Есть ли у меня пробелы в базе: java core, concurrency, паттерны GoF, SQL?
Что происходит на рынке? Актуальны ли мои знания?

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

Советы сеньору

У тебя есть опыт в определённых технологиях. Ты принимаешь много решений самостоятельно и несёшь за них ответственность. Здесь чётких рекомендаций нет, но пора подумать над вопросами:
Есть ли какие-то базовые знания, в которых я не уверен?
На этом этапе критически важно устранить все пробелы!
Что мне интересно? На каком проекте я хочу работать? Какие задачи решать? С какими людьми работать?
Что мне нужно, чтобы попасть на желаемый проект и комфортно там работать?

Твоя задача - не суета и страдания, а получать много денег и удовольствие от работы. Одновременно.
1