Как стать автором
Обновить

Комментарии 13

Почему в андройде многие используют унылый Dagger 2, пишут кучу бойлерплэйт кода, все эти Module для каждого фрагмента и т.п. Потом когда надо что-то менять, то начинают править все модули?
Почему не использовать простой Koin? Это джавовская привычка всё усложнять или есть какой-то реальный профит от Dagger 2?
А) Первый релиз Koin был «on Jun 12, 2017 0.1.0», Dagger более проверенная библиотека. Использовать в коммерческом продакшене не проверенные библиотеки просто неразумно.

В) Был у Вас проект чисто на Java с легаси в 4 года, что лучше? Поддерживать предыдущий вреймворк для DI или внедрить новый (чтобы ничего не раздолбать)?

С) Все зависит от задач. Иногда лучше вложить в «болерплейт» но иметь более гибкий функционал. Если это домашний проект который может поддерживать 1-2 программиста и все свеженькое и только на Котлине — пожалуйста. Если у вас команда 40+ разработчиков и вы пилите монорепу то стоит задуматься.

Все еще зависит от вашей зоны ответсвенности и как вы оцениваете долгосрочные (3+ года) перспективы приложения. Junior & Intermediate обычно пушат за фэнси фреймворки, Senior задумываются о целесообразности в Architecture старается сделать так, чтобы при любом условии шоколадная масса как можно дольше не растекалась.
Я спрашиваю конкретно про Android-приложения, а вы про какой-то кровавый интерпрайз. Android-приложение часто пишет один-два девелопера, или команда из 7 человек и часто в условиях ужатых бюджетов.
Потом все эти абстракции про хорошую архитектуру меня не сильно интересуют. Меня интересует:
1. Скрость разработки. Как быстро я смогу сделать новую фичу.
2. Простота сопровождения. Насколько долго нужно курить мануал, чтобы что-то сделать новому человеку на проекте. И чтобы было меньше способов выстрелить себе в ногу.
3. Насколько просто писать тесты.

Потому, например, мы на Android перешли на Kotlin и его синтаксический сахар очень упрощает нам жизнь.
Доводилось мне видеть некоторое дерь приложения, «архитектура которых не сильно интересовала разработчиков». Вот честно — лучше бы там была архитектура, чем новомодности и быстро-фреймворки. (Против котлина ничего против не имею)
Профит точно такой же как и от Koin — зависимости внедряются!
Очень слабое обоснование, книжный пример.
Нет ни слова про инстанциирование и RAII, есть масса примеров когда RAII имеет место быть в языках с управляемой памятью.
В общем случае нет ничего плохого, что один класс инстанциирует внутри другой. Когда вы выносите зависимый класс в конструктор, то всего лишь меняет отношении с композиции на агрегацию и если в первом случае время жизни объектов гарантированно совпадает, то во втором уже — зависит.
Ни слова не говорится о видимости объектов. Если класс A публичный, а B и С пакетные? Для чего мне надо выставлять еще один тип(класс B) в публичный интерфейс?

Абсолютно не говорится о том, когда надо применять DI, какие плюсы и минусы этого решения, с какой гранулярностью надо применять DI.

Потом из-за таких статей видишь вагоны типов в публичном контракте либы и вызовы в глубинных классах container.Resolve(someType).
Такое ощущение, что автор просто наращивает трафик к своему бложику по андроид девелопменту.
В общем случае нет ничего плохого, что один класс инстанциирует внутри другой.


Тут можно поспорить. Использование оператора new порождает жесткую связанность.
Жесткая связанность не имеет в себе ничего плохого, если мы говорим о моделях или об утилитарных классах, которые не обращаются к любому источнику данных (файлы, БД, веб-сервисы и т/д). Скажем, глупо передавать через конструктор StringBuilder.
В остальных случаях жесткая связанность — это катастрофа, которая превращает написание юнит/интеграционных тестов в пытку.
Предположим, у нас есть класс, который использует сервис, обращающийся к внешнему АПИ.
И если мы инстанцируем его внутри класса, то это всегда будет реальное АПИ и мы не можем его замокать без хаков.
Да, есть PowerMock, рефлексия и так далее, но это уже жесткие хаки, которых хотелось бы избежать.
Предположим, у нас есть класс, который использует сервис, обращающийся к внешнему АПИ.
И если мы инстанцируем его внутри класса, то это всегда будет реальное АПИ и мы не можем его замокать без хаков.
В этом случае конечно да, но даже в вашем случае, намного важнее иметь четко обозначенный контракт сервиса, нежели фактическое место его инстанциирования. Если такой контракт есть, то вытащить контруктор в контейнер проблем не составит. А ведь могут написать и так, что в интерфейсе будет куча методов, которые нужны только конкретному клиенту. Первое что приходит в голову:
if (!service.isInitialized()){
service.init(/* some parameters или например this*/);
// А текущйи тип что-нить реализует типа IParameterSource
}
И вроде DI и все за интерфейсами с контейнером, а в результате сложно трассируемая дичь. Хотя тестами покрыть такую муть не проблема.

Код вроде такого, заменить на использование контейнера сильно проще.
new Service(AppContext.param1(), AppContext.patam2())
Лаконичный простой и понятный код легче модифицировать, чем пытаться что-то реализовать на будущее.

Т.е. я к тому, что DI далеко не панацея, если пишут г-но делы, то они и с DI такого понаделают.
НЛО прилетело и опубликовало эту надпись здесь

Не знаю, но термин "dependency injection" мне звучит как законная цель антивируса. :D

Третий способ: пусть кто-нибудь ещё обрабатывает зависимости вместо нас

Недостатки

Неа. Основной недостаток всех DI-фреймворков — неявное динамическое связывание в рантайме. Это затрудняет анализ и рефакторинг кода, ослабляет контроль над выполнением, и делает возможным компиляцию и запуск кода с невалидными зависимостями. Хотя есть фреймворки типа Dagger, которые делают связывание при компиляции.


Для простого DI вообще не нужно никакого фреймворка. Делаете класс-контекст ручками так, как вам это заблагорассудится, и определяете в нем все зависимости. Например так:


// лучше назвать его ApplicationContext
class OurThirdPartyGuy {
  fun provideClassC(){
    return ClassC() //just creating an instance of the object and return it.
  }

  // каждый метод должен возвращать уже полностью настроенный объект и не содержать параметров
  fun provideClassB(){
    return ClassB(provideClassC())
  }

  fun provideClassA(){
    return ClassA(provideClassB())
  }
}

Разница лишь в том, что в фреймворк создает класс контекста автоматически на основании расставленных аннотаций и правил. И фреймворк зачастую делает несколько больше, нежели просто DI — например, управление конфигурациями, управление жизненным циклом объектов (scopes), AOP и проксирование методов. Но при необходимости все это так же не составит труда сделать ручками.

Это затрудняет анализ и рефакторинг кода, ослабляет контроль над выполнением, и делает возможным компиляцию и запуск кода с невалидными зависимостями

По пунктам:
Анализ и рефакторинг кода — если понимать как работает фреймворк — то ни капельки не затрудняет. Даже наоборот.
Ослабляет контроль над выполнением — каким образом?
Компиляцию и запуск кода с невалидными зависимостями — для этого есть тесты. Напортачили с зависимостями — тесты упали. Если тестов нет — это обнаружится при стартапе приложения. Деплоить сразу в прод никто же не станет?)

Но при необходимости все это так же не составит труда сделать ручками.

Зачем? Просто зачем писать руками то, что написано давно за нас?
Анализ и рефакторинг кода — если понимать как работает фреймворк — то ни капельки не затрудняет.

Очень даже. Возьмите чужой код, и попробуйте сразу разобраться, как он работает. Для анализа так или иначе вам придется использовать функции поиска вашей IDE типа "Find Usages...". При использовании фреймворка, где объекты не создаются въявную в коде и не содержат статических ссылок, такой анализ станет более трудоемким.


Ослабляет контроль над выполнением — каким образом?

Последовательность инициализации бинов не определена (бывают случаи, где это важно, например инициализация сервисов при стартапе), еще хуже обстоят дела с финализацией — не определено когда стопаются объекты и в какой последовательности. Концепция "областей живучести" (scopes — не знаю как перевести) работает не всегда так, как ожидается, например если вы инъектируете прототайп-бин в синглтон, то он такаже становится синглтонам, что критично для не thread-safe объектов. Использование AOP позволяет динамически модифицировать поведение объекта, что иногда запутывает (некоторые умудряются засандалить в AOP бизнес-логику).


Компиляцию и запуск кода с невалидными зависимостями — для этого есть тесты.

Для юнит-тестов используется как правило специально заряженный тестовый контекст, урезанный и мокированный. Опробовать реальный контекст Вы сможете только, подняв полное приложение, в окружении, наиболее приближенном к продакшну. А это уже интеграционное тестирование, и цикл у него несоизмеримо бОльший.


Зачем? Просто зачем писать руками то, что написано давно за нас?

А что такого супер магического написано-то? Как я уже сказал, типичный DI-фреймворк автоматически, то есть неявно, создает контекст приложения на основании большого числа факторов: модулей, правил, файлов конфигурации, аннотаций объектов, сканирования classpath… Поэтому на этапе чтения кода очень трудно определить как именно будет построен контекст. И это есть явный антипаттерн. А если убрать автоконфигурацию, то от вашего фреймворка ничего и не останется.


P.S. я не против DI-фреймворков, сам давно использую Guice. Но меня жутко бесит, когда для связи трех бинов сразу тащат в проект какой-нибудь Spring.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории