Информация об изменениях

Сообщение О "наивном" DI и об архитектурном бессилии от 27.07.2016 6:40

Изменено 11.10.2016 8:12 IQuerist

О "наивном" DI и об архитектурном бессилии

Никогда я не имел желания холиварить на тему DI, т.к. во всех проектах, где обнаруживалось страстное желание автора использовать DI и над которыми мне приходилось работать, использование DI выглядело как совершенно очевидный антипаттерн и мне всегда было абсолютно не понятно для чего автор тратит время и силы ничего не получая взамен?

Собственно ситуация имхо довольно типичная — мне то "опыт подказывает", а молодежь старательно "набивает шишки", но при этом, я почему-то сходу не нашел ни одного внятного объяснения от популяризаторов DI того, почему наивное использование DI будет провальным. Поэтому я решил таки сформулировать свой вариант

Посмотрим на типичный пример наивного DI:

public class HomeController : Controller

public HomeController(
IBoringItemDataReader,
IBoringItemDataWriter,
BoringItemChildDataReader,
IBoringItemChildDataWriter,
IAppDataJsonConverter,
...
)

Гиперболизируя можно было бы добавить еще IJsonConverter чтобы механизм конвертирования json можно было изменить и что ни будь вроде IDateTimeProvider, работа с датой и временем вряд ли поменяется, но на всякий случай стоит "уменьшить зависимости" .

Посмотрим на реализацию IBoringItemDataReader:

public class BoringItemDataReader : IBoringItemDataReader

и с десяток совершенно очевидных хелперных stateless методов типа:

GetBoringItemById
GetBoringItemByNumber
GetBoringItemByDataInterval
...

Ребята... вот эта низкоуровневая "требуха" это что ли "сервис"? Вот эти все "внутренности наружу" это инкапсуляция и архитектура? Как получилось, что попытка "соблюсти" принцип DIP очевидно вызывает нарушение всех остальных принципов SOLID и на это закрывают глаза? Как можно внедрять "зависимость сервиса" в ситуации, когда разработчик не способен создать сам внедряемый сервис? Проблема тут имхо базовая — разработчик не имеет навыков создания объектной декомпозиции и пытается компенсировать это использованием сложных механизмов, смысла которых он не понимает.

Лично для меня DI изначально был всего лишь удобным механизмом построения "плагинной архитектуры". Часть знакомых уверяла, что DI жизненно необходим для тестирования, но тут ситуация становилась совсем абсурдной, т.к. архитектурное решение (использовать DI) вроде как принималось, но никаких тестов при этом не было и в будущем они никогда не появлялись.

Как-то так... имхо главная начальная проблема DI — неспособность разработчиков создавать объектные декомпозиции. Это ведет к созданию абсолютно неправильной, очень низкоуровневой и хрупкой системы зависимостей нарушающей 4 из 5 принципов SOLID. Пример того, как некоторые пытаются лечить гланды через задний проход. Кстати неплохое название для антипаттерна — Colonoscopy Injection и для всей братии зомбированных шаблонами "наивных архитекторов" — архитект-проктолог.

Update:

Обсуждение натолкнуло на мысль, что на самом деле наивный DI нарушает принцип DIP в первую очередь. Т.к. каноничная формулировка DIP гласит:

    Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
    Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

имхо совершенно очевидно, что наивный сервис слепленный из хелперных методов DAL, ни в коем случае не является "абстракцией". Он предельно конкретный с предельно конкретными entity типами даже если структура этих типов продублирована, а данные копируются с помощью мапперов (эдакий карго-культ "абстрации"). Имхо здесь совершенно четко определяемая проблемы — интерфейсы верхних уровней начинают определять низкоуровневые интерфейсы DAL. Разрозненные хелперные методы не перестанут быть хелперными методами от того, что их вызывают через интерфейс.

Update 2:

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

Вот в чем проблема и смысл топика, а вовсе не в том, что люди которым приходится отхреначивать DI из провальных проектов как-то не очень правильно понимают идеи DI.

Update 3

Мое имхо по итогам обсуждения поста:

Ребята это секта... секта свидетелей DI фреймворков, отпочковавшаяся от секты свидетелей TDD. Вообще имхо DI фреймворки появились из за того, что сектантам TDD было необходимо тестировать приватную логику, а по их канонам unit test этого делать нельзя. Поэтому был применен ряд шельмований, приватную логику перекрестили в сервисы вытащили их наружу. Здесь можно вспомнить COM, который решает аналогичные DI фреймворкам задачи, но в те времена не было движения TDD и поэтому COM не стал религией, а остался инфраструктурным решением.

Ни DI ни TDD ничего не гарантируют разработчику, ни от чего не защищают и ни в чем не помогают. Зато гарантированно служат серьезным источником оверхеда. Как же им удается иногда демонстрировать свою полезность? А все элементарно... например код веб систем, можно разделить на две части — бизнес логика и код взаимодействия с системными сервисами UI, DB и т.д. Взаимодействие с системными сервисами разработчику понятно, отлично проработано и имеет долгую историю. Что провоцирует наивные умы, особенно на первых этапах проекта, запихивать бизнес логику прямо в код который реализует взаимодействие с системными сервисами. Так создаются шедевры спагетти кода. Что делает TDD? Оно постулирует — сначала пишем тесты. Все верно, в юнит тестах нет взаимодействия с системными сервисами поэтому структуру бизнес логики приходится прорабатывать отдельно от всего т.е. приходится строить архитектуру. Вот и весь трюк. Цена такого подхода — значительный оверхед кода (уровень которого зависит от религиозного фанатизма разработчика), потому как бизнес логика гарантированно будет уточнятся и изменяться и тесты придется переписывать, но как мы теперь понимаем, для проработки бизнес логики и построения архитекуры тесты на самом деле лишь полезны, но никак не обязательны.

Update 4 (вероятно крайний)

После сосредоточения на теме обнаружился забавный баг. DI на деле выглядит примерно так:

public SomeClass (MyClass myObject)
{
this.myObject = myObject;
}

и не требует никаких интерфейсов и фреймворков. А все остальное от лукавого.

Хвала stackoverflow бельгийцы (!) сохранили способность воздерживаться от троллинга и сектантщины.

http://stackoverflow.com/questions/130794/what-is-dependency-injection

Особую благодарность хочу выразить ".", хотя клизьма с антитроллингоном ему тоже не повредит .
О "наивном" DI и об архитектурном бессилии

Никогда я не имел желания холиварить на тему DI, т.к. во всех проектах, где обнаруживалось страстное желание автора использовать DI и над которыми мне приходилось работать, использование DI выглядело как совершенно очевидный антипаттерн и мне всегда было абсолютно не понятно для чего автор тратит время и силы ничего не получая взамен?

Собственно ситуация имхо довольно типичная — мне то "опыт подказывает", а молодежь старательно "набивает шишки", но при этом, я почему-то сходу не нашел ни одного внятного объяснения от популяризаторов DI того, почему наивное использование DI будет провальным. Поэтому я решил таки сформулировать свой вариант

Посмотрим на типичный пример наивного DI:

public class HomeController : Controller

public HomeController(
IBoringItemDataReader,
IBoringItemDataWriter,
BoringItemChildDataReader,
IBoringItemChildDataWriter,
IAppDataJsonConverter,
...
)

Гиперболизируя можно было бы добавить еще IJsonConverter чтобы механизм конвертирования json можно было изменить и что ни будь вроде IDateTimeProvider, работа с датой и временем вряд ли поменяется, но на всякий случай стоит "уменьшить зависимости" .

Посмотрим на реализацию IBoringItemDataReader:

public class BoringItemDataReader : IBoringItemDataReader

и с десяток совершенно очевидных хелперных stateless методов типа:

GetBoringItemById
GetBoringItemByNumber
GetBoringItemByDataInterval
...

Ребята... вот эта низкоуровневая "требуха" это что ли "сервис"? Вот эти все "внутренности наружу" это инкапсуляция и архитектура? Как получилось, что попытка "соблюсти" принцип DIP очевидно вызывает нарушение всех остальных принципов SOLID и на это закрывают глаза? Как можно внедрять "зависимость сервиса" в ситуации, когда разработчик не способен создать сам внедряемый сервис? Проблема тут имхо базовая — разработчик не имеет навыков создания объектной декомпозиции и пытается компенсировать это использованием сложных механизмов, смысла которых он не понимает.

Лично для меня DI изначально был всего лишь удобным механизмом построения "плагинной архитектуры". Часть знакомых уверяла, что DI жизненно необходим для тестирования, но тут ситуация становилась совсем абсурдной, т.к. архитектурное решение (использовать DI) вроде как принималось, но никаких тестов при этом не было и в будущем они никогда не появлялись.

Как-то так... имхо главная начальная проблема DI — неспособность разработчиков создавать объектные декомпозиции. Это ведет к созданию абсолютно неправильной, очень низкоуровневой и хрупкой системы зависимостей нарушающей 4 из 5 принципов SOLID. Пример того, как некоторые пытаются лечить гланды через задний проход. Кстати неплохое название для антипаттерна — Colonoscopy Injection и для всей братии зомбированных шаблонами "наивных архитекторов" — архитект-проктолог.

Update:

Обсуждение натолкнуло на мысль, что на самом деле наивный DI нарушает принцип DIP в первую очередь. Т.к. каноничная формулировка DIP гласит:

    Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
    Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

имхо совершенно очевидно, что наивный сервис слепленный из хелперных методов DAL, ни в коем случае не является "абстракцией". Он предельно конкретный с предельно конкретными entity типами даже если структура этих типов продублирована, а данные копируются с помощью мапперов (эдакий карго-культ "абстрации"). Имхо здесь совершенно четко определяемая проблемы — интерфейсы верхних уровней начинают определять низкоуровневые интерфейсы DAL. Разрозненные хелперные методы не перестанут быть хелперными методами от того, что их вызывают через интерфейс.

Update 2:

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

Вот в чем проблема и смысл топика, а вовсе не в том, что люди которым приходится отхреначивать DI из провальных проектов как-то не очень правильно понимают идеи DI.

Update 3

Мое имхо по итогам обсуждения поста:

Ребята это секта... секта свидетелей DI фреймворков, отпочковавшаяся от секты свидетелей TDD. Вообще имхо DI фреймворки появились из за того, что сектантам TDD было необходимо тестировать приватную логику, а по их канонам unit test этого делать нельзя. Поэтому был применен ряд шельмований, приватную логику перекрестили в сервисы вытащили их наружу. Здесь можно вспомнить COM, который решает аналогичные DI фреймворкам задачи, но в те времена не было движения TDD и поэтому COM не стал религией, а остался инфраструктурным решением.

Ни DI ни TDD ничего не гарантируют разработчику, ни от чего не защищают и ни в чем не помогают. Зато гарантированно служат серьезным источником оверхеда. Как же им удается иногда демонстрировать свою полезность? А все элементарно... например код веб систем, можно разделить на две части — бизнес логика и код взаимодействия с системными сервисами UI, DB и т.д. Взаимодействие с системными сервисами разработчику понятно, отлично проработано и имеет долгую историю. Что провоцирует наивные умы, особенно на первых этапах проекта, запихивать бизнес логику прямо в код который реализует взаимодействие с системными сервисами. Так создаются шедевры спагетти кода. Что делает TDD? Оно постулирует — сначала пишем тесты. Все верно, в юнит тестах нет взаимодействия с системными сервисами поэтому структуру бизнес логики приходится прорабатывать отдельно от всего т.е. приходится строить архитектуру. Вот и весь трюк. Цена такого подхода — значительный оверхед кода (уровень которого зависит от религиозного фанатизма разработчика), потому как бизнес логика гарантированно будет уточнятся и изменяться и тесты придется переписывать, но как мы теперь понимаем, для проработки бизнес логики и построения архитекуры тесты на самом деле лишь полезны, но никак не обязательны.

Update 4 (вероятно крайний)

После сосредоточения на теме обнаружился забавный баг. DI на деле выглядит примерно так:

public SomeClass (MyClass myObject)
{
this.myObject = myObject;
}

и не требует никаких интерфейсов и фреймворков. А все остальное от лукавого.

Хвала stackoverflow бельгийцы (!) сохранили способность воздерживаться от троллинга и сектантщины.

http://stackoverflow.com/questions/130794/what-is-dependency-injection

Особую благодарность хочу выразить ".", хотя клизьма с антитроллингоном ему тоже не повредит .

Т.е. все мои претензии высказанные в посте по отношению к DI стоит считать претензиями к DI фреймворкам. И получается любопытный вывод — если с простым DI ваши проекты, с завидным постоянством превращаются в big ball of mud, то как использование DI фреймворка может это предотвратить?