В одном из недавних постов я рекомендовал использовать готовые решения для тривиальных задач. Разумеется, я не изменил своего мнения за неделю, однако хочется эту мысль немного развить на примере такой технологии как DI -контейнеры.
Если кратко, то это инструмент берет на себя заботу о построении требуемых зависимостей на себя. Например, программист использует в своем коде исключительно интерфейсы в качестве входных параметров конструкторов некоего класса, а создание объекта данного класса отдает на откуп механизму, который, согласно своим настройкам, будет решать - какие реализации зависимостей будут использованы при создании объекта. Звучит отлично. И это на самом деле отлично. Но я бы хотел заострить внимание на слове «инструмент».
Лично я не очень хороший строитель и ремонтник. В моем доме нет ряда инструментов, которым присваивают гордое звание «должны быть в каждом доме», как например перфоратор. Не потому что я не могу его себе позволить или потому что не умею пользоваться. Я просто представляю область применения данного инструмента и понимаю что, он мне не нужен. Если бы я жил в целиком деревянном доме – я не знаю, для чего бы мне понадобился перфоратор. Или любой другой строительный инструмент. Зачем дома торцевая пила, если вы не занимаетесь оптовой нарезкой досок? Ради разового ремонта? Возьмите в прокат.
К чему я это все в контексте программирования вообще и DI-контейнеров в частности? К тому, что данный инструмент также имеет свою область применения. Совать данное решение в каждый первый проект «просто потому что» – идея ниже среднего.
Несколько лет назад я наткнулся на использование данного инструмента в одном из учебников по C#. Разумеется, это сопровождалось пояснениями - насколько он хорош и зачем он применяется. Через короткое время во время поиска работы я почти на каждом собеседовании слышал вопросы - что это такое, и какие преимущества мы имеем от использования. К сожалению, очень редко встречались материалы по недостаткам и области применения. Складывается впечатление, что разработчики на местах начали использовать данный инструмент, просто потому что это модно.
На моем текущем проекте сотни интерфейсов и классов их реализующих, но 99% процентов этих интерфейсов имеют только одну реализацию. Вопрос – зачем? Самое интересное, что структура проекта такова, что проект может работать в самых разных режимах. Он может работать в тестовом режиме и промышленном, а в зависимости от настроек – вообще может быть двумя разными приложениями. Самое очевидное решение для реализации таких проектов – использовать такой DI-контейнер и мы его используем. Только вот DI-контейнер почему-то крайне редко используется для настройки режима работы. А режим работы может быть реализован ифчиками. Вопрос – зачем мы его используем? Лично я показывал коллегам примеры как можно упрощать код, используя различные реализации интерфейсов в зависимости от режима работы приложения, однако, большинство коллег все равно использует ифчики и костыли. Представляете проект, которые в рамках обработки запроса периодически пытается выяснить - кем он является? Нет? А я такой каждый день вижу. Это усложняет код и вводит в логику работы приложения, логику которой там быть не должно никогда.
Что еще неприятного может быть при использовании данного инструмента? Как я уже писал – баги есть всегда. Если вы не пишете unit-тесты, то баги вы будете ловить только при запуске проекта. Так вот – если вы не пишете unit-тесты и используете DI-контейнер, то повышаете риск бага примерно в 2 раза. Забыли настроить зависимость, сделали циклическую зависимость и привет баги в runtime. Причем написать тест на исключение этих багов занимает 5 минут, но я всё равно встречаю людей, которые не понимают, зачем их писать.
Одним из удобств использования интерфейсов, которые затем разруливает DI-контейнер, является удобство покрытия unit-тестами, так как это упрощает мокирование зависимостей. Вопрос – а вы это используете? Пишете тесты? Мокируете зависимости? Нет? А зачем тогда используете этот инструмент?
На этом пока буду закругляться. Закончить хочется простейшей мыслью о том, что любой инструмент имеет свою область применения, и любое использование должно быть хоть чем-то оправдано. Когда вы берете инструмент – вы должны отдавать себе отчет для чего вы это делаете. То, что в книжке написано, что это хорошо – не является хорошим объяснением использования. Указание вышестоящего специалиста использовать – тоже так себе объяснение, но тут хотя бы вы можете спросить данного специалиста о мотивах. Ну а если уж взяли в руки – старайтесь использовать по максимуму. Не надо велосипедить паттерн синглетных объектов – контейнер может взять это на себя. Обязательно пишите тесты на Resolve зависимостей – это упростит вашу жизнь в разы. Ну и помните, что подобные слова справедливы не только для DI-контейнеров, но и для любого другого программного инструмента.
M