Przejdź do treści

Duplikować kod czy nie duplikować?

Często gdy mamy napisać jakiś kod, okazuje się, że już gdzieś napisaliśmy taki sam lub podobny. Wtedy stajemy przed dylematem – czy zduplikować go, czy użyć ponownie?

Kiedyś bardzo dużo kodu duplikowano. Baliśmy się dotknąć już działającego kodu, aby przypadkiem czegoś nie zepsuć. Dlatego duplikowaliśmy wszystko na potęgę.

W międzyczasie została sformułowana zasada „Don’t repeat yourself” (DRY). Aby walczyć z duplikacją kodu, zaczęliśmy zawsze i wszędzie wspominać o tej zasadzie. Intencje mieliśmy dobre, ale niewystarczająco przyłożyliśmy się do zrozumienia tej zasady i tego, kiedy należy ją stosować, a kiedy nie.

Nasze zachowanie doprowadziło do jej nadużyć. Kod, który powinien zostać zduplikowany był używany ponownie. Jakby tego było mało, coraz częściej zdarzało się, że robiliśmy przedwczesną optymalizację w tym zakresie. Aby nie dopuścić do możliwości zduplikowania kodu w przyszłości, staraliśmy się pisać go tak, aby był on przygotowany do ponownego użycia. Niestety, takie ponowne użycie w wielu przypadkach nigdy nie następowało.

W związku z tym zaczęliśmy promować kolejne zasady, które miały zapobiec nadużywaniu zasady DRY. Najpierw były to zasady: „Write everything twice” (WET) oraz „Rule of three” (ROT). W międzyczasie pojawiła się jeszcze zasada: „You aren’t gonna need it” (YAGNI). Sytuacja w kodzie trochę się polepszyła, jednakże stosowanie nowych zasad nie wpłynęło na próbę zrozumienia idei zasady DRY i wciąż zdarzają się jej nadużycia.

WET, ROT i YAGNI

Zasady WET i ROT mówią o tym samym. Chodzi w nich o to, że gdy mamy już napisany jakiś kod i przyjdzie potrzeba użycia go ponownie gdzieś indziej, najpierw powinniśmy go zduplikować. Będzie to skutkowało posiadaniem dwóch kopii tego samego lub bardzo podobnego kodu. Dopiero gdy trzeci raz będziemy potrzebowali napisać taki sam lub podobny kod, powinniśmy go zmodyfikować i uwspólnić. Inaczej mówiąc: mamy już dwie kopie danego kodu i kolejne jego zduplikowanie spowodowałoby pojawienie się trzeciej kopii – wtedy powinniśmy uwspólnić dany kod.

Zasada YAGNI ma na celu uchronienie nas przed przedwczesną optymalizacją kodu (w celu jego ponownego wykorzystania gdzieś indziej). Powinniśmy skupić się na pisaniu tylko tego kodu, który jest niezbędny na dany moment. Ta zasada nie mówi jednak nic o tym, kiedy warto uwspólnić kod. Skupia się tylko i wyłącznie na tym, abyśmy nie pisali kodu który, chociaż wydaje nam się potrzebny, z reguły i tak nam się w przyszłości nie przyda.

Wszystkie te zasady (WET, ROT i YAGNI) są jak najbardziej w porządku. Warto je stosować i na pewno dadzą nam wiele dobrego. Jednakże moim zdaniem nie do końca poprawnie adresują problem duplikacji kodu – kiedy powinniśmy zduplikować kod, a kiedy go użyć ponownie.

DRY

Autorom tej zasady chodziło w dużej mierze na automatyzacji powtarzalnych procesów. Załóżmy, że mamy taki scenariusz: aby wdrożyć nową wersję naszej aplikacji, musimy najpierw ręcznie skompilować naszą aplikację, następnie ją spakować i przenieść na serwer. W takiej sytuacji warto napisać skrypt, który zrobi to za nas – dzięki temu unikniemy duplikacji powtarzalnych procesów. Jako inny przykład można podać sytuację, w której dla każdej klasy modelu musimy stworzyć repozytorium, serwis i kontroler (albo jakieś inne powtarzające się klasy/pliki) – aby w kółko nie robić tych samych czynności, warto napisać generator, który za nas stworzy te wszystkie klasy i umieści je w odpowiednich projektach.

Oczywiście ta zasada miała mieć również zastosowanie do pisanego przez nas kodu. Jeśli jakiś fragment kodu jest używany w kilku miejscach i w każdym z nich powinien działać zawsze dokładnie tak samo, wtedy warto taki kod uwspólnić. Jako przykład możemy tutaj podać klasę (lub metodę), która potrzebuje wyliczyć łączną sumę zamówień. Jeśli pojawi się druga klasa (lub metoda), dla której będziemy musieli wyliczyć dokładnie tę samą sumę, w dokładnie taki sam sposób – wtedy powinniśmy uwspólnić kod.

Zasada ta ma również zastosowanie do bazy danych. W przeszłości często zdarzało się nam tworzyć wielkie tabele, które miały niekończącą się liczbę kolumn. Teraz może ten problem nie występuje aż tak często, jednakże widywałem takie systemy. Te bazy danych praktycznie zawsze posiadały kilka tabel, które mały część kolumn dokładnie taką samą. Te kolumny powinny zostać przeniesione do osobnej tabeli, abyśmy uniknęli niepotrzebnej duplikacji danych. Przykładem może być zamówienie i faktura. W obu tych dokumentach występują dane klienta, dla którego dany dokument został wystawiony. Ten klient jest dokładnie tym samym klientem dla obu dokumentów, więc jego danego powinny znaleźć się w tabeli „klienci”, a nie być zduplikowane w tabelach „zamówienia” i „faktury”.

Kiedy duplikować kod?

Wydaje mi się, że dość dobrze radzimy sobie z uwspólnieniem istniejącego kodu i jego ponownym wykorzystaniem. Robimy to aż za często. Kiedy więc nie powinniśmy tego robić? Kiedy powinniśmy powielić daną klasę czy metodę, zamiast użyć ją ponownie w innym miejscu? Przedstawię trzy różne konteksty.

Kontekst biznesowy

Załóżmy, że mamy aplikację do zamówień, w której mamy klasę „zamówienie” i klasę „klient”. Każda z tych klas powinna posiadać adres. W takiej sytuacji zamiast duplikować pola adresu w klasie „zamówienie” i w klasie „klient” możemy stworzyć osobną klasę „adres”. W tej klasie umieszczamy wszystkie niezbędne pola, a w klasach „zamówienie” i „klient” dodajemy tylko referencję do klasy „adres”. Brzmi sensownie, prawda? Okazuje się, że i tak, i nie.

Wszystko zależy od tego jak będziemy chcieli traktować te adresy. Czy w obu przypadkach będą miały mieć dokładnie te same pola? Czy będą na nich dozwolone dokładnie te same operacje? W niektórych naszych systemach tak właśnie będzie. Adresy występujące w obu tych klasach będą dokładnie tymi samymi adresami – wtedy warto klasę „adres” uwspólnić, czyli w obu klasach („zamówienie” i „klient”) wykorzystać tę samą klasę „adres”.

Z drugiej strony możemy mieć system, w którym mimo że te adresy będą bardzo podobne, to jednak będą się odrobinę różnić. Może to być trochę inny zestaw pól albo dostępnych operacji. W takich sytuacjach warto kod zduplikować. Powinniśmy zrobić osobną klasę dla adresu zamówienia i osobną dla adresu klienta. Powinniśmy tak zrobić nawet wtedy, gdy na danym moment klasa „adres zamówienia” i klasa „adresu klienta” wyglądają identycznie, ale biznesowo są one czymś różnym. Biznes dla którego tworzymy dany system inaczej rozumie „adres zamówienia” i „adres klienta”. W związku z tym mimo że na danym moment te adresy z naszej perspektywy wyglądają identycznie, za jakiś czas mogą zacząć żyć własnym życiem.

Dlatego bardzo dużo zależy od tego jak nasz biznes patrzy na daną klasę. Jeśli dla niego dana klasa jest czymś innym w kontekście jednej klasy, a czymś innym w kontekście drugiej klasy, nie powinniśmy tego na siłę uwspólnić, tylko taki kod należy zduplikować.

To samo tyczy się metod – jeśli dana metoda ma w każdym użytym kontekście działać dokładnie tak samo, należy ją użyć ponownie. Jednakże jeżeli musimy dodać nowy parametr do naszej metody albo zmodyfikować jakiś warunek – w takich sytuacjach powinniśmy zastanowić się, czy przypadkiem lepszym rozwiązaniem nie będzie duplikacja tej metody. Co więcej, powinniśmy pamiętać też o tym, że nie zawsze całą metodę trzeba duplikować, czasem można wspólne fragmenty wyodrębnić do mniejszych metod, a powielić i ewentualnie zmodyfikować to, co ma lub może być różne.

Kontekst wydajnościowy

Kolejnym przykładem, kiedy warto coś zduplikować jest wydajność. Jeśli duplikacja jakiegoś fragmentu kodu albo danych pozytywnie wpłynie na wydajność naszej aplikacji, warto rozważyć taką duplikację.

Weźmy pod rozwagę nasze relacyjne bazy danych. Operacja „join” bywa w nich kosztowna. Czasem ten koszt jest zauważalny, a czasem nie. Jeśli mamy problemy z wydajnością naszego zapytania i zrezygnowanie z kilku operacji „join” pozytywnie wpłynie na naszą wydajność, wtedy warto zastanowić się nad zduplikowaniem niektórych danych z jednej tabel w innej tabeli.

Podobny scenariusz może być w sytuacji, kiedy aby pobrać potrzebne nam dane, musimy wysłać zapytanie do innego systemu. Zapytanie po sieci również może być kosztowne. Dlatego w niektórych przypadkach powinniśmy sobie pozwolić na zduplikowanie części danych z innego systemu w naszym systemie. Oczywiście powinniśmy to zrobić tylko, jeśli będzie to miało pozytywny wpływ na działanie naszej aplikacji.

Warto jednak pamiętać, że w takich sytuacjach nie należy duplikować wszystkiego, a jedynie to, co faktycznie da nam jakieś korzyści.

Kontekst zależności

Kolejną ważną rzeczą jeśli chodzi o duplikowanie i ponowne użycie kodu jest sprzężenie (coupling). W im większej liczbie miejsc używamy tej samej klasy lub metody, tym sprzężenie nam rośnie. A im większe sprzężenie, tym kod staje się trudniejszy w utrzymaniu i rozwoju. Aby je ograniczyć możemy powielić dany kod. Powinniśmy jednak zdawać sobie sprawę z dwóch rzeczy.

Z jednej strony zduplikowany kod łatwo usunąć, jeśli był użyty tylko w jednym miejscu. Jeśli funkcjonalność w której był użyty nie jest nam już potrzebna, taki kod możemy z czystym sumieniem usunąć.

Z drugiej strony jeśli zduplikowaliśmy jakiś kod i pojawi się wymaganie, że w każdym miejscu musimy zmienić działanie tego kod, to takich miejsc do zmiany może być dużo i nie tak łatwo może być je wszystkie znaleźć.

Dlatego stając przed dylematem czy dany kod zduplikować, czy użyć go ponownie, powinniśmy zastanowić się jakie konsekwencje dana decyzja może spowodować i czy są one dla nas akceptowalne.

Podsumowanie

Moim zdaniem odpowiedź na pytanie czy duplikować, czy nie duplikować dany kod jest obecnie sporym problemem. Często zdarza nam się popadać ze skrajności w skrajność – od nadmiernego uogólniania kodu, do nadmiernego jego duplikowania. Ma to potem negatywny wpływ na rozwój i utrzymanie naszych systemów. W związku z tym zachęcam do świadomego stosowania wszystkich przytoczonych przeze mnie zasad. Warto poświęcić trochę czasu na zastanawianie się, czy coś jest faktycznie duplikatem, czy nie, i czy warto dany fragment kodu uwspólnić, czy też nie.

3 komentarze do “Duplikować kod czy nie duplikować?”

  1. Pingback: dotnetomaniak.pl

  2. Ciekawy artukuł, przez nadurzycie DRY tworzymy klasy Utils z metodami posiadającymi wiele parametrów tylko po to aby na siłe uniknąc duplikacji kodu.

Możliwość komentowania została wyłączona.

Discover more from ADMU Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading