
Event Listener to tylko warstwa pośrednicząca
Jak pisać Event Listenery by były przyjazne i nie nastręczały nam problemów? Najpierw trzeba zrozumieć czym one tak na prawdę są i jaką mają odpowiedzialność.
Przez wielu programistów określenie „legacy” jest odbierane bardzo negatywnie. Oznacza ono aplikację z dużym, albo nawet bardzo dużym długiem technicznym, który jest bardzo trudny a czasami niemożliwy do spłacenia. Praca z taką aplikacją jest bolesna, nierzadko powoduje frustrację i niechęć do dalszej pracy nad nią.
Po czym jednak poznać aplikację legacy? Nie ma sztywnych zasad, dzięki którym aplikację należy traktować jako taką – wszystko zależy od jej stanu i subiektywnego spojrzenia na nią. Możemy jednak wyłonić kilka wskazówek, które mogą zasugerować ocenę aplikacji jako legacy.
Nie każdy punkt z tej listy jest tak samo ważny jak pozostałe. Należy tą listę odczytywać jako wskazówki, a nie wymogi do oceny. Przy każdym punkcie zobaczysz komentarz opisujący dany problem, subiektywny czas spłacenia długu technicznego a także uwagi dotyczące jego spłacenia. No to lecimy.
Zaczynamy od tego punktu nie bez powodu – jest on najczęściej spotykany w przeróżnych programach OpenSource i nie tylko, a co za tym idzie, razem z nim w parze idzie wiele powiązanych błędów.
Problemem jest możliwość odpalenia praktycznie każdego skryptu PHP znajdującego się na serwerze, z poziomu przeglądarki. Dzieje się tak, ponieważ domena jest podłączona pod główny katalog aplikacji. WordPress, Joomla! i wiele innych skryptów PHP, posiada tego typu strukturę plików. Domena powinna być podpięta pod katalog dostępny publicznie, na przykład /var/www/web
, a pozostałe pliki powinny być katalog wyżej, lub katalog obok, na przykład /va/www/src
.
– Koszt spłacenia długu: 4/5
Gdy aplikacja jest mała, albo mało złożona, spłacenie takiego długu nie powinno być uciążliwe. Jeśli jednak w aplikacji mamy zakodowane na sztywno ścieżki do plików/katalogów, zmiana tej struktury może być czasochłonna i ciężka do wdrożenia, bez popełnienia błędów.
Niektóre serwery posiadają funkcję listowania plików/katalogów po wejściu na stronę, w sytuacji gdy w katalogu nie istnieje główny plik uruchomieniowy (często jest to plik index.html
lubindex.php
). Funkcja ta umożliwiała stworzenie szybkiego podglądu listy plików i katalogów na danym serwerze – szybszy i tańszy sposób na FTP 🙂
Gdy na takim serwerze zamieszczona była aplikacja, gdzie pliki wykonywalne były dostępne z poziomu przeglądarki, można było wylistować te pliki i uruchomić każdy z nich w oczekiwaniu na reakcje aplikacji czy serwera i w efekcie zaatakowanie jej. Bywa, że aplikacja posiada dziesiątki pustych plików index.html
, w każdym katalogu, które uniemożliwiają takie listowanie.
– Koszt spłacenia długu: 1/5
Samo usunięcie plików nie będzie problemem. Punkt ten jest też połączony z punktem wyżej, czyli strukturą katalogów. Gdy spełniony zostanie punkt powyżej, ten będzie sprowadzał tylko do usunięcia plików z serwera.
Kolejny punkt powiązany z pierwszym. Publiczny dostęp do plików PHP zmuszał developerów do blokowania ich wykonywania, przez sprawdzanie jakiejś stałej zdefiniowanej we front-kontrolerze (w pliku index.php
na który kieruje Apache lub nginx). Dzięki temu, przy odpaleniu strony „po bożemu”, stała była zdefiniowana w systemie i aplikacja działała bez przeszkód, a gdy jakiś plik wykonywalny był uruchamiany bezpośrednio z poziomu przeglądarki – kod kończył wykonywanie, ponieważ nie było tej specjalnej stałej. Dzięki temu, nie można było wykonać skryptu z plików (czyli bezpośrednio wywołując plik z poziomu przeglądarki) bez działającej aplikacji.
<?php if (!defined('SUPER_CONST')) die();
Jednak gdy tej linijki sprawdzającej brakowało w jakimś pliku, powodowało to dziurę w systemie, która mogła pociągnąć za sobą wielkie konsekwencje.
– Koszt spłacenia długu: 1/5
Ponownie, samo usunięcie linijek z kodem sprawdzającym to chwila, jednak należy najpierw wykonać punkt pierwszy z listy.
W przypadku PHP, gdy posiadamy świetny system OOP, używanie funkcji w rozbudowanych aplikacjach to katorga dla programistów. Wystarczy zobaczyć jak wyglądają wtyczki do WordPressa. Rozbudowa i utrzymanie takiej aplikacji powoduje wiele wyrzeczeń.
– Koszt spłacenia długu: 5/5
To chyba jeden z najbardziej kosztownych punktów tej listy. Wymaga tak na prawdę przepisania aplikacji od nowa, a jeśli nie całej, to jej lwiej części.
Miałem kiedyś okazje pracować na systemie, gdzie dostępna była klasa, która posiadała około 6000 linijek kodu – sześć tysięcy! Proszę, nie…
– Koszt spłacenia długu: 5/5
Koszt jest również bardzo duży, jednak wydaje się relatywnie mniejszy niż poprzedni dotyczący funkcji, bo w pewnym stopniu jakakolwiek obiektowość już istnieje w tym systemie.
Zdublowany kod, wymieszana odpowiedzialność, rozbudowane klasy/metody, brak spójności co do argumentów czy zwracanych wartości a także problemy z zależnościami są kulą u nogi takiej aplikacji. Przemyślana struktura klas oraz ich metod wymaga samozaparcia i doświadczenia, a z rozrostem aplikacji, poświęcony na to czas, zwraca się.
– Koszt spłacenia długu: 4/5
Gdy aplikacja jest OOP, sama refaktoryzacja jest możliwa i nie powinna być trudna sama w sobie, jednak jest czasochłonna w zależności od ilości plików w projekcie.
Jedną z najstarszych naleciałości PHP jest mieszanie kodu HTML z PHP (a także SQL) w jednym pliku. Wymieszanie powoduje przenikanie się warstw prezentacji i logiki z dostępem do danych, co powoduje problemy w rozbudowie kodu. Powoduje to także powielanie tego samego kodu w wielu miejscach aplikacji – zamiast wydzielić zapytanie SQL do osobnej klasy, pisze się zapytanie bezpośrednio w kontrolerze.
– Koszt spłacenia długu: 2/5
Refaktoryzacja jest relatywnie prosta – kod jest już napisany, należy go tylko przenieść w odpowiednie miejsca, rozdzielając warstwy aplikacji od siebie.
Testy pozwalają nam upewnić się, że napisany przez nas nowy kawałek kodu działa zgodnie z naszymi oczekiwaniami, a stary i zmodyfikowany kod, po naszych zmianach, nadal działa bezproblemowo. Dzięki testom mamy również pewność, że nowa funkcjonalność nie zepsuła czegoś, co już jest w aplikacji.
Brak testów to brak świadomości na temat błędów oraz brak możliwości weryfikacji wprowadzanych funkcjonalności pod względem ewentualnych bugów, wynikłych podczas ich wprowadzania.
– Koszt spłacenia długu: 3/5
Dodanie testów wiąże się z decyzją dotyczącą ilości tych testów, tego ile procent aplikacji mają pokryć, a także z czasem jaki trzeba poświęcić na ich napisanie.
Zależności pomiędzy klasami można tworzyć na wiele sposobów, jednak chyba najgorszym jest tworzenie obiektów klas zależnych bezpośrednio w konstruktorze. Tworzą się silne powiązania pomiędzy częściami systemu, i poprzez brak abstrakcji uzależniamy nasz kod od konkretnych wdrożeń, zamiast od interfejsów.
Takie silne powiązania powodują, że kod jest ciężki w rozwoju, i jeszcze cięższy w testowaniu. Rezultatem jest kod z zależnościami, których nie możemy zmockować w testach jednostkowych. Również często używany jest Singleton Pattern, który zaśmieca aplikację, a kod stworzony z jego użyciem jest mocno uzależniony od danego singletona i praktycznie niemożliwy w testowaniu.
– Koszt spłacenia długu: 3/5
W zależności od ilości klas, zmiana ta nie jest relatywnie pracochłonna, ale w połączeniu z dodaniem kontenera DI (jeśli go brakuje), może zająć ciut więcej czasu. Należy doliczyć dodatkowy czas na całkowite przepisanie klas Singletonowych – jeśli te istnieją w systemie.
Ten punkt w dzisiejszych czasach może budzić myśli typu: „jakim cudem, przecież composer jest wszędzie”. Jednak istnieją aplikacje, które nie używają żadnego autoloadera, ani tym bardziej composera, a wszystkie klasy ładowane są za pomocą zwykłych dyrektyw include
.
Koszt spłacenia długu: 3/5
Wszystko zależy od ilości plików, które należy zaktualizować, oraz struktury klas. Dodatkowo, autoloader najlepiej działa gdy w systemie występuje standard PSR-4 lub chociaż PSR-1. Composer działa również na katalogach wyszukując w nich klas.
W dzisiejszych frameworkach (chociażby Symfony) żądanie i odpowiedź do przeglądarki są definiowane w formie obiektów Request i Response, a na tych obiektach opiera się lwia część aplikacji, by dostarczyć content do przeglądarki. Niestety, brak tego flow zastępuje zwykłe echo
, niekiedy z dodatkowymi wywołaniami funkcji header()
, które modyfikują typ odpowiedzi (na przykład JSON). Powoduje to brak kontroli nad flow przetwarzania danych, i późniejsze problemy w ich przetwarzaniu. Na przykład dodanie cache dla całej aplikacji wymaga zmiany odpowiedzi (dodanie nagłówków chociażby) w bardzo wielu miejscach aplikacji.
Koszt spłacenia długu: 2/5
W zależności od ilości miejsc występowania. Sama naprawa błędu to tylko zmiana echo $content
na new Response($content)
, i dodanie paru nagłówków w razie potrzeby.
Aplikacje legacy można podzielić na dwie grupy. Te które nadają się do refaktoryzacji, i te które nadają się tylko do przepisania na nowo. Im więcej punktów potrafisz wskazać w swojej aplikacji, tym bardziej zalicza się ona do tej drugiej grupy.
Mam nadzieję, że dzięki temu artykułowi będziesz mógł wskazać słabe punkty aplikacji na której pracujesz, którą rozwijasz, i dzięki temu będziesz miał świadomość na co zwrócić uwagę przy rozwoju swojego kodu. A może nawet uda Ci się podjąć kroki w celu zmniejszenia tych problemów – czego serdecznie Ci życzę.
A jak jest u Ciebie? Może masz inny pogląd, może coś byś dodał do tej listy?
Polub stronę PHPCenter.pl by zyskać dostęp do najnowszych wiadomości ze świata PHP!
Polub naszą stronę aby dostawać powiadomienia o nowych, niesamowitych treściach!