У кожного розробника є та сама папка. Та, що називається utils або helpers, яку ви копіюєте з репозиторію в репозиторій. Ви вставляєте її, витрачаєте двадцять хвилин на видалення посилань на старі схеми баз даних, виривання перевірок авторизації, які тут не потрібні, і перейменування змінних, щоб ваш новий лінтер перестав кричати. Раніше я робив так із системою тем, яку створив — Dynamic Theme Kit. Спочатку це була лише функція всередині одного додатка, і протягом місяців я ставився до неї як до портативного інструмента. Я помилявся. Копіювання коду — це не повторне використання. Це дублювання з додатковими кроками.

Пастка мислення в межах одного проєкту

Коли ви створюєте функцію всередині проєкту, ви робите сотні невидимих припущень. Колірна палітра може передбачати певне налаштування CSS-in-JS. Шкала відступів може посилатися на дизайн-токен із брендбуку вашої компанії. Перемикання між світлою та темною темами може викликати ендпоінт налаштувань користувача, унікальний для бекенду саме цього додатка. Ці залежності здаються нешкідливими, тому що всередині проєкту вони справді нешкідливі. Вони там на своєму місці.

Проблема починається, коли ви намагаєтеся винести цей код. Ви виявляєте, що «бажаний для повторного використання» компонент насправді є мережею прихованих зв'язків, що приєднують його до того єдиного коду. Я зрозумів це на прикладі DTK. Він генерував змінні тем, так. Але він також очікував певну структуру папок. Він імпортував визначення типів десь глибоко в директорії типів оригінального додатка. Він передбачав наявність глобального об'єкта конфігурації, який існував лише в тому єдиному репозиторії. Я ніколи цього не помічав, бо в межах того проєкту все завжди було на місці.

Перетворення DTK на окремий пакет означало хірургічне втручання, а не розширення. Мені не потрібно було більше функцій. Мені потрібно було менше зв'язків.

Виокремлення Dynamic Theme Kit

Найважча робота полягала в тому, щоб сісти за кодову базу і запитати для кожної функції та кожного експорту: чи служить це логіці тем, чи проєкту? Я вичистив пресети стилів. Я прибрав припущення, що споживачем буде React-додаток. Я повністю видалив стандартні колірні палітри. Оригінальний проєкт мав вбудовану корпоративну естетику в темно-синіх та грифельних тонах. Це довелося прибрати. Пакет не може постачати ваші фірмові кольори.

Новий набір робитиме лише одну річ. Він приймає об'єкт конфігурації — деякі значення кольорів, деякі числа відступів, деякі шкали типографіки — і генерує CSS custom properties. Це все. Він не застосовує їх. Він не вирішує, де вони мають бути у вашому DOM. Йому байдуже, чи використовуєте ви Tailwind, Styled Components чи звичайний HTML. Він надає вашому додатку змінні, а ваш проєкт сам вирішує, як їх використовувати.

Спочатку це обмеження здавалося обмежувальним. Виявилося, що воно дарує свободу.

Що ламається, коли ви насправді намагаєтеся це повторно використати

Перш ніж щось публікувати, мені потрібен був доказ того, що абстракція справді працює. Я витягнув із свого архіву три невеликі особисті проєкти: інструмент попереднього перегляду markdown, трекер звичок і лендінг для події. Жоден із них не мав спільної структури папок чи фреймворку. Я встановив DTK локально в кожному з них і спробував налаштувати теми.

Перша спроба одразу провалилася. Імена змінних, які генерував DTK, були занадто специфічними. Він видавав такі токени, як --primary-action та --background-overlay, які передбачали певний макет інтерфейсу. У попередньому переглядачі markdown ці назви не мали сенсу. Там не було кнопки дії. Там не було оверлею. Я переписав логіку генерації так, щоб вона створювала нейтральні структурні назви, які описували значення, а не віджет.

Я також виявив, що мої значення за замовчуванням були занадто агресивними. Коли користувач передавав неповну конфігурацію, DTK заповнював прогалини значеннями, які виглядали нормально у щільному дашборді, але ламали лаконічний лендінг. Я перейшов до прозорих значень за замовчуванням, де відсутні токени просто не рендерилися, дозволяючи проєкту-споживачу самому визначати запасні варіанти (fallbacks).

Потім була документація. Те, що здавалося мені очевидним — «просто передайте об'єкт конфігурації» — було занадто розпливчастим для того, хто читає README опівночі. Я переписав її, додавши реальні об'єкти, реальні шляхи до файлів і чіткі пояснення того, що відбувається під час виклику функції та що має зробити ваш додаток після цього.

Ці невеликі особисті проєкти стали випробувальними майданчиками. Ставки були низькими, але вони виявили реальні недоліки, яких я б не помітив, просто дивлячись на вихідний код ізольовано.

Справжнє випробування: Продакшн у Web Weavers World

Personal projects are sandboxes. They do not have deadlines, stakeholders, or legacy CSS that predates your package. The real test came when I integrated DTK into Web Weavers World, my business site. This was a live property with existing styles, client expectations, and analytics to consider. If the package broke something, I could not just delete the repo and start over.

I added DTK to the build pipeline, pointed it at a new color configuration, and let it generate a fresh set of CSS variables. The integration took an afternoon, not a week. That was the signal. Previously, adding a new theme meant writing new CSS, hunting down hardcoded hex values in twenty files, and hoping I did not miss an edge case. Now I add a palette to the configuration file, DTK generates the variables, and the rest of the site consumes them. The theme logic went from being a fragile manual process to something I trust enough to hand off to collaborators.

Three Questions That Changed How I Build

Going through this process forced me to formalize a mental checklist I now use before I abstract anything:

  • Is this variable truly generic? If the name or the logic references a domain concept from the original project, it stays behind.
  • Does this belong in the package or the application? Business rules, brand identities, and layout assumptions live in the app. Plumbing that generates standardized output lives in the package.
  • Am I solving a reusable problem or a project-specific one? This is the hardest to answer honestly. We like to think our solutions are universal. Usually they are local.

Answering these questions forced me to simplify my design, often by removing code rather than adding it. DTK taught me that reuse is not a gift you give yourself. It is a discipline you practice by saying no to convenience.

A Different Way to Think About Refactoring

I used to measure refactors by how much shorter they made the code. Fewer lines felt like progress. Now I measure them by how many doors they open. The Dynamic Theme Kit is not elegant because it is concise. It is useful because it survived three unrelated personal projects and a production business site without needing to change its internals.

That is the metric that matters. Code that works once is an expense. Code that works repeatedly is an asset. Before I start any feature now, I stop. I ask if I am building something I will need again. If the answer is yes, I build it differently from the first line. I isolate the inputs. I define the outputs. I remove the assumptions.

The best refactor does not make your code shorter. It makes your code work in places you have not imagined yet.