Является ли инъекция зависимостей бедного человека хорошим способом ввести тестируемость в устаревшее приложение?

14

В прошлом году я создал новую систему, используя Dependency Injection и контейнер IOC. Это научило меня много о DI!

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

Цель курса - привести юнит-тесты в бизнес-логику!
Бизнес-логика, которая переплетается с вызовами базы данных для предотвращения тестирования.

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

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

Кроме того, является ли использование DI Бедного человека в качестве низового подхода к истинной инъекции зависимостей ценным способом разъяснить необходимость и преимущества принципа?

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

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

Airn5475
источник
2
Просто записка. I consider it a challenge to decouple code and introduce an IOC container into a legacy applicationконечно это. Это называется технический долг. Вот почему перед любым серьезным обновлением предпочтительнее небольшой и непрерывный рефакторинг. Сокращение основных недостатков дизайна и переход на IoC будет менее сложным.
Laiv
С того же сайта, внедрение зависимости не
Мейсон Уилер,

Ответы:

25

Критика в отношении инъекции для Бедного в NerdDinner связана не столько с тем, используете ли вы DI-контейнер, сколько с правильной настройкой классов.

В статье говорится, что

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

    public SearchController() : this(new DinnerRepository()) { }

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

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

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

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

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

Роберт Харви
источник
Спасибо за ваши мысли! Я понимаю «конструктор-нарушитель» и то, как мы должны его избегать. Я понимаю, что наличие этой зависимости ничего не разъединяет, но допускает модульные тесты. Я на начальном этапе внедрения DI и проведения модульных тестов как можно быстрее. Я пытаюсь избежать усложнения контейнера DI / IOC или фабрики в начале игры. Как уже упоминалось, позже, когда поддержка DI возрастет, мы могли бы реализовать Контейнер и удалить эти «оскорбительные конструкторы».
Airn5475
Конструктор по умолчанию не требуется для модульных тестов. Без него вы можете передать любую зависимость, которая вам нравится, включая фиктивный объект.
Роберт Харви
2
@ Airn5475 быть осторожным, чтобы не более абстрагирования тоже. Эти тесты должны проверять осмысленное поведение - тестирование того, что a XServiceпередает параметр в XRepositoryи возвращает то, что он возвращает, не слишком полезно для всех и не дает вам больших возможностей расширения. Напишите свои модульные тесты, чтобы проверить осмысленное поведение и убедиться, что ваши компоненты не настолько узки, что вы фактически переносите всю свою логику в корень композиции.
Муравей P
Я не понимаю, как включение конструктора-нарушителя помогло бы для модульных тестов, конечно же, это наоборот? Удалите нарушающий конструктор, чтобы DI был правильно реализован и передавал имитируемую зависимость при создании объектов.
Роб
@Rob: это не так. Это просто удобный способ для конструктора по умолчанию поддерживать действительный объект.
Роберт Харви
16

Вы делаете ложное предположение о том, что такое «DI бедняков».

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

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

Термин «DI бедного человека» звучит как плохой поступок. По этой причине в наши дни поощряется термин «чистый ДИ». поскольку он звучит более позитивно и фактически более точно описывает процесс.

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

Дэвид Арно
источник
8
DI «Бедный человек» или «Чистый», в зависимости от того, что вы предпочитаете, также смягчает многие проблемы с контейнерами IoC. Раньше я использовал контейнеры IoC для всего, но чем больше проходит времени, тем больше я предпочитаю избегать их вообще.
Муравей P
7
@AntP, я с тобой: я на 100% чистый DI в эти дни и активно избегаю контейнеров. Но я (мы) в этом меньшинстве, насколько я могу судить.
Давид Арно
2
Кажется очень печально - когда я ухожу из «волшебного» лагеря IoC-контейнеров, библиотек насмешек и т. Д. И обнаруживаю, что охватывает ООП, инкапсуляцию, двойные тестовые тесты и проверку состояния, кажется, что тенденция к магии все еще на вверх. +1 в любом случае.
Муравей P
3
@AntP & David: Приятно слышать, что я не одинок в лагере Pure DI. Контейнеры DI были созданы для устранения недостатков в языках. Они добавляют ценность в этом смысле. Но, на мой взгляд, реальный ответ заключается в том, чтобы исправить языки (или перейти на другие языки), а не создавать бесполезность поверх них.
JimmyJames
1

Смена парадигмы в устаревших командах / основах кода чрезвычайно рискованна:

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

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

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

Внедрение DI-фреймворка или даже концепции «Pure DI» в линейку бизнес-приложений, не говоря уже об огромной унаследованной кодовой базе без участия руководства, команды и, в особенности, спонсорства ведущего разработчика, станет смертельным звоном. для вас социально и политически в команде / компании. Делать подобные вещи крайне рискованно и в худшем случае может быть политическим самоубийством.

Внедрение зависимостей - это решение проблемы.

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

Инъекция зависимости полезна только в малых дозах для очень узкого диапазона вещей:

  • Вещи, которые сильно меняются или имеют множество альтернативных реализаций, которые статически связаны.

    • Драйверы JDBC являются прекрасным примером.
    • HTTP-клиенты, которые могут отличаться от платформы к платформе.
    • Системы ведения журналов, которые зависят от платформы.
  • Системы плагинов, которые имеют настраиваемые плагины, которые могут быть определены в коде конфигурации в вашей среде и обнаружены автоматически при запуске и динамически загружаться / перезагружаться во время работы программы.


источник
10
Убийственное приложение для DI - модульное тестирование. Как вы указали, большинству программ само по себе не нужно много DI. Но тесты также являются клиентом этого кода, поэтому мы должны разработать для тестируемости. В частности, это означает размещение швов в нашей конструкции, где тесты могут наблюдать за поведением. Это не требует причудливой структуры DI, но требует, чтобы соответствующие зависимости были явными и заменяемыми.
Амон
4
Если бы наши разработчики знали, если заранее, что может измениться, это было бы полдела. Я был на длинных разработках, где многие предположительно «исправленные» вещи изменились. Проверка будущего с использованием DI не плохая вещь. Использование его повсеместно все время попахивает культовым грузовым программированием.
Робби Ди
чрезмерное усложнение теста будет означать, что они устареют и будут помечены как игнорируемые, а не обновлены в будущем. DI в устаревшем коде - ложная экономика. Все, что они будут делать, - это сделать вас «плохим парнем», чтобы поместить весь этот «нестандартный, слишком сложный код, который никто не понимает», в базу кода. Вы были предупреждены.
4
@JarrodRoberson, это действительно токсичная среда. Одним из ключевых признаков хорошего разработчика является то, что они оглядываются назад на код, который они написали в прошлом, и думают: «Ух, я действительно это написал? Это так неправильно!» Это потому, что они выросли как разработчик и узнали новые вещи. Тот, кто смотрит на код, который они написали пять лет назад, и не видит в этом ничего плохого, ничему не научился за эти пять лет. Так что если люди, сказав, что они делали это неправильно в прошлом, делают вас врагом, то быстро бегите из этой компании, потому что они - тупиковая команда, которая будет сдерживать вас и понижать до низкого уровня.
Дэвид Арно
1
Не уверен, что я согласен с вещами плохого парня, но для меня ключевым моментом является то, что вам не нужен DI для проведения модульного тестирования. Реализация DI, чтобы сделать код более тестируемым, просто исправляет проблему.
Ant P