Скільки коштує занадто багато введення залежності?


38

Я працюю над проектом, який використовує (Spring) Dependency Injection для буквально всього, що є залежністю класу. Ми знаходимось у точці, коли файл конфігурації Spring виріс приблизно до 4000 рядків. Не так давно я дивився один з переговорів дядька Боба на YouTube (на жаль, я не зміг знайти посилання), у якому він рекомендує ввести лише головну частину залежностей (наприклад, фабрики, базу даних, ...) в основний компонент, з якого вони потім будуть бути розповсюдженим.

Перевагами цього підходу є від'єднання рамки DI від більшої частини програми, а також він робить Spring config більш чистим, оскільки фабрики будуть містити набагато більше того, що було в конфігурації раніше. Навпаки, це призведе до поширення логіки створення серед багатьох заводських класів, а тестування може стати складнішим.

Тож моє запитання справді полягає в тому, які інші переваги чи недоліки ви бачите в тому чи іншому підході. Чи є найкращі практики? Дякую за ваші відповіді!


3
Файл конфігурації 4000 рядків не є виною ін'єкції залежності ... існує маса способів її виправити: модулювати модуль у декілька менших файлів, перейти на ін'єкцію на основі анотацій, використовувати файли JavaConfig замість xml. В основному проблема, з якою ви стикаєтеся, - це неспроможність керувати складністю та розміром потреб у введенні залежностей вашої програми.
Можливо_Фактор

1
@Maybe_Factor TL; DR розділив проект на більш дрібні компоненти.
Вальфрат

Відповіді:


44

Як завжди, це залежить ™. Відповідь залежить від проблеми, яку намагається вирішити. У цій відповіді я спробую торкнутися деяких загальних мотивуючих сил:

Віддавайте перевагу меншим кодовим базам

Якщо у вас 4000 рядків коду конфігурації Spring, я вважаю, що база коду має тисячі класів.

Навряд чи це питання, яке ви можете вирішити після факту, але, як правило, я, як правило, віддаю перевагу меншим програмам із меншими базами коду. Якщо ви займаєтеся дизайном , керованим доменом , ви можете, наприклад, створити базу коду для обмеженого контексту.

Я базую цю пораду на своєму обмеженому досвіді, оскільки я писав кодекс бізнес-кодексу на основі більшої частини своєї кар'єри. Я можу собі уявити, що якщо ви розробляєте настільний додаток, вбудовану систему чи інше, що речі важче розірвати.

Хоча я усвідомлюю, що ця перша порада легко найменш практична, я також вважаю, що вона є найважливішою, і тому я її включаю. Складність коду змінюється нелінійно (можливо експоненціально) залежно від розміру бази коду.

Фаворит Чистий DI

Хоча я все ще розумію, що це питання представляє існуючу ситуацію, рекомендую Pure DI . Не використовуйте контейнер DI, але якщо ви це зробите, принаймні використовуйте його для реалізації композиції на основі конвенції .

Я не маю практичного досвіду з Spring, але я припускаю, що під файлом конфігурації розуміється XML-файл.

Налаштування залежностей за допомогою XML - це найгірше для обох світів. По-перше, ви втрачаєте безпеку типу компіляції, але нічого не отримуєте. Файл конфігурації XML може бути таким же великим, як і код, який він намагається замінити.

Порівняно з проблемою, яку він вирішує вирішити, файли конфігурації ін'єкцій залежностей займають неправильне місце на тактовій складності конфігурації .

Справа про введення грубозернистої залежності

Я можу зробити справу про введення грубозернистої залежності. Я також можу скласти справу про введення дрібнозернистої залежності (див. Наступний розділ).

Якщо ви вводите лише кілька "центральних" залежностей, то більшість класів може виглядати так:

public class Foo
{
    private readonly Bar bar;

    public Foo()
    {
        this.bar = new Bar();
    }

    // Members go here...
}

Це по - , як і раніше відповідає Design Patterns «и прихильність об'єкт складу над спадкуванням класів , тому що Fooсостовляющие Bar. З точки зору ремонту, це все ще можна вважати досяжним, тому що якщо вам потрібно змінити склад, ви просто редагуєте вихідний код дляFoo .

Це навряд чи менш рентабельне, ніж введення залежності. Справді, я б сказав, що простіше безпосередньо редагувати клас, який використовує Bar, а не слідкувати за опосередкованою властивістю введення залежності.

У першому виданні своєї книги про впорскування залежностей я розрізняю мінливі та стійкі залежності.

Летючі залежності - це ті залежності, які слід розглянути як введення. Вони включають

  • Залежності, які слід повторно налаштувати після компіляції
  • Залежності, розвинені паралельно іншою командою
  • Залежності від недетермінованої поведінки або поведінки з побічними ефектами

Стійкі залежності, з іншого боку, - це залежності, які ведуть себе чітко визначеним чином. У певному сенсі ви можете стверджувати, що ця відмінність спричиняє введення грубозернистої залежності, хоча я мушу визнати, що я не цілком усвідомлював це, коли писав книгу.

З точки зору тестування, однак, це ускладнює тестування одиниць. Ви більше не можете використовувати тест на одиницю Fooнезалежно від Bar. Як пояснює JB Rainsberger , інтеграційні тести страждають від комбінаторного вибуху складності. Вам буквально доведеться написати десятки тисяч тестових випадків, якщо ви хочете охопити всі шляхи шляхом інтеграції навіть 4-5 класів.

Контр-аргумент цьому часто полягає в тому, що ваше завдання не програмувати клас. Ваше завдання - розробити систему, яка вирішує деякі конкретні проблеми. Це мотивація розвитку, орієнтованого на поведінку (BDD).

Інший погляд на це подає DHH, який стверджує, що TDD призводить до тестових пошкоджень конструкції . Він також підтримує грубозернисті тести інтеграції.

Якщо взяти цю точку зору на розробку програмного забезпечення, то введення грубозернистої залежності має сенс.

Справа про введення дрібнозернистої залежності

З іншого боку, дрібнозерниста ін'єкція залежності може бути описана як ін'єкція всіх речей!

Моє основне занепокоєння щодо введення грубозернистої залежності - це критика, висловлена ​​Дж. Б. Райнсбергером. Ви не можете покрити всі шляхи коду тестами інтеграції, оскільки вам потрібно написати буквально тисячі або десятки тисяч тестових випадків, щоб охопити всі кодові шляхи.

Прихильники BDD будуть протиставлятися аргументу, що вам не потрібно покривати всі шляхи коду тестами. Потрібно покрити лише ті, які виробляють ділову цінність.

На моєму досвіді, однак, всі «екзотичні» кодові шляхи також виконуватимуться у розгорнутому обсязі, і якщо їх не перевірити, багато з них матимуть дефекти та спричинятимуть винятки під час виконання (часто винятки з нульовим посиланням).

Це призвело до того, що я віддаю перевагу дрібнозернистій ін'єкції залежності, оскільки це дозволяє мені перевіряти інваріанти всіх об'єктів у відриві.

Улюблене функціональне програмування

Хоча я схиляюся до дрібнозернистої ін'єкції залежності, я перенесла свій акцент на функціональне програмування, серед інших причин, оскільки це властиво перевірити .

Чим більше ви рухаєтеся до коду SOLID, тим він стає більш функціональним . Рано чи пізно ви також можете зайнятися зануренням. Функціональна архітектура - це архітектура портів і адаптерів , а введення залежності також є спробою і портів, і адаптерів . Різниця, однак, полягає в тому, що така мова, як Haskell, застосовує цю архітектуру через систему типів.

Віддавайте перевагу статичному набору функціонального програмування

На даний момент я по суті відмовився від об'єктно-орієнтованого програмування (OOP), хоча багато проблем OOP суттєво пов'язані з основними мовами, такими як Java та C # більше, ніж сама концепція.

Проблема з основними мовами OOP полягає в тому, що майже неможливо уникнути проблеми комбінаторного вибуху, яка, неперевірена, призводить до винятків у процесі виконання. Статично типізовані мови, такі як Haskell і F #, з іншого боку, дозволяють кодувати багато точок прийняття рішень у системі типів. Це означає, що замість того, щоб писати тисячі тестів, компілятор просто скаже вам, чи мали ви справу з усіма можливими кодовими шляхами (певною мірою; це не срібна куля).

Також введення залежності не функціонує . Справжнє функціональне програмування повинно відкидати все поняття залежності . Результат - простіший код.

Підсумок

Якщо я змушений працювати з C #, я віддаю перевагу дрібнозернистій ін'єкції залежності, оскільки це дозволяє мені охопити всю базу коду з керованою кількістю тестових випадків.

Зрештою, моя мотивація - швидкий зворотний зв'язок. Тим не менш, тестування приладів - не єдиний спосіб отримати зворотний зв'язок .


1
Мені дуже подобається ця відповідь, і особливо це твердження, яке використовується у весняному контексті: "Налаштування залежностей за допомогою XML - це найгірше для обох світів. По-перше, ви втрачаєте безпеку типу компіляції, але нічого не отримуєте. Конфігурація XML файл може бути таким же великим, як код, який він намагається замінити. "
Томас Карлайл

@ThomasCarlisle не був пунктом конфігурації XML, що вам не потрібно торкатися коду (навіть компілювати), щоб змінити його? Я ніколи (або ледве) не використовував це через дві речі, про які згадував Марк, але ти щось отримуєш у відповідь.
El Mac

1

Величезні класи налаштування DI - це проблема. Але подумайте про код, який він замінює.

Вам потрібно створити всі ці сервіси та інше. Код для цього буде або в app.main()початковій точці, і вводиться вручну, або щільно з'єднаний, як this.myService = new MyService();у межах класів.

Зменшіть розмір класу налаштування, розділивши його на кілька класів налаштування та зателефонуйте їм із початкової точки програми. тобто.

main()
{
   var c = new diContainer();
   var service1 = diSetupClass.SetupService1(c);
   var service2 = diSetupClass.SetupService2(c, service1); //if service1 is required by service2
   //etc

   //main logic
}

Вам не потрібно посилатися на службу1 або 2, крім того, щоб передати їх іншим методам налаштування ci.

Це краще, ніж намагатися дістати їх з контейнера, оскільки він виконує порядок виклику налаштувань.


1
Для тих, хто хоче вивчити цю концепцію далі (побудова свого класового дерева на початковій точці програми), найкращий термін для пошуку - "корінь композиції"
e_i_pi

@Ewan, що ця ciзмінна?
superjos

ваш клас налаштування ci зі статичними методами
Ewan

ург повинен бути ді. Я продовжую думати про «контейнер»
Евані
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.