W praktyce zasada dry principles, czyli Don't Repeat Yourself, nie jest zakazem kopiowania kodu, tylko sposobem porządkowania wiedzy w projekcie. W tym artykule pokazuję, jak rozumieć ją w architekturze i wzorcach projektowych, gdzie naprawdę pomaga, kiedy zaczyna szkodzić oraz jak odróżnić sensowną abstrakcję od tej, która tylko robi wrażenie „eleganckiej”.
Najkrócej: DRY porządkuje wiedzę, ale nie każdą podobność warto usuwać od razu
- DRY dotyczy przede wszystkim tej samej wiedzy biznesowej zapisanej w kilku miejscach, a nie każdego podobnego fragmentu kodu.
- Najmocniej działa tam, gdzie jedna zmiana powinna propagować się spójnie: w logice domenowej, schematach danych, testach i dokumentacji.
- Za wcześnie wprowadzona abstrakcja bywa droższa niż chwilowa duplikacja, dlatego często czekam do drugiej albo trzeciej kopii.
- W aplikacjach webowych najłatwiej o rozjazd między frontendem, backendem, bazą danych i opisem API.
- Dobry DRY upraszcza utrzymanie, ale zbyt agresywny DRY potrafi pogorszyć czytelność i zwiększyć sprzężenie.
Co naprawdę oznacza zasada DRY w architekturze oprogramowania
Ja patrzę na DRY przede wszystkim jak na zasadę o wiedzy, a dopiero potem o kodzie. Jeśli ta sama reguła biznesowa, ten sam kontrakt albo ta sama decyzja techniczna żyją w kilku miejscach, każda zmiana kosztuje więcej i zwiększa ryzyko niespójności. W dobrze zaprojektowanym systemie jedna rzecz ma jedno miejsce odpowiedzialności, a reszta korzysta z niej zamiast przepisywać ją od nowa.
To właśnie dlatego DRY tak dobrze łączy się z architekturą i wzorcami. Metoda, moduł, klasa, komponent, generator kodu czy wspólna konfiguracja nie są celem samym w sobie. Są narzędziami do tego, żeby zmiana jednej reguły nie wymagała ręcznej korekty w pięciu innych miejscach. Tę samą logikę widać też poza kodem źródłowym: w schematach bazy danych, w pipeline’ach budowania, w testach i w dokumentacji.
W praktyce oznacza to jedno: nie pytam najpierw, czy da się coś zwinąć do helpera. Najpierw pytam, czy to naprawdę jest ta sama wiedza, czy tylko podobny kształt kodu. Ten rozdział jest ważny, bo właśnie tu większość zespołów zaczyna się mylić, a stąd już blisko do niepotrzebnego refaktoru.
Gdzie powielanie wiedzy najczęściej wchodzi tylnymi drzwiami
W aplikacjach webowych duplikacja rzadko wygląda spektakularnie. Częściej wciska się małymi szczelinami: w walidację formularza, w mapowanie statusów API, w nazwy pól w bazie i w treść dokumentacji. Problem nie polega na samym podobieństwie, tylko na tym, że jedna reguła zaczyna żyć osobno w kilku warstwach systemu.
| Obszar | Jak wygląda duplikacja | Lepszy kierunek |
|---|---|---|
| Frontend i backend | Ta sama walidacja e-maila, limit znaków albo status zamówienia opisana dwa lub trzy razy | Jedno źródło reguł, wspólny kontrakt lub generowanie typów z API |
| Baza danych | Ta sama informacja wpisana w kilku tabelach, formularzach lub triggerach | Normalizacja i jedno canonical source of truth dla danych |
| Testy | Te same dane wejściowe kopiowane ręcznie do wielu przypadków testowych | Fabryki danych, helpery testowe i wspólne fixture’y |
| Dokumentacja | Opis endpointu powtórzony osobno w README, komentarzu i notatkach zespołu | Jedna aktualna dokumentacja generowana albo utrzymywana w jednym miejscu |
| Konfiguracja | Te same stałe, flagi lub progi zapisane w kilku plikach | Jeden moduł konfiguracji lub centralny plik ustawień |
Wzorce projektowe pomagają dokładnie tutaj: izolują wspólną część od zmiennej. Strategy, Template Method, kompozycja czy nawet prosty generator potrafią usunąć powtarzaną wiedzę bez budowania sztucznego labiryntu. Ale żeby to miało sens, trzeba najpierw rozpoznać, co jest naprawdę wspólne, a co tylko podobne z zewnątrz. I tu dochodzimy do najtrudniejszej części całej układanki.
Jak odróżnić dobrą abstrakcję od złej
Najczęstszy błąd polega na tym, że ktoś widzi dwa podobne fragmenty i od razu próbuje je scalić. Ja wolę prostsze kryterium: jeśli dwa miejsca zmieniają się z tego samego powodu, to kandydat do wspólnej abstrakcji jest mocny. Jeśli podobieństwo wynika tylko z kształtu kodu, a biznesowo te rzeczy idą w różnych kierunkach, lepiej jeszcze poczekać.
| Sygnał | Dobra abstrakcja | Zła abstrakcja |
|---|---|---|
| Powód zmiany | Jedna reguła zmienia się w kilku miejscach naraz | Podobny zapis, ale inne cele i inny rytm zmian |
| Nazewnictwo | Nazwa brzmi domenowo i mówi, po co to istnieje | Nazwa jest generyczna, bo pasuje do wszystkiego i do niczego |
| Zasięg użycia | Ma sens w kilku realnych punktach systemu | Używa jej tylko jedno miejsce albo dwa przypadkowe przypadki |
| Czytelność | Upraszcza śledzenie logiki | Zmienia prosty przepływ w skakanie po plikach |
| Ryzyko | Zmiana w jednym miejscu poprawia spójność całości | Jedna drobna korekta może zepsuć kilka odległych funkcji |
W praktyce czekam zwykle do drugiej albo trzeciej kopii, ale nie traktuję tego jak świętej reguły. Jeśli widzę, że kod jeszcze dojrzewa, lepiej pozwolić mu chwilę „oddychać” niż zaklejać go zbyt wcześnie wspólnym helperem. Taka ostrożność bardzo dobrze współgra z nowoczesnym podejściem do projektowania, bo pozwala najpierw zrozumieć problem, a dopiero potem go uogólnić.
Jak stosować DRY w kodzie, testach, bazie i dokumentacji
Najbardziej praktyczne wdrożenie DRY zaczyna się od rozdzielenia warstw. W każdej z nich powtarza się coś innego, więc i rozwiązanie powinno być inne. W kodzie chodzi o logikę, w bazie o dane, w testach o zestawy wejściowe, a w dokumentacji o opis reguł i kontraktów.
| Warstwa | Co warto współdzielić | Na co uważać |
|---|---|---|
| Kod domenowy | Reguły biznesowe, obliczenia, mapowania statusów, walidację | Nie wyciągaj do wspólnego miejsca rzeczy, które zmieniają się z różnych powodów |
| Testy | Fabryki danych, helpery do setupu, wspólne asercje dla tej samej intencji | Zbyt ogólne helpery ukrywają, co tak naprawdę testujesz |
| Baza danych | Jedno źródło prawdy dla rekordów, słowników i relacji | Nie kopiuj tej samej informacji do kilku tabel tylko dlatego, że później łatwiej się ją odczytuje |
| Dokumentacja | Opis API, kontrakty, decyzje architektoniczne, znaczenie statusów i błędów | Nie utrzymuj ręcznie kilku wersji tego samego opisu, bo zaczną się rozjeżdżać |
W projektach webowych dobrze działa zasada: jeśli coś ma być zgodne w czasie, trzymaj to w jednym miejscu i generuj resztę. To może być schemat OpenAPI, centralny moduł walidacji, wspólny słownik statusów albo zestaw komponentów korzystających z jednego źródła konfiguracji. Drobna uwaga: generowanie nie zwalnia z odpowiedzialności. Jeśli źródło jest złe, wszystkie wygenerowane kopie też będą złe, tylko szybciej.
Tu pojawia się jeszcze jeden ważny niuans. DRY nie znaczy automatycznie „mniej kodu”, tylko „mniej sprzecznej wiedzy”. Czasem lepiej mieć dwa czytelne miejsca, które wyraźnie pokazują różnice, niż jedno wspólne miejsce tak ogólne, że nikt nie rozumie, co właściwie robi.
Najczęstsze błędy, które psują DRY zamiast pomagać
Najwięcej szkód widzę wtedy, gdy ktoś myli DRY z obowiązkiem maksymalnego uogólniania. To prowadzi do pomocniczych klas, które robią wszystko, do komponentów z dziesięcioma parametrami i do utility folderów, w których po kilku miesiącach nikt niczego nie znajduje.
- Zwalczanie każdego podobieństwa - dwie podobne funkcje nie muszą jeszcze oznaczać jednego wspólnego abstraktu. Jeśli ich powody zmiany są inne, podobieństwo może być chwilowe.
- Abstrakcja po pierwszej kopii - zbyt wczesne wspólne API często okazuje się za ciasne albo za szerokie. Potem trzeba je rozbijać, a to kosztuje więcej niż poczekanie.
- Helpery bez nazwy domenowej - jeśli nazwa mówi tylko o technice, a nie o znaczeniu, użytkownik kodu musi zgadywać, po co ta abstrakcja istnieje.
- Ukrywanie różnic - wspólny interfejs bywa kuszący, ale jeśli przykrywa realne odchylenia, kończy się warunkami, flagami i wyjątkami w środku środka.
- Brak testów po refaktorze - DRY ma zmniejszać koszt zmian, nie zwiększać ryzyko regresji. Po większym wydzieleniu wspólnej logiki testy są obowiązkowe.
Jeśli mam wskazać jeden sygnał ostrzegawczy, to jest nim moment, w którym osoba z zespołu musi otworzyć trzy pliki, żeby zrozumieć jedną prostą regułę. Wtedy abstrakcja przestała pomagać, a zaczęła zasłaniać sens. I właśnie dlatego czasem bardziej opłaca się wrócić krok w tył, niż brnąć w źle dobrane uogólnienie.
Kiedy świadomie zostawić powtórzenie i wrócić do niego później
Nie każda duplikacja jest problemem, który trzeba naprawić natychmiast. W praktyce bywa tak, że kopiowanie kodu pomaga utrzymać tempo pracy, zrozumieć domenę albo doprecyzować wymagania. To podejście jest szczególnie sensowne, gdy funkcjonalność dopiero się krystalizuje, a zespół jeszcze nie wie, jaka abstrakcja naprawdę przetrwa kolejne iteracje.
- Prototypowanie - gdy rozwiązanie jest jeszcze eksperymentem, szybciej jest skopiować fragment i sprawdzić kierunek niż budować od razu rozbudowaną warstwę wspólną.
- Różne tempo zmian - podobne fragmenty mogą dziś wyglądać tak samo, ale jutro zaczną żyć osobno, bo obsługują inne wymagania.
- Różni właściciele modułów - jeśli dwa zespoły rozwijają elementy niezależnie, wymuszenie jednego wspólnego komponentu bywa organizacyjnie droższe niż technicznie wygląda.
- Wysoka czytelność lokalna - czasem lepiej powtórzyć 3 linie niż ukryć prostą logikę za kolejnym poziomem pośrednim.
To jest dla mnie najuczciwsza wersja DRY: minimalizować koszt zmian, ale nie poświęcać czytelności dla samej czystości abstrakcji. Jeśli zmiana jednej reguły naprawdę wymaga korekty w kilku miejscach, warto ją scalić. Jeśli jednak podobieństwo jest tylko powierzchowne, chwilowe albo przypadkowe, powtórzenie może być rozsądniejszym wyborem niż elegancka, lecz krucha konstrukcja. Właśnie tak rozumiem praktyczne podejście do DRY w architekturze i wzorcach - nie jako dogmat, tylko jako narzędzie do lepszego zarządzania zmianą.