Każdy programista ma taki folder. Ten o nazwie utils lub helpers, który kopiuje z repozytorium do repozytorium. Wklejasz go, spędzasz dwadzieścia minut na usuwaniu odniesień do starych schematów bazy danych, wycinaniu kontroli uwierzytelniania, które nie mają zastosowania, i zmienianiu nazw zmiennych, aby nowy linter przestał krzyczeć. Kiedyś robiłem to z systemem motywowania, który zbudowałem – Dynamic Theme Kit. Zaczęło się od funkcji wewnątrz jednej aplikacji i przez miesiące traktowałem to jak przenośne narzędzie. Myliłem się. Kopiowanie kodu to nie ponowne użycie. To duplikacja z dodatkowymi krokami.

Pułapka myślenia ograniczonego do jednego projektu

Kiedy budujesz funkcję wewnątrz projektu, robisz setki niewidocznych założeń. Paleta kolorów może zakładać konkretną konfigurację CSS-in-JS. Skala odstępów może odwoływać się do design tokenu z przewodnika marki Twojej firmy. Przełączanie między trybem jasnym a ciemnym może wywoływać endpoint preferencji użytkownika, unikalny dla backendu danej aplikacji. Te zależności wydają się nieszkodliwe, ponieważ wewnątrz projektu faktycznie są nieszkodliwe. Przynależą do niego.

Problem zaczyna się, gdy próbujesz wyciągnąć ten kod. Odkrywasz, że „wielokrotnego użytku” komponent jest w rzeczywistości siecią ukrytych powiązań łączących go z tym jednym kodem źródłowym. Nauczyłem się tego na przykładzie DTK. Generował zmienne motywu, owszem. Ale oczekiwał też konkretnej struktury folderów. Importował definicję typu z jakiegoś głębokiego miejsca w katalogu typów oryginalnej aplikacji. Zakładał obecność globalnego obiektu konfiguracji, który istniał tylko w tym jednym repozytorium. Nigdy tego nie zauważyłem, ponieważ wewnątrz tego projektu wszystko zawsze było na swoim miejscu.

Przekształcenie DTK w samodzielny pakiet oznaczało operację, a nie rozbudowę. Nie potrzebowałem więcej funkcji. Potrzebowałem mniej połączeń.

Wyodrębnianie Dynamic Theme Kit

Najtrudniejsza była praca z kodem źródłowym i zadawanie sobie pytania przy każdej funkcji i każdym eksporcie: czy to służy logice motywowania, czy służy projektowi? Usunąłem predefiniowane style. Usunąłem założenie, że konsumentem będzie aplikacja React. Całkowicie usunąłem domyślne palety kolorów. Oryginalny projekt miał wbudowaną korporacyjną estetykę w kolorach granatowym i łupkowym. To musiało zniknąć. Pakiet nie może dostarczać kolorów Twojej marki.

Nowy zestaw miał robić dokładnie jedną rzecz. Przyjmuje obiekt konfiguracji – pewne wartości kolorów, liczby określające odstępy, skale typograficzne – i generuje CSS custom properties. To wszystko. Nie stosuje ich. Nie decyduje, gdzie mają trafić w Twoim DOM. Nie obchodzi go, czy używasz Tailwind, Styled Components, czy czystego HTML. Daje Twojej aplikacji zmienne, a Twój projekt decyduje, jak ich użyć.

To ograniczenie początkowo wydawało się uciążliwe. Okazało się wyzwalające.

Co się psuje, gdy faktycznie próbujesz go ponownie użyć

Zanim cokolwiek opublikowałem, potrzebowałem dowodu, że ta abstrakcja faktycznie trzyma się kupy. Wyciągnąłem trzy małe, osobiste projekty z mojego archiwum: narzędzie do podglądu markdown, tracker nawyków i landing page dla wydarzenia. Żaden z nich nie dzielił frameworka ani struktury folderów. Zainstalowałem DTK lokalnie w każdym z nich i spróbowałem je ostylować.

Pierwsza próba zakończyła się natychmiastową porażką. Nazwy zmiennych generowane przez DTK były zbyt specyficzne. Wyprowadzały tokeny takie jak --primary-action i --background-overlay, które sugerowały konkretny układ UI. W podglądzie markdown te nazwy nie miały sensu. Nie było tam przycisku akcji. Nie było nakładki (overlay). Zmieniłem logikę generowania tak, aby tworzyła neutralne, strukturalne nazwy, które opisują wartość, a nie widget.

Zauważyłem też, że moje wartości domyślne były zbyt agresywne. Gdy użytkownik przekazywał niepełną konfigurację, DTK wypełniał luki wartościami, które wyglądały dobrze w gęstym dashboardzie, ale psuły wygląd minimalistycznego landing page'a. Przeszedłem na przejrzyste wartości domyślne, gdzie brakujące tokeny po prostu nie były renderowane, pozwalając projektowi korzystającemu z pakietu na zdefiniowanie własnych rozwiązań zapasowych (fallbacks).

Potem była dokumentacja. To, co wydawało mi się oczywiste – „po prostu przekaż obiekt konfiguracji” – było niejasne dla kogoś, kto czyta README o północy. Przepisałem ją, używając realnych obiektów, realnych ścieżek do plików i jasnych wyjaśnień, co dzieje się podczas wywołania funkcji, a co Twoja aplikacja musi zrobić później.

Te małe, osobiste projekty posłużyły jako poligon doświadczalny. Nie wiązało się to z dużym ryzykiem, ale obnażyło realne wady, których nie wyłapałbym, wpatrując się w kod źródłowy w izolacji.

Prawdziwy test: Produkcja w Web Weavers World

Projekty osobiste to piaskownice. Nie mają terminów, interesariuszy ani przestarzałego CSS, który istniał przed Twoim pakietem. Prawdziwy test przyszedł, gdy zintegrowałem DTK z Web Weavers World, moją stroną biznesową. Była to działająca witryna z istniejącymi stylami, oczekiwaniami klienta i analizą, którą trzeba było wziąć pod uwagę. Gdyby pakiet coś popsuł, nie mogłem po prostu usunąć repozytorium i zacząć od nowa.

Dodałem DTK do pipeline'u budowania, skierowałem go na nową konfigurację kolorów i pozwoliłem wygenerować nowy zestaw zmiennych CSS. Integracja zajęła jedno popołudnie, a nie tydzień. To był sygnał. Wcześniej dodanie nowego motywu oznaczało pisanie nowego CSS, szukanie zakodowanych na sztywno wartości hex w dwudziestu plikach i liczenie na to, że nie pominę żadnego przypadku brzegowego. Teraz dodaję paletę do pliku konfiguracyjnego, DTK generuje zmienne, a reszta strony z nich korzysta. Logika motywu przeszła drogę od kruchego, manualnego procesu do czegoś, któremu ufam na tyle, by przekazać to współpracownikom.

Trzy pytania, które zmieniły sposób, w jaki buduję

Przejście przez ten proces zmusiło mnie do sformalizowania listy kontrolnej, której używam teraz, zanim zacznę cokolwiek abstrahować:

  • Czy ta zmienna jest naprawdę generyczna? Jeśli nazwa lub logika odnosi się do koncepcji domenowej z oryginalnego projektu, zostaje ona w nim.
  • Czy to należy do pakietu, czy do aplikacji? Reguły biznesowe, tożsamość marki i założenia dotyczące układu należą do aplikacji. Mechanizmy, które generują ustandaryzowany wynik, należą do pakietu.
  • Czy rozwiązuję problem uniwersalny, czy specyficzny dla projektu? To najtrudniejsze pytanie, na które można odpowiedzieć szczerze. Lubimy myśleć, że nasze rozwiązania są uniwersalne. Zazwyczaj są lokalne.

Odpowiedzenie na te pytania zmusiło mnie do uproszczenia projektu, często poprzez usuwanie kodu, a nie dodawanie go. DTK nauczyło mnie, że ponowne wykorzystanie nie jest prezentem, który dajesz samemu sobie. To dyscyplina, którą ćwiczysz, mówiąc „nie” wygodnictwu.

Inne podejście do myślenia o refaktoryzacji

Kiedyś mierzyłem refaktoryzację tym, jak bardzo skracała ona kod. Mniejsza liczba linii wydawała się postępem. Teraz mierzę ją tym, ile drzwi otwiera. Dynamic Theme Kit nie jest elegancki dlatego, że jest zwięzły. Jest użyteczny, ponieważ przetrwał trzy niezwiązane ze sobą projekty osobiste i produkcyjną stronę biznesową bez konieczności zmiany swojej wewnętrznej struktury.

To jest metryka, która ma znaczenie. Kod, który działa raz, jest kosztem. Kod, który działa wielokrotnie, jest aktywem. Zanim zacznę implementować jakąkolwiek funkcjonalność, zatrzymuję się. Pytam, czy buduję coś, czego będę potrzebował ponownie. Jeśli odpowiedź brzmi „tak”, buduję to inaczej już od pierwszej linii. Izoluję wejścia. Definiuję wyjścia. Usuwam założenia.

Najlepsza refaktoryzacja nie sprawia, że Twój kod jest krótszy. Sprawia, że Twój kod działa w miejscach, których jeszcze nie sobie wyobrażałeś.