fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
7 minut

Kontroler jest jak wyrostek


28.04.2016

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ówprezentacjascreencasty.

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 ;).

Notify of
Grzegorz Gajos

Fajny wpis, miło poczytać wkońcu o takich rzeczach po polsku :). Kontrolery mają jeszcze jedną poważną wadę, ich proces konstrukcji jest zwykle ukryty. Powoduje to, że zaczynamy walczyć z frameworkiem i tworzyć łaty w obrębie jego metod, które przetworzą nam ten pseudo MVC w coś co chcemy napisać. Warto też wspomnieć, że zwykle kontroler we frameworku webowym ma niewiele wspólnego z literą C z MVC. Główna idea MVC polega na powiadamianiu widoku wprost z modelu. W webie wszystko routujemy przez kontroler. Trafiłem swego czasu na ciekawy framework, który właśnie próbuje przerwać zmowę milczenia i nie zobaczymy w nim wogóle warstwy… Read more »

Paweł Świątkowski

Proszę bardzo – framework, w którym jedna akcja = jedna klasa: http://hanamirb.org/guides/actions/overview/

Piotr Bielecki
Piotr Bielecki

Aż miło sie na sercu robi jak człowiek przeczyta mądre rady i zauważy, że to robi u siebie w projekcie a doszedł do tego sam :)

Dodam, że przecież w samym kontrolerze możemy zdefiniować jaki routing/url ma być użyty aby trafić do tej konkretnej akcji.
np.

[Route(“{productId:int}/{productTitle}”)]
public ActionResult Show(int productId) { … }

Paweł
Paweł

Dawno temu obejrzałem prezentację Sandro Mancuso na temat organizacji kodu w projekcie i obowiązkach poszczególnych warstw.
Autor przekonywał, że kontrolery powinny być naiwne i nawigować do klas implementujących przypadki użycia. Osobom, którym temat wydaje się ciekawy polecam przejrzeć sobie ten wykład. Slajdy dostępne są tutaj: http://www.slideshare.net/sandromancuso/crafted-design-geecon-2014#22, natomiast nagranie: https://vimeo.com/101106002

Dawid K

Problem z ciągnącymi się kilometrami kontrolerami nie wynika z nadmiaru akcji, lecz z tego, że ludzie nie rozumieją na czym MVC polega i w kontrolery wpychają wszelką logikę: aplikacyjną, walidacyjną, biznesową, infrastrukturalną i rozmytą. Tymczasem kontroler powinien tylko odebrać żądanie, wywołać serwis i w zależności od jego wyniku, przekierować na odpowiedni widok. Gdyby ludzie się tego trzymali, to nawet 20 akcji w kontrolerze nie byłoby wielką tragedią, bo i tak zajęłyby mniej niż 100 linijek. Zgodzę się, że Twoje podejście to ciekawa idea, ale kontroler per akcja brzmi z kolei jako przesada w drugą stronę. Oczywiście implementacja Twojego pomysłu skróciłaby… Read more »

MIBa
MIBa

Kontroler ma za zadanie odebrać parametry i przekazać je do serwisów. 1000 linii kodu? Bzdura! Odebrać dane, zwalidować, przekazać do serwisu, przekonwertować na viewModel i wypluć(widok lub DTO). czyste MCV? Warstwy panowie! :) Warstwy :) Persistenc Layer , Model , Serwis, Validators , Converters. Domyślny routing może ssać w większych apkach – rozbijamy na mniejsze jednostki i korzystamy z własnego routingu. No i w sumie mamy to.

Radosław Maziarka

My w aktualnym projekcie używamy CQRS więc w kontrolerze mamy multum małych akcji z przekierowaniem query / command do mediatora i tyle.

Marek
Marek

Jak w takim scenariuszu realizujesz warstwę serwisową? Serwisy mają też po jednej metodzie? Załóżmy, że mamy temat – zarządzanie użytkownikami. Czy wtedy tworzysz osobne klasy wykonujące operacje na użytkowniku (np. AddUserService, DeleteUserService, ViewUserService) ?

Radosław Maziarka

Dla każdego commanda mamy odpowiedni handler, który odpowiada za obsługę. Np dla AddUserCommand mam AddUserCommandHandler.

tomaszk-poz
tomaszk-poz

Wpis na czasie, jak patrzę na moje kontrolery to mam mieszane uczucia. Ale sprawa dla mnie nie jest taka oczywista. Moja apka nigdy nie wyjdzie poza Web (jakbym ją sprzedał drugiemu klientowi to bym urżnął ze szczęścia). Żeby wywalić z kontrolera, to co w nim nie powinno być (wg. kanonów) musiałbym dumać nad kolejnymi interfejsami, serwisami etc. Oczywiście cześć rzeczy wyrzucę (bo klient zamówił modyfikację/rozbudowę) i pasuje ale resztę zostawię, bo tylko się narobię a nic nie zyskam. Zresztą i tak jakaś walidacja ukryta jest w kontrolerze – chociażby w informacji o parametrze metody – czy może być null wymusza… Read more »

Michał
Michał

U nas był ten sam problem.
Został rozwiązany trochę inaczej. Aktualnie dla każdej “strony”, czyli obszaru obsługiwanego przez jeden controller jest tworzony FactoryObject, który trzymamy dla każdego użytkownika w sesji – zapamiętujemy w nim system uprawnień i dostępów oraz znajduje się w nim sama logika łączenia modelu z kontrolerem – czyli nasz kod. Akcje w kontrolerze ograniczyły się nam do 2-3 linijek.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Facebook

Książka

Zobacz również