Pierwszy raz z wstrzykiwaniem zależności (ang. Dependency Injection, albo krótko DI) zetknęłam się trzy lata temu. Trafiłam wtedy na świetną książkę Marka Seemanna “Dependency Injection in .NET“. Wtedy wydawało mi się, że DI to jest TO! Rozwiązanie mnóstwa problemów, które wtedy mieliśmy w kodzie.
Podobnie jak wielu ludzi utożsamiałam oczywiście DI z użyciem stosownego kontenera, takiego jak Autofac. W końcu “wstrzykiwanie dla ubogich” (ang. poor-man’s DI) brzmi jakoś niestosownie. Przez ostatnie parę lat pewne koncepcje lepiej poukładały mi się w głowie. W praktyce wyszły różne plusy i minusy, o których nie miałam pojęcia czytając książkę.
Jednym z popularnych przekonań na temat wstrzykiwania zależności jest, że dzięki temu twój kod jest łatwiejszy do przetestowania. Na myśli mamy tutaj oczywiście testy automatyczne, najczęściej jednostkowe, stworzone za pomocą narzędzi typu NUnit, MSTest, itp.
Niby prawda, a jednak nie do końca.
Dwa powody, dla których kod był ciężki do przetestowania z którymi spotykałam się najczęściej:
- użycie statycznych klas i metod w metodzie, którą chciałam przetestować;
- tworzenie instancji obiektu (czyli po prostu zawołanie new MyClass()) bezpośrednio w metodzie, którą chciałam przetestować.
Użycie kontenera rozwiązuje oba powyższe problemy ponieważ:
- nie da się wstrzyknąć statycznej klasy;
- to kontener tworzy instajcę obiektu i robi to poza metodą, którą testujesz.
Można więc powiedzieć, że użycie kontenera narzuca pewne reguły dzięki którym kod staje się łatwiejszy do przetestowania.
Dobra wiadomość jest taka, że te same reguły można zastosować bez użycia kontenera. Po prostu nie nadużywaj statycznych klas i przekazuj potrzebne zależności jako parametr do metod, które ich potrzebują. Jeśli wiele metod w jednej klasie potrzebuje tej samej zależności to można też przekazać ją jako parametr konstruktora.
Proste, prawda?
Dodatkowo udaje nam się uniknąć problemów związanych z użyciem kontenerów, takich jak niewłaściwe zarządzanie czasem życia obiektów czy nadużywanie interfejsów.
Zła wiadomość natomiast jest taka, że programiści to też ludzie. Z mojego doświadczenia wynika, że dużo łatwiej jest wprowadzić nowe zasady jeśli przy okazji pojawia się jakaś nowa “zabawka”. Ludzie mają większą motywację (bo nowe, bo brzmi mądrze, bo można wpisać w CV, bo jesteśmy cool, bo wszyscy tak robią, itede, itepe).
Użycie kontenera dostarcza też całkiem przydatnych argumentów przy egzekwowaniu nowych zasad. Na przykładzie używania statycznych klas (przykład z życia wzięty). Tłumaczenie czemu statyczne klasy sprawiają, że naszego kodu nie da się przetestować robiło się męczące po 10 razach, powiedzieć “bo tak” nie wypadało. Za to “bo statycznej klasy nie da się wstrzyknąć” brzmi mądrze, przekonuje bo przecież zdecydowaliśmy już, że używamy kontenera i w dodatku to jedno krótkie zdanie.
Na koniec najważniejsze. Nie jest prosto zmienić nawyki, szczególnie jeśli przez lata pisaliśmy kod, który jest nietestowalny i jeśli nasz architekt kocha statyczne klasy i zawsze wie lepiej. Łatwiej uzasadnić czas i wysiłek potrzebny na nauczenie się nowych zasad jeśli wiąże się z wprowadzeniem nowego narzędzia. Bez sensu, a jednak tak działamy 🙂
Wydaje mi się, że właśnie te praktyczne aspekty przyczyniły się do powstania nieporozumień. Teoretycznie nie potrzebujesz wstrzykiwania zależności, ani tym bardziej fajnego kontenera, żeby twój kod był łatwiejszy do przetestowania. W praktyce jednak (paradoksalnie) jest to łatwiejsze do wprowadzenia w zespole, który dopiero uczy się jak pisać testowalny kod.
Po jakimś czasie możesz dojść do wniosku, że kontener i DI to jest przesada, że nie potrzebujesz takiej armaty. Ale wtedy już pisanie testowalnego kodu będzie utrwalonym nawykiem.