1. «Почистити код» — це ще не рефакторинг
Слово refactoring новачки часто розуміють занадто побутово: «навести лад», «зробити чистіше», «прибрати жах». Звучить мило, але в такому вигляді це небезпечне визначення. Код стане акуратнішим і «розумнішим», а бізнес-поведінка зміниться — і це вже не рефакторинг, а інша робота під виглядом прибирання.
Якщо говорити строго, то refactoring — це structural change without behavior change. Зміна структури без зміни видимої зовні поведінки. Тому refactor іде через той самий issue-to-PR loop, що й feature та bugfix, але з додатковим критерієм — поведінка збережена.
Рефакторинг = змінюємо форму коду, але не змінюємо його зовнішній контракт.
Це означає, що мета refactor-завдання — не «зробити красивішим за будь-яку ціну», а читабельність, менше дублювання, ізоляція відповідальності, тестопридатність. І все це за однієї умови: зовнішній світ не повинен помітити, що всередині ви переставили меблі.
Невеликий приклад із Commerce OS. Припустимо, у OrderService довгий метод: перевірка замовлення, потім логіка. Винесли перевірку в приватний метод, вхід і вихід не чіпали — рефакторинг.
public OrderResult finalizeOrder(Order order) {
validateOrder(order);
if (!order.isPaid()) {
return OrderResult.rejected("NOT_PAID");
}
if (order.stock() == 0) {
return OrderResult.rejected("OUT_OF_STOCK");
}
return OrderResult.finalized("Замовлення підтверджено");
}
private void validateOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Замовлення є обов'язковим");
}
}
Тут код став трохи краще організованим, але зовнішній зміст методу не змінився: контролер, тести, інтеграції бачать той самий результат.
А якщо в процесі «покращення» ви змінили текст помилки, порядок бізнес-перевірок, HTTP-статус, схему відповіді або момент надсилання події — це вже не «просто прибирання», а зміна поведінки. Код став елегантнішим, а checkout перестав працювати? Не архітектурне осяяння, а звичайний баг. У красивій упаковці.
2. У коду немає табличок «всередині» і «ззовні»
«Поведінка ззовні не змінюється» звучить просто. Але де межа? Для одного модуля зовнішній світ — HTTP-клієнт. Для іншого — сусідній сервіс, база, черга подій, лист, webhooks, адмінка. Рефакторинг починається з питання: що тут вважається зовні спостережуваною поведінкою?
Для Commerce OS мисліть так:
| Зазвичай належить до зовні спостережуваної поведінки | Зазвичай належить до внутрішньої структури |
|---|---|
| HTTP status, JSON-відповідь, публічний контракт сервісу, записи в БД, надіслані події, текст помилки, який бачить клієнт, порядок спостережуваних побічних ефектів | імена локальних змінних, виділення приватного helper-методу, перестановка внутрішніх рядків без зміни результату, розбиття довгого методу, перенесення логіки між private-методами |
Ключове слово тут — зазвичай. Бо один і той самий код у різних системах може бути на різній глибині контракту. Наприклад, перейменування приватної змінної майже ніколи не впливає на поведінку. А от перейменування публічного методу, який використовує зовнішній модуль, уже може бути breaking change. Тобто не refactoring, а зміна контракту.
Є й більш підступний випадок: код візуально майже не змінюється, а поведінка вже поїхала. Подивіться на такий фрагмент:
if (!order.isPaid()) {
return OrderResult.rejected("NOT_PAID");
}
if (order.stock() == 0) {
return OrderResult.rejected("OUT_OF_STOCK");
}
Якщо ви просто поміняєте ці перевірки місцями, код залишиться «майже таким самим», але для замовлення, яке і не сплачене, і потрапляє в OUT_OF_STOCK, система поверне іншу причину відмови. Ззовні видно? Так. Отже, це зміна behavior.
Саме тому фраза «я ж лише трохи переставив умови» рев’юера не заспокоює. Порядок перевірок і подій, момент запису в базу, тексти помилок, форма відповіді — не косметика, а поведінка системи. Якщо після правки клієнт, сусідній модуль або тест може законно запитати «чому відповідь тепер інша?» — ви вийшли за межі refactor-завдання.
3. Межа: refactor vs bugfix, feature, rewrite
На практиці головна плутанина виникає не всередині самого refactoring, а на межі із сусідніми типами роботи. Людина щиро каже: «Я просто почистив сервіс» — а насправді виправлено дефект, додано сценарій, оновлено залежність. Для роботи з Claude цю межу потрібно робити буквальним, а не інтуїтивним.
Найзручніше порівняти категорії в таблиці:
| Що ви робите | Чи змінюється зовнішня поведінка | Що це? | Приклад у Commerce OS |
|---|---|---|---|
| Виділяєте приватний helper, спрощуєте умову, прибираєте дублювання | Ні | Refactoring | Винесли перевірку замовлення з finalizeOrder() у validateOrder() |
| Виправляєте неправильний результат | Так, і це навмисно | Bugfix | Виправили дублювання refund-запитів |
| Додаєте новий сценарій або поле | Так | Feature | Додали прапорець estimatedDelivery у відповідь API |
| Переписуєте модуль майже з нуля | Формально може бути «ні», але ризик величезний | Rewrite | Повністю переписали OrderService на нову внутрішню модель |
| Поступово покращуєте старий модуль із safety net і межами | Зазвичай намагаєтеся зберегти поведінку, але масштаб ширший | Modernization | Чистите зв’язність і boundaries старого підсистемного блоку |
| Оновлюєте framework, runtime, залежності, конфіг-модель | Часто змінюються середовище та контракт виконання | Migration | Переходите на нову major-версію Spring Boot |
Найчастіший самообман виглядає так: «Заодно виправив старий баг, поки робив refactor». Ні, не «заодно». Баг виправлено — поведінка змінилася. Це bugfix.
Друга популярна пастка: «Нічого не змінював для користувача, просто переписав модуль нормально». Модуль переписано цілком — ризик уже інший. Без щільної safety net і маленьких кроків це rewrite з елементами надії. Надія — непогане відчуття, але погана стратегія code review.
Є ще підступний різновид — «рефакторинг із міграцією всередині». Наприклад, ви виносите publisher в окремий клас, а заодно оновлюєте бібліотеку подій, «вона й так стара». Усе, дві задачі в одному PR: рев’юер оцінює structural improvement і dependency change разом. Гарантована плутанина.
Тому інженерне правило тут дуже просте:
Якщо зміна має покращити поведінку — це не refactor.
Якщо зміна має змінити середовище виконання — це не refactor.
Якщо зміна має лише покращити структуру — тоді так, це кандидат на refactoring.
4. Хороший refactor розв’язує structural problem, а не покращує поведінку
Щоб усе це не лишилося на рівні красивих визначень, корисно подивитися на зміни, які справді потрапляють до категорії refactoring. Міра тут не «стало коротше», а «стало зрозуміліше, легше тестувати, але контракт зовні той самий».
Найчастіше сюди потрапляють виділення функції, перейменування внутрішніх сутностей, зменшення вкладеності умов, усунення дублювання, розділення великої функції на кілька маленьких, винесення side effects ближче до їхнього власника, видалення справді мертвого коду після перевірки посилань і тестів.
Наприклад, ось такий метод можна спростити без зміни поведінки:
private boolean canApproveRefund(Order order) {
if (order != null) {
if (order.isPaid()) {
return !order.isArchived();
}
}
return false;
}
Після спрощення:
private boolean canApproveRefund(Order order) {
return order != null
&& order.isPaid()
&& !order.isArchived();
}
Якщо для всіх вхідних значень результат залишився тим самим — це нормальний refactor.
Інший хороший кандидат — зменшення дублювання. Одну приватну перевірку з трьох місць виносите в один helper. Головне — не почати «трохи покращувати правила»: тоді ви змінюєте логіку, а не виносите дублювання.
Окремо варто сказати про видалення мертвого коду. Воно виглядає нешкідливо, але тут новачки особливо люблять потрапляти в пастку. Код здається не використовуваним — поки ви не відкрили references, call hierarchy і не глянули на тести. Видаляти «мертві» гілки без опори на harness і code intelligence — не акуратність, а лотерея з неприємним призом.
Тобто хороший refactor майже завжди відповідає на просте питання: яку structural problem він розв’язує? «Зменшує дублювання», «ізолює відповідальність», «робить метод більш тестопридатним» — правильний шлях. «Тепер користувач побачить кращу поведінку» — не refactor.
5. Не давайте Claude творчого розгону
Найчастіша помилка під час роботи з Claude над refactor-завданням звучить так: «Зроби цей файл нормальним». Модель справді намагається допомогти — і, як це часто буває з дуже старанними помічниками, допомагає надто широко: лізе в непов’язані файли, «про всяк випадок» править логіку. Тому хороший refactor із Claude починається не з редагування, а з класифікації можливостей.
Поганий запит виглядає так:
Зроби OrderService чистішим і сучаснішим.
У такого запиту немає ані меж, ані списку «чого не робити», ані критерію behavior preserved. Для AI це майже запрошення до творчості.
Набагато краще спочатку попросити Claude нічого не змінювати — лише знайти кандидати на refactor і розкласти їх за ризиком і користю:
Знайди в src/main/java/com/acmeretail/orders/OrderService.java
локальні можливості для refactor.
Нічого не змінюй.
Для кожної пропозиції вкажи:
1. тип зміни;
2. чому це корисно;
3. ризик;
4. які файли будуть зачеплені;
5. що має залишитися незмінним зовні.
Не пропонуй feature changes, bug fixes, dependency upgrades
і framework changes.
Такий запит робить дві важливі речі: відокремлює аналіз від реалізації та прямо забороняє сусідні категорії. Claude у вузькому коридорі, ви — зі списком кандидатів, із якого обираєте одну structural problem.
Коли конкретну мету вже обрано, другий запит теж має бути вузьким:
Виконай лише один крок refactor:
винеси локальну валідацію замовлення з finalizeOrder()
у private helper validateOrder().
Обмеження:
- не змінювати public API;
- не змінювати тексти помилок;
- не змінювати порядок бізнес-перевірок;
- не додавати залежності;
- після змін запусти наявні тести orders.
Якщо побачиш bugfix opportunity, зупинися й опиши її окремо.
Оце вже хороша робота з Claude: ви не просите «покращити все», а змінюєте рівно один structural aspect і відразу ставите guardrails. Якщо Workflow Kit тримає ці обмеження в CLAUDE.md, Claude ще рідше імпровізує на тему «перепишімо заодно сусідній модуль».
Але навіть у акуратного запиту залишається дуже приземлене питання: harness узагалі побачить, чи ви випадково зсунули саме цей observable result? Якщо ні — перед першою правкою зафіксуйте кілька вузьких checks на поточну поведінку.
І тут дуже важливо пам’ятати стару формулу курсу: Claude edits are proposals expressed as diffs. Навіть ідеальний prompt не скасовує читання diff і прогін harness. Claude корисний не тим, що «знає, як красиво», а тим, що швидко пропонує маленьку трансформацію в заданих межах.
6. Один refactor PR — одна structural problem
Навіть якщо ви прекрасно розумієте теорію, PR усе одно норовить розповзтися. Claude помічає сусіднє дублювання, старий TODO, криву назву методу — і радісно пропонує «заодно» все підчистити. Тут і вирішується: професійний refactor чи класика про «трохи прибралися, а половина сервісу переїхала».
Уявіть типовий випадок у Commerce OS. Ви прийшли в OrderService ізолювати валідацію. Claude помічає у refund-гілці спірну бізнес-логіку. Спокуса: «Раз я вже тут — виправлю одразу». Ні. Саме тут PR втрачає чистоту.
Правильний TASK_SPEC.md звучить приблизно так:
Goal: simplify validation flow in OrderService.finalizeOrder.
Non-goals: change refund rules, response texts, DB writes.
Acceptance: existing order tests pass without changes.
Бачите, що тут важливо? Прямо сказано і «що робимо», і «чого не чіпаємо». У refactor-PR це критично: такі PR люблять вдавати безпечні, поки в них тихо не ховається нова бізнес-логіка.
Є корисний побутовий тест. Якщо ви не можете описати мету PR одним реченням без сполучника «і ще» — значить, PR уже розрісся. «Спрощували валідацію замовлення» — добре. «Спрощували валідацію замовлення, заодно виправили refund-логіку та оновили бібліотеку подій» — три розмови з рев’юером в одному diff.
В інженерній культурі є дуже зріла дія, яка новачкам спочатку здається нудною: вчасно зупинитися. Зрозуміли під час refactor, що поведінку й справді треба змінити, — це не провал, а нове завдання. Професійність не в тому, щоб непомітно впхати його в поточний PR, а в тому, щоб чесно винести окремо.
Саме так рефакторинг перестає бути романтичним «зробимо красиво» і стає нормальною інженерною практикою: вузькою, перевірюваною, рев’юваною й безпечною для проєкту. А це куди корисніше за будь-яку красу заради краси.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ