Как найти и починить in-memory пагинацию в Spring Data JPA
N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.
В чем суть.
Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.
Чем плохо:
❌ Запрос выполняется долго, тк по сети передается куча данных
❌ Забивается оперативная память. В худшем случае получаем OutOfMemoryError
Проблема возникает при сочетании предварительной загрузки связных сущностей и Pageable.
Простой пример. У постов есть комментарии со связью OneToMany:
Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.
Как найти места со скрытой пагинацией?
Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения
Как починить скрытую пагинацию?
Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре
Зачем Hibernate использует in-memory пагинацию?
При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.
Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности
Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.
Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:
Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.
Вся суть хибернейта🤌
По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚
N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.
В чем суть.
Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.
Чем плохо:
❌ Запрос выполняется долго, тк по сети передается куча данных
❌ Забивается оперативная память. В худшем случае получаем OutOfMemoryError
Проблема возникает при сочетании предварительной загрузки связных сущностей и Pageable.
Простой пример. У постов есть комментарии со связью OneToMany:
@Entity
public class Post {
@OneToMany
private List<Comment> comments;
}
Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
@EntityGraph
List<Post> findAllPosts(PageRequest.of(0, 5));
Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.
Как найти места со скрытой пагинацией?
Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true
🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения
Как починить скрытую пагинацию?
Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
spring.jpa.properties.hibernate.default_batch_fetch_size=50
Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре
Зачем Hibernate использует in-memory пагинацию?
При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.
Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности
Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.
Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:
Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.
Вся суть хибернейта🤌
По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚
🔥159👍71❤31👎5
Обычно при работе с Postgres используется пул соединений. Объясняется это тем, что установка соединения с БД - это долго и дорого. А почему?
Anonymous Poll
15%
Сервису и Postgres нужно договориться о протоколах, форматах, алгоритмах и тд
6%
Каждое соединение создает свою копию индексов для ускорения поиска
16%
Внутри сервиса выделяется область памяти для приема данных от БД
63%
Для каждого соединения стартует отдельный процесс в ОС
🔥79👍11❤2
База по Postgres
Сегодня расскажу про 3 важных сущности Postgres: соединения, буфер и WAL. Это та база, которая точно пригодится на практике.
💫Соединение (connection)💫
Чтобы пообщаться с БД, сервис устанавливает коннекшн с БД. Но что такое “соединение”, и зачем оно нужно?
База данных - обычное приложение, которое работает с внешними клиентами (нашими сервисами). Чтобы клиенты не мешали друг другу, нужно обеспечить должную изоляцию и безопасность данных.
В Postgres проблема решается просто - работа с каждым клиентом происходит в отдельном процессе. Переносим вопрос изоляции на операционную систему💅
Так делает не только постгрес. Если откроете 10 вкладок в Google Chrome, в диспетчере задач появятся 10 новых процессов.
Решение простое и надёжное, но минусы тоже есть:
🌚 Новый процесс долго запускается. В ОС происходит куча всего, чтобы обеспечить изоляцию памяти
🌚 Большой расход оперативки
Пул соединений нужен, чтобы не создавать процессы заново каждый раз и ограничить их количество.
💫Буфер💫
Также в оперативке создаётся область общей памяти, которая называется буфер. Там обитают данные, которые только что прочитаны с диска, и которые скоро отправятся на запись.
Почему апдейты не сразу пишутся на диск?
Запись на диск - медленная операция. Поэтому выполняется асинхронно, чтобы не заставлять пользователя ждать.
С буфером вы встретитесь при выполнении EXPLAIN ANALYZE. Строка
означает, что в буфер с диска прочитано 5000 страниц/блоков. Каждый блок 8 КБ, итого с диска считано 40 Мб данных.
Эти цифры часто нужны для анализа нагрузки.
💫WAL💫
Свежие обновления не сразу пишутся на диск, а некоторое время лежат только в оперативной памяти.
В случае сбоя данные потеряются. Чтобы этого не произошло, суть изменений записывается в конец специального файлика, который называется WAL (write ahead log).
Что, запись на диск? Это же медленно!!!1
Все верно, но сравните масштаб. В случае апдейта изменения встраиваются в огромную структуру, надо обновить индексы, плотнее укомплектовать данные, обновить статистику и ещё миллион вещей.
В случае WAL мы пишем минимум информации в конец файла. Это гораздо быстрее💫
Помимо повышения надёжности, WAL используется для некоторых видов репликации.
Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
Сегодня расскажу про 3 важных сущности Postgres: соединения, буфер и WAL. Это та база, которая точно пригодится на практике.
💫Соединение (connection)💫
Чтобы пообщаться с БД, сервис устанавливает коннекшн с БД. Но что такое “соединение”, и зачем оно нужно?
База данных - обычное приложение, которое работает с внешними клиентами (нашими сервисами). Чтобы клиенты не мешали друг другу, нужно обеспечить должную изоляцию и безопасность данных.
В Postgres проблема решается просто - работа с каждым клиентом происходит в отдельном процессе. Переносим вопрос изоляции на операционную систему💅
Так делает не только постгрес. Если откроете 10 вкладок в Google Chrome, в диспетчере задач появятся 10 новых процессов.
Решение простое и надёжное, но минусы тоже есть:
🌚 Новый процесс долго запускается. В ОС происходит куча всего, чтобы обеспечить изоляцию памяти
🌚 Большой расход оперативки
Пул соединений нужен, чтобы не создавать процессы заново каждый раз и ограничить их количество.
💫Буфер💫
Также в оперативке создаётся область общей памяти, которая называется буфер. Там обитают данные, которые только что прочитаны с диска, и которые скоро отправятся на запись.
Почему апдейты не сразу пишутся на диск?
Запись на диск - медленная операция. Поэтому выполняется асинхронно, чтобы не заставлять пользователя ждать.
С буфером вы встретитесь при выполнении EXPLAIN ANALYZE. Строка
Buffers: shared hit=32 read=5000
означает, что в буфер с диска прочитано 5000 страниц/блоков. Каждый блок 8 КБ, итого с диска считано 40 Мб данных.
Эти цифры часто нужны для анализа нагрузки.
💫WAL💫
Свежие обновления не сразу пишутся на диск, а некоторое время лежат только в оперативной памяти.
В случае сбоя данные потеряются. Чтобы этого не произошло, суть изменений записывается в конец специального файлика, который называется WAL (write ahead log).
Что, запись на диск? Это же медленно!!!1
Все верно, но сравните масштаб. В случае апдейта изменения встраиваются в огромную структуру, надо обновить индексы, плотнее укомплектовать данные, обновить статистику и ещё миллион вещей.
В случае WAL мы пишем минимум информации в конец файла. Это гораздо быстрее💫
Помимо повышения надёжности, WAL используется для некоторых видов репликации.
Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
🔥722👍55❤41
Брейкпойнты в IDEA
Иногда смотрю, как люди дебажат, и сердце кровью обливается💔
Большинство разработчиков знают только, как добавить и удалить брейкпойнт. Но у IDEA много фич для дебага, перечислю самые полезные:
1️⃣ Условия остановки
Если метод часто вызывается, или брейкпойнт стоит в цикле, не тратьте время на ожидание нужных значений:
⭐ Правый щелчок по брейкпойнту
⭐ Добавить в Condition условие остановки. Можно использовать все доступные переменные, объекты и методы
2️⃣ Посмотреть значения параметров в динамике
Вариант для котят: добавить в код System.out.println с нужным полем/выражением.
Вариант для тигров:
🐯 Зажать Shift и добавить брейкпойнт
🐯 Щёлкнуть галочку Evaluate and log
🐯 Вписать нужное выражение
Отладчик не будет останавливать выполнение, а запишет в консоль значение выражения. Незаменимая фича для отладки многопоточных приложений, кода сторонних библиотек и удалённого дебага.
3️⃣ Отключение брейкпойнта
Ненужный брейкпойнт можно не удалять, а отключить:
▪️Щёлкнуть колёсиком по брейкпойнту
ИЛИ
▪️Правый щёлчок по брейкпойнту → снять галочку с Enabled
4️⃣ Массовые сокращения
Когда в проекте много брейкпойнтов, IDE при дебаге немного тормозит. Чтобы удалить ненужные, открываем полный список:
▫️Правый клик по любому брейкпойнту
▫️Ссылка More
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
Обязательно попробуйте! Пусть дебаг проходит легко и приятно😊
Иногда смотрю, как люди дебажат, и сердце кровью обливается💔
Большинство разработчиков знают только, как добавить и удалить брейкпойнт. Но у IDEA много фич для дебага, перечислю самые полезные:
1️⃣ Условия остановки
Если метод часто вызывается, или брейкпойнт стоит в цикле, не тратьте время на ожидание нужных значений:
⭐ Правый щелчок по брейкпойнту
⭐ Добавить в Condition условие остановки. Можно использовать все доступные переменные, объекты и методы
2️⃣ Посмотреть значения параметров в динамике
Вариант для котят: добавить в код System.out.println с нужным полем/выражением.
Вариант для тигров:
🐯 Зажать Shift и добавить брейкпойнт
🐯 Щёлкнуть галочку Evaluate and log
🐯 Вписать нужное выражение
Отладчик не будет останавливать выполнение, а запишет в консоль значение выражения. Незаменимая фича для отладки многопоточных приложений, кода сторонних библиотек и удалённого дебага.
3️⃣ Отключение брейкпойнта
Ненужный брейкпойнт можно не удалять, а отключить:
▪️Щёлкнуть колёсиком по брейкпойнту
ИЛИ
▪️Правый щёлчок по брейкпойнту → снять галочку с Enabled
4️⃣ Массовые сокращения
Когда в проекте много брейкпойнтов, IDE при дебаге немного тормозит. Чтобы удалить ненужные, открываем полный список:
▫️Правый клик по любому брейкпойнту
▫️Ссылка More
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
Обязательно попробуйте! Пусть дебаг проходит легко и приятно😊
🔥389👍95❤52👎1
CRaC (Coordinated Restore at Checkpoint)
- свежая фича с большим потенциалом. Свежая настолько, что ее нет в базовой OpenJDK, только в отдельной сборке.
В основе CRaC лежит простая и интересная идея. Расскажу, в чем суть, и как это применимо к практике.
Как стартует типичный сервис в java вселенной:
✨ Запускается JVM: считывает файлики с классами, загружает их в оперативку и тд
✨ Запускается контекст спринга. Конфиги, бины, постпроцессоры, прокси классы
Общий запуск занимает несколько минут. В основном благодаря спрингу.
Какую схему предлагает CRaC:
▫️ Берём работающее приложение
▫️ Делаем снимок оперативки
▫️ Разворачиваем этот снимок на другой машине
▫️ Приложение стартует за пару секунд🥹
Но это в теории. А что на практике?
На деле пока не всё гладко. Если приложение сложнее HelloWorld, нас ждут сложности с БД соединениями, задачами по расписанию, безопасностью параметров, развертыванием и тд. Вот тут человек запускал нормальное приложение, и это стоило немалых трудов. Но в итоге оно стартовало на 90% быстрее🚀
Проблемы в целом не критичные. Большую часть может взять на себя Spring, если решит всерьез поддержать CRaC. Сейчас поддержка минимальная - можно сделать снимок сразу после создания бинов. Надо, конечно, что-то посерьезнее.
Работает CRaC только в Linux, в других ОС нет поддержки снэпшотов памяти. Но можно запустить сервис в докере на основе Linux. И здесь тоже хочется автоматизации. Сейчас собрать и запустить образ с CRaC - сложная консольная многоходовочка.
Несмотря на незрелость, я вижу у CRaC большие перспективы. При хорошей поддержке он серьезно повлияет на расстановку сил в джава мире.
Мнение субъективное, но опыт позволяет делать такие прогнозы. Ставьте огонечки, если надо рассказать подробнее 🔥
- свежая фича с большим потенциалом. Свежая настолько, что ее нет в базовой OpenJDK, только в отдельной сборке.
В основе CRaC лежит простая и интересная идея. Расскажу, в чем суть, и как это применимо к практике.
Как стартует типичный сервис в java вселенной:
✨ Запускается JVM: считывает файлики с классами, загружает их в оперативку и тд
✨ Запускается контекст спринга. Конфиги, бины, постпроцессоры, прокси классы
Общий запуск занимает несколько минут. В основном благодаря спрингу.
Какую схему предлагает CRaC:
▫️ Берём работающее приложение
▫️ Делаем снимок оперативки
▫️ Разворачиваем этот снимок на другой машине
▫️ Приложение стартует за пару секунд🥹
Но это в теории. А что на практике?
На деле пока не всё гладко. Если приложение сложнее HelloWorld, нас ждут сложности с БД соединениями, задачами по расписанию, безопасностью параметров, развертыванием и тд. Вот тут человек запускал нормальное приложение, и это стоило немалых трудов. Но в итоге оно стартовало на 90% быстрее🚀
Проблемы в целом не критичные. Большую часть может взять на себя Spring, если решит всерьез поддержать CRaC. Сейчас поддержка минимальная - можно сделать снимок сразу после создания бинов. Надо, конечно, что-то посерьезнее.
Работает CRaC только в Linux, в других ОС нет поддержки снэпшотов памяти. Но можно запустить сервис в докере на основе Linux. И здесь тоже хочется автоматизации. Сейчас собрать и запустить образ с CRaC - сложная консольная многоходовочка.
Несмотря на незрелость, я вижу у CRaC большие перспективы. При хорошей поддержке он серьезно повлияет на расстановку сил в джава мире.
Мнение субъективное, но опыт позволяет делать такие прогнозы. Ставьте огонечки, если надо рассказать подробнее 🔥
🔥346👍40❤21👎4
Прогноз на 2030 год
Предсказывать будущее - вещь неблагодарная (особенно в России), но иногда сложно устоять. Прошлый пост искрится от огоньков (спасибо❤️), поэтому поделюсь своим видением.
У каждой технологии в сердечке есть какая-то идея. Свой оригинальный способ решить задачу или проблему. Если появится решение кардинально проще, быстрее или дешевле, со временем технология отпадет за ненадобностью.
Поэтому делаю прогноз - если Spring сделает удобную поддержку CRaC (создание и загрузка снэпшотов оперативки), фреймворки типа Micronaut и Quarkus станут не нужны.
Объясню, почему.
Spring - прекрасный фреймворк, но его приложения стартуют чудовищно долго. Основная магия работает через рефлекшн в момент запуска.
Это архитектурная особенность, от нее никуда не уйти.
Несколько лет назад появилась альтернатива - проворачивать ту же логику в момент компиляции. Такой подход используют фреймворки Quarkus и Micronaut.
Да, не такие мощные и удобные как Spring, зато приложение стартует в разы быстрее. Многим это важно, поэтому популярность Micronaut и Quarkus с каждым годом растет. Сервисы пишутся, люди работают.
Представим, что Spring и Docker серьезно вложились в поддержку CRaC. Снэпшоты создаются и развертываются одной кнопкой.
Посмотрите на картинку внизу👇Это время запуска приложений с CRaC для Spring Boot, Micronaut и Quarkus. Время везде одинаковое.
Micronaut и Quarkus теряют свое конкурентное преимущество, проигрывают спрингу и забрасываются. Недавно написанные сервисы становятся легаси. Spring матереет, и ещё больше укрепляет монополию.
Ситуация не уникальна. Сейчас в джава вселенной происходит очень похожий процесс с виртуальными потоками.
В чем суть:
Традиционный сервер обрабатывает каждый запрос в отдельном потоке. Этим ограничивается количество запросов в работе. Даже если сервер не загружен на 100%, он физически неспособен взять больше работы.
Реактивный подход предлагает альтернативу. Да, код писать не так удобно. Да, приходиться решать кучу проблем. Зато пропускная способность улетает в космос🚀
Сервисы пишутся, люди работают.
А потом появились виртуальные потоки и решили исходную проблему традиционного сервера. Реактивный подход теряет смысл, традиционная многопоточность укрепляет позиции.
Прекрасный момент, чтобы напомнить про
⭐ Лучший бесплатный курс про основы многопоточки⭐
🔥 Самый полный и практический курс по конкарренси🔥
Какой вывод можно сделать из ситуации со спрингом и виртуальными потоками? Все ли проблемы со временем сами решатся?
Увы, но это лотерея. Новые решения могут появиться, а могут и нет. Хорошая идея может выстрелить, а может споткнуться о плохую реализацию.
В любом случае, наблюдать за этим очень интересно😊 И вернёмся к посту через 5 лет, посмотрим, сбудется ли прогноз!
Предсказывать будущее - вещь неблагодарная (особенно в России), но иногда сложно устоять. Прошлый пост искрится от огоньков (спасибо❤️), поэтому поделюсь своим видением.
У каждой технологии в сердечке есть какая-то идея. Свой оригинальный способ решить задачу или проблему. Если появится решение кардинально проще, быстрее или дешевле, со временем технология отпадет за ненадобностью.
Поэтому делаю прогноз - если Spring сделает удобную поддержку CRaC (создание и загрузка снэпшотов оперативки), фреймворки типа Micronaut и Quarkus станут не нужны.
Объясню, почему.
Spring - прекрасный фреймворк, но его приложения стартуют чудовищно долго. Основная магия работает через рефлекшн в момент запуска.
Это архитектурная особенность, от нее никуда не уйти.
Несколько лет назад появилась альтернатива - проворачивать ту же логику в момент компиляции. Такой подход используют фреймворки Quarkus и Micronaut.
Да, не такие мощные и удобные как Spring, зато приложение стартует в разы быстрее. Многим это важно, поэтому популярность Micronaut и Quarkus с каждым годом растет. Сервисы пишутся, люди работают.
Представим, что Spring и Docker серьезно вложились в поддержку CRaC. Снэпшоты создаются и развертываются одной кнопкой.
Посмотрите на картинку внизу👇Это время запуска приложений с CRaC для Spring Boot, Micronaut и Quarkus. Время везде одинаковое.
Micronaut и Quarkus теряют свое конкурентное преимущество, проигрывают спрингу и забрасываются. Недавно написанные сервисы становятся легаси. Spring матереет, и ещё больше укрепляет монополию.
Ситуация не уникальна. Сейчас в джава вселенной происходит очень похожий процесс с виртуальными потоками.
В чем суть:
Традиционный сервер обрабатывает каждый запрос в отдельном потоке. Этим ограничивается количество запросов в работе. Даже если сервер не загружен на 100%, он физически неспособен взять больше работы.
Реактивный подход предлагает альтернативу. Да, код писать не так удобно. Да, приходиться решать кучу проблем. Зато пропускная способность улетает в космос🚀
Сервисы пишутся, люди работают.
А потом появились виртуальные потоки и решили исходную проблему традиционного сервера. Реактивный подход теряет смысл, традиционная многопоточность укрепляет позиции.
Прекрасный момент, чтобы напомнить про
⭐ Лучший бесплатный курс про основы многопоточки⭐
🔥 Самый полный и практический курс по конкарренси🔥
Какой вывод можно сделать из ситуации со спрингом и виртуальными потоками? Все ли проблемы со временем сами решатся?
Увы, но это лотерея. Новые решения могут появиться, а могут и нет. Хорошая идея может выстрелить, а может споткнуться о плохую реализацию.
В любом случае, наблюдать за этим очень интересно😊 И вернёмся к посту через 5 лет, посмотрим, сбудется ли прогноз!
🔥162👍64❤28👎11
Как изменится результат выполнения кода выше, если заменить collect(toList()) на toList()?
Anonymous Poll
4%
В списке providers не будет элементов из special
4%
В списке providers изменится порядок элементов
13%
Возникнет ошибка компиляции
42%
Возникнет ошибка в рантайме
37%
Ничего не изменится
👍24🔥15❤6
Коллекторы и тесты
Сразу разберём вопрос выше. Можно ли безопасно заменить collect(toList()) на toList()?
Ответ: нет, в коде выше получим ошибку в рантайме:
❤️ collect(toList()) возвращает ArrayList, который с радостью принимает новые элементы.
💔 toList() возвращает неизменяемый список. Вызов addAll выбросит исключение.
Это очевидная ошибка JDK. Коллекторы - часть Stream API. В рамках функционального стиля логично возвращать неизменяемую коллекцию.
Плюс ошибка на уровне проектирования - неизменяемые коллекции должны иметь интерфейс без методов add/set/remove. Пользователь не должен запоминать эти нюансы и получать внезапные ошибки в рантайме.
Побухтеть на JDK - дело святое, но сфокусируемся на другом. Для начала опишу историю целиком:
Человек поправил toList в коде фичи А. Там не было addAll, и все отлично работало. Затем человек сделал автозамену по всему сервису. Изменение с виду абсолютно безобидное, подвоха никто не ждал.
К счастью, код был хорошо покрыт тестами. Некоторые упали, отсюда мы и узнали эту милую особенность JDK💅
Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд.
Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом.
Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️
Сразу разберём вопрос выше. Можно ли безопасно заменить collect(toList()) на toList()?
Ответ: нет, в коде выше получим ошибку в рантайме:
❤️ collect(toList()) возвращает ArrayList, который с радостью принимает новые элементы.
💔 toList() возвращает неизменяемый список. Вызов addAll выбросит исключение.
Это очевидная ошибка JDK. Коллекторы - часть Stream API. В рамках функционального стиля логично возвращать неизменяемую коллекцию.
Плюс ошибка на уровне проектирования - неизменяемые коллекции должны иметь интерфейс без методов add/set/remove. Пользователь не должен запоминать эти нюансы и получать внезапные ошибки в рантайме.
Побухтеть на JDK - дело святое, но сфокусируемся на другом. Для начала опишу историю целиком:
Человек поправил toList в коде фичи А. Там не было addAll, и все отлично работало. Затем человек сделал автозамену по всему сервису. Изменение с виду абсолютно безобидное, подвоха никто не ждал.
К счастью, код был хорошо покрыт тестами. Некоторые упали, отсюда мы и узнали эту милую особенность JDK💅
Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд.
Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом.
Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️
👍236🔥80❤30👎6
Что будет в консоли при выполнении кода выше?
Anonymous Poll
22%
3
14%
4
25%
Ошибка компиляции
39%
Ошибка в рантайме
👍33
Загадка Set.of🔮
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
Set.of(1, 2, null)получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
// 8
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
// 9
Set<String>set = Set.of(“a“, “b“);
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
Set<Long> adminIds = Set.of(user1.getId(), user2.getId());
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
🔥248👍65❤29👎3
Почему в Set.of нельзя добавить дубликаты и null?
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
🔥218👍65❤35👎10
Сколько операций на запись может одновременно проводить ConcurrentHashMap?
Anonymous Poll
13%
1
7%
16
11%
Количество бакетов / 16
55%
Количество бакетов
3%
Количество бакетов * 2
12%
Неограниченное количество
👍42
Необычный вопрос на собеседовании
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
public class MapController {
private final Map<String, Object> map = new ConcurrentHashMap<>();
@PostMapping
public void putValue(@RequestParam String k, @RequestParam Object v) {
map.put(k, v);
}
@GetMapping
public Object getValue(@RequestParam String k) {
return map.get(k);
}
}Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
🔥279👍77❤59👎11
Почему однопоточный Redis такой быстрый?
В прошлом посте предложила вам задачку: сравнить Redis и велосипедик на основе ConcurrentHashMap + Spring MVC.
ConcurrentHashMap — многопоточный, и вроде должен быть лучше. Но именно однопоточный Redis является базовым выбором для кэша.
Как однопоточный Redis справляется с нагрузкой?
Секрет в том, как он работает с запросами. Есть 2 основные модели:
🌊 Каждый запрос обрабатывается в своем потоке (thread per request).
Такая модель используется, когда мы подключаем Spring MVC. Наш велосипедик тоже на ней работает.
У каждого потока свой стэк, переменные изолированы. Код легко писать, читать и дебажить. Идеальный вариант для сложных энтерпрайзных задач!
Но есть недостаток - число запросов в работе ограничено числом потоков в ОС. Обычно это несколько тысяч.
Из-за этой модели наш велосипед и проигрывает:
😒 Миллионы запросов просто не дойдут до ConcurrentHashMap, максимум несколько тысяч.
😒 Прочитать и записать в мэп - простые операции. Отправлять таких малышей в отдельный поток - как забивать краном гвозди. Очень большие накладные расходы на каждый запрос.
Redis использует другую модель:
🏃 EventLoop - малое число потоков бешено переключаются между запросами. В работу можно взять миллионы запросов!
Такая схема используется в реактивных серверах типа Netty, поддерживает многопоточность в JS и питоне.
Поэтому Redis и побеждает наш велосипед: возни с потоками нет, ограничений на запросы нет. Вся мощь процессора уходит на полезную работу, поэтому даже один поток справляется с большим объемом задач.
❓ Можно ли взять лучшее из двух миров? Использовать многопоточность вместе с EventLoop?
Можно! Один поток Redis не использует все доступные ядра процессора, поэтому добавить десяток потоков - вполне рабочая идея.
Такую схему используют KeyDB и DragonflyDB. На сайте публикуют бенчмарки, где они обходят Redis в 5-25 раз. 25 раз звучит слишком мощно, но про 5-10 раз можно верить.
❓ Почему чаще используется Redis, а не более быстрые альтернативы?
Потому что Redis появился в 2009, используется на сотнях проектов и закрепился в сознании как базовое решение для кэша. Подводные камни известны, инфраструктура налажена, куча статей и докладов.
KeyDB и DragonflyDB - свежие БД пирожки. Один вышел в 19 году, другой в 22. На конференциях особо не светились, громких кейсов внедрения пока нет.
Энтерпрайз мир тяжело принимает новые технологии. Плюс не всегда нужно лучшее решение, иногда достаточно хорошего😊
В прошлом посте предложила вам задачку: сравнить Redis и велосипедик на основе ConcurrentHashMap + Spring MVC.
ConcurrentHashMap — многопоточный, и вроде должен быть лучше. Но именно однопоточный Redis является базовым выбором для кэша.
Как однопоточный Redis справляется с нагрузкой?
Секрет в том, как он работает с запросами. Есть 2 основные модели:
🌊 Каждый запрос обрабатывается в своем потоке (thread per request).
Такая модель используется, когда мы подключаем Spring MVC. Наш велосипедик тоже на ней работает.
У каждого потока свой стэк, переменные изолированы. Код легко писать, читать и дебажить. Идеальный вариант для сложных энтерпрайзных задач!
Но есть недостаток - число запросов в работе ограничено числом потоков в ОС. Обычно это несколько тысяч.
Из-за этой модели наш велосипед и проигрывает:
😒 Миллионы запросов просто не дойдут до ConcurrentHashMap, максимум несколько тысяч.
😒 Прочитать и записать в мэп - простые операции. Отправлять таких малышей в отдельный поток - как забивать краном гвозди. Очень большие накладные расходы на каждый запрос.
Redis использует другую модель:
🏃 EventLoop - малое число потоков бешено переключаются между запросами. В работу можно взять миллионы запросов!
Такая схема используется в реактивных серверах типа Netty, поддерживает многопоточность в JS и питоне.
Поэтому Redis и побеждает наш велосипед: возни с потоками нет, ограничений на запросы нет. Вся мощь процессора уходит на полезную работу, поэтому даже один поток справляется с большим объемом задач.
❓ Можно ли взять лучшее из двух миров? Использовать многопоточность вместе с EventLoop?
Можно! Один поток Redis не использует все доступные ядра процессора, поэтому добавить десяток потоков - вполне рабочая идея.
Такую схему используют KeyDB и DragonflyDB. На сайте публикуют бенчмарки, где они обходят Redis в 5-25 раз. 25 раз звучит слишком мощно, но про 5-10 раз можно верить.
❓ Почему чаще используется Redis, а не более быстрые альтернативы?
Потому что Redis появился в 2009, используется на сотнях проектов и закрепился в сознании как базовое решение для кэша. Подводные камни известны, инфраструктура налажена, куча статей и докладов.
KeyDB и DragonflyDB - свежие БД пирожки. Один вышел в 19 году, другой в 22. На конференциях особо не светились, громких кейсов внедрения пока нет.
Энтерпрайз мир тяжело принимает новые технологии. Плюс не всегда нужно лучшее решение, иногда достаточно хорошего😊
🔥234👍70❤23👎8
⚡Требуется Java эксперт⚡
Последнее время плотно занимаюсь новым проектом. Цель проста - помочь джавистам профессионально расти и быть на коне в текущих сложных условиях.
Планы амбициозные, но на первом этапе буду работать над ультра востребованной темой - подготовке к собеседованиям. Хочу сделать этот процесс проще, лучше и приятнее. Заодно протестирую несколько концепций. Пойму, в ту ли сторону я смотрю и находит ли это отклик.
Для реализации мне нужны помощники, поэтому открываю вакансию: Java эксперт. Ищу людей, которые
⭐ Знают и любят джава разработку
⭐ Работали минимум на 3 разных проектах и видели всякое
⭐ Критически мыслят, видят логические несостыковки
⭐ Умеют работать с первоисточниками и проверять информацию. Не боятся простыни текста на английском
⭐ Большой плюс, если вы часто проводите собеседования и видите, где у людей случаются затыки
Что нужно делать: писать статьи по моему ТЗ. Сначала проработаем популярные темы типа HashMap, ACID, N+1, потом углубимся в другие нужные вещи.
Это будет серьезный умственный труд. Естественно, оплачиваемый.
Как присоединиться:
▫️ Заполнить анкету с небольшим тех.скринингом
▫️ Если все ок, я пришлю вам на почту оплачиваемое тестовое задание
Если вам интересно в этом поучаствовать, заполняйте анкету, не стесняйтесь. Если у вас есть знакомый, который подходит под описание, пришлите ему этот пост❤️
Последнее время плотно занимаюсь новым проектом. Цель проста - помочь джавистам профессионально расти и быть на коне в текущих сложных условиях.
Планы амбициозные, но на первом этапе буду работать над ультра востребованной темой - подготовке к собеседованиям. Хочу сделать этот процесс проще, лучше и приятнее. Заодно протестирую несколько концепций. Пойму, в ту ли сторону я смотрю и находит ли это отклик.
Для реализации мне нужны помощники, поэтому открываю вакансию: Java эксперт. Ищу людей, которые
⭐ Знают и любят джава разработку
⭐ Работали минимум на 3 разных проектах и видели всякое
⭐ Критически мыслят, видят логические несостыковки
⭐ Умеют работать с первоисточниками и проверять информацию. Не боятся простыни текста на английском
⭐ Большой плюс, если вы часто проводите собеседования и видите, где у людей случаются затыки
Что нужно делать: писать статьи по моему ТЗ. Сначала проработаем популярные темы типа HashMap, ACID, N+1, потом углубимся в другие нужные вещи.
Это будет серьезный умственный труд. Естественно, оплачиваемый.
Как присоединиться:
▫️ Заполнить анкету с небольшим тех.скринингом
▫️ Если все ок, я пришлю вам на почту оплачиваемое тестовое задание
Если вам интересно в этом поучаствовать, заполняйте анкету, не стесняйтесь. Если у вас есть знакомый, который подходит под описание, пришлите ему этот пост❤️
🔥81👍28❤22👎19
Самая унылая тема на собеседовании это
Anonymous Poll
30%
SOLID
27%
Методы Object
13%
Интерфейс против абстрактного класса
14%
ArrayList против LinkedList
17%
Пулы строк и Integer с задачками на ==
❤14🔥5👍3👎2
Принципы SOLID и групповой транс
Недавно слушала записи собеседований и обратила внимание на интересный феномен. Связан он, как вы уже догадались, с обсуждением SOLID.
Типичный диалог на собеседовании выглядит так:
👨💼: Расскажите про SOLID
👱♂️: [расшифровка каждой буквы]
👨💼: Используете в работе?
👱♂️: Конечно!
Хорошо, если на этом все заканчивается. Но бывает и продолжение: "расскажите принципы своими словами". На этом месте даже опытные и умные люди впадают в транс. Например, кандидат описывает принцип Open-closed:
👱♂️: Чтобы добавить новый метод, нужно не менять существующий класс, а создать новый
👨💼 : Угу, давайте дальше
Ни кандидат, ни собеседующий не создают новый класс для каждого метода. Почему один так отвечает, а другой принимает такой ответ — загадка.
Dependency Inversion часто объясняют как "нужно всё делать через интерфейс".
Это считается реализацией DI? Чем отличается от инкапсуляции? Для каждого класса нужно создавать интерфейс? Работа с любым классом напрямую — это нарушение принципа? Почему в названии есть инвершн, что инвертируется-то?
Вопросы здравые, но люди в трансе:) У других абстрактных тем вроде принципов ООП или паттернов такого эффекта нет.
SOLID — самая унылая тема на собеседовании. Формальная, абстрактная и скучная для всех сторон. Собеседование длится всего час-полтора, нет никакого смысла тратить время на ритуальный обмен фразами.
❓ Применяются ли принципы SOLID на практике?
▫️ Single Responsibility - да. Что неудивительно, это самый понятный и простой принцип.
▫️ Open-closed, Liskov и Interface Segregation тесно связаны с наследованием и сложными иерархиями классов. Для бизнес логики большинства систем это не очень актуально. Там максимум интерфейс и несколько реализаций.
▫️ Dependency Inversion - самый непонятный и недооцененный принцип. За 10+ лет в разработке я встречала мало людей, которые его поняли. Это не просто "делаем всё через интерфейс". Можно реализовать его даже без интерфейсов, суть вообще в другом.
К четвергу напишу пост и подробно объясню🔥
Недавно слушала записи собеседований и обратила внимание на интересный феномен. Связан он, как вы уже догадались, с обсуждением SOLID.
Типичный диалог на собеседовании выглядит так:
👨💼: Расскажите про SOLID
👱♂️: [расшифровка каждой буквы]
👨💼: Используете в работе?
👱♂️: Конечно!
Хорошо, если на этом все заканчивается. Но бывает и продолжение: "расскажите принципы своими словами". На этом месте даже опытные и умные люди впадают в транс. Например, кандидат описывает принцип Open-closed:
👱♂️: Чтобы добавить новый метод, нужно не менять существующий класс, а создать новый
👨💼 : Угу, давайте дальше
Ни кандидат, ни собеседующий не создают новый класс для каждого метода. Почему один так отвечает, а другой принимает такой ответ — загадка.
Dependency Inversion часто объясняют как "нужно всё делать через интерфейс".
Map<String, String> map = new HashMap<>();
Это считается реализацией DI? Чем отличается от инкапсуляции? Для каждого класса нужно создавать интерфейс? Работа с любым классом напрямую — это нарушение принципа? Почему в названии есть инвершн, что инвертируется-то?
Вопросы здравые, но люди в трансе:) У других абстрактных тем вроде принципов ООП или паттернов такого эффекта нет.
SOLID — самая унылая тема на собеседовании. Формальная, абстрактная и скучная для всех сторон. Собеседование длится всего час-полтора, нет никакого смысла тратить время на ритуальный обмен фразами.
❓ Применяются ли принципы SOLID на практике?
▫️ Single Responsibility - да. Что неудивительно, это самый понятный и простой принцип.
▫️ Open-closed, Liskov и Interface Segregation тесно связаны с наследованием и сложными иерархиями классов. Для бизнес логики большинства систем это не очень актуально. Там максимум интерфейс и несколько реализаций.
▫️ Dependency Inversion - самый непонятный и недооцененный принцип. За 10+ лет в разработке я встречала мало людей, которые его поняли. Это не просто "делаем всё через интерфейс". Можно реализовать его даже без интерфейсов, суть вообще в другом.
К четвергу напишу пост и подробно объясню🔥
🔥369👍65❤26👎17
Dependency Inversion
Сегодня расскажу, в чем суть принципа DI из SOLID, и почему с ним так много проблем. Вспомним формулировку:
✍️ Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
✍️ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Ставь огонёк, если даже не вчитывался в этот набор слов😊
Формулировка и правда полный отстой. Первая часть про какие-то модули, зависящие от каких-то абстракций. Вторая напоминает инкапсуляцию.
Роберт Мартин, он же дядюшка Боб, в целом молодец, но конкретно здесь плохо донёс свою идею. В оригинальной статье всё очень сумбурно. Интерфейсы добавляются на каждом шагу и отвлекают от сути dependency inversion. Ничего удивительного, что большинство людей не поняли, о чём речь.
Дядюшка Боб не смог, тетя Диана объяснит🤌
Все очень просто. У каждого класса есть своя область ответственности, single responsibility:
🔴 Кнопку нажимают, и она отправляет сигнал об этом. Ничего больше
💡 Лампа включается и выключается, когда ей говорят. Ничего больше
💏 Взаимодействие этих сущностей должно быть описано отдельно
Где здесь инверсия:
В нашем мире все описывается последовательно, причина -> следствие. Нажали кнопку -> включилась лампа. Перекладывая на код, в классе Кнопка будет поле Лампа:
В мире с Dependency inversion Кнопка и Лампа ничего не знают друг о друге, логика взаимодействия описана в классе Электросхема. Она ловит сигналы от Кнопки и отправляет команды Лампе:
Если у лампы появится новый режим, разбираться с этим будет электросхема, а не кнопка. Если кнопка поменяется на датчик движения, обработка поменяется только в электросхеме.
Интерфейсы можно добавить, чтобы жонглировать реализациями, но это не главное. Dependency inversion говорит о том, что логика взаимодействия компонентов должна находиться НЕ в самих компонентах.
Кстати, если перейти с уровня классов на уровень контроллеры/сервисы, получится Clean architecture. В основе та же идея: компоненты отдельно, взаимодействие отдельно.
Всё очень просто❤️
Сегодня расскажу, в чем суть принципа DI из SOLID, и почему с ним так много проблем. Вспомним формулировку:
✍️ Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
✍️ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Ставь огонёк, если даже не вчитывался в этот набор слов😊
Формулировка и правда полный отстой. Первая часть про какие-то модули, зависящие от каких-то абстракций. Вторая напоминает инкапсуляцию.
Роберт Мартин, он же дядюшка Боб, в целом молодец, но конкретно здесь плохо донёс свою идею. В оригинальной статье всё очень сумбурно. Интерфейсы добавляются на каждом шагу и отвлекают от сути dependency inversion. Ничего удивительного, что большинство людей не поняли, о чём речь.
Дядюшка Боб не смог, тетя Диана объяснит🤌
Все очень просто. У каждого класса есть своя область ответственности, single responsibility:
🔴 Кнопку нажимают, и она отправляет сигнал об этом. Ничего больше
💡 Лампа включается и выключается, когда ей говорят. Ничего больше
💏 Взаимодействие этих сущностей должно быть описано отдельно
Где здесь инверсия:
В нашем мире все описывается последовательно, причина -> следствие. Нажали кнопку -> включилась лампа. Перекладывая на код, в классе Кнопка будет поле Лампа:
public class Button
private Lamp lamp;
public void push() {
// включить/выключить лампу
}
}
В мире с Dependency inversion Кнопка и Лампа ничего не знают друг о друге, логика взаимодействия описана в классе Электросхема. Она ловит сигналы от Кнопки и отправляет команды Лампе:
public class Circuit {
private Button button;
private Lamp lamp;
public void process() {
// получить сигнал от кнопки
// сказать лампе включиться / выключиться
}
}Если у лампы появится новый режим, разбираться с этим будет электросхема, а не кнопка. Если кнопка поменяется на датчик движения, обработка поменяется только в электросхеме.
Интерфейсы можно добавить, чтобы жонглировать реализациями, но это не главное. Dependency inversion говорит о том, что логика взаимодействия компонентов должна находиться НЕ в самих компонентах.
Кстати, если перейти с уровня классов на уровень контроллеры/сервисы, получится Clean architecture. В основе та же идея: компоненты отдельно, взаимодействие отдельно.
Всё очень просто❤️
🔥301❤139👍78👎47