Bacz, bo w gąszczu uogólnień – skryty – rwie uproszczeń strumień.
Zgodnie z obietnicą wypada przedstawić drugi z rezultatów inspiracji wynikłej ze swoistego dialogu (diaBlogu ;)) pomiędzy Krzysztofem Morcinkiem a mną. Tym razem skupię się na następującym fragmencie jego wpisu:
(problemem – przyp. mój) może być, gdy zwiążemy wspólnym kodem zupełnie różne miejsca aplikacji, które tylko przypadkowo kiedyś robiły podobne rzeczy. A teraz nie mogą się samodzielnie rozwijać. Potrzebna zmiana w Module Users wymaga zmiany w tzw Core, która z kolei rozwaliłaby moduł Pictures. Szybkim rozwiązaniem jest rozpięcie tych modułów, ale gdy ktoś zbyt skupia się na DRY może nie chcieć do tego dopuścić.
Dodatkowo Krzysztof uzupełnia swój wpis odwołaniem do artykułu na 97rzeczy.devblogi.pl p.t. Strzeż się współdzielenia.
Co znamienne – zarówno w cytowanym fragmencie, jak i w rzeczonym artykule mamy do czynienia z pewną pułapką, w którą sam co raz wpadam. Chodzi o abstrahowanie, uogólnianie. Pułapką jest to, że czynimy to zbyt często porwani nurtem uproszczeń i nie dostrzegamy, że danego zagadnienia aż tak uprościć się nie da, nie gubiąc jednocześnie jego kontekstu, sensu. Mój sposób na radzenie sobie z taką pułapką, to zamieszczanie kodu, który dane zagadnienie obrazuje. Dopóki kod istnieje i wszelkie tezy oraz wnioski z niego płynące są prawdziwe, nie ma szans na pułapkę. Wystarczy jednak choć na chwilę pozbyć się kodu, byśmy radośnie pomknęli strumieniem uproszczeń wprost do morza ignorancji.
Co znamienne dodatkowo – autor tego artykułu sam wspomina o kontekście, ale nie bardzo go przytacza. Wszystko jest więc bardzo ogólne i daje ogólny wniosek zawarty w tytule „Strzeż się współdzielenia”.
W zalinkowany przez Krzysztofa artykule autor wspomina o epizodzie programistycznym (chyba z początków programowania, bo wtrąca, że użył metody umiejętności nabytej na studiach). Podejrzewam, że sam autor już nie pamięta, o co dokładnie chodziło. W jego pamięci zapadło jednak to, co w owym czasie było dla niego najbardziej odczuwalne – „blamaż”, jaki stał się jego udziałem, kiedy użył współdzielenia kodu. Bez odpowiedzi pozostaje jednak, jak owo współdzielenie zostało zrealizowane (brak kodu – przykładu) i czego dotyczyło. Być może podobieństwo, które dostrzegł autor, nie kwalifikowało tego kodu do współdzielenia. Czyż pętle nie są do siebie podobne? Są, ale przecież nie uwspólniamy kodu tylko dlatego, że w obu wypadkach jest pętla. Jeśli podobieństwo było tego rodzaju, to wówczas autor artykułu popełnił błąd, ale to nie może prowadzić do wysnucia wniosku strzeż się współdzielenia (co mi przypomina taki wniosek wysnuty przez jaskółki kiedy nisko latamy ludzie sprowadzają deszcz mówiąc: będzie deszcz).
Brak kontekstu i przykładów uniemożliwia rzeczowe odniesienie się do wskazanego artykułu, można jedynie dywagować, bawić się w domniemania – trochę szkoda na to czasu.
Wróćmy teraz do przykładu Krzysztofa. I w tym przypadku mowa jest o uzależnianiu modułów, tutaj jednak jest już jakaś namiastka przykładu, bo mamy wymienione dwa moduły (Users i Pictures) i wskazanie ich zależności od trzeciego – wspólnego (Core). Niestety brak przykładu kodu, uniemożliwia stwierdzenie co tak naprawdę podlega współdzieleniu (pewnie wówczas można by od razu krzyknąć „szach mat”, wskazując jednocześnie rozwiązanie) i dlaczego owo współdzielenie rozwala jeden z modułów. Nie pomaga tutaj także – wydawałoby się pomocne – stwierdzenie, że wspólny kod zawiera fragmenty kodu tylko przypadkowo robiące to samo (chyba, że chodzi właśnie o użycie siekierki do dłubania w zębach). Mimo to przyjmijmy, że kod jednak jest podobny, da się uwspólnić i podywagujmy, jakie zmiany mogą w nim wystąpić? Mogą to być: konieczność usunięcia błędów (wówczas za jednym zamachem pozbawiamy błędu oba zależne moduły), ulepszenie (np. wydajniejszy algorytm), dostosowanie (np. do różnych platform, źródeł danych, itp.). We wszystkich trzech przypadkach trudno dopatrzyć się niebezpieczeństwa rozwalenia tym samym któregokolwiek z modułów. Jedyna zmiana, jaka mogłaby dać taki skutek, to zmiana logiki działania współdzielonego kodu. Może się bowiem okazać, że jeden moduł ma działać wg dotychczasowej logiki, a drugi wg nowej. Rzecz w tym, że jeśli programuje się zgodnie z zasadami SOLID (uzależniamy się wyłącznie od abstrakcji – np. interfejsów), kod piszemy zgodnie z zasadą otwarte/zamknięte, to taka zmiana nadal niczego nie rozwala. Powstaje kolejna – specjalizowana klasa – implementująca ten sam interfejs, dziedzicząca ze wspólnej klasy bazowej (lub nie, jeśli zmiana jest diametralna) i to wszystko – nadal nie nastąpiła żadna rozwałka. Owszem może się okazać konieczna refaktoryzacja na poziomie nazewnictwa (jeśli poprzednią nazwę wybrano niezbyt szczęśliwie – cóż często bywa), ale to nie jest nic co byłoby nie do przyjęcia. Można stworzyć dwie klasy o nowych, bardziej adekwatnych nazwach, zaś starą nazwę zostawić w klasie dziedziczącej z tej nowej, przejmującej jej zadania (dodatkowo oznaczyć ją jako przestarzałą, aby w odpowiednim momencie zrefaktoryzować oprogramowanie, które jej używa). Dopóki interfejsy nie ulegają zmianom – wszystko jest w należytym porządku.
Rzecz w tym, że nie żyjemy w idealnym świecie, kod nigdy nie jest doprowadzony do optymalnej postaci, zatem mamy SOLID jako taki, a nie SOLID. I to, a nie zaś współdzielenie kodu, może powodować reperkusje. Zaś najgorsze, co w takim momencie możemy zrobić, to zżymać się, że te wszystkie wspaniałe metody nie działają. Rzecz w tym, że ich po prostu nie zastosowaliśmy.
Nie dajmy się zatem zwariować sobie samym (nie upraszczajmy uogólniając), mamy w firmie do dyspozycji cały sztab ludzi, który nas w tym (zwariowaniu) altruistycznie i profesjonalnie wyręczy ;).