Инъекция зависимости: как ее продать [закрыто]

95

Пусть будет известно, что я большой поклонник внедрения зависимостей (DI) и автоматизированного тестирования. Я мог бы говорить об этом весь день.

Фон

Недавно наша команда только что получила этот большой проект, который должен быть построен с нуля. Это стратегическое приложение со сложными бизнес-требованиями. Конечно, я хотел, чтобы он был красивым и чистым, что для меня означало: ремонтопригодность и возможность тестирования. Поэтому я хотел использовать DI.

сопротивление

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

Мой ход

Это может звучать странно, но сторонние библиотеки обычно не одобрены нашей командой архитекторов (подумайте: «не говорите о Unity , Ninject , NHibernate , Moq или NUnit , чтобы я не порезал вам палец»). Поэтому вместо того, чтобы использовать установленный DI-контейнер, я написал чрезвычайно простой контейнер. Он в основном связал все ваши зависимости при запуске, внедрил все зависимости (конструктор / свойство) и удалил любые одноразовые объекты в конце веб-запроса. Это было чрезвычайно легко и просто делало то, что нам было нужно. И тогда я попросил их пересмотреть это.

Ответ

Ну, чтобы сделать это коротким. Я встретил тяжелое сопротивление. Основным аргументом было: «Нам не нужно добавлять этот уровень сложности в уже сложный проект». Также «Это не значит, что мы будем подключать различные реализации компонентов». И «Мы хотим сделать это простым, если возможно, просто собрать все в одну сборку. DI - это ненужная сложность без выгоды».

Наконец мой вопрос

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

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

Обновить

Спасибо за ответы всех. Это действительно ставит вещи в перспективе. Достаточно приятно иметь другой взгляд, чтобы дать вам обратную связь, пятнадцать - это действительно здорово! Это действительно хорошие ответы, которые помогли мне увидеть проблему с разных сторон, но я могу выбрать только один ответ, поэтому я просто выберу один из лучших. Спасибо всем, что нашли время ответить.

Я решил, что это, вероятно, не лучшее время для внедрения DI, и мы не готовы к этому. Вместо этого я сконцентрирую свои усилия на том, чтобы сделать тестируемую конструкцию и попытаться представить автоматизированное модульное тестирование. Я знаю, что написание тестов - это дополнительные издержки, и если когда-нибудь будет решено, что дополнительные издержки не стоят того, лично я все равно буду считать это выигрышной ситуацией, поскольку дизайн все еще подлежит тестированию. И если когда-либо выбор или DI - выбор в будущем, дизайн может легко справиться с этим.

Mel
источник
64
Звучит так, будто вам нужно переехать в другую компанию ...
Сардатрион
24
Использование DI является (по крайней мере, в Java, C #, ...) почти естественным следствием использования модульных тестов. Использует ли ваша компания модульные тесты?
Себастьянгеигер
27
Или просто не фокусироваться на DI. ДИ отличается от «ремонтопригодного и проверяемого». Когда есть разные мнения о проекте, вы должны составить и иногда принять, что вы не решаете. Я, например, видел катастрофы, усиленные инверсией элементов управления, DI и несколькими уровнями структур, делающими сложные вещи, которые должны быть простыми (я не могу спорить в вашем случае).
Денис Сегюре
34
"... Я мог бы говорить об этом весь день." Похоже, вам нужно прекратить зацикливаться на чем-то, чем чаще всего злоупотребляют из-за догматической приверженности какой-то белой книге дня.
21
Судя по всему, у ваших коллег есть объективные аргументы, которые говорят против использования DI-контейнеров: «нам не нужно добавлять этот уровень сложности в и без того сложный проект» - возможно, они правы? У вас на самом деле выгоды от дополнительной сложности? Если так, то об этом можно поспорить. Они утверждают, что «DI - это ненужная сложность без какой-либо выгоды». Если вы можете показать объективную выгоду, то вам следует выиграть это довольно легко, не так ли? Тестирование упоминается довольно часто в других комментариях; но тестирование принципиально не нуждается в DI, если только вам не нужны макеты (что не является данностью).
Конрад Рудольф

Ответы:

62

Взяв пару встречных аргументов:

«Мы хотим, чтобы все было просто, если возможно, просто соберите все в одну сборку. DI - ненужная сложность без выгоды».

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

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

Продайте ДИ на тех преимуществах тестирования, которые оно вам дает. Если проект сложный, то вам понадобятся хорошие, надежные юнит-тесты.

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

Здесь я говорю о том, что вам нужно рассмотреть преимущества, которые приносит DI, а не спорить о DI ради DI. Заставляя людей согласиться с утверждением:

«Нам нужны X, Y и Z»

Затем вы переносите проблему. Вы должны убедиться, что DI является ответом на это утверждение. Таким образом, вы, коллеги, будете владеть решением, а не чувствовать, что оно навязано им.

ChrisF
источник
+1. Кратко и по существу. К сожалению, исходя из аргументов, выдвинутых коллегами Мела, ему будет трудно убедить их, что стоит даже попробовать - то, что сказал Спойк в своем ответе, скорее проблема людей, чем техническая проблема.
Патрик Свик
1
@ Trustme-I'maDoctor - О, я согласен, что это проблема людей, но с этим подходом вы демонстрируете преимущества, а не спорите о DI ради себя.
ChrisF
Правда, и я желаю ему удачи, его жизнь как разработчика будет проще с надлежащей тестируемостью и т. Д. :)
Patryk Ćwiek
4
+1 Я думаю, что ваш последний абзац должен быть первым. Это главное в проблеме. Если бы была предложена возможность модульного тестирования, и было предложено преобразовать всю систему в модель DI, я бы тоже сказал ему «нет».
Эндрю Т Финнелл
+1 очень полезно на самом деле. Я бы не сказал, что DI является абсолютной необходимостью для модульного тестирования, но он делает вещи намного проще.
Мел
56

Из того, что вы пишете, не DI сам по себе является табу, но использование DI-контейнеров, а это совсем другое. Я полагаю, что у вас не возникнет проблем с вашей командой, когда вы просто напишите независимые компоненты, которые получат другие компоненты, которые им нужны, например, через конструктор. Только не называй это "DI".

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

Док Браун
источник
10
+1 прагматический обходной путь в политический / догматический тупик
k3b
32
В любом случае стоит подумать о подключении всех ваших объектов вручную и вводить DI-контейнер только тогда, когда он начинает становиться волосатым. Если вы сделаете это, они не будут видеть контейнеры DI как добавленную сложность - они увидят это как добавление простоты.
Фил
2
Единственная проблема состоит в том, что паттерном передачи слабосвязанных зависимостей в иждивенцев является DI. IoC, или использование «контейнера» для разрешения слабосвязанных зависимостей, представляет собой автоматизированный DI.
KeithS
51

Так как вы спросили, я встану на сторону вашей команды против DI.

Дело против внедрения зависимости

Существует правило, которое я называю «Сохранение сложности», которое аналогично правилу в физике, называемому «Сохранение энергии». В нем говорится, что никакие технические разработки не могут уменьшить общую сложность оптимального решения проблемы, все, что он может сделать, - это изменить эту сложность. Продукты Apple не менее сложны, чем продукты Microsoft, они просто переносят сложность из пользовательского пространства в инженерное пространство. (Однако это ошибочная аналогия. Хотя вы не можете создавать энергию, инженеры имеют неприятную привычку создавать сложности на каждом шагу. На самом деле, многим программистам нравится добавлять сложность и использовать магию, что по определению сложность, которую программист не до конца понимает. Эту избыточную сложность можно уменьшить. Обычно то, что выглядит как уменьшение сложности, просто переносит сложность куда-то еще, обычно в библиотеку.

Точно так же DI не уменьшает сложность связывания клиентских объектов с серверными объектами. Что он делает, так это берет его из Java и перемещает его в XML. Это хорошо, потому что он объединяет все в один (или несколько) XML-файл (-ы), где легко увидеть все, что происходит, и где можно что-то изменить, не перекомпилируя код. С другой стороны, это плохо, потому что файлы XML не являются Java и, следовательно, недоступны для инструментов Java. Вы не можете отлаживать их в отладчике, вы не можете использовать статический анализ для отслеживания их иерархии вызовов и так далее. В частности, ошибки в структуре DI становятся чрезвычайно трудными для обнаружения, идентификации и исправления.

Поэтому намного сложнее доказать правильность программы при использовании DI. Многие люди утверждают, что программы, использующие DI, легче тестировать , но тестирование программ никогда не сможет доказать отсутствие ошибок . (Обратите внимание, что связанному документу около 40 лет, и поэтому он имеет некоторые тайные ссылки и цитирует некоторые устаревшие практики, но многие основные утверждения все еще остаются в силе. В частности, что P <= p ^ n, где P - вероятность того, что в системе нет ошибок, p - вероятность того, что компонент системы не содержит ошибок, а n - количество компонентов. Конечно, это уравнение упрощено, но оно указывает на важную истину.) Авария NASDAQ во время IPO Facebook - недавний пример, гдеони утверждали, что потратили 1000 часов на тестирование кода и до сих пор не обнаружили ошибок, вызвавших сбой. 1000 часов - это половина человеко-года, поэтому они не скупятся на тестирование.

Правда, вряд ли кто-нибудь когда-либо предпримет (не говоря уже о том, чтобы выполнить) задачу формальной проверки правильности любого кода, но даже слабые попытки доказательства выбивают большинство режимов тестирования для поиска ошибок. Сильные попытки доказательства, например L4.verified , неизменно выявляют большое количество ошибок, которые тестирование не обнаруживало и, вероятно, никогда не обнаружит, если тестировщик уже не знал точную природу ошибки. Все, что затрудняет проверку кода при проверке, можно рассматривать как уменьшение вероятности обнаружения ошибок , даже если это увеличивает возможность тестирования.

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

Инъекция зависимости также не требуется для инверсии зависимости . Вы можете легко использовать статические фабричные методы для реализации инверсии зависимости. Именно так и было сделано в 1990-х годах.

По правде говоря, хотя я использую DI в течение десятилетия, единственное преимущество, которое он обеспечивает, я нахожу убедительным, - это возможность настроить сторонние библиотеки для использования сторонних библиотек (например, настроить Spring для использования Hibernate и оба для использования Log4J). ) и включение IoC через прокси. Если вы не используете сторонние библиотеки и не используете IoC, то в этом нет особого смысла, и это действительно добавляет необоснованную сложность.

Old Pro
источник
1
Я не согласен, возможно уменьшить сложность. Я знаю, потому что я сделал это. Конечно, некоторые проблемы / требования обуславливают сложность, но сверх того большая ее часть может быть устранена с усилием.
Рики Кларксон
10
@ Рики, это тонкое различие. Как я уже говорил, инженеры могут добавить сложность, и эту сложность можно убрать, так что вы, возможно, уменьшили сложность реализации, но по определению вы не можете уменьшить сложность оптимального решения проблемы. Чаще всего то, что выглядит как уменьшение сложности, - это просто перенести его в другое место (обычно в библиотеку). В любом случае, это второстепенно по отношению к моему основному выводу: DI не уменьшает сложность.
Old Pro
2
@ Ли, настройка DI в коде еще менее полезна. Одним из наиболее распространенных преимуществ DI является возможность изменять реализацию службы без перекомпиляции кода, и если вы настраиваете DI в коде, вы теряете это.
Old Pro
7
@OldPro - Конфигурирование в коде имеет преимущество безопасности типов, и большинство зависимостей являются статическими и не имеют преимуществ от внешнего определения.
Ли
3
@ Ли Если зависимости статичны, то почему они вообще не жестко запрограммированы, без обхода через DI?
Конрад Рудольф
25

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

  • «Это всегда проблема людей»
  • « Перемены страшные»

Конечно, вы можете привести множество контраргументов с вескими техническими причинами и преимуществами, но это поставит вас в плохую ситуацию с остальной частью компании. У них уже есть позиция, что они этого не хотят (потому что им удобно, как все работает сейчас). Так что это может даже повредить вашим отношениям с коллегами, если вы будете настойчивы в этом. Поверьте мне в этом, потому что это случилось со мной и в конечном итоге меня уволили несколько лет назад (молодым и наивным, каким я был); это ситуация, в которую ты не хочешь ввязываться.

Дело в том, что вам нужно достичь определенного уровня доверия, прежде чем люди зайдут так далеко, чтобы изменить свой нынешний способ работы. Если вы не находитесь в положении, когда вам доверяют, или вы не можете решать такие вещи, как роль архитектора или технического руководителя, вы очень мало можете сделать, чтобы повернуть своих коллег. Потребуется много времени, чтобы убедить и приложить усилия (например, предпринять небольшие шаги по улучшению), чтобы завоевать доверие и изменить свою корпоративную культуру. Мы говорим о временном интервале в несколько месяцев или лет.

Если вы обнаружите, что текущая культура компании не работает с вами, то, по крайней мере, вы знаете еще одну вещь, которую следует спросить на следующем собеседовании. ;-)

оборота Спойк
источник
23

Не используйте DI. Забудь это.

Создайте реализацию фабричного шаблона GoF, который «создает» или «находит» ваши конкретные зависимости и передает их вашему объекту, оставляя его при этом, но не называйте его DI и не создавайте сложную обобщенную структуру. Держите это простым и актуальным. Убедитесь, что зависимости и ваши классы таковы, что переключение на DI возможно; но держите фабрику мастером и держите ее чрезвычайно ограниченной по объему.

Со временем можно надеяться, что он вырастет и даже в один прекрасный день перейдет на DI. Пусть ведущие приходят к выводу самостоятельно и не торопятся. Даже если вы никогда этого не достигнете, по крайней мере, ваш код имеет некоторые преимущества DI, например, код, тестируемый модулем.

jasonk
источник
3
+1 за рост вместе с проектом и «не называй это DI»
Кевин Маккормик
5
Я согласен, но в конце концов, это DI. Он просто не использует контейнер DI; он передает нагрузку вызывающей стороне, а не централизует ее в одном месте. Однако было бы разумно называть это «Внешними зависимостями» вместо DI.
Хуан Мендес
5
Мне часто приходило в голову, что для того, чтобы действительно зацепить концепцию башни из слоновой кости, такой как DI, вы должны были пройти тот же процесс, что и евангелисты, чтобы осознать, что в первую очередь нужно решить проблему. Авторы часто забывают об этом. Придерживайтесь языка шаблонов низкого уровня, и в результате может возникнуть (или не возникнуть) необходимость в контейнере.
Том W
@JuanMendes из их ответа Я думаю, что именно экстернализация зависимостей - это именно то, против чего они выступают, потому что им тоже не нравятся сторонние библиотеки, и они хотят всего в одной сборке (монолит?). Если это правда, то называть это «Внешними зависимостями» не лучше, чем вызывать DI с их точки зрения.
Джонатан Нойфельд
22

Я видел проекты, которые доводили DI до крайности, чтобы проводить модульное тестирование и макетировать объекты. Настолько, что конфигурация DI стала языком программирования сама по себе с такой конфигурацией, которая необходима для работы системы, как с реальным кодом.

Страдает ли система сейчас из-за отсутствия соответствующих внутренних API-интерфейсов, которые невозможно протестировать? Вы надеетесь, что переключение системы на модель DI позволит вам лучше провести модульное тестирование? Вам не нужен контейнер для модульного тестирования вашего кода. Использование модели DI позволит вам проще провести модульное тестирование с объектами Mock, но это не обязательно.

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

Думайте о CodeRot как о болезни. Вы не хотите начинать произвольно взламывать вещи. У вас есть пациент, вы видите больное место, поэтому вы начинаете фиксировать это место и все вокруг него. Как только эта область станет чистой, вы переходите к следующей. Рано или поздно вы коснетесь большей части кода, и для этого не потребовалось это «масштабное» изменение архитектуры.

Мне не нравится, что тестирование DI и Mock являются взаимоисключающими. И после того, как я увидел проект, который, из-за отсутствия лучшего слова, безумно использовал DI, я бы тоже устал, не видя пользы.

Есть несколько мест, где есть смысл использовать DI:

  • Уровень доступа к данным для обмена стратегиями создания данных
  • Уровень аутентификации и авторизации.
  • Темы для пользовательского интерфейса
  • Сервисы
  • Точки расширения плагина, которые может использовать ваша собственная система или сторонние разработчики.

Требование, чтобы каждый разработчик знал, где находится файл конфигурации DI (я понимаю, что контейнеры могут быть созданы с помощью кода, но зачем вам это делать.), Когда они хотят выполнить RegEx, разочаровывает. О, я вижу, что есть IRegExCompiler, DefaultRegExCompiler и SuperAwesomeRegExCompiler. Тем не менее, нигде в коде я не могу выполнить анализ, чтобы увидеть, где используется Default и SuperAwesome.

Моя личность отреагировала бы лучше, если бы кто-то показал мне что-то лучше, а затем попытался бы убедить меня своими словами. Есть кое-что, что можно сказать о ком-то, кто потратит время и силы на улучшение системы. Я не считаю, что многие разработчики заботятся о том, чтобы сделать продукт по-настоящему лучше. Если бы вы могли обратиться к ним с реальными изменениями в системе и показать им, как она сделала это лучше и проще для вас, это стоит больше, чем любое онлайн-исследование.

Таким образом, лучший способ добиться изменений в системе - медленно делать код, к которому вы сейчас обращаетесь, лучше с каждым проходом. Рано или поздно вы сможете превратить систему в нечто лучшее.

оборота Эндрю Финнелл
источник
«Тестирование DI и Mock взаимоисключающие» Это неверно, они также не взаимоисключающие. Они просто хорошо работают вместе. Вы также перечислили места, где DI хорош, сервисный уровень, дао и пользовательский интерфейс - это почти везде, не так ли?
НимЧимпский
@ Добавить действительные баллы. Конечно, я бы не хотел, чтобы это зашло слишком далеко. Больше всего я хотел автоматизированное тестирование для бизнес-классов, так как мы должны внедрять очень сложные бизнес-правила. И я понимаю, что все еще могу тестировать их без контейнера, но лично мне имеет смысл использовать контейнер. Я на самом деле сделал небольшой образец, который показал концепцию. Но на это даже не смотрели. Я предполагаю, что моя ошибка была в том, что я не создал тест, который показал бы, насколько легко тестировать / издеваться.
Мел
Так много согласия! Я действительно очень ненавижу файлы конфигурации DI и их влияние на отслеживание потока программ. Все это превращается в «жуткое действие на расстоянии» без видимых связей между частями. Когда становится легче читать код в отладчике, чем в исходных файлах, что-то действительно не так.
Zan Lynx
19

DI не требует инвазивных контейнеров Inversion of Control или поддельных структур. Вы можете отделить код и позволить DI через простой дизайн. Вы можете проводить модульное тестирование с помощью встроенных возможностей Visual Studio.

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

Просто помните, что компромисс необходим в этих сценариях.

Теластин
источник
2
Я согласен, DI не требует контейнеров IoC. хотя я бы не назвал это инвазивным (по крайней мере, этой реализацией), так как код был спроектирован так, чтобы быть независимым от контейнера (главным образом, инъекции конструктора) и все вещи DI происходят в одном месте. Таким образом, все это все еще работает без контейнера DI - я помещаю в конструкторы без параметров, которые «внедряют» конкретные реализации в другой конструктор с зависимостями. Таким образом, я все еще могу легко высмеивать это. Так что да, я согласен, DI на самом деле достигается за счет дизайна.
Мел
+1 за компромисс. Если никто не хочет идти на компромисс, все заканчивают страданием.
Тьяарт
14

Я не думаю, что вы можете продавать DI больше, потому что это догматическое решение (или, как @Spoike назвал его более вежливым, политическим ).

В этой ситуации спорить с общепринятыми лучшими практиками, такими как принципы разработки SOLID, уже невозможно.

Кто-то, кто имеет больше политической власти, чем вы, принял (возможно, неправильное) решение не использовать DI.

Вы, с другой стороны, выступили против этого решения, внедрив собственный контейнер, поскольку использование установленного контейнера было запрещено. Эта политика свершившегося факта, которую вы попробовали, могла еще больше усугубить ситуацию.

Анализ

По моему мнению, DI без разработки на основе тестов (TDD) и модульного тестирования не имеет существенного преимущества, которое перевешивает первоначальные дополнительные затраты.

Этот вопрос действительно о

  • мы не хотим DI ?

или это больше о

  • tdd / unittesting это пустая трата ресурсов ?

[Обновить]

Если вы видите свой проект из классических ошибок в представлении управления проектами , вы видите

#12: Politics placed over substance.
#21: Inadequate design. 
#22: Shortchanged quality assurance.
#27: Code-like-hell programming.

пока остальные видят

#3: Uncontrolled problem employees. 
#30: Developer gold-plating. 
#32: Research-oriented development. 
#34: Overestimated savings from new tools or methods. 
k3b
источник
В настоящее время мы не проводим модульное тестирование в нашей команде. Я подозреваю, что ваше последнее заявление точно. Я несколько раз проводил модульное тестирование, но никто не проявлял к нему особого интереса, и всегда были причины, по которым мы не могли его принять.
Мел
10

Если вам придется «продать» принцип своей команде, то вы, вероятно, уже прошли несколько миль по неверному пути.

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

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

Поместите его на место.
Лучше всего использовать DI там, где это явно полезно, а не там, где это обычно. Например, многие приложения используют DI для выбора и настройки баз данных. И если ваше приложение поставляется со слоем базы данных, то это заманчивое место для его размещения. Но если ваше приложение поставляется со встроенной невидимой пользователем базой данных, которая в остальном тесно интегрирована в код, то шансы на необходимость замены БД во время выполнения приближаются к нулю. И продажа DI, говоря: «Послушай, мы легко можем сделать то, что мы никогда и никогда не захотим делать», - это, вероятно, поставит тебя в список «у ​​этого парня плохие идеи» с твоим боссом.

Но, скажем, вместо этого у вас есть отладочный вывод, который обычно выводит IFDEF в выпуске. И каждый раз, когда у пользователя возникают проблемы, ваша группа поддержки должна отправить ему отладочную сборку, чтобы он мог получить вывод журнала. Здесь вы можете использовать DI, чтобы клиент мог переключаться между драйверами журналов - LogConsoleдля разработчиков, LogNullдля клиентов и LogNetworkдля клиентов с проблемами. Одна унифицированная сборка, конфигурируемая во время выполнения.

Тогда вам даже не нужно называть это DI, вместо этого он просто называется «Хорошая идея Мела», и вы теперь в списке хороших идей вместо плохих. Люди увидят, насколько хорошо работает шаблон, и начнут использовать его там, где это имеет смысл в их коде.

Когда вы правильно продадите идею, люди не узнают, что вы в чем-то их убедили.

tylerl
источник
8

Конечно, Inversion of Control (IoC) имеет множество преимуществ: он упрощает код, добавляет магию, которая выполняет типичные задачи и т. Д.

Тем не мение:

  1. Это добавляет много зависимостей. Простое приложение hello world, написанное на Spring, может весить десяток МБ, и я видел Spring, добавленный только для того, чтобы избежать нескольких строк конструктора и сеттера.

  2. Он добавляет вышеупомянутое волшебство , которое трудно понять посторонним (например, разработчикам GUI, которым необходимо внести некоторые изменения в бизнес-уровень). Посмотрите замечательную статью « Новаторские взгляды, а не мысли» - New York Times , в которой рассказывается, как эксперты недооценивают этот эффект .

  3. Твердеть, чтобы отслеживать использование классов. В таких средах, как Eclipse, есть отличные возможности для отслеживания использования данных классов или вызовов методов. Но этот след теряется, когда происходит внедрение зависимости и отражение.

  4. Использование IoC обычно не заканчивается внедрением зависимостей, оно заканчивается бесчисленными прокси-серверами, и вы получаете трассировку стека, насчитывающую сотни строк, 90% из которых являются прокси-серверами и вызываются с помощью отражения. Это значительно усложняет анализ трассировки стека.

Так что, будучи большим фанатом Spring и IoC, мне недавно пришлось переосмыслить приведенные выше вопросы. Некоторые из моих коллег - веб-разработчики, взятые в мире Java из мира Web, управляемого Python и PHP, и очевидные для javaist вещи для них не очевидны. Некоторые из моих коллег делают трюки (конечно, без документов), что делает ошибку очень трудно найти. Конечно злоупотребление IoC делает вещи легче для них;)

Мой совет: если вы будете принудительно использовать IoC на своей работе, вы будете нести ответственность за любое злоупотребление, которое совершит кто-либо другой;)

Łukasz Lech
источник
+1, как и для любого мощного инструмента, его легко использовать неправильно, и вы ДОЛЖНЫ понимать, когда его НЕ использовать.
Фил
4

Я думаю, что ChrisF во что-то правильно. Не продавайте ДИ в себе. Но продайте идею о модульном тестировании кода.

Один из подходов к включению контейнера DI в архитектуру может состоять в том, чтобы затем медленно позволить тестам превратить реализацию в нечто, что хорошо сочетается с этой архитектурой. Например, допустим, вы работаете с контроллером в приложении ASP.NET MVC. Допустим, вы пишете класс так:

public class UserController {
    public UserController() : this (new UserRepository()) {}

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

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

Если вы, коллеги, видите, что это приводит к гораздо более чистым тестам, возможно, они поймут, что это лучший способ. Может быть, вы можете заставить их начать кодировать так.

Как только они поймут, что это лучший дизайн, чем UserController, зная о конкретной реализации UserRepository, вы можете сделать следующий шаг и показать им, что у вас может быть компонент, контейнер DI, который может автоматически предоставлять нужные вам экземпляры.

Пит
источник
Большой! В настоящее время я делаю это, потому что я действительно хочу автоматизированное модульное тестирование, даже если я не могу получить DI. Я забыл упомянуть, что юнит-тесты также не проводятся в нашей команде :( так что, если я буду первым.
Мел
2
В таком случае, я бы сказал, что здесь настоящая проблема. Найдите корень сопротивления юнит-тестированию и атакуйте его. Пусть пока я лгу. Тестирование важнее ИМХО.
Кристиан Хорсдал
@ChristianHorsdal Я согласен, я хотел, чтобы это было свободно связано, чтобы позволить легкую насмешку / тестирование.
Мел
Эта идея действительно крутая ... Отличная распродажа для тестирования
bunglestink
2

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

Вооружившись всеобъемлющим набором или наборами юнит-тестов, вы можете уверенно приступить к развитию своего дизайна с течением времени. Без них вы сталкиваетесь со все более и более сложной борьбой за то, чтобы держать свой дизайн в ногу с изменяющимися требованиями, борьбой, которую в конечном итоге проигрывают многие команды.

Поэтому я советую сначала протестировать юнит-тестирование и рефакторинг, а затем, в свою очередь, ввести DI и, наконец, DI-контейнеры.

kimj100
источник
2

Я слышу несколько громких замечаний от вашей команды здесь:

  • «Никаких сторонних библиотек» - АКА «Не изобретено здесь» или «NIH». Опасение заключается в том, что, внедряя стороннюю библиотеку и, таким образом, делая базу кода зависимой от нее, если в библиотеке есть ошибка или дыра в безопасности, или даже просто ограничение, которое вам нужно преодолеть, вы становитесь зависимыми от этой третьей участник, чтобы исправить свою библиотеку, чтобы ваш код работал. В то же время, поскольку его «ваша» программа потерпела неудачу, была взломана или не выполняет то, о чем они просили, вы по-прежнему несете ответственность (а сторонние разработчики скажут, что их инструмент «как есть»). ", Используйте на свой риск).

    Противоположным аргументом является то, что код, решающий проблему, уже существует в сторонней библиотеке. Вы строите свои компьютеры с нуля? Вы написали Visual Studio? Вы отказываетесь от встроенных библиотек в .NET? Нет. У вас есть уровень доверия к Intel / AMD и Microsoft, и они постоянно находят ошибки (и не всегда быстро их исправляют). Добавление сторонней библиотеки позволяет быстро получить работоспособную базу кода без необходимости заново изобретать колесо. А армия юристов Microsoft посмеется вам в лицо, когда вы скажете им, что ошибка в библиотеках криптографии .NET делает их ответственными за случай взлома вашего клиента при использовании вашей программы .NET. В то время как Microsoft непременно попытается устранить дыры в безопасности «нулевого часа» в любом из своих продуктов,

  • «Мы хотим собрать все в одну сборку» - на самом деле, вы этого не делаете. По крайней мере, в .NET, если вы вносите изменения в одну строку кода, вся сборка, содержащая эту строку кода, должна быть перестроена. Хороший дизайн кода основан на нескольких сборках, которые вы можете перестраивать настолько независимо, насколько это возможно (или, по крайней мере, нужно перестраивать зависимости, а не зависимости). Если они ДЕЙСТВИТЕЛЬНО хотят выпустить EXE, который является просто EXE-файлом (который имеет значение в определенных обстоятельствах), для этого есть приложение; ILMerge.

  • «ДИ это табу» - WTF? DI - это общепринятая лучшая практика на любом языке O / O с кодовой конструкцией «интерфейс». Как ваша команда придерживается методологий проектирования GRASP или SOLID, если они не слабо связывают зависимости между объектами? Если они не беспокоятся, то они увековечивают фабрику спагетти, которая делает продукт такой сложной болотой, какой она есть. Теперь DI увеличивает сложность за счет увеличения числа объектов, и, хотя он облегчает замену другой реализации, он усложняет изменение самого интерфейса (добавляя дополнительный слой объекта кода, который должен измениться).

  • «Нам не нужно добавлять этот уровень сложности в уже сложный проект», - они могут иметь смысл. Важной вещью, которую следует учитывать при разработке любого кода, является YAGNI; «Вам это не нужно». Дизайн, спроектированный SOLIDly с самого начала, - это проект, который сделан SOLID «на веру»; Вы не знаете, понадобится ли вам легкость изменений, которую обеспечивают дизайны SOLID, но у вас есть предчувствие. Хотя вы можете быть правы, вы также можете быть очень неправы; очень твердый дизайн может в конечном итоге рассматриваться как «код лазаньи» или «код равиоли», имеющий так много слоев или кусков размером с кусочек, что вносить кажущиеся тривиальными изменения становится трудно, потому что вам приходится копаться в таком количестве слоев и / или прослеживать такой сложный стек вызовов.

    Я поддерживаю правило трех ударов: заставить его работать, сделать его чистым, сделать его твердым, Когда вы впервые пишете строку кода, она просто должна работать, и вы не получаете баллов за дизайн башни из слоновой кости. Предположим, что это разовое и делать то, что ты должен делать. Во второй раз, когда вы смотрите на этот код, вы, вероятно, хотите расширить или повторно использовать его, и это не тот случай, когда вы думали, что это будет. На этом этапе, сделайте его более элегантным и понятным путем рефакторинга; упростите запутанную логику, извлеките повторяющийся код в циклы и / или вызовы методов, добавьте несколько комментариев тут и там для любого клуджа, который вам нужно оставить на месте, и т. д. В третий раз, когда вы посмотрите на этот код, это, вероятно, будет большой проблемой. Теперь вы должны придерживаться твердых правил; вставлять зависимости вместо их построения, извлекать классы для хранения методов, которые менее связаны с «мясом» кода,

  • «Мы не будем подключать разные реализации» - у вас, сэр, есть команда, которая не верит в юнит-тесты на ваших руках. Я утверждаю, что невозможно написать модульный тест, который выполняется изолированно и не имеет побочных эффектов, не имея возможности «насмехаться» над объектами, которые имеют эти побочные эффекты. Любая нетривиальная программа имеет побочные эффекты; если ваша программа читает или записывает в БД, открывает или сохраняет файлы, обменивается данными по сети или периферийному сокету, запускает другую программу, выводит окна, выводит данные на консоль или иным образом использует память или ресурсы вне «песочницы» своего процесса, она имеет побочный эффект. Если он не делает ничего из этого, что он делает, и почему я должен его купить?

    Честно говоря, модульное тестирование - не единственный способ доказать, что программа работает; Вы можете написать интеграционные тесты, код для математически проверенных алгоритмов и т. д. Однако модульное тестирование очень быстро показывает, что при заданных входах A, B и C этот фрагмент кода выводит ожидаемый X (или нет) и, таким образом, что код работает должным образом (или не работает) в данном случае. Наборы модульных тестов могут с высокой степенью уверенности продемонстрировать, что код работает должным образом во всех случаях, и они также предоставляют то, чего не может математическое доказательство; демонстрация того, что программа по-прежнему работает так, как она была задумана. Математическое доказательство алгоритма не доказывает, что он был реализован правильно, и не доказывает, что любые внесенные изменения были реализованы правильно и не сломали его.

Суть в том, что если эта команда не видит никакой ценности в том, что будет делать DI, то они не будут ее поддерживать, и все (по крайней мере, все старшие ребята) должны что-то купить, чтобы добиться реальных перемен. случаться. Они уделяют большое внимание YAGNI. Вы уделяете большое внимание твердотельному и автоматизированному тестированию. Где-то посередине лежит правда.

оборота KeithS
источник
1

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

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

Если вы не используете DI, вы можете проявить творческий подход и сделать свой код DI- «готовым». Используйте конструктор без параметров для вызова других конструкторов:

MyClassConstructor()
{
    MyClassConstructor(new Dependency)
    Property = New Dependent Property
}

MyClassConstructor(Dependency)
{
    _Dependency = Dependency
}
JLH
источник
0

Я думаю, что одна из их самых больших проблем на самом деле как раз наоборот: DI не делает проект сложным. В большинстве случаев это уменьшает сложность.

Просто подумайте без DI, что вы собираетесь получить зависимый объект / компонент, который вам нужен? Обычно это происходит из-за поиска в репозитории, или получения синглтона, или создания самого себя.

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

Если вы создаете себя в некоторых случаях, что не подходит, это даже создает больше сложности. Вы должны знать, как правильно создать объект, вам нужно знать, какие значения инициализировать объект до работоспособного состояния, все это создает дополнительную сложность для вашего кода.

Таким образом, DI не делает проект сложным, он делает его проще, делая компоненты проще.

Еще один важный аргумент - вы не собираетесь подключать другую реализацию. Да, это правда, что в производственном коде не принято иметь много разных реализаций в реальном производственном коде. Однако есть одно главное место, которое требует другой реализации: Mock / Stub / Test Doubles в модульных тестах. Чтобы заставить DI работать, в большинстве случаев он опирается на модель разработки, основанную на интерфейсе, что, в свою очередь, делает возможной насмешку / заглушку в модульных тестах. Помимо замены реализации, «интерфейсно-ориентированный дизайн» также делает дизайн чище и лучше. Конечно, вы все еще можете проектировать с интерфейсом без DI. Тем не менее, модульные тесты и DI подталкивают вас к принятию такой практики.

Если ваши «боги» в компании не считают, что «простой код», «модульные тесты», «модульность», «единая ответственность» и т. Д. - все это то, против чего они выступают, у них не должно быть оснований отказываться от использования DI.

Адриан Шум
источник
Это хорошо в теории, но на практике добавление DI-контейнера - дополнительная сложность. Вторая часть вашего аргумента показывает, почему это стоит того, чтобы работать, но это все еще работает.
Шлингель
Фактически из моего прошлого опыта, DI СОКРАЩАЕТСЯ, а не добавляет сложности. Да, в систему добавлены некоторые дополнительные элементы, что усложняет задачу. Однако с помощью DI сложность других компонентов в системе значительно снижается. В конце концов, сложность фактически уменьшается. Для второй части, если они не выполняют никаких модульных тестов, это НЕ дополнительные работы. Без DI их модульные тесты в основном будут трудны для написания и поддержки. С DI усилие значительно уменьшается. Таким образом, это фактически СНИЖЕНИЕ работы (если они не являются и не доверяют модульным тестам)
Адриан Шум
Я должен подчеркнуть одну вещь: DI в моем ответе не означает никакой существующей структуры DI. Это просто методология проектирования / разработки компонентов вашего приложения. Вы можете получить большую выгоду, если ваш код управляется интерфейсом, ваши компоненты ожидают, что зависимости будут введены вместо поиска / создания. При таком дизайне, даже если вы пишете какой-то специфический «загрузчик» для приложения, который выполняет подключение компонентов (без DI fw общего назначения), вы все равно получаете выгоду от того, что я упомянул: уменьшение сложности и сокращение работы в модульном тестировании
Адриан Шум
0

«Мы хотим, чтобы все было просто, если возможно, просто соберите все в одну сборку. DI - ненужная сложность без выгоды».

Преимущество заключается в том, что он способствует проектированию интерфейсов и позволяет легко издеваться. Что, следовательно, облегчает юнит-тестирование. Модульное тестирование обеспечивает надежный, модульный и легко обслуживаемый код. Если ваш начальник все еще придерживается своей плохой идеи, ну, вы ничего не можете поделать - это принятая лучшая в отрасли практика, против которой они спорят.

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

ним чимпский
источник
Буквальное удвоение или утроение количества классов в вашей системе - вот что я считаю полезным. Это похоже на дискуссию о попытке получить 100% покрытие модульных тестов путем написания одного теста на метод или написания значимых модульных тестов.
Эндрю Т Финнелл
Я не понимаю, вы думаете, что юнит-тестирование это хорошая идея? Если вы делаете, вы тоже издеваетесь над объектами тоже? Это проще с интерфейсами и DI. Но, похоже, вы против этого.
НимЧимпский
Это действительно проблема курицы и яйца. Интерфейсы / DI и модульное тестирование настолько тесно связаны друг с другом, что трудно сделать одно без другого. Но я верю, что модульное тестирование - это лучший и простой способ начать с существующей кодовой базы. Постепенно рефакторинг старого клуджого кода в связные компоненты. После этого вы можете утверждать, что создание объекта должно выполняться с помощью инфраструктуры DI, а не newвезде, и для написания фабричных классов, поскольку у вас есть все эти компоненты на месте.
Спойк
0

Работаете ли вы с этими людьми в каких-то небольших проектах или только в одном большом? Проще убедить людей попробовать что-то новое на вторичном / третичном проекте, чем хлеб с маслом команды / компаний. Основными причинами этого является то, что в случае неудачи затраты на его удаление / замену значительно уменьшаются, и гораздо меньше труда получить его повсюду в небольшом проекте. В большом существующем у вас либо есть большие начальные затраты, чтобы внести изменения везде одновременно, либо длительный период, в течение которого код представляет собой смесь старого / нового подхода, добавляющего трения, потому что вы должны помнить, как разработан каждый класс работать.

Дэн Нили
источник
0

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

  • Лучше иметь автомобиль сложным в тестировании, но легким в ремонте, чем легким в тестировании и трудным в ремонте.

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

  • если у нас есть метод, выполняющий 5 различных функций

    DI-люди говорят:

    • нет разделения интересов. [в чем проблема этого? они пытаются сделать так, чтобы это выглядело плохо, что в логике и программировании гораздо лучше знать, что друг друга делают тесно, чтобы лучше избегать конфликтов и неправильных ожиданий и легко видеть влияние изменения кода, потому что мы не можем сделать все ваши объекты, не связанные друг с другом на 100%, или, по крайней мере, мы не можем гарантировать это {разве почему важно регрессионное тестирование?}]
    • Сложный [Они хотят уменьшить сложность, создав дополнительные пять дополнительных классов каждый для обработки одной функции и дополнительный класс (Container) для создания класса для необходимой функции, теперь основанной на настройках (XML или Dictionary) в дополнение к философии «иметь значение по умолчанию / или не «обсуждения» ... затем они перенесли сложность в контейнер и в структуру, [для меня это выглядит как перемещение сложности из одного места в два места со всеми сложностями языковой специфики, чтобы справиться с этим. ]

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

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

user114367
источник