piątek, 25 maja 2012

Dublowaniu kodu mówię stanowcze TAK!

bo trzeba być oryginalnym

Często spotykam się z sytuacją, że programista za wszelką cenę stara się wyeliminować wszystkie powtórzenia w swoim kodzie. Dlaczego? Ponieważ uważa, że duplikacja kodu to czyste zło i każda linijka kodu powinna być unikalna. Tylko czy takie przeświadczenie jest słuszne? Nie.

logika i realizacja

Aplikacja, każda, bez wyjątku, składa się z dwóch zasadniczych części - logiki oraz jej realizacji.

Czym jest logika? Załóżmy, że mamy stworzyć aplikację do zarządzania restauracją, a dla uproszczenia pełną - obsługą zamówień. Co robimy najpierw? Dowiadujemy się jak to wszystko działa obecnie, czym jest owo zamówienie, z czego się składa, kiedy jest tworzone, a kiedy zamykane, co się dzieje z nim dalej, ile może ich być, kto nimi zarządza, kto z nimi może coś zrobić i co itp., itd. Czyli staramy się pojąć, jak w rzeczywistości ta restauracja obsługuje cały ten proces. I to jest część, która jest niezależna od sposoby realizacji aplikacji, nie ważne w jakim języku ją napiszemy, ba, możemy nawet opisać ten proces w jakimś rzeczywistym dokumencie. Tak czy inaczej nie zmienia się.
Wszystkie informacje zebrane w tym momencie pozwolą nam na opracowanie logiki naszej aplikacji.

Czym jest natomiast owa realizacja? Ma na nią wpływ język, który wybraliśmy, frameworki, ORMy, sposoby zapisu danych itp., itd. Jest to kod, który pozwala nam przekuć wymagania klienta na kod, czyli walidatory, filtry, formularze, widoki itp., czyli jest to pewna funkcjonalność, od której logika jest niezależna. Nawet jeśli jest ona (funkcjonalność) tworzona specjalnie dla danej aplikacji.

przejdźmy to sedna

Po tych krótkich wyjaśnieniach mogę przejść do meritum:)

Najczęściej w logice aplikacji nie ma miejsca na duplikację kodu, ponieważ w procesie zazwyczaj to jeden model jest odpowiedzialny za daną rzecz. Dlatego też w modelach obsługujących logikę tych procesów bardzo rzadko jest miejsce na powtórzenia kodu. Więc, jeżeli posiadacie te same fragmenty kodu w różnych miejscach, to warto się nad nimi poważnie zastanowić. Albo wynieść je wyżej, albo przeanalizować jeszcze raz rozwiązanie, na które się zdecydowaliście. Warto poczytać trochę na temat wysokiej spójności i niskiem sprzężeniu, które poruszają to zagadnienie.

Jednak inaczej rzecz się ma z funkcjonalnością. Nie zawsze unikanie powtarzającego się kodu jest dobrym pomysłem. Dlaczego? Często (a na pewno dużo częściej niżbyśmy chcieli) prowadzi to do utworzenia drzew dziedziczeń. Mamy kilka klas z bardzo ubogą funkcjonalnością, po których dziedziczy cała masa innych. Tylko dlatego, żeby uniknąć trzech-czterech linijek kodu. Najgorsze jednak są sytuacje, gdy klasy pochodne nie są ze sobą w żaden sposób powiązane. Rodzą się zależności, które nie powinny mieć miejsca i pewnego dnia odkrywamy, że aby wprowadzić modyfikację do widoku trzeba przepisywać filtry. Brzmi absurdalnie? Może, ale niestety takie sytuacje mają miejsce.

na koniec

Zdaję sobie sprawę, że może trochę wyolbrzymiłem cały problem, jednak miało to na celu tym wyraźniejsze zwrócenie Waszej uwagi na problem.
Zgadzam się, że kod zduplikowany należy usuwać, refaktoryzować klasy tak, aby było go jak najmniej, jednak należy to robić ostrożnie, ponieważ czasami może się zdarzyć, że w przyszłości przyniesie to więcej szkód niż pożytku.

przykład

[edit 2012-06-04]
W związku z tym, że w prawie każdym komentarzu jest prośba o przykład o to i on:)

Wyobraźcie sobie, że mamy aplikację z dziesiątkami kontrolerów (co wcale nie jest trudne do osiągnięcia:). Oczywiście taka ilość kontrolerów spowodowała, że wydzieliliśmy kilka klas abstrakcyjnych, ponieważ jest kilka grup kontrolerów, których funkcjonalność jest bardzo podobna. Całkiem prawdopodobny scenariusz? Wydaje mi się, że tak:)

W aplikacji mamy możliwość edycji swoich danych, personalizowania jej ustawień i kilka innych rzeczy ustawianych per user. Ponieważ i zarządzanie danymi i personalizowanie ustawień są na tyle złożonymi przypadkami użycia i w dodatku nie atomowymi (czyli do obsługi każdego jest kilka metod) decydujemy się na stworzenie dwóch kontrolerów na tą okazję. Brzmi rozsądnie? Mam nadzieję:)

Podczas edycji/personalizacji do pobierania użytkownika, którego dane mają być modyfikowane, wykorzystujemy metodę z klasy nadrzędnej getUser(), która zwraca obiekt będący reprezentacją aktualnie zalogowanego użytkownika.

Okazuje się jednak, że tymi danymi (większością) może zarządzać administrator. Prawdopodobe?
Na szczęście kontrolery do zarządzania użytkownikami już mamy, pozostaje jedynie rozszerzyć ich funkcjonalność. Decydujemy się na nadpisanie metody getUser(). Zwraca ona aktualnie zalogowanego użytkownika lub użytkownika, który został zdefiniowany w żądaniu np. poprzez podanie id. W ACL'u sprawdzamy (w przypadku przesłania id), czy użytkownik to admin, czy nie.

Działa? Nawet całkiem nieźle:) Ale co dalej? Przecież mamy dwa kontrolery tego typu! W dwóch, w identyczny sposób została nadpisana metoda getUser()! Tworzymy klasę abstrakcyjną!

Oczywiście takich metod, których implementacja to dwie, trzy linie kodu, jest mnóstwo, czasami się powtarzają. Czy na każdy przypadek mamy tworzyć klasę abstrakcyjną? A co, gdyby jedna z klas z przykładu zawierała również kilka metod, które znajdują się w innych klasach? W rozbudowanych aplikacjach taki scenariusz wcale nie jest nieprawdopodobny.

Rozwiązań problemu jest kilka:
  • tworzenie klas abstrakcyjnych
  • skorzystanie z helperów lub innego rodzaju funkcjonalności, która jest wywoływana w magiczny sposób
  • pozostawienie duplikatów w kodzie
Ja decyduje się na rozwiązanie numer 3. Czy jest najlepsze? Może nie, ale lepszego do tej pory nie mam:P