Всем привет!
В продолжение темы функциональности IDEA и подготовки кода к code review, см. https://t.me/javaKotlinDevOps/148
Некоторые проверки и исправления можно подвесить на одно из двух событий:
1) на сохранение файла: Alt-Alt - Actions on Save. Рекомендую включить Reformat code и Optimize import.
2) перед commit на Git сервер - открыть окно Commit (Ctrl-K) и нажать там шестеренку. Рекомендую включить Analyze code, Check TODO и если выполнение тестов занимает приемлемое время - то еще и прогон тестов.
Легко заметить, что набор опций в обоих случаях похож, но на сохранении можно включить только те, где фиксы применяются без участия человека. В частности и Cleanup, и Analyze выполняют правила из набора инспекций (Inspections), только в первом случае включаются только те правила, где есть quick fixes, которые можно применить автоматически.
Насчет Cleanup - IMHO его вполне можно включить на commit, главное перепроверить набор активных правил: Shift-Shift - Inspections, а там включить фильтр Cleanup only. К слову - там еще есть профили с набором правил, можно добавить свой.
И еще важный момент - инспекции из IDEA можно запустить из командной строки, и т.об. включить в CI процесс: https://www.jetbrains.com/help/idea/command-line-code-inspector.html
Они частично повторяют проверки SonarQube, но не идентичны им.
#idea #code_review #clean_code #git
В продолжение темы функциональности IDEA и подготовки кода к code review, см. https://t.me/javaKotlinDevOps/148
Некоторые проверки и исправления можно подвесить на одно из двух событий:
1) на сохранение файла: Alt-Alt - Actions on Save. Рекомендую включить Reformat code и Optimize import.
2) перед commit на Git сервер - открыть окно Commit (Ctrl-K) и нажать там шестеренку. Рекомендую включить Analyze code, Check TODO и если выполнение тестов занимает приемлемое время - то еще и прогон тестов.
Легко заметить, что набор опций в обоих случаях похож, но на сохранении можно включить только те, где фиксы применяются без участия человека. В частности и Cleanup, и Analyze выполняют правила из набора инспекций (Inspections), только в первом случае включаются только те правила, где есть quick fixes, которые можно применить автоматически.
Насчет Cleanup - IMHO его вполне можно включить на commit, главное перепроверить набор активных правил: Shift-Shift - Inspections, а там включить фильтр Cleanup only. К слову - там еще есть профили с набором правил, можно добавить свой.
И еще важный момент - инспекции из IDEA можно запустить из командной строки, и т.об. включить в CI процесс: https://www.jetbrains.com/help/idea/command-line-code-inspector.html
Они частично повторяют проверки SonarQube, но не идентичны им.
#idea #code_review #clean_code #git
Telegram
(java || kotlin) && devOps
Всем привет!
Теперь перейдем к рекомендациям для авторов Pull Request (PR), они же Merge Request.
1) не нужно тратить время ревьювера на то, что могут сделать роботы) Я про чистку import и форматирование кода. Рекомендую поставить эти действия на автовыполнение…
Теперь перейдем к рекомендациям для авторов Pull Request (PR), они же Merge Request.
1) не нужно тратить время ревьювера на то, что могут сделать роботы) Я про чистку import и форматирование кода. Рекомендую поставить эти действия на автовыполнение…
Всем привет!
Когда-то давным-давно все, ну или почти все, использовали сборку JDK от Oracle. Хочу рассказать что же изменилось и что можно использовать сейчас.
В 2019 году Oracle изменила лицензионную политику и использовать JDK, а также JRE, бесплатно можно лишь для личных нужд. Версия, которую можно так использовать, получила название OpenJDK. Для коммерческих целей нужно приобретать лицензию, вне зависимости от того, нужна вам поддержка Oracle или нет. Эта версия - Oracle JDK. Кроме лицензионных условий сейчас эти сборки ничем не отличаются. Закон обратной силы не имеет, поэтому все предыдущие сборки можно использовать бесплатно, но любой hotfix JDK после 2019 года попадет под новые условия.
Это была первая проблема. Но кроме нее есть еще одна. Каждая версия OpenJDK от Oracle - это важно, что от Oracle - распространяется только полгода, потом идет переход на новую версию. И патчи выходят только для самой последней версии. Это касается и LTS - версий с долговременной поддержкой. Т.е Oracle OpenJDK 17 патчилась и распространялась полгода, потом стала доступна OpenJDK 18. Для тех, кто всегда готов к обновлениям - гуд) Но это не только лишь все)
Подробнее про это все можно почитать тут https://habr.com/ru/articles/448632/
Что же можно выбрать взамен? Есть довольно много компаний и opensource сообществ, которые берут исходники от Oracle OpenJDK, добавляют туда необходимые патчи, компилируют все это под определенные ОС и процессоры и выпускают для свободного скачивания. Отличаются они по следующим характеристикам:
1) набор версий Java. Как правило у всех есть 3 последние LTS - 8, 11, 17. У большинства есть последняя версия не LTS версия. У некоторых - даже 6-я и 7-я Java! А наличие 6-й Java означает не просто доступность ее для скачивания, но и обратное портирование туда критических патчей.
2) набор поддерживаемых ОС - Linux, Windows, MacOS
3) набор поддерживаемых процессорных архитектур - x64, x86 (32 бита), ARM
4) поддержка из коробки native image https://www.graalvm.org/native-image/
5) поддержка из коробки минималистичного образа Linux - Alpine. Я писал об различиях Docker образов тут https://t.me/javaKotlinDevOps/131
6) формат дистрибутива - инсталлятор, Docker образ. А если говорить про Дinux - поддержка разных менеджеров пакетов - rpm, deb...
7) формат поддержки - как правило он всегда доступен за деньги, но в некоторых случаях - JDK от ИТ гигантов - SAP, Microsoft, Amazon - поддержка доступна только вместе с продуктами этих компаний. Т.е. по сути эти JDK не для всех, а для тех кто использует соответствующее облако или продукт.
Неплохая статья со сравнением - https://bell-sw.com/blog/oracle-java-alternatives-comparison-of-openjdk-distributions
В конце статьи - сравнительная таблица.
Я бы особо выделил Temurin - JDK от Opensource сообщества и Liberica - JDK от российских разработчиков, который по перечисленным выше 7 пунктам обгоняет всех конкурентов.
P.S. Еще один интересный факт: IntelliJ также выпускает свою версию JDK - с доработками для отрисовки IDE UI.
#jdk #java
Когда-то давным-давно все, ну или почти все, использовали сборку JDK от Oracle. Хочу рассказать что же изменилось и что можно использовать сейчас.
В 2019 году Oracle изменила лицензионную политику и использовать JDK, а также JRE, бесплатно можно лишь для личных нужд. Версия, которую можно так использовать, получила название OpenJDK. Для коммерческих целей нужно приобретать лицензию, вне зависимости от того, нужна вам поддержка Oracle или нет. Эта версия - Oracle JDK. Кроме лицензионных условий сейчас эти сборки ничем не отличаются. Закон обратной силы не имеет, поэтому все предыдущие сборки можно использовать бесплатно, но любой hotfix JDK после 2019 года попадет под новые условия.
Это была первая проблема. Но кроме нее есть еще одна. Каждая версия OpenJDK от Oracle - это важно, что от Oracle - распространяется только полгода, потом идет переход на новую версию. И патчи выходят только для самой последней версии. Это касается и LTS - версий с долговременной поддержкой. Т.е Oracle OpenJDK 17 патчилась и распространялась полгода, потом стала доступна OpenJDK 18. Для тех, кто всегда готов к обновлениям - гуд) Но это не только лишь все)
Подробнее про это все можно почитать тут https://habr.com/ru/articles/448632/
Что же можно выбрать взамен? Есть довольно много компаний и opensource сообществ, которые берут исходники от Oracle OpenJDK, добавляют туда необходимые патчи, компилируют все это под определенные ОС и процессоры и выпускают для свободного скачивания. Отличаются они по следующим характеристикам:
1) набор версий Java. Как правило у всех есть 3 последние LTS - 8, 11, 17. У большинства есть последняя версия не LTS версия. У некоторых - даже 6-я и 7-я Java! А наличие 6-й Java означает не просто доступность ее для скачивания, но и обратное портирование туда критических патчей.
2) набор поддерживаемых ОС - Linux, Windows, MacOS
3) набор поддерживаемых процессорных архитектур - x64, x86 (32 бита), ARM
4) поддержка из коробки native image https://www.graalvm.org/native-image/
5) поддержка из коробки минималистичного образа Linux - Alpine. Я писал об различиях Docker образов тут https://t.me/javaKotlinDevOps/131
6) формат дистрибутива - инсталлятор, Docker образ. А если говорить про Дinux - поддержка разных менеджеров пакетов - rpm, deb...
7) формат поддержки - как правило он всегда доступен за деньги, но в некоторых случаях - JDK от ИТ гигантов - SAP, Microsoft, Amazon - поддержка доступна только вместе с продуктами этих компаний. Т.е. по сути эти JDK не для всех, а для тех кто использует соответствующее облако или продукт.
Неплохая статья со сравнением - https://bell-sw.com/blog/oracle-java-alternatives-comparison-of-openjdk-distributions
В конце статьи - сравнительная таблица.
Я бы особо выделил Temurin - JDK от Opensource сообщества и Liberica - JDK от российских разработчиков, который по перечисленным выше 7 пунктам обгоняет всех конкурентов.
P.S. Еще один интересный факт: IntelliJ также выпускает свою версию JDK - с доработками для отрисовки IDE UI.
#jdk #java
Хабр
Java теперь платная? Развенчиваем слухи (или нет?)
Уже 2 дня как вступили в силу изменения лицензионной политики Oracle на распространение сборок Java SE . В среде разработчиков-слоупоков (я тоже в их числе) начали носиться кошмарные слухи. Что...
Всем привет!
Я уже писал про паттерны https://t.me/javaKotlinDevOps/52 и их важность. Но есть штука поважнее паттернов - базовые принципы разработки. Чтобы стало понятнее приведу пример - SOLID.
Почему принципы важнее паттернов? Паттерн - это решение частной задачи. Лично я знаю больше паттернов, чем применял на практике) А в разработке я давно. Принципы же применимы практически к любой задачи.
Тот же S из SOLID - Single Responsibility: дорабатываешь какой-то метода - применим, создаешь новый класс - тоже, делишь код по модулям - аналогично, проектируешь набор микросервисов...
Я не люблю повторять то, что уже хорошо описано в интернете, поэтому вот статья с неплохим описанием - https://skillbox.ru/media/code/eto-klassika-eto-znat-nado-dry-kiss-solid-yagni-i-drugie-poleznye-sokrashcheniya/
Оффтопик - не ожидал от skillbox, обычно все ссылки у меня на Хабр или baeldung.
Что бы я добавил к описанным в статье принципам:
1) null safety. Плюсы: не получишь NullPointerException, не нужен код с проверкой на null, не нужно думать - так, на уровень выше я уже на null проверяю, тут вроде не нужно.. но если в будущем этот метод будет вызываться откуда-то еще. Жаль в Java ее достичь сложно, есть куча библиотек с аннотациями @Null\@NotNull, действуют они по разному, на эту тему можно отдельную статью написать. Важно то, что простого решения в Java нет. Зато есть в Kotlin)
2) иммутабельность. Главный плюс - большая устойчивость к ошибкам. Приведу пример: объект - это ссылка на область в памяти. Где еще в сервисе используется объект - часто быстро определить сложно. Вывод - меняя что-то в переданном в метод объекте можно поломать программу в неожиданном месте. Также неожиданным плюсом может быть большая производительность. Самый очевидный пример - иммутабельность строк. Еще - если у вас есть List и нужно убрать из него лишнее - возможно (возможно, надо проводить тесты!) оптимальнее будет создать новый список с нужными объектами, т.к. каждая модификация существующего - это перемещения в heap. Главное чтобы памяти было много и использовался современный сборщик мусора. Еще плюс - если объект иммутабельный, то его можно спокойно использовать в многопоточной программе. Изменения состояния нет, синхронизация доступа не нужна. Ну и бонусом - иммутабельный объект можно использовать как ключ, в том же HashSet\HashMap. В Java для иммутабельности есть records и final, в Kotlin - data class.
3) понятные наименования - я про переменные, методы, классы. Часто вижу две ошибки. Первая - злоупотребление сокращениями. Вторая - ситуативные названия. Т.е. при реализации конкретной фичи название кажется очевидным для автора кода. Но вот приходит новый разработчик. Он знает только о сервисе в целом, никакую старую аналитику он читать не будет, сразу полезет в код - в итоге многие названия покажутся ему непонятными. Общий принцип - называйте так, чтобы назначение кода было понятно новому разработчику. А любые более менее сложные условия выносите в методы с говорящими названиями. За подробностями - снова порекомендую книгу "Чистый код" Мартина.
#arch #patterns #solid
Я уже писал про паттерны https://t.me/javaKotlinDevOps/52 и их важность. Но есть штука поважнее паттернов - базовые принципы разработки. Чтобы стало понятнее приведу пример - SOLID.
Почему принципы важнее паттернов? Паттерн - это решение частной задачи. Лично я знаю больше паттернов, чем применял на практике) А в разработке я давно. Принципы же применимы практически к любой задачи.
Тот же S из SOLID - Single Responsibility: дорабатываешь какой-то метода - применим, создаешь новый класс - тоже, делишь код по модулям - аналогично, проектируешь набор микросервисов...
Я не люблю повторять то, что уже хорошо описано в интернете, поэтому вот статья с неплохим описанием - https://skillbox.ru/media/code/eto-klassika-eto-znat-nado-dry-kiss-solid-yagni-i-drugie-poleznye-sokrashcheniya/
Оффтопик - не ожидал от skillbox, обычно все ссылки у меня на Хабр или baeldung.
Что бы я добавил к описанным в статье принципам:
1) null safety. Плюсы: не получишь NullPointerException, не нужен код с проверкой на null, не нужно думать - так, на уровень выше я уже на null проверяю, тут вроде не нужно.. но если в будущем этот метод будет вызываться откуда-то еще. Жаль в Java ее достичь сложно, есть куча библиотек с аннотациями @Null\@NotNull, действуют они по разному, на эту тему можно отдельную статью написать. Важно то, что простого решения в Java нет. Зато есть в Kotlin)
2) иммутабельность. Главный плюс - большая устойчивость к ошибкам. Приведу пример: объект - это ссылка на область в памяти. Где еще в сервисе используется объект - часто быстро определить сложно. Вывод - меняя что-то в переданном в метод объекте можно поломать программу в неожиданном месте. Также неожиданным плюсом может быть большая производительность. Самый очевидный пример - иммутабельность строк. Еще - если у вас есть List и нужно убрать из него лишнее - возможно (возможно, надо проводить тесты!) оптимальнее будет создать новый список с нужными объектами, т.к. каждая модификация существующего - это перемещения в heap. Главное чтобы памяти было много и использовался современный сборщик мусора. Еще плюс - если объект иммутабельный, то его можно спокойно использовать в многопоточной программе. Изменения состояния нет, синхронизация доступа не нужна. Ну и бонусом - иммутабельный объект можно использовать как ключ, в том же HashSet\HashMap. В Java для иммутабельности есть records и final, в Kotlin - data class.
3) понятные наименования - я про переменные, методы, классы. Часто вижу две ошибки. Первая - злоупотребление сокращениями. Вторая - ситуативные названия. Т.е. при реализации конкретной фичи название кажется очевидным для автора кода. Но вот приходит новый разработчик. Он знает только о сервисе в целом, никакую старую аналитику он читать не будет, сразу полезет в код - в итоге многие названия покажутся ему непонятными. Общий принцип - называйте так, чтобы назначение кода было понятно новому разработчику. А любые более менее сложные условия выносите в методы с говорящими названиями. За подробностями - снова порекомендую книгу "Чистый код" Мартина.
#arch #patterns #solid
Telegram
(java || kotlin) && devOps
Всем привет!
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста.…
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста.…
Всем привет!
Сегодня пост про очередной типичный вопрос на собеседовании.
Звучит он так: расскажите про жизненный цикл сборки Maven, он же build lifecycle.
Я вкратце уже писал про это https://t.me/javaKotlinDevOps/10. Но т.к. типичный ответ на собеседовании - перечисление циклов и фаз сборки, то хочется раскрыть тему. На самом деле вопрос про другое - в чем суть цикла сборки?
1) набор фаз сборки JVM проекта уже придуман и зафиксирован умными людьми) из команды Maven в виде Default Lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference Т.е. считается, что список покрывает все потребности сборки. Как пример продуманности могу привести фазы pre-integration-test и post-integration-test, которые нужны для того, чтобы поднять и опустить контейнер сервлетов, менеджер очередей или БД до и после фазы интеграционных тестов (integration-test). Для обычных тестов (test) таких вспомогательных фаз нет.
2) последовательность фаз также зафиксирована, порядок менять нельзя да и нет смысла
3) большинство фаз являются опциональными, запускаются только если к фазе подключен хоть один плагин и либо в проекте присутствуют определенные файлы, либо есть настройки плагина, указывающие что ему делать - например, generate-sources или компиляция Kotlin
4) на одной фазе могут работать несколько целей (goal) из разных плагинов - например, компиляция Java и Kotlin на фазе compile или несколько тестовых фреймворков на фазе test
5) запуская определенный шаг сборки - mvn install - вы запускаете все предыдущие шаги цикла. И в большинстве случаев это правильно. Если такое поведение не требуется - нужно запустить конкретную цель у конкретного плагина, для моего примера это mvn install:install (плагин:цель). Т.е. тут совпадают название фазы сборки, плагина и цели. Так бывает не всегда, например, для фазы test-compile цель выглядит так compiler:testCompile
Итого: цель жизненного цикла сборки - стандартизировать процесс сборки.
Да и в принципе стандартизация - конек Maven. Тут и типовой набор папок https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html, и цикл сборки, и сложность написания своего кода для сборки, и достаточно богатый набор стандартных плагинов, для подключения которых ничего не надо делать (если быть точным - неплохо бы зафиксировать версию в pom), и архетипы, и конвенция по наименованию артефактов, и наконец центральный репозиторий - https://mvnrepository.com/repos/central
#maven #interview_question
Сегодня пост про очередной типичный вопрос на собеседовании.
Звучит он так: расскажите про жизненный цикл сборки Maven, он же build lifecycle.
Я вкратце уже писал про это https://t.me/javaKotlinDevOps/10. Но т.к. типичный ответ на собеседовании - перечисление циклов и фаз сборки, то хочется раскрыть тему. На самом деле вопрос про другое - в чем суть цикла сборки?
1) набор фаз сборки JVM проекта уже придуман и зафиксирован умными людьми) из команды Maven в виде Default Lifecycle https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference Т.е. считается, что список покрывает все потребности сборки. Как пример продуманности могу привести фазы pre-integration-test и post-integration-test, которые нужны для того, чтобы поднять и опустить контейнер сервлетов, менеджер очередей или БД до и после фазы интеграционных тестов (integration-test). Для обычных тестов (test) таких вспомогательных фаз нет.
2) последовательность фаз также зафиксирована, порядок менять нельзя да и нет смысла
3) большинство фаз являются опциональными, запускаются только если к фазе подключен хоть один плагин и либо в проекте присутствуют определенные файлы, либо есть настройки плагина, указывающие что ему делать - например, generate-sources или компиляция Kotlin
4) на одной фазе могут работать несколько целей (goal) из разных плагинов - например, компиляция Java и Kotlin на фазе compile или несколько тестовых фреймворков на фазе test
5) запуская определенный шаг сборки - mvn install - вы запускаете все предыдущие шаги цикла. И в большинстве случаев это правильно. Если такое поведение не требуется - нужно запустить конкретную цель у конкретного плагина, для моего примера это mvn install:install (плагин:цель). Т.е. тут совпадают название фазы сборки, плагина и цели. Так бывает не всегда, например, для фазы test-compile цель выглядит так compiler:testCompile
Итого: цель жизненного цикла сборки - стандартизировать процесс сборки.
Да и в принципе стандартизация - конек Maven. Тут и типовой набор папок https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html, и цикл сборки, и сложность написания своего кода для сборки, и достаточно богатый набор стандартных плагинов, для подключения которых ничего не надо делать (если быть точным - неплохо бы зафиксировать версию в pom), и архетипы, и конвенция по наименованию артефактов, и наконец центральный репозиторий - https://mvnrepository.com/repos/central
#maven #interview_question
Telegram
(java || kotlin) && devOps
Всем привет!
Хочу рассказать собрать в одном посте несколько мало и среднеизвестных фичей Maven.
Поехали!
Для начала немного теории. Жизненный цикл сборки = фиксированная последовательность фаз сборки. К фазе сборки можно подключить плагин(ы), а точнее…
Хочу рассказать собрать в одном посте несколько мало и среднеизвестных фичей Maven.
Поехали!
Для начала немного теории. Жизненный цикл сборки = фиксированная последовательность фаз сборки. К фазе сборки можно подключить плагин(ы), а точнее…
Всем привет!
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Сегодня пост с картинками, поэтому ловите https://telegra.ph/Urovni-izolyacii-sovremennogo-Java-prilozheniya-07-29
#java #jvm #docker #performance
Telegraph
Уровни изоляции современного Java приложения
Всем привет! Уже писал про плюсы Docker - https://t.me/javaKotlinDevOps/165 Но все ли так безоблачно?) Обычно, когда сравнивают Docker с виртуальной машиной (VM) приводят такую схему Из нее видно, что Docker - более легковесное решение по сравнению с VM.…
Всем привет!
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Нашёл отличное сравнение скорости конкатенации строк разными методами, от + до StringBuffer, StringBuilder и StringJoiner. И даже есть такая экзотика как String.format со стримами.
Для затравки три интересных факта.
1) StringBuffer даже с синхронизацией существенно быстрее обычной конкатенации.
2) String.format очень(!) медленный.
3) скорость обычной конкатенации с увеличением числа строк растёт экспоненциально.
Подробнее тут https://www.baeldung.com/java-string-concatenation-methods
#java #performance #string
Baeldung
Performance Comparison Between Different Java String Concatenation Methods | Baeldung
Explore different string concatenation methods in Java and evaluate their performance using JMH.
Всем привет!
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Про DI и DI.
Аббревиатура DI может расшифровываться на Dependency Inversion, а может как Dependency Injection.
Dependency Inversion - это буква D из SOLID - базовых принципов разработки.
Означает, что высокоуровневые классы не должны зависеть от конкретных реализации, и в Java API любых классов лучше использовать интерфейсы везде, где это возможно. Почему такая ремарка: интерфейс с единственной реализацией - очень странная штука) Но я отвлекся) Следование принципу облегчает тестирование и расширение функциональности системы, т.к. позволяет легко заменить любую реализацию.
Dependency Injection - это механизм внедрения зависимостей, важнейшая особенность которого - собственно внедрение зависимостей отдается на откуп внешнему модулю. Самые известный пример - Spring c его IoC контейнером, но есть и другие заточенные конкретно на эту задачу и поэтому более шустрые альтернативы.
Если подходить формально - это два разных понятия, кроме аббревиатуры никак не связанные. Но с другой стороны Dependency Injection по сути - это инструмент, сильно облегчающий реализацию принципа Dependency Inversion. А хороший инструмент помогает писать правильный код. Важное замечание - Spring IoC не обеспечит за вас реализацию инверсии зависимостей. Если метод API завязывается на конкретную реализацию или уровни приложения связаны циклически - Spring тут не поможет. Поможет предварительное проектирование на уровне кода и TDD.
#code_architecture #interview_question #arch #patterns #solid
Всем привет!
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Всем привет!
Раз уж я начал говорить про SOLID - рассмотрим еще одну букву - O.
Open-closed principe - принцип открытости-закрытости.
На первый взгляд все просто - не нужно менять существующие классы, нужно их расширять. Очень хорошо ложится на области видимости Java и концепцию наследования классов.
Но это все очевидные вещи, первый уровень сложности)
Второй уровень - как можно сделать Open, т.е. расширить функционал? Наследование. Да, наследование - это хорошо. Но я отлично помню монолит с огромными иерархиями наследования, уровней по 10+. Бегать по этой иерархии искать где и что используется и точно ли я ничего не сломаю в новом наследнике - такое себе удовольствие. Да и SonarQube ругается, и правильно делает).
Альтернативы:
1) композиция и агрегация - объект базового класса можно включить в себя как поле, и уже в новом классе реализовать нужные требования. https://metanit.com/sharp/patterns/1.2.php
2) частный случай предыдущего пункта - паттерны декоратор и прокси, когда мы реализуем тот же интерфейс, что и базовый класс. См. разницу https://t.me/javaKotlinDevOps/124
3) комбинация аннотации и аспекта, реализующего дополнительное поведение над определенным методом класса
4) аннотации и создание прокси класса через CGLib или аналогичную библиотеку
Третий уровень - как правильно сделать Close? Закрыли мы класс для изменений, поставили final. А там конструкция вида:
private final List<String> POSIBLE_STORAGES = List.of("Oracle", "EhCache");
и далее везде в коде сохранения выбор из этих двух вариантов...
При желании наверняка обойти можно, но это значит, что автор класса не подумал про принцип открытости-закрытости.
Нужно было выносить в отдельный метод, а еще лучше - в отдельный объект Storage.
#arch #solid #inteview_question
Раз уж я начал говорить про SOLID - рассмотрим еще одну букву - O.
Open-closed principe - принцип открытости-закрытости.
На первый взгляд все просто - не нужно менять существующие классы, нужно их расширять. Очень хорошо ложится на области видимости Java и концепцию наследования классов.
Но это все очевидные вещи, первый уровень сложности)
Второй уровень - как можно сделать Open, т.е. расширить функционал? Наследование. Да, наследование - это хорошо. Но я отлично помню монолит с огромными иерархиями наследования, уровней по 10+. Бегать по этой иерархии искать где и что используется и точно ли я ничего не сломаю в новом наследнике - такое себе удовольствие. Да и SonarQube ругается, и правильно делает).
Альтернативы:
1) композиция и агрегация - объект базового класса можно включить в себя как поле, и уже в новом классе реализовать нужные требования. https://metanit.com/sharp/patterns/1.2.php
2) частный случай предыдущего пункта - паттерны декоратор и прокси, когда мы реализуем тот же интерфейс, что и базовый класс. См. разницу https://t.me/javaKotlinDevOps/124
3) комбинация аннотации и аспекта, реализующего дополнительное поведение над определенным методом класса
4) аннотации и создание прокси класса через CGLib или аналогичную библиотеку
Третий уровень - как правильно сделать Close? Закрыли мы класс для изменений, поставили final. А там конструкция вида:
private final List<String> POSIBLE_STORAGES = List.of("Oracle", "EhCache");
и далее везде в коде сохранения выбор из этих двух вариантов...
При желании наверняка обойти можно, но это значит, что автор класса не подумал про принцип открытости-закрытости.
Нужно было выносить в отдельный метод, а еще лучше - в отдельный объект Storage.
#arch #solid #inteview_question
Telegram
(java || kotlin) && devOps
Всем привет!
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шабло…
Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шабло…
Всем привет!
Продолжая тему SOLID. Есть в этой аббревиатуре буква L - принцип Барбары Лисков, который гласит, что все потомки должны соблюдать контракт, объявленный в родителе.
Объясню на пальцах. Есть класс или даже скорее интерфейс А. Есть класс Б, как-то использующий А - например, принимающий его как параметр в методе или конструкторе. И принцип Лисков будет соблюдаться в том случае, если класс Б не "упадет" при получении любого (!) потомка класса А, т.е. класс Б не должен разбираться в том, какого именно потомка А он получил.
На первый взгляд все просто - в Java и Kotlin строгая типизация и инкапсуляция, поэтому если мы объявили интерфейс с определенными методами и типами параметров - все потомки обязаны их реализовать. Что тут может сломаться?
Если копнуть глубже, то в Java наследники все же могут изменять контракт:
1) заменить тип возвращаемого методом результата на тип-наследник
2) добавлять в сигнатуру метода исключения-наследники и увеличивать число объявленных исключений, тоже наследниками
3) повышать область видимости метода
Все это расширяет ограничения в исходном контракте. Поломать корректно написанный класс Б, который знает только про А - интерфейс предка - так не получится. Хотя расширяя таким образом контракт, нужно быть осторожным, т.к. мы подталкиваем потребителей нашего нового класса к использованию конкретного класса вместо интерфейса, а это уже может привести к нарушению другой буквы - D )))
Как же можно точно нарушить контракт? Для этого нужно ответить на вопрос - что есть контракт?
Контракт не равно Java API. Это может быть:
1) назначение метода. Например, называя метод getXXX или checkXXX мы как бы говорим потребителю, что он не меняет состояние объекта, т.е. нет побочных эффектов
2) порядок вызова методов класса. Опять же исходя из названий методов мы можем предположить некий порядок вызовов - init() вызываем в начале, build() - в случае паттерна Builder в конце.
3) назначение класса. Если опять же исходя из названия класса он предназначен только для работы с кэшом - нужно в потомках лазить в БД, это должны быть разные сервисные классы. Если предполагается, что в сервисном классе данного уровня нужно отбрасывать логи и метрики - все потомки должны это делать. И наоборот.
4) политика по исключениям. Если предполагается, что метод возвращает внутреннюю ошибку как результат вызова (Pair или свой класс), то именно так и должны поступать все потомки
5) время выполнения. Если в контракте явно предполагается, что метод что-то рассчитывает in-process и не ходит во внешние системы - контракт нужно соблюдать.
6) зависимости от других классов. Нужно стремится к тому, чтобы все зависимости были явно указаны в API и передавались в класс при создании. Т.е. класс Б не должен как-то настраивать среду для работы конкретного потомка класса А. Да, речь про Inversion of Control.
7) иммутабельность. Если предполагается иммутабельная реализация - все потомки должны быть такими.
Как описать такой контракт? В первую очередь - в именах методов и классов. Если этого не хватает - в JavaDoc. Часто так бывает, что JavaDoc не несет новой информации для изучающего код, но в данном случае будет полезен.
И последняя очевидная, но IMHO важная мысль: чтобы наследники соответствовали контракту - для начала контракт нужно описать)
#arch #solid #interview_question
Продолжая тему SOLID. Есть в этой аббревиатуре буква L - принцип Барбары Лисков, который гласит, что все потомки должны соблюдать контракт, объявленный в родителе.
Объясню на пальцах. Есть класс или даже скорее интерфейс А. Есть класс Б, как-то использующий А - например, принимающий его как параметр в методе или конструкторе. И принцип Лисков будет соблюдаться в том случае, если класс Б не "упадет" при получении любого (!) потомка класса А, т.е. класс Б не должен разбираться в том, какого именно потомка А он получил.
На первый взгляд все просто - в Java и Kotlin строгая типизация и инкапсуляция, поэтому если мы объявили интерфейс с определенными методами и типами параметров - все потомки обязаны их реализовать. Что тут может сломаться?
Если копнуть глубже, то в Java наследники все же могут изменять контракт:
1) заменить тип возвращаемого методом результата на тип-наследник
2) добавлять в сигнатуру метода исключения-наследники и увеличивать число объявленных исключений, тоже наследниками
3) повышать область видимости метода
Все это расширяет ограничения в исходном контракте. Поломать корректно написанный класс Б, который знает только про А - интерфейс предка - так не получится. Хотя расширяя таким образом контракт, нужно быть осторожным, т.к. мы подталкиваем потребителей нашего нового класса к использованию конкретного класса вместо интерфейса, а это уже может привести к нарушению другой буквы - D )))
Как же можно точно нарушить контракт? Для этого нужно ответить на вопрос - что есть контракт?
Контракт не равно Java API. Это может быть:
1) назначение метода. Например, называя метод getXXX или checkXXX мы как бы говорим потребителю, что он не меняет состояние объекта, т.е. нет побочных эффектов
2) порядок вызова методов класса. Опять же исходя из названий методов мы можем предположить некий порядок вызовов - init() вызываем в начале, build() - в случае паттерна Builder в конце.
3) назначение класса. Если опять же исходя из названия класса он предназначен только для работы с кэшом - нужно в потомках лазить в БД, это должны быть разные сервисные классы. Если предполагается, что в сервисном классе данного уровня нужно отбрасывать логи и метрики - все потомки должны это делать. И наоборот.
4) политика по исключениям. Если предполагается, что метод возвращает внутреннюю ошибку как результат вызова (Pair или свой класс), то именно так и должны поступать все потомки
5) время выполнения. Если в контракте явно предполагается, что метод что-то рассчитывает in-process и не ходит во внешние системы - контракт нужно соблюдать.
6) зависимости от других классов. Нужно стремится к тому, чтобы все зависимости были явно указаны в API и передавались в класс при создании. Т.е. класс Б не должен как-то настраивать среду для работы конкретного потомка класса А. Да, речь про Inversion of Control.
7) иммутабельность. Если предполагается иммутабельная реализация - все потомки должны быть такими.
Как описать такой контракт? В первую очередь - в именах методов и классов. Если этого не хватает - в JavaDoc. Часто так бывает, что JavaDoc не несет новой информации для изучающего код, но в данном случае будет полезен.
И последняя очевидная, но IMHO важная мысль: чтобы наследники соответствовали контракту - для начала контракт нужно описать)
#arch #solid #interview_question
Всем привет!
Уже несколько раз упоминал в последних постах JavaDoc\KDoc. Нужен ли он вообще?
Мой ответ: да, но не всегда.
Основной критерий - JavaDoc должен приносить пользу. Т.е. давать дополнительную информацию. Дополнительную к чему?
1) названию модуля
2) названию пакета
3) названию класса\паттерна
4) названию метода или поля
Что это может быть?
1) контракт, как использовать и наследовать класс
2) причины выбора того или иного алгоритма
3) пример использования, его также можно поместить в unit тесты
4) TODO, причины объявления deprecated
5) ссылки на используемые в коде алгоритмы
6) краткое описание бизнес-логики, т.к. разработчики не ходят в wiki\Confluence )))
7) особые случаи использования, неочевидное поведение кода с некоторыми параметрами. Хотя это скорее антипаттерн, само наличие неочевидного поведения - повод подумать про рефакторинг
Почему все же не стоит писать "дежурный" комментарий над классом и публичными методами?
1) на это тратится время
2) главная причина - рефакторинг кода. При рефакторинге обычно меняют имена методов, полей и классов. Иногда забывают. Но если говорить о комментариях - тут все наоборот: иногда их обновляют, но чаще - забывают. И получаем код, на который было потрачено время, но который мало того, что не приносит пользу - так еще и вводит в заблуждение.
#java #javadoc
Уже несколько раз упоминал в последних постах JavaDoc\KDoc. Нужен ли он вообще?
Мой ответ: да, но не всегда.
Основной критерий - JavaDoc должен приносить пользу. Т.е. давать дополнительную информацию. Дополнительную к чему?
1) названию модуля
2) названию пакета
3) названию класса\паттерна
4) названию метода или поля
Что это может быть?
1) контракт, как использовать и наследовать класс
2) причины выбора того или иного алгоритма
3) пример использования, его также можно поместить в unit тесты
4) TODO, причины объявления deprecated
5) ссылки на используемые в коде алгоритмы
6) краткое описание бизнес-логики, т.к. разработчики не ходят в wiki\Confluence )))
7) особые случаи использования, неочевидное поведение кода с некоторыми параметрами. Хотя это скорее антипаттерн, само наличие неочевидного поведения - повод подумать про рефакторинг
Почему все же не стоит писать "дежурный" комментарий над классом и публичными методами?
1) на это тратится время
2) главная причина - рефакторинг кода. При рефакторинге обычно меняют имена методов, полей и классов. Иногда забывают. Но если говорить о комментариях - тут все наоборот: иногда их обновляют, но чаще - забывают. И получаем код, на который было потрачено время, но который мало того, что не приносит пользу - так еще и вводит в заблуждение.
#java #javadoc
Всем привет!
Когда-то на своей первой работе я услышал от коллеги совет: "Не надо бросаться реализовывать в коде первую попавшуюся идею. Подумай, найди пару альтернативных решений и сравни их".
Так вот, спустя более 15 лет практики хочу сказать - это очень важная и полезная мысль!)
По моему собственному опыту в 25-50% случаев первая пришедшая в голову идея не является оптимальной. Иногда приходится выкинуть все сделанное, иногда - сильно ее видоизменить. Причем часто это происходит еще в процессе работы над исходной задачей, при попытке вписать свой новый код в существующий сервис.
По сути речь идет о проектировании и его качестве. Когда нужно проектирование? Почти всегда, кроме исправления тривиальных багов - опечаток, замены текстовок например. А на самом деле даже простой в исправлении баг может указать на необходимость рефакторинга, а рефакторинг в любом случае требует проектирования.
Хорошим вариантом проверить качество идеи является практика TDD - в этом случае придется сразу написать типовые примеры использования вашего нового класса в виде тестов. Соответственно, сразу будет видно как новый код интегрируется с существующим, подходит ли для всех случаев использования.
При этом важно отметить, что TDD не заменяет проектирование.
Единственная альтернатива проектированию, которую я вижу - закладывать 1,5х или 2х времени для работы над задачей, чтобы была возможность все переделать) Альтернатива так себе, по моей оценке время на проектирование будет ниже.
Еще худший вариант: видя, что новый код придется переделывать - "докостылить" его до работающего варианта и записать себе задачу с техдолгом. К таким техдолгам, увы, долго не возвращаются.
Резюме - закладывайте время на предварительное проектирование по любой не тривиальной задаче. Ищите и сравнивайте альтернативные варианты, выбирайте оптимальный.
#arch
Когда-то на своей первой работе я услышал от коллеги совет: "Не надо бросаться реализовывать в коде первую попавшуюся идею. Подумай, найди пару альтернативных решений и сравни их".
Так вот, спустя более 15 лет практики хочу сказать - это очень важная и полезная мысль!)
По моему собственному опыту в 25-50% случаев первая пришедшая в голову идея не является оптимальной. Иногда приходится выкинуть все сделанное, иногда - сильно ее видоизменить. Причем часто это происходит еще в процессе работы над исходной задачей, при попытке вписать свой новый код в существующий сервис.
По сути речь идет о проектировании и его качестве. Когда нужно проектирование? Почти всегда, кроме исправления тривиальных багов - опечаток, замены текстовок например. А на самом деле даже простой в исправлении баг может указать на необходимость рефакторинга, а рефакторинг в любом случае требует проектирования.
Хорошим вариантом проверить качество идеи является практика TDD - в этом случае придется сразу написать типовые примеры использования вашего нового класса в виде тестов. Соответственно, сразу будет видно как новый код интегрируется с существующим, подходит ли для всех случаев использования.
При этом важно отметить, что TDD не заменяет проектирование.
Единственная альтернатива проектированию, которую я вижу - закладывать 1,5х или 2х времени для работы над задачей, чтобы была возможность все переделать) Альтернатива так себе, по моей оценке время на проектирование будет ниже.
Еще худший вариант: видя, что новый код придется переделывать - "докостылить" его до работающего варианта и записать себе задачу с техдолгом. К таким техдолгам, увы, долго не возвращаются.
Резюме - закладывайте время на предварительное проектирование по любой не тривиальной задаче. Ищите и сравнивайте альтернативные варианты, выбирайте оптимальный.
#arch
Всем привет!
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Всем привет!
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Всем привет!
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Всем привет!
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Всем привет!
Есть такая отличная штука в Kotlin, как контекстные функции.
Вот документация https://kotlinlang.org/docs/scope-functions.html
Вот пример, хорошо иллюстрирующий зачем они нужны:
val man = Person("Vasya").apply {
age = 20 // same as this.age = 20
city = "Moscow"
}
Код стал проще, читается хорошо. Если, конечно, ты знаешь про контекстные функции)
Но как и любая вещь, контекстные функции могут быть использованы не только во благо(
Вот несколько антипаттернов:
val params = claim.systemState?.let {
FailureParams(
claim.partnerName,
it.name,
)
}
Что мне здесь не нравится - читаемость. Если читать сверху вниз, то получается, что мы берем статус из заявки и присваиваем переменной params ... не его, а совершенно другой объект, созданный внутри let. Скорость понимания кода страдает.
return claim.also {
saveToCache(it)
}
Опять же, мне не нравится читаемость кода. Мы возвращаем результат метода, claim. А нет, не возвращаем. Вначале пишем его в кэш. А потом уже возвращаем.
Гораздо проще было бы:
saveToCache(claim)
return claim
Ну и наконец самый хит:
return claim.also {
saveToCache(someOtherObject)
}
Зачем? Почему? Не понятно)
P.S. По ссылке выше есть неплохая табличка https://kotlinlang.org/docs/scope-functions.html#function-selection Это по сути навигатор по контекстным функциям - позволяет выбрать одну из 6 для вашего конкретного случая. На первое время точно будет полезной
#kotlin #antipatterns
Есть такая отличная штука в Kotlin, как контекстные функции.
Вот документация https://kotlinlang.org/docs/scope-functions.html
Вот пример, хорошо иллюстрирующий зачем они нужны:
val man = Person("Vasya").apply {
age = 20 // same as this.age = 20
city = "Moscow"
}
Код стал проще, читается хорошо. Если, конечно, ты знаешь про контекстные функции)
Но как и любая вещь, контекстные функции могут быть использованы не только во благо(
Вот несколько антипаттернов:
val params = claim.systemState?.let {
FailureParams(
claim.partnerName,
it.name,
)
}
Что мне здесь не нравится - читаемость. Если читать сверху вниз, то получается, что мы берем статус из заявки и присваиваем переменной params ... не его, а совершенно другой объект, созданный внутри let. Скорость понимания кода страдает.
return claim.also {
saveToCache(it)
}
Опять же, мне не нравится читаемость кода. Мы возвращаем результат метода, claim. А нет, не возвращаем. Вначале пишем его в кэш. А потом уже возвращаем.
Гораздо проще было бы:
saveToCache(claim)
return claim
Ну и наконец самый хит:
return claim.also {
saveToCache(someOtherObject)
}
Зачем? Почему? Не понятно)
P.S. По ссылке выше есть неплохая табличка https://kotlinlang.org/docs/scope-functions.html#function-selection Это по сути навигатор по контекстным функциям - позволяет выбрать одну из 6 для вашего конкретного случая. На первое время точно будет полезной
#kotlin #antipatterns
Kotlin Help
Scope functions | Kotlin
Всем привет!
Сегодня пост о крутой фиче Kotlin, которая решила одну важную проблему. И добавила другую)
Я о Null safety.
Суть ее в том, что в Kotlin любой тип представлен в двух ипостасях - одна может содержать null значения, другая - нет.
String - не может содержать null,
String? - может.
Это два разных типа, неявное приведение второго к первому "без приседаний" невозможно.
Что дает Null safety?
По умолчанию предлагается использовать тип not null, и если так и делать, то кажется, что про NPE - NullPointerException - можно забыть. А заодно забыть о проверках на null как в коде, так и в тестах. Небольшой оффтоп - проверка not null значений на null в тестах - еще один антипаттерн. Говорит о том, что пишущий этот код еще не до конца познал Kotlin)
Вроде бы все хорошо. Но есть нюанс. Что будет, если присвоить not null переменной nullable значение? Ошибка компиляции. А всегда ли компилятор знает, что это nullable значение? Если тип Kotlin - то всегда. Если тип Java - то в общем случае не знает. Что же он делает в таком случае? А ничего, просто разрешает присваивание.
//Java
public class JavaNullability {
private Boolean value;
public Boolean getValue() {
return value;
}
}
// Kotlin
class KotlinNullability {
constructor(test: JavaNullability) {
val nullValue: Boolean? = test.value
val notNullValue: Boolean = test.value
// компилятор разрешает оба варианта
}
}
Что же будет в runtime? Привычный нам в Java NPE на втором присваивании. Да, null safety не защищает от NPE в Kotlin коде.
Что тут можно сделать?
1) при взаимодействии Kotlin-Java знать nullability Java параметров, с которым мы работаем. Это может быть описание контракта - OpenAPI спецификация или любой другой контракт.
2) явная проверка на null в коде присваивания
3) самый плохой вариант, антипаттерн - всегда присваивать значения из Java nullable типам в Kotlin. Почему это плохо? nullable типы расползаются по всей программе, и в итоге мы теряем все преимущества Kotlin null safety.
P.S. Но в целом: null safety - это круто! Подробнее можно о ней можно почитать в официальной документации: https://kotlinlang.org/docs/null-safety.html#nullable-receiver
#kotlin #java #antipatterns #nullsafety
Сегодня пост о крутой фиче Kotlin, которая решила одну важную проблему. И добавила другую)
Я о Null safety.
Суть ее в том, что в Kotlin любой тип представлен в двух ипостасях - одна может содержать null значения, другая - нет.
String - не может содержать null,
String? - может.
Это два разных типа, неявное приведение второго к первому "без приседаний" невозможно.
Что дает Null safety?
По умолчанию предлагается использовать тип not null, и если так и делать, то кажется, что про NPE - NullPointerException - можно забыть. А заодно забыть о проверках на null как в коде, так и в тестах. Небольшой оффтоп - проверка not null значений на null в тестах - еще один антипаттерн. Говорит о том, что пишущий этот код еще не до конца познал Kotlin)
Вроде бы все хорошо. Но есть нюанс. Что будет, если присвоить not null переменной nullable значение? Ошибка компиляции. А всегда ли компилятор знает, что это nullable значение? Если тип Kotlin - то всегда. Если тип Java - то в общем случае не знает. Что же он делает в таком случае? А ничего, просто разрешает присваивание.
//Java
public class JavaNullability {
private Boolean value;
public Boolean getValue() {
return value;
}
}
// Kotlin
class KotlinNullability {
constructor(test: JavaNullability) {
val nullValue: Boolean? = test.value
val notNullValue: Boolean = test.value
// компилятор разрешает оба варианта
}
}
Что же будет в runtime? Привычный нам в Java NPE на втором присваивании. Да, null safety не защищает от NPE в Kotlin коде.
Что тут можно сделать?
1) при взаимодействии Kotlin-Java знать nullability Java параметров, с которым мы работаем. Это может быть описание контракта - OpenAPI спецификация или любой другой контракт.
2) явная проверка на null в коде присваивания
3) самый плохой вариант, антипаттерн - всегда присваивать значения из Java nullable типам в Kotlin. Почему это плохо? nullable типы расползаются по всей программе, и в итоге мы теряем все преимущества Kotlin null safety.
P.S. Но в целом: null safety - это круто! Подробнее можно о ней можно почитать в официальной документации: https://kotlinlang.org/docs/null-safety.html#nullable-receiver
#kotlin #java #antipatterns #nullsafety
Kotlin Help
Null safety | Kotlin
Всем привет!
Чтобы проиллюстрировать предыдущий пост - предлагаю сравнить по критерию null safety три языка: Groovy, Java и Kotlin.
Тестовая задача такая - передать null Boolean значение в конструктор и проверить его в if.
Groovy
import groovy.transform.TupleConstructor
@TupleConstructor
class GroovyNullability {
Boolean value
void checkValue() {
if (!value) {
println 'groovy value is false'
}
}
}
Т.к. в Groovy все, что не true считается false - вызов checkValue распечатает строку.
Java
public class JavaNullability {
private Boolean value;
public JavaNullability(Boolean value) {
this.value = value;
}
public void checkValue() {
if (!value) {
System.out.println("java value is false");
}
}
}
Вызов checkValue упадет c NPE: "Cannot invoke "java.lang.Boolean.booleanValue()" because "this.value" is null" на преобразовании Boolean к boolean использования в логическом выражении (boolean expression).
Kotlin
class KotlinNullability {
var value: Boolean = false
constructor(test: Boolean?) {
value = test!!
}
fun checkValue() {
if (!value) {
println("kotlin value is false")
}
}
}
Чтобы присвоить null значение not null переменной придется пойти на хитрость - через !! указать, что мы уверены, что в nullable переменной не будет null значений. И если же null все же придет - тоже будет java.lang.NullPointerException, причем без подробностей. Но на шаг раньше, еще на присваивании not null переменной.
К слову - если передать Java объект с null значением, через класс-обвертку:
constructor(test: JavaNullability) {
value = test.value
}
NPE будет чуть более подробным: "java.lang.NullPointerException: test.value must not be null"
#kotlin #java #groovy #nullsafety
Чтобы проиллюстрировать предыдущий пост - предлагаю сравнить по критерию null safety три языка: Groovy, Java и Kotlin.
Тестовая задача такая - передать null Boolean значение в конструктор и проверить его в if.
Groovy
import groovy.transform.TupleConstructor
@TupleConstructor
class GroovyNullability {
Boolean value
void checkValue() {
if (!value) {
println 'groovy value is false'
}
}
}
Т.к. в Groovy все, что не true считается false - вызов checkValue распечатает строку.
Java
public class JavaNullability {
private Boolean value;
public JavaNullability(Boolean value) {
this.value = value;
}
public void checkValue() {
if (!value) {
System.out.println("java value is false");
}
}
}
Вызов checkValue упадет c NPE: "Cannot invoke "java.lang.Boolean.booleanValue()" because "this.value" is null" на преобразовании Boolean к boolean использования в логическом выражении (boolean expression).
Kotlin
class KotlinNullability {
var value: Boolean = false
constructor(test: Boolean?) {
value = test!!
}
fun checkValue() {
if (!value) {
println("kotlin value is false")
}
}
}
Чтобы присвоить null значение not null переменной придется пойти на хитрость - через !! указать, что мы уверены, что в nullable переменной не будет null значений. И если же null все же придет - тоже будет java.lang.NullPointerException, причем без подробностей. Но на шаг раньше, еще на присваивании not null переменной.
К слову - если передать Java объект с null значением, через класс-обвертку:
constructor(test: JavaNullability) {
value = test.value
}
NPE будет чуть более подробным: "java.lang.NullPointerException: test.value must not be null"
#kotlin #java #groovy #nullsafety