Всем привет!
Одна из моих любимых тем: разработка - искусство компромиссов. Поиск по тэгам #dev_compromises и #arch_compromises. Следствие этого подхода, принцип, который я бы назвал - "не все так однозначно".
Вопрос - как вы относитесь к рефлексии в Java?
Досрочный ответ: рефлексия - это плохо, лучше не использовать. Если дошло до рефлексии - значит в архитектуре проблема.
Чтобы лучше разобраться в теме надо ответить на вопрос: а почему плохо?
Ответа два:
1) рефлексия позволяет нарушить принципы ООП и, следовательно, архитектуру приложения. Автор скрыл внутренности класса через private, а мы туда лезем своими "грязными руками")))
2) снижение производительности. Тут частично работает тот факт, что делаются лишние вызовы кода. Но самое главное - JIT компилятор плохо умеет оптимизировать такой код, т.к. он слишком динамический. Изменится может сам класс, который приходит на вход метода с рефлексией
Окей, инкапсуляция нарушается, код работает медленно. Не используем?
А что с поиском аннотаций по коду? Не своих - до них мы еще дойдем - чужих, чтобы получить некие метаданные об объекте. Большинство вариантов вот тут основано на рефлексии https://www.baeldung.com/java-scan-annotations-runtime
Или у нас есть аннотации, созданные с помощью Spring AOP - это проще, чем AspectJ, если у вас используется Spring. А Spring AOP использует динамические прокси, создаваемые в runtime https://www.baeldung.com/spring-aop-vs-aspectj А с помощью чего создается прокси в runtime - правильно, рефлексии.
Да что там AOP - создание бинов из @Configuration - это тоже вызов методов @Bean через рефлексию.
Почему же рефлексию используют и предлагают к использованию, если это такая проблемная технология?
Вернемся к двум ее недостаткам:
1) не надо использовать рефлексию для вызова private методов или доступа к private полям. Если такая задача встала - у вас в самом деле проблемы с архитектурой
2) не надо использовать рефлексию часто и при этом в высоконагруженных приложениях. Тот же Spring использует рефлексию только при старте приложения для инициализации прокси и бинов. И к слову в т.ч. и поэтому старт Spring приложения может быть долгим, и люди с этим борются, см. мой цикл статей про ускорение старта #java_start_boost Более того, разработчикам Spring для поддержки native image пришлось серьезно допиливать его в т.ч. из-за динамических прокси и @Configuration https://docs.spring.io/spring-boot/reference/packaging/native-image/introducing-graalvm-native-images.html
Итого: рефлексию стоит рассматривать как возможность получить метаданные о классе. Помня при этом о производительности.
И если вопрос упирается в производительность - всегда есть альтернативы рефлексии. Это работа с байт-кодом не в runtime:
1) compile time (свой компилятор, см. статью про AspectJ)
2) post-compile time (свой плагин для сборки, см. Jandex для поиска аннотаций https://smallrye.io/jandex/jandex/3.2.2/index.html)
3) load-time (свой агент+classloader, см. статью про AspectJ)
Все варианты сложнее в реализации и подключении к проекту, зато вносят минимальное влияние в runtime.
P.S. Да, если при упоминании о динамических прокси вы вспомнили про задачку с собесов о вложенном @Transactional - это оно. И ответ на этот вопрос не так очевиден https://habr.com/ru/articles/347752/
#java #reflection #dev_compromises
Одна из моих любимых тем: разработка - искусство компромиссов. Поиск по тэгам #dev_compromises и #arch_compromises. Следствие этого подхода, принцип, который я бы назвал - "не все так однозначно".
Вопрос - как вы относитесь к рефлексии в Java?
Досрочный ответ: рефлексия - это плохо, лучше не использовать. Если дошло до рефлексии - значит в архитектуре проблема.
Чтобы лучше разобраться в теме надо ответить на вопрос: а почему плохо?
Ответа два:
1) рефлексия позволяет нарушить принципы ООП и, следовательно, архитектуру приложения. Автор скрыл внутренности класса через private, а мы туда лезем своими "грязными руками")))
2) снижение производительности. Тут частично работает тот факт, что делаются лишние вызовы кода. Но самое главное - JIT компилятор плохо умеет оптимизировать такой код, т.к. он слишком динамический. Изменится может сам класс, который приходит на вход метода с рефлексией
Окей, инкапсуляция нарушается, код работает медленно. Не используем?
А что с поиском аннотаций по коду? Не своих - до них мы еще дойдем - чужих, чтобы получить некие метаданные об объекте. Большинство вариантов вот тут основано на рефлексии https://www.baeldung.com/java-scan-annotations-runtime
Или у нас есть аннотации, созданные с помощью Spring AOP - это проще, чем AspectJ, если у вас используется Spring. А Spring AOP использует динамические прокси, создаваемые в runtime https://www.baeldung.com/spring-aop-vs-aspectj А с помощью чего создается прокси в runtime - правильно, рефлексии.
Да что там AOP - создание бинов из @Configuration - это тоже вызов методов @Bean через рефлексию.
Почему же рефлексию используют и предлагают к использованию, если это такая проблемная технология?
Вернемся к двум ее недостаткам:
1) не надо использовать рефлексию для вызова private методов или доступа к private полям. Если такая задача встала - у вас в самом деле проблемы с архитектурой
2) не надо использовать рефлексию часто и при этом в высоконагруженных приложениях. Тот же Spring использует рефлексию только при старте приложения для инициализации прокси и бинов. И к слову в т.ч. и поэтому старт Spring приложения может быть долгим, и люди с этим борются, см. мой цикл статей про ускорение старта #java_start_boost Более того, разработчикам Spring для поддержки native image пришлось серьезно допиливать его в т.ч. из-за динамических прокси и @Configuration https://docs.spring.io/spring-boot/reference/packaging/native-image/introducing-graalvm-native-images.html
Итого: рефлексию стоит рассматривать как возможность получить метаданные о классе. Помня при этом о производительности.
И если вопрос упирается в производительность - всегда есть альтернативы рефлексии. Это работа с байт-кодом не в runtime:
1) compile time (свой компилятор, см. статью про AspectJ)
2) post-compile time (свой плагин для сборки, см. Jandex для поиска аннотаций https://smallrye.io/jandex/jandex/3.2.2/index.html)
3) load-time (свой агент+classloader, см. статью про AspectJ)
Все варианты сложнее в реализации и подключении к проекту, зато вносят минимальное влияние в runtime.
P.S. Да, если при упоминании о динамических прокси вы вспомнили про задачку с собесов о вложенном @Transactional - это оно. И ответ на этот вопрос не так очевиден https://habr.com/ru/articles/347752/
#java #reflection #dev_compromises
Baeldung
Scanning Java Annotations at Runtime | Baeldung
Learn about scanning Java annotations at runtime.
Всем привет!
Продолжаю читать книгу "Эффективная работа с унаследованным кодом". Наткнулся на интересную мысль, на первый взгляд подтверждающую тезис: разработка - искусство компромиссов.
Мы все используем библиотеки. Когда нужно написать тест (а книга в основном про то, как упростить написание тестов для legacy) - часто в наш класс передаются библиотечные объекты. И тут могут быть две проблемы:
1) объект-синглтон
2) final объект или объект с методами, которые не возможно переопределить.
Эти две проблемы приводят к одному и тому же - мы не можем создать mock, приходится инициализировать для тестов реальный объект из библиотеки. А он может быть "тяжелым", превращающий модульный тест в интеграционный.
Как вариант решения этой проблемы предлагается:
1) для singleton создавать фасад, который разрешает замену объекта через setter, т.е. по сути нарушать суть паттерна singleton
2) для final методов и объектов - убрать final, объявляя их логическими final в документации.
Оба предложения по сути об одном - нарушаем принципы проектирования на уровне языка, зато делаем возможным тестирование кода.
В целом - "соль" в этом есть. Код без тестов опаснее кода, нарушающего принципы проектирования. Но я бы уточнил, что оба метода - это крайние меры. А по хорошему, если вы разрабатываете код, который кто-то когда-то будет тестировать - надо заранее озаботится о тестируемости, и именно:
1) не создавать singleton самому, использовать для этого IoC контейнер. Spring если библиотека внутренняя, и у вас используется Spring, или CDI аннотации в других случаях. Причем это актуально не только для разработчиков библиотек, а для всех.
2) создавать интерфейсы для классов, вынесенных в клиентское API. Есть интерфейс - всегда можно создать mock в тесте без всяких ухищрений. Я против создания интерфейсов всегда и везде https://t.me/javaKotlinDevOps/235, но Java API - это как раз подходящий случай. Одних интерфейсов конечно же мало, нужно еще и четкое разделение на модель и сервисы. Первые можно использоваться AS IS в тестах, вторые часто приходится мокать. Да, спроектировать все правильно не так-то легко, но как говорится - дорогу осилит идущий.
#book_review #ood #dev_compromises #libraries
Продолжаю читать книгу "Эффективная работа с унаследованным кодом". Наткнулся на интересную мысль, на первый взгляд подтверждающую тезис: разработка - искусство компромиссов.
Мы все используем библиотеки. Когда нужно написать тест (а книга в основном про то, как упростить написание тестов для legacy) - часто в наш класс передаются библиотечные объекты. И тут могут быть две проблемы:
1) объект-синглтон
2) final объект или объект с методами, которые не возможно переопределить.
Эти две проблемы приводят к одному и тому же - мы не можем создать mock, приходится инициализировать для тестов реальный объект из библиотеки. А он может быть "тяжелым", превращающий модульный тест в интеграционный.
Как вариант решения этой проблемы предлагается:
1) для singleton создавать фасад, который разрешает замену объекта через setter, т.е. по сути нарушать суть паттерна singleton
2) для final методов и объектов - убрать final, объявляя их логическими final в документации.
Оба предложения по сути об одном - нарушаем принципы проектирования на уровне языка, зато делаем возможным тестирование кода.
В целом - "соль" в этом есть. Код без тестов опаснее кода, нарушающего принципы проектирования. Но я бы уточнил, что оба метода - это крайние меры. А по хорошему, если вы разрабатываете код, который кто-то когда-то будет тестировать - надо заранее озаботится о тестируемости, и именно:
1) не создавать singleton самому, использовать для этого IoC контейнер. Spring если библиотека внутренняя, и у вас используется Spring, или CDI аннотации в других случаях. Причем это актуально не только для разработчиков библиотек, а для всех.
2) создавать интерфейсы для классов, вынесенных в клиентское API. Есть интерфейс - всегда можно создать mock в тесте без всяких ухищрений. Я против создания интерфейсов всегда и везде https://t.me/javaKotlinDevOps/235, но Java API - это как раз подходящий случай. Одних интерфейсов конечно же мало, нужно еще и четкое разделение на модель и сервисы. Первые можно использоваться AS IS в тестах, вторые часто приходится мокать. Да, спроектировать все правильно не так-то легко, но как говорится - дорогу осилит идущий.
#book_review #ood #dev_compromises #libraries
Telegram
(java || kotlin) && devOps
Всем привет!
В коде я часто вижу раздражающий меня паттерн - интерфейс с одной реализацией.
Почему так делается и когда так делать не стоит?
Для начала - когда полезны интерфейсы:
1) соблюдение принципа инверсии зависимостей, буква D из SOLID. См. ht…
В коде я часто вижу раздражающий меня паттерн - интерфейс с одной реализацией.
Почему так делается и когда так делать не стоит?
Для начала - когда полезны интерфейсы:
1) соблюдение принципа инверсии зависимостей, буква D из SOLID. См. ht…
Всем привет!
Прочитал сегодня хороший пост (английский): https://korshakov.com/posts/death-of-best-practices
Чем же он хорош?
Иллюстрирует 2 мысли, про которые я топлю в своем блоге (и топлю в целом):
1) разработка - искусство компромиссов. Важно не абсолютное следование ... микросервисной архитектуре, принципам Single Responsibility или Dependency Inversion. Нет никакой магии в цифре 80% когда мы говорим о покрытии кода тестами. Денормализация таблиц из той же оперы. Важно выдавать результат, требуемый бизнесов. На код-ревью можно поспорить о принципах, но Pull Request должен быть влит в течение дня
2) быстрые, а следовательно частые релизы - наше все. Или по-менеджерски - Deployment Frequency и Lead Time (а это две грани одной и той же идеи). Это позволяет быстро править баги и уязвимости, адаптироваться к изменениям архитектуры и нормативным требованиям, проводить бета и A\B тестирование, соблюдать сроки выхода фичей.
Что спорно в посте - кликбейтный заголовок. Читать "Чистый код" или "Мифический человеко-месяц" - это не зло. Зло - следовать догматически отдельным идеям из этих книг, не разбираясь, что хотел сказать автор, и не адаптируя их к текущему проекту.
P.S. Тоже что ли кликбейтные заголовки начать делать)
#principles #dev_compomisses
Прочитал сегодня хороший пост (английский): https://korshakov.com/posts/death-of-best-practices
Чем же он хорош?
Иллюстрирует 2 мысли, про которые я топлю в своем блоге (и топлю в целом):
1) разработка - искусство компромиссов. Важно не абсолютное следование ... микросервисной архитектуре, принципам Single Responsibility или Dependency Inversion. Нет никакой магии в цифре 80% когда мы говорим о покрытии кода тестами. Денормализация таблиц из той же оперы. Важно выдавать результат, требуемый бизнесов. На код-ревью можно поспорить о принципах, но Pull Request должен быть влит в течение дня
2) быстрые, а следовательно частые релизы - наше все. Или по-менеджерски - Deployment Frequency и Lead Time (а это две грани одной и той же идеи). Это позволяет быстро править баги и уязвимости, адаптироваться к изменениям архитектуры и нормативным требованиям, проводить бета и A\B тестирование, соблюдать сроки выхода фичей.
Что спорно в посте - кликбейтный заголовок. Читать "Чистый код" или "Мифический человеко-месяц" - это не зло. Зло - следовать догматически отдельным идеям из этих книг, не разбираясь, что хотел сказать автор, и не адаптируя их к текущему проекту.
P.S. Тоже что ли кликбейтные заголовки начать делать)
#principles #dev_compomisses
Chaotic good engineering
Death of Best Practices
And why "clean code" is not the best way to build