MVC jest królem! Z modelu, przez kontroler, do widoku! Mówili. Słuchaliśmy. Polegliśmy.
Niezależnie od wykorzystywanej technologii czy frameworka: jeżeli “dziubiesz webówki” to prawdopodobnie działasz w MVC. I to “C” może reprezentować różne pojęcia. Czasami jest to Controller, czasami Module, czasami coś jeszcze innego.
Reality check
Ale co to jest ten kontroler? Jak sama nazwa wskazuje: coś, co kontroluje. Czyli: musi być ważne i poważne, prawda? Pan kontroler biletów w autobusie to nawet mandat może wypisać, a w zły dzień to i “wryjdaćmożedać“. Ale: skoro jest to coś ważnego, to wypada o to dbać, tak? Oczywiście! – zakrzykniesz ochoczo.
O kontrolery trzeba dbać? Trzeba? To teraz zerknij, misiu, w swój kod…
To teraz zerknij w trzewia dowolnego kontrolera w swoim systemie. Albo dowolnego projektu open source. I co widzisz? Ano… kod, oczywiście. Niezmierzone morze kodu. Ile razy musisz wcisnąć page down, żeby dojechać do końca pliku? 5, 10 czy może 100? Normalka.
Coś gdzieś poszło nie tak. Po necie krąży sporo memów, heheszków z rozdętych klas “Utils”, “Common”, “XManager” itd. To teraz zastanówmy się: czym od takich śmiechowych klas różni się typowy kontroler? Ręki może nie, ale paznokieć dam sobie uciąć, że w ogromnej większości przypadków: niczym. Brudny, poplamiony, połatany, rozdęty, poprzecierany wór spięty sznurem od snopowiązałki.
Diagnoza
Na konferencjach i szkoleniach opowiadam o SOLID, dużą wagę przywiązując do Single Responsibility Principle oraz Dependency Inversion Principle. I to jest ciekawa sprawa, bo… obie te zasady pięknie pokazują, czym zwykle jest kontroler.
Więcej o SRP i DI dowiesz się tutaj: cykl postów, prezentacja, screencasty.
Zacznijmy od DI. Standardowa implementacja Dependency Injection polega na tym, że wszystkie komponenty, z których klasa korzysta, są przekazane do niej jako parametry konstruktora. Jasne? Oczywiście. Zdrowy rozsądek i dobre praktyki mówią, że jeżeli liczba takich parametrów niebezpiecznie rośnie, to “coś jest nie tak”. Ta “zbyt duża liczba parametrów” jest definiowana różnie. Dla jednego będzie to 5, dla innego 7. Dla mnie to 3. Ponownie otwórz kontroler w swoim projekcie i policz parametry jego konstruktora. Moja 3-letnia córka jeszcze pół roku temu umiała liczyć tylko do 12, więc niestety w większości przypadków pewnie by poległa. Gdybyśmy mieli zdigitalizowany odpowiednik Domestosa, to wylałbym go w całości na kontrolery właśnie. To TU znajdziesz siedlisko wszelkiego ohydztwa. To TU masz klasy z 15-20 parametrami, z których większość wykorzystana jest tylko w jednej metodzie. To TU znajdziesz dziesiątki niepowiązanych ze sobą metod, zgrupowanych w jednym worku, bo “nazwa jest podobna”. Bo “URLe muszą mieć taki sam prefix”.
Zwykle rolą kontrolera jest sprawienie, żeby działał routing.
DI pokazuje, że jest problem. Ale jaki problem?
Problem z SRP. Kto z Was widział kiedyś kontroler, który robi jedną rzecz? Ma jedną odpowiedzialność? I ta odpowiedzialność nie może być określona np. jako “obsługuje księgarnię internetową”. Albo “realizuje operacje bankowe”. Na tym stopniu granularności każdy system można by wrzucić w jeden kontroler i pozamiatane.
Więc właśnie – kontrolery zawierają całą masę akcji. I to każda AKCJA realizuje jedną odpowiedzialność. Zebranie ich wszystkich do jednej brzydkiej kupy, zwanej kontrolerem, to po prostu praktyka. Bo tak napisane w dokumentacji czy tutorialu. Bo tak uczyli na studiach. Wreszcie: bo tak już było w projekcie, do którego dołączam.
Ostatnimi czasy do bloga dołączyły tysiące nowych Czytelników, więc Wam wszystkim przypomnę świętą zasadę, którą doskonale znają starzy bywalcy: “Think for yourself. Question authority.“. Co jeżeli wykładowcy na studiach się mylą? Albo nie tyle mylą, co powtarzają zdania przeczytane w dokumentacji. Dokumentacji, która prawdopodobnie bazuje na jakimś banalnym przykładzie, zamykającym się w 2-3 operacjach. Takie podejście się nie skaluje. Czego zresztą jesteś świadkiem, zerkając w kod, w którym grzebiesz na co dzień.
Leczenie
Skoro kontroler to tylko tępy wór na akcje – po co nam on? Przecież głównym jego zadaniem jest przyjęcie miliona zależności na potrzeby pojedynczych akcji.
Skoro kontroler to tylko klasa wymagana przez framework, aby odpowiednio zdefiniować routing – dlaczego godzimy się na to, zamiast pokazać maszynom, kto tu rządzi? Przecież nie trzeba bazować na standardowej konfiguracji dowolnego frameworka przy generowaniu URLi (<controller_name>/<action>/<params>).
Kontroler jest jak wyrostek. Jest zbędny, do wycięcia. Powinniśmy skupiać się na akcjach.
Wreszcie: skoro akcja jest naszą jednostką, w ramach której tak naprawdę realizujemy jedną operację i jesteśmy w stanie zaimplementować wszystkie dobre praktyki, to… chyba właśnie na akcjach, a nie kontrolerach, powinniśmy się skupić, co nie?
Kontrolery owszem, złowrogo kontrolują. I ograniczają. Nas, programistów. Ubijmy je.
To akcja powinna być klasą. Grupowanie ich w sztuczne byty zwane kontrolerami nie sprawdza się. Ja to wiem, i Ty też to wiesz.
Co zatem można zrobić?
MVA: Model/View/Action
Po pierwsze: z każdego kontrolera wyodrębnić osobą klasę dla każdej akcji. Prawdopodobnie będzie to oznaczało utworzenie X “mini-kontrolerów”, żeby używany framework potrafił całość spiąć do kupy, ale… to nie ma znaczenia. Te małe klasy niech dziedziczą z klasy “ControllerBase”, czy jakkolwiek by się to nie nazywało. To jest tylko po to, aby uszczęśliwić maszynę. Maszyna zobaczy “Controller”, ucieszy się, i będzie działać. Ale my jesteśmy mądrzejsi od maszyn. Dla nas będą to akcje.
Po drugie: skonfigurować routing tak jak chcemy. Znaleźć sposób na wyrzucenie konwencji sterujących procesem generowania URLi gdzieś na zewnątrz, do osobnych bytów. Kto to widział, żeby struktura adresów wpisywanych w przeglądarce definiowała strukturę klas w systemie! Ada Lovelace zabiłaby nas śmiechem.
Po trzecie: ucieszyć się, widząc jak automatycznie “samo” spada skomplikowanie kodu, liczba zależności w konstruktorach, licznik linii kodu per plik (wbrew pozorom: to JEST istotne). I klaskać jak dziecko z lizakiem, gdy rodzi się możliwość ogarnięcia powstałych klas normalnym ludzkim mózgiem.
Horyzont
Taki proces niesie za sobą kolejną zaletę: otwiera drogę do totalnego “ogłupienia” warstwy kontrolerów. Ta warstwa aplikacji, zależna od frameworków i wiążąca nas z cudzym kodem, powinna być ultra-cienka. Mega-głupia. Uber-prosta. Tak, aby kompletnie odseparować NASZ kod od kodu powstałego na potrzeby zewnętrznych bibliotek. Zawierając tam jakąkolwiek logikę biznesową: strzelamy sobie w kolano. Z łuku.
Warstwa aplikacji, wiążąca nas z kodem frameworków, powinna być ultra-cienka
Ale to innym razem.
Ale cliffhanger, co nie? Z tego klifu dyndając, wykorzystaj drugą kończynę górną i… dopisz się do newslettera poniżej! Chyba, że to ja z klifu dyndam, patrząc z przerażeniem na rozwierające się w dole kontrolerowe, tfu, krokodyle, paszcze.
P.S. Wiem, że co do zbędności wyrostka są różne teorie i średnio mnie to interesuje ;).