Przejdź do treści
Utrzymanie i przekazanie

Funkcja formatująca cenę jest w siedmiu plikach, w dwóch już innej. jscpd pokaże to przed odbiorem

Zmiana formatu ceny otwiera siedem plików. Jak zmierzyć duplikację w kodzie z modelu przed odbiorem i co wpisać do protokołu zdawczego projektu.

Redakcjadeveloper front-end9 minkod + tabela

Klient sklepu oddanego pół roku temu prosi o jedną zmianę: cena ma mieć spację nierozdzielającą przed złotówką i dwa miejsca po przecinku także wtedy, gdy kwota jest całkowita. W repozytorium funkcja formatująca cenę występuje w siedmiu plikach. W pięciu wygląda identycznie, w dwóch ktoś ją w międzyczasie poprawił i dziś koszyk zaokrągla inaczej niż ekran podsumowania zamówienia.

Zmiana wyceniona na godzinę zaczyna się więc od ustalenia, ile jest wersji prawdy. To nie jest wpadka jednego wykonawcy, tylko domyślny kształt projektu, w którym nikt nie zmierzył powtórzeń przed odbiorem. Akurat tę rzecz da się policzyć jedną komendą i wpisać do protokołu jako liczbę, a nie jako wrażenie.

Dlaczego ten sam blok wraca w kolejnych plikach

Model pisze komponent w oknie kontekstu, które obejmuje otwarty plik i kilka sąsiednich. Nie ma w nim katalogu utils sprzed trzech miesięcy ani wiedzy, że funkcja o tej samej roli nazywa się tam toPln. Polecenie „dodaj sekcję z ceną” ma przez to dwie technicznie poprawne odpowiedzi: sięgnąć po istniejącą funkcję albo napisać nową, lokalną. Druga jest tańsza w generowaniu, nie wymaga czytania reszty projektu i zawsze działa od razu, więc pada częściej. Przy ręcznym pisaniu hamulcem był koszt przepisania tego samego po raz trzeci; ten hamulec zniknął.

Analiza 211 mln zmienionych linii z lat 2020–2024, którą opublikował GitClear, pokazuje wzrost udziału linii zaklasyfikowanych jako kopiowane z 8,3 proc. w 2021 roku do 12,3 proc. w 2024 i spadek udziału linii przenoszonych, czyli typowego śladu refaktoryzacji, z 25 proc. do mniej niż 10 proc. w tym samym okresie. Kopiowanie pierwszy raz w tym zbiorze przeważyło nad przenoszeniem. Materiał pochodzi z repozytoriów dużych firm technologicznych, nie z projektów agencyjnych, więc jest wskazówką co do kierunku, a nie pomiarem konkretnego kodu na twoim dysku.

Skutek widać dopiero przy pierwszej zmianie po odbiorze. W ankiecie Stack Overflow z 2025 roku 66 proc. programistów wskazało jako główną frustrację rozwiązania z modelu, które są prawie dobre, a 45,2 proc. uznało debugowanie wygenerowanego kodu za bardziej czasochłonne niż debugowanie własnego. Powtórzony blok jest dokładnie taki: prawie dobry w każdej z siedmiu kopii z osobna i niespójny jako całość.

Pomiar: jscpd na repozytorium przed odbiorem

Powtórzenia tekstowe wykrywa jscpd – skaner, który porównuje strumienie tokenów, a nie surowe znaki, więc nie zgubi klonu przez inne wcięcia czy przeniesiony nawias. Za klon uznaje domyślnie fragment od 5 linii i 50 tokenów. Skan gotowego projektu to jedna komenda:

npx jscpd src \
  --min-lines 5 \
  --min-tokens 50 \
  --reporters console,json,html \
  --output .jscpd \
  --ignore "**/*.test.*,**/dist/**,**/*.generated.*"

Wynik czyta się w dwóch miejscach: procent zduplikowanych linii w całym katalogu oraz lista par plików z numerami linii. Pierwsza liczba służy do porównań w czasie, druga do pracy. Przy projekcie, w którym jedna funkcja formatująca siedzi w siedmiu plikach, to właśnie lista par mówi, czy masz do czynienia z jednym rozlanym blokiem, czy z dwudziestoma drobnymi.

Opcja Co robi Kiedy jej użyć przy odbiorze
--min-lines, --min-tokens Próg długości klonu; domyślnie 5 linii i 50 tokenów Obniż do 3 linii, gdy szukasz powielonych stałych i warunków, a nie całych komponentów
--ignore-identifiers Pomija różnice w nazwach zmiennych Gdy ten sam blok został wklejony i przemianowany pod nowy komponent
--ignore-literals Pomija różnice w wartościach literałów Przy powielonych konfiguracjach różniących się tylko etykietą lub adresem
--baseline Zapisuje bieżący stan jako punkt odniesienia W dniu odbioru, żeby rozdzielić dług zastany od długu dokładanego później
--fail-on-new-clones Przerywa build przy klonach spoza punktu odniesienia W CI projektu utrzymaniowego, gdy przepisanie wszystkiego nie wchodzi w grę
--threshold Zwraca kod 1, gdy duplikacja przekroczy zadany próg Dopiero po ustaleniu własnej wartości bazowej; wpisany w ciemno blokuje pracę

Nie ma uniwersalnego progu, poniżej którego projekt jest zdrowy. Wynik zależy od języka, frameworka i tego, co wykluczyłeś z --ignore, więc pojedyncza liczba z jednego skanu nic nie znaczy. Znaczenie ma różnica: ten sam skan z tymi samymi opcjami na dzień odbioru i trzy miesiące później.

Klony, których jscpd nie pokaże

Najdroższy rodzaj powtórzenia to ten, który już się rozjechał. Dwa fragmenty podejmują tę samą decyzję biznesową, ale innym kodem, więc żaden skaner tekstowy ich nie zestawi:

// koszyk/Suma.tsx
const cena = (grosze / 100).toFixed(2) + ' zł';

// zamowienie/Podsumowanie.tsx
const cena = new Intl.NumberFormat('pl-PL', {
  style: 'currency', currency: 'PLN'
}).format(grosze / 100);

Pierwsza wersja da 1234.50 zł ze zwykłą spacją i kropką dziesiętną, druga 1234,50 zł ze spacją nierozdzielającą. Tokenowo nie mają ze sobą nic wspólnego i w zestawieniu jscpd nie wystąpią, a to one odpowiadają za zgłoszenie klienta. Jscpd ma na takie przypadki tryby --similarity i --semantic, ale ich czułość trzeba dobrać do projektu, a przy niskim progu zaczynają zwracać pary, które łączy tylko kształt.

Praktyczniejsze jest przeszukanie kodu po tym, co w nim powtarzalne niezależnie od implementacji: nazwach walut i jednostek, wyrażeniach regularnych, stawkach, limitach, kluczach w localStorage, ścieżkach API. Każda taka stała występująca w więcej niż jednym pliku to kandydat na decyzję podjętą dwa razy. Lista z takiego przeglądu jest krótka i da się ją zrobić w godzinę, a tłumaczy większość niespójności, które klient widzi na ekranie.

Nie każde powtórzenie jest długiem

Skaner nie odróżnia powtórzenia szkodliwego od obojętnego, więc tę część robi człowiek. Zostaw w spokoju:

  • testy – powtarzalna struktura „ustaw, wykonaj, sprawdź” jest tam zaletą, a wspólna fabryka danych testowych potrafi ukryć to, co test miał pokazać;
  • kod generowany – klienty API, typy ze schematu, migracje; wyciąganie z nich części wspólnej zostanie nadpisane przy następnym generowaniu;
  • pliki konfiguracyjne i warstwę prezentacji bez logiki – dwa podobne układy sekcji marketingowych nie są tym samym co dwa razy policzony podatek;
  • drugie wystąpienie bloku – dopóki nie ma trzeciego, nie wiadomo jeszcze, co w nim jest wspólną regułą, a co przypadkiem; przedwczesna abstrakcja kosztuje więcej niż jedna kopia.

Rozstrzygające pytanie brzmi: czy zmiana reguły biznesowej wymusi równoczesną zmianę we wszystkich kopiach. Jeśli tak, to jest dług i trzeba go policzyć. Jeśli nie, to są po prostu dwa podobne fragmenty i nie ma czego naprawiać.

Co wpisać do protokołu odbioru

Protokół zdawczy projektu robionego z modelem w zespole różni się od klasycznego jednym elementem: zawiera stan powtórzeń i zasady, na jakich będzie pilnowany. Bez tego pierwsza zmiana po odbiorze wygląda jak wycena z sufitu, bo nikt – ani wykonawca, ani klient – nie wie, ile plików obejmuje. Jeśli w pracowni nie ma jeszcze spisanej żadnej procedury odbioru, ta nadaje się na pierwszą – kryteria wyboru procesu do opisania w pierwszej kolejności tłumaczą, dlaczego powtarzalne przekazanie projektu opłaca się spisać przed resztą.

Pozycja Postać Do czego służy później
Wynik skanu z dnia odbioru Raport HTML i JSON z jscpd, w repozytorium Punkt odniesienia dla każdego kolejnego pomiaru
Konfiguracja skanu .jscpd.json z progami i wykluczeniami Powtarzalność: ten sam pomiar za rok, nie inny
Lista świadomych powtórzeń Tabela: pliki, powód, warunek wyjścia Oddziela decyzję projektową od przeoczenia
Mapa powielonych stałych Lista reguł i miejsc ich wystąpienia Wycena zmiany reguły bez otwierania projektu
Bramka w CI --baseline plus --fail-on-new-clones Zatrzymuje narastanie długu u następnego wykonawcy

Protokół jest przy okazji materiałem wprowadzającym dla osoby przejmującej projekt: pokazuje jej ryzyka, zanim sama na nie trafi w trakcie zlecenia. Jeśli przejmuje go ktoś, kto dopiero zaczyna brać zlecenia z modelem w zespole, uporządkowana ścieżka wejścia w pracę z AI na zleceniach skraca ten etap skuteczniej niż kolejna lista narzędzi.

Kolejność prac przy przejmowaniu cudzego projektu wygląda tak:

  • uruchom skan na świeżym klonie repozytorium, zanim cokolwiek zmienisz;
  • odrzuć z wyniku testy, kod generowany i katalogi budowania;
  • posortuj pary plików po liczbie linii i weź pierwsze dziesięć;
  • przy każdej parze odpowiedz, czy zmiana reguły dotyka obu kopii;
  • przeszukaj kod po stałych biznesowych, których skaner nie złapie;
  • zapisz wynik jako punkt odniesienia i włącz bramkę w CI;
  • dopiero teraz wyceń zgłoszenie klienta.

Wyciągać teraz czy wycenić utrzymanie

Są dwie uczciwe drogi i obie wymagają rozmowy z klientem. Pierwsza: wspólna funkcja powstaje przy okazji bieżącego zgłoszenia, zmiana obejmuje siedem plików zamiast jednego i kosztuje kilka razy więcej, ale następna zmiana formatu ceny to jedna linia. Druga: zostawiasz siedem kopii i mówisz wprost, że każda zmiana w tym obszarze mnoży się przez siedem, więc stawka utrzymania to uwzględnia. Czego nie da się obronić, to wycena jednej kopii i rachunek za siedem.

Decyzja opiera się na trzech wielkościach, które masz już policzone po skanie: ile par dotyczy logiki biznesowej, jak często ten obszar był zmieniany w historii repozytorium i czy projekt zostaje u ciebie na utrzymaniu. Przy kodzie, który nikt nie rusza od roku, wyciąganie wspólnej części jest kosztem bez zwrotu. Przy module zmienianym co miesiąc zwraca się po drugim zgłoszeniu. A jeśli ten sam obszar wraca w każdym miesiącu i zjada czas całego zespołu, problemem nie jest już format ceny, tylko ograniczenie przepustowości w firmie usługowej, które liczy się inaczej niż pojedyncze zgłoszenie.

Raport DORA z 2025 roku opisuje AI jako wzmacniacz, a nie przyspieszacz: 59 proc. badanych ocenia jej wpływ na jakość kodu pozytywnie, przy równoczesnym wzroście niestabilności dostarczania. Wzmacnia też sposób, w jaki oddajesz projekty. Jeśli protokół odbioru kończył się na „działa u klienta”, model doda do tego siedem kopii funkcji formatującej. Jeśli kończył się liczbą i warunkiem jej pilnowania, dostaniesz kod, w którym kolejna zmiana ma znaną cenę. Pierwszy skan zrób na projekcie, który oddajesz w tym tygodniu, i dopisz jego wynik do protokołu, zanim klient zgłosi pierwszą poprawkę.