Іноді зміна здається маленькою: додати одне правило поведінки для посилання, кнопки або іншої дії в інтерфейсі.
Потім відкривається перший компонент, другий, третій, десятий і виявляється, що одна й та сама логіка повторюється в багатьох місцях.
Технічно можна пройти всі файли й акуратно внести однакову зміну.
Але для мене велика кількість однакових правок є окремим сигналом: можливо, поведінка знаходиться не на тому рівні системи.
Не кожна масова зміна означає погану архітектуру. Але якщо причина зміни одна, а місць для редагування десятки, я завжди перевіряю межу відповідальності.
Я дивлюся не на кількість файлів, а на причину зміни
Є нормальні ситуації, коли зміна закономірно проходить через багато файлів.
Наприклад, перейменування публічного типу, оновлення контракту або зміна дизайну всього застосунку.
Але інша ситуація виглядає підозріліше: десятки компонентів змінюються через одне спільне правило поведінки.
Тоді я питаю не "як швидше змінити всі файли?", а "чому кожен із цих файлів взагалі знає про це правило?".
Повторювана умова часто важливіша за повторюваний код
<a :href="item.url" :target="isExternal(item.url) ? '_blank' : undefined" :rel="isExternal(item.url) ? 'noopener noreferrer' : undefined"> {{ item.label }}</a>Дублювання не завжди виглядає як великий скопійований метод.
Іноді повторюється лише маленьке рішення: зовнішнє посилання відкривати так, внутрішнє інакше, для одного типу дії додати атрибут, для іншого ні.
Кілька рядків здаються недостатньо великими для окремої абстракції.
Але якщо саме ці кілька рядків потрібно синхронно змінювати в багатьох компонентах, повторюється вже не код - повторюється правило.
Низькорівневий компонент не завжди повинен знати правило
Компонент, який просто малює посилання, може випадково отримати занадто багато відповідальності.
Спочатку він знає лише href і текст.
Потім додається перевірка зовнішньої адреси. Потім виняток для певного типу дії. Потім окреме правило для безпеки. Потім ще один формат URL.
У результаті кожен renderer починає самостійно приймати однакові рішення.
У таких випадках я перевіряю, чи можна підготувати вже готову поведінку вище і залишити компоненту тільки відображення.
Спільне правило краще визначати в одному місці
final readonly class ActionLink{ public function __construct( public string $url, public ?string $target, public ?string $rel, ) {}} final class ActionLinkResolver{ public function resolve(string $url): ActionLink { $external = $this->isExternal($url); return new ActionLink(Не обов’язково використовувати окремий service або value object саме в такій формі.
Важливіша сама межа: одне місце приймає рішення, а решта компонентів отримують уже підготовлений результат.
Тоді зміна правила не вимагає пам’ятати про кожен renderer окремо.
Хороша межа зменшує кількість знань у компоненті
Компонент знає: - що таке external URL- коли потрібен target- який rel безпечний- які типи дій є винятками- як нормалізувати URL- як намалювати посиланняКомпонент знає: - URL- target- rel- як намалювати посилання Спільний resolver знає: - правила поведінкиЯ не створюю абстракцію після першого дублювання
Зворотна крайність теж небезпечна: побачити два однакові рядки й одразу створити універсальний helper.
На ранньому етапі два схожі компоненти можуть виглядати однаково, але розвиватися в різні боки.
Тому я дивлюся не лише на повторення коду, а на повторення причини зміни.
Якщо компоненти змінюються разом через одне й те саме правило вже кілька разів, межа стає набагато очевиднішою.
Які ознаки змушують мене зупинитися перед масовою правкою
Перед тим як змінювати багато схожих файлів, я перевіряю кілька ознак:
- у всіх файлах додається однакова умова;
- правило не стосується зовнішнього вигляду конкретного компонента;
- помилка в одному renderer створить поведінку, відмінну від інших;
- для нового типу компонента це правило доведеться знову копіювати;
- зміна правила в майбутньому знову потребуватиме глобального пошуку;
- компоненти починають знати деталі, які не потрібні для самого rendering.
Чим більше відповідей "так", тим сильніший сигнал, що рішення варто підняти вище.
Централізація змінює і те, що я тестую
Коли правило живе в багатьох компонентах, його доводиться перевіряти в багатьох місцях або ризикувати, що один renderer поводиться інакше.
Після централізації основні варіанти поведінки можна перевірити біля самого правила.
А компонентні тести залишаються простішими: вони перевіряють, що вже підготовлені властивості правильно потрапили в HTML.
Це не лише менше коду. Це ще й чіткіше розділення того, що вирішує правило і що відображає компонент.
Не завжди потрібно рефакторити все за один раз
Якщо компонентів уже багато, повний рефакторинг може бути більшим ризиком, ніж сама функція.
Тоді я використовую поступовий підхід:
- визначити нову спільну межу;
- покрити правило окремими тестами;
- перевести один тип компонента;
- перевірити результат;
- поступово прибрати стару локальну логіку.
Централізація не повинна сама ставати великою небезпечною перепискою системи.
Цей симптом стосується не лише UI
Той самий принцип я використовую і в backend-коді.
Якщо одна зміна правила змушує редагувати багато controller, job, listener або repository, це теж може бути сигналом, що рішення рознесене занадто низько.
Наприклад, форматування URL, правила доступу, нормалізація даних, формування параметрів інтеграції або визначення статусу часто краще мати в одному власнику правила.
Не тому, що "все має бути DRY", а тому, що одна бізнесова або технічна причина зміни повинна мати якомога чіткіше місце.
Що я перевіряю, коли одна функція розповзається по коду
У такій ситуації я ставлю собі кілька питань:
- Чи всі файли змінюються через одну причину?
- Чи повторюється правило, а не лише кілька рядків синтаксису?
- Який компонент або сервіс насправді повинен володіти цим рішенням?
- Чи можна передавати вниз уже готовий результат замість сирих даних для повторного рішення?
- Чи стане додавання наступного компонента простішим після рефакторингу?
- Чи можна протестувати правило в одному місці?
- Чи не створюю я занадто загальну абстракцію раніше, ніж межа стала зрозумілою?
Масова правка іноді показує архітектуру краще за діаграму
Я не вважаю велику кількість змінених файлів автоматичною проблемою.
Але вона дуже добре показує, де система має спільну причину зміни.
Якщо одна поведінка змушує мене проходити десятки схожих компонентів, я спочатку можу завершити функцію правильно, але потім обов’язково перевіряю, чи не повинно саме правило жити вище.
Правильна абстракція для мене не та, що просто скорочує кількість рядків.
Вона повинна зменшувати кількість місць, які потрібно пам’ятати наступного разу, коли зміниться те саме правило.
Коли одна причина зміни розкидана по десятках файлів, проблема може бути не в кількості файлів, а в місці, де живе рішення.



