Підтримка й ефективність
Рефакторинг: змінювати структуру, зберігаючи потрібну поведінку
Уявімо, що агент пропонує «спростити» перевірку бронювання, бо однакові умови повторюються у двох місцях. Код стане коротшим, але чи зможе гість і далі…
Уявімо, що агент пропонує «спростити» перевірку бронювання, бо однакові умови повторюються у двох місцях. Код стане коротшим, але чи зможе гість і далі скасувати лише власне бронювання? Чи збереже адміністратор свій окремий дозвіл? Чи залишиться відповідь системи такою, якої очікують клієнти? Без відповідей менша кількість рядків нічого не доводить.
Навчальний результат. Після цієї глави читач може відрізнити рефакторинг від виправлення дефекту, функціональної зміни, архітектурної міграції та переписування; назвати контракт і докази, потрібні для безпечної зміни структури; знайти зміну зовнішньої поведінки, яку агент приховав у «прибиранні коду»; вибрати між локальним рефакторингом і поступовою заміною.
Глава продовжує теми перевірки поведінки з глав 48–52 і розмову про ціну майбутніх змін із глави 66. Тут фокус вужчий: спершу сформулювати, яку поведінку треба зберегти, а потім рухати код невеликими кроками. У главі 68 окремо розглядається, як підготувати перевірену зміну до випуску.
67.1. Що саме називаємо рефакторингом
Мартін Фаулер визначає рефакторинг як зміну внутрішньої структури програми, що полегшує розуміння й майбутні зміни без зміни спостережуваної поведінки. Коли команда додає нову функцію, виправляє неправильний результат або змінює спосіб сумісності, це може бути потрібною роботою, але її не слід називати чистим рефакторингом. Фаулер, «Definition of Refactoring»
Спостережувана поведінка — це те, що може помітити користувач, інший компонент, оператор або зовнішня система й на що вони обґрунтовано покладаються. До контракту можуть належати:
- значення, форма й порядок даних;
- успішні та помилкові відповіді, тип помилки й умова її виникнення;
- інтерфейс прикладного програмування (Application Programming Interface, API), формат запиту й відповіді;
- перевірка особи та дозволів;
- порядок подій і повідомлень, повторні спроби та їхні побічні ефекти;
- важливі для споживачів витрати часу, пам’яті чи пропускної здатності;
- операційні сигнали, наприклад записи аудиту, на які спирається чергова зміна.
Не кожна внутрішня деталь є контрактом. Назва локальної змінної може змінитися; порядок двох обчислень також може змінитися, якщо він не впливає на результат, помилки, побічні ефекти чи значущу продуктивність. Але якщо звіт, оператор або суміжний компонент споживає цей «внутрішній» ефект, команда має врахувати його, перш ніж вважати його невидимим.
Рефакторинг покращує форму коду, зберігаючи контракт. Виправлення дефекту змінює поведінку, яку команда вважає неправильною. Функціональна зміна додає або змінює можливість. Міграція переносить реалізацію, дані чи трафік на іншу межу або систему. Переписування створює значну частину системи заново. У великій роботі ці речі можуть поєднуватися, але команда має назвати кожну окремо, показати її докази й погодити відповідний ризик.
Такий поділ дає змогу носити «один капелюх за раз»: спершу зробити невеликий рефакторинг зі збереженням контракту, тоді додати нову поведінку окремим кроком. Це метафора Фаулера з опису робочих процесів: збереження поведінки та додавання функції мають різні критерії успіху. Коли їх з’єднати у великий нерозрізненний diff, рецензенту складніше побачити, чи зламався контракт, чи змінилася вимога, чи зроблено обидва кроки. Фаулер, «Workflows of Refactoring»
67.2. Спершу встановіть контракт і причину зміни
«Код негарний» або «агент знайшов дублікат» — це привід подивитися, а не обґрунтування змінювати систему. Спочатку сформулюйте конкретну мету: наприклад, правило авторизації зараз дублюється й уже розходилося; типовий запит вимагає одночасної зміни чотирьох компонентів; модуль неможливо перевірити без запуску зовнішньої системи; потрібну функцію не можна додати в безпечному місці.
Потім з’ясуйте, що реалізація робить зараз і на які докази спираємося:
- Прочитайте вимогу чи контракт і визначте, хто його підтримує.
- Знайдіть усіх викликачів, споживачів, конфігураційні перемикачі, побічні ефекти й суміжні повідомлення.
- Перегляньте наявні перевірки, історію зміни та інциденти, що стосуються цієї поведінки.
- Складіть короткий список інваріантів: які значення, помилки, дозволи й наслідки мають залишитися тими самими.
- Позначте невідоме. Якщо вимога, межа чи споживач невідомі, це не дозвіл агентові вибрати зручну для нього поведінку.
Якщо документація неповна, характеристичні перевірки можуть зафіксувати те, що програма робить сьогодні. Майкл Фезерс описує їх як спосіб спершу зрозуміти фактичну поведінку, а потім виявити неочікувану зміну після структурної роботи. Вони не встановлюють, що зафіксована поведінка правильна. Після спостереження власник контракту має вирішити, чи зберігати її, чи окремо змінювати. Фезерс, у викладі Альберто Савойї, «Working Effectively With Characterization Tests»
Це розрізнення важливе для застарілих систем. Наприклад, стара функція може повертати порожній рядок для відсутнього імені. Тест, що записує цей результат, допомагає не змінити його ненавмисно. Але лише власник продуктового контракту може сказати, чи порожній рядок справді треба залишити, чи це дефект, який слід виправити окремо.
Коли доступної специфікації недостатньо, починайте з малої ділянки й репрезентативних випадків: успішного результату, відмови, межового значення, порожнього або відсутнього поля, повтору, якщо він має значення. Вибір залежить від контракту. Автоматизовані перевірки корисні, але не вся поведінка легко вкладається в тест: іноді потрібні огляд відповідей, перевірка звіту, вимірювання затримки або підтвердження власника. Сам факт, що весь наявний набір перевірок пройшов, не доводить, що він охопив потрібний інваріант.
67.3. Зміну варто робити малими перевірюваними кроками
Після визначення інваріантів оберіть локальну операцію: перейменувати поняття, винести функцію, прибрати дубль, відокремити залежність або перемістити відповідальність за однією чіткою межею. Кожен крок має бути невеликим настільки, щоб рецензент міг відповісти: що змінилося в структурі, що змінилося в поведінці й де саме це видно?
Наприклад, таку дубльовану умову:
if booking.owner_id == actor.id or actor.is_admin:
cancel(booking)
можна спершу винести без зміни умов:
def may_cancel(booking, actor):
return booking.owner_id == actor.id or actor.is_admin
Результат виклику має лишитися тим самим для кожного значущого випадку. Потім обидва старі місця починають викликати спільну функцію. Якщо нове правило — наприклад, адміністратор більше не може скасовувати бронювання після оплати — додається в цю саму функцію, це вже зміна поведінки. Її не можна непомітно включити в рефакторинг лише тому, що умову вже винесли.
§67.2 допомагає встановити фактичний контракт до зміни. Для кожного кроку оберіть докази відповідно до ризику:
| Що може змінитися | Потрібний доказ |
|---|---|
| Вихід функції або помилка | Перевірки очікуваних значень і крайових випадків |
| Дозвіл або відмова | Позитивний і негативний сценарії для кожної важливої ролі |
| Зовнішня схема чи суміжний виклик | Перевірка контракту на обох сторонах межі |
| Зберігання даних, повтор чи порядок подій | Перевірка інтеграційного потоку, послідовності й повторного виконання |
| Час відповіді чи ресурс | Порівняння за однакового навантаження й середовища |
| Тестовий помічник або набір перевірок | Перевірка, що сам тест досі помічає помилковий результат |
| Операційна поведінка | Перевірка журналів, звітів, сигналів і процедури відновлення |
Коли команда змінює саме тестові допоміжні засоби, недостатньо побачити зелений результат: можна випадково вилучити твердження з перевірки. Google Testing Blog описує прийом перевірити, що навмисно неправильний код призводить до очікуваного падіння перевірки, а тоді повернути правильний код. Це приклад для ситуації, коли команда свідомо перевіряє дієвість тесту, а не універсальна інструкція запускати зламаний код у робочому середовищі. Google Testing Blog, «Refactoring Tests in the Red»
Розмір diff також є частиною доказу. Перейменування файлів, переформатування, нова функція й структурне переміщення в одному наборі змін затуляють одне одного. Якщо велике форматування або зміна генератора потрібні, зробіть її окремим кроком чи покажіть, як окремо рецензувати семантичну зміну. Агент має вказати точні файли й ділянки, де перевірив контракт, а автор — переглянути підсумкову різницю саме на цій ревізії. В інструментах для агентної розробки, як-от Codex, є перегляд змінених файлів, порівняння гілок і коментарі до рядків; це допомагає навести увагу на diff, але сам інтерфейс перегляду не доводить збереження поведінки. OpenAI, «Mastering remote engineering work from your phone», 23 червня 2026 року
67.4. Не маскуйте зовнішню зміну під внутрішню
Переміщення приватної функції в інший файл зазвичай не змінює API. Перейменування публічного поля, зміна коду помилки, видалення атрибута відповіді або зміна дозволу можуть його змінити. У другому випадку команді потрібні ті самі рішення, що для будь-якої сумісної зміни: хто погоджує контракт, чи потрібен перехідний період, як довго співіснують старий і новий варіанти, як сповіщають споживачів, як вимірюють міграцію і як відновлюються.
Обережна заміна часто проходить такими етапами:
- Створити спільну межу чи адаптер, який уміє звертатися до старої реалізації.
- Під’єднати до межі нову реалізацію, не перемикаючи весь трафік.
- Перевірити її на контрольованому обсязі або порівняти результати там, де це безпечно.
- Перемикати виклики поступово, за споживачами чи функціональними ділянками.
- Зберігати потрібну сумісність і засоби відновлення, поки дані й залежності не підтвердять завершення переходу.
- Видалити стару реалізацію та тимчасовий шар, коли є докази, що вони більше не потрібні.
Фаулер називає один із таких підходів Branch by Abstraction — поступове переключення за допомогою спільної абстракції. Такий перехід дає змогу замінювати реалізацію без довгої гілки, від’єднаної від змін системи, але вимагає обліковувати й згодом прибрати тимчасову межу. Фаулер, «Branch By Abstraction»
Перехідний адаптер — це тимчасове зобов’язання з власником і критерієм видалення, а не нова безкоштовна постійна архітектура. Заздалегідь назвіть ознаку завершення: частка переведених викликів, узгоджені дані, відсутність звернень до старого шляху протягом погодженого періоду. Інакше «тимчасовий» шар може пережити й стару, й нову реалізацію.
67.5. Модульність і чистота — це компроміси, не універсальна нагорода
Менше дублювання корисне тоді, коли копії справді реалізують одне правило й мають змінюватися разом. Якщо зовні схожий код виражає різні політики, примусове об’єднання створює спільний модуль із умовами для кожного випадку, прихованою залежністю або надто широким доступом. Тоді структурно «менше» коду може означати більше зв’язності.
У Software Architecture: The Hard Parts автори розглядають архітектурні рішення через компроміси: зв’язок між компонентами, межі модулів, дані та умови міграції потрібно оцінювати у конкретному контексті. Зв’язність може бути статичною — через спільну збірку або спільний код — і динамічною — через виклики, повідомлення й дані під час роботи. Роз’єднати один зв’язок іноді означає додати інший, наприклад мережевий виклик, синхронізацію чи дублювання даних. Тому мета не «нуль зв’язності», а зрозуміла й прийнятна її форма для типових змін і потрібних характеристик системи. Ford, Richards, Sadalage та Dehghani, Software Architecture: The Hard Parts, 2021; сторінка видавця
Це узгоджується з Фредеріком Бруксом: частина складності є сутнісною — вона походить від самої задачі й її понять; інша складність є випадковою й виникає через невдалі засоби подання чи зайві перепони. Брукс наголошує, що спрощення нотації саме по собі не усуває складність вимог, станів і взаємодій. Він також пов’язує складність структури з ускладненим оглядом цілого та послабленням концептуальної цілісності: читачеві важче побачити спільний задум системи. Отже, рефакторинг має прибирати непотрібну складність, не стираючи різницю між реальними правилами домену. Брукс, «No Silver Bullet», технічний звіт Університету Північної Кароліни, 1986
Бруксова «помилка другої системи» додає ще одне застереження. Після першого обмеженого рішення архітектор може спробувати вмістити в наступну систему всі відкладені ідеї, і вона стає надмірно складною. Для рефакторингу це означає: не перетворюйте локальне винесення функції на привід додати універсальний механізм правил, новий фреймворк або архітектуру майбутньої уявної платформи. У The Mythical Man-Month цей ефект описано окремо; сам факт другого покоління не є доказом, що його треба зменшувати чи переписувати. [Брукс, The Mythical Man-Month, глава 5 «The Second-System Effect»](https://www.oreilly.com/library/view/mythical-man-month-the/0201835959/ch05.xhtml)
67.6. Три розглянуті випадки
Випадок 1. Два місця перевіряють право скасувати бронювання
Ситуація. У сервісі бронювання веб-сторінка й команда підтримки мають власні умови дозволу скасувати бронювання. Під час попередньої зміни перевірку веб-сторінки оновили, а перевірку підтримки забули: перша дозволяє скасування власнику бронювання й адміністратору, друга — лише власнику. Власник поведінки підтверджує, що погоджене правило однакове для обох шляхів і включає адміністратора. Отже, спершу треба окремо виправити функціональну розбіжність; рефакторинг сам по собі не має вирішувати, яке правило правильне.
Дія. Власник поведінки формулює інваріанти: власник може скасувати своє бронювання; адміністратор може скасувати чуже; сторонній гість отримує відмову; відмова не створює запису скасування; відповідь і журнал аудиту лишаються сумісними. Команда спершу виправляє й окремо перевіряє шлях підтримки, який порушував погоджене правило. Лише після вирівнювання поведінки команда записує позитивні та негативні приклади й виносить спільне рішення про дозвіл у функцію, яку викликають обидва шляхи. Рефакторинг нижче зберігає вже погоджене правило; нового правила про оплату до нього не додають.
Після окремого виправлення поведінки, але безпосередньо перед винесенням, одна й та сама умова стоїть у кожному обробнику:
if booking.owner_id == actor.id or actor.is_admin:
cancel_booking(booking)
Після винесення обидва шляхи використовують одне рішення:
def may_cancel_booking(booking, actor):
return booking.owner_id == actor.id or actor.is_admin
if may_cancel_booking(booking, actor):
cancel_booking(booking)
Функція має лише одну відповідальність: вирішити дозвіл. Порядок, у якому викликається скасування і створюється запис аудиту, не змінюють.
Спостережуваний результат. Окреме виправлення спершу приводить шлях підтримки до погодженого правила. Рефакторинг після нього не змінює поведінки: власник може скасувати бронювання через обидва шляхи; адміністратор може скасувати чуже; сторонній гість отримує відмову; для відмови не створюється скасування; відповідь і аудит не змінюються. Майбутню зміну правила тепер можна зробити в одному місці. Цей результат підтверджують відповідні перевірки обох шляхів, контракт відповіді й запису аудиту, а не лише менший diff.
Чому це рефакторинг. Саме винесення змінює структуру: після окремого виправлення повторне правило отримує одну реалізацію, а задекларовані позитивні й негативні сценарії лишаються сталими. Попереднє виправлення шляху підтримки було окремою зміною поведінки, бо воно усувало виявлену розбіжність. Якби власник поведінки підтвердив різні правила, об’єднувати перевірки було б неправильно, навіть якби функції зовні здавалися однаковими.
Межі. Приклади вище — навчальний умовний сценарій, а не звіт про конкретний продукт. У реальній системі треба врахувати інші дозволи, часове вікно скасування, подвійний запит, журналювання та сповіщення. Невідомий контракт або відсутність перевірки на одному зі шляхів є причиною зупинитися й зібрати докази, а не припустити, що об’єднання безпечне.
Випадок 2. Агент «спрощує» обробку відсутнього імені
Ситуація. Агент змінює побудову відповіді профілю. Старий код відрізняє відсутнє значення від порожнього рядка, але агент вважає їх еквівалентними:
nickname = user.nickname if user.nickname is not None else ""
У пропозиції це замінено на коротший вираз:
nickname = user.nickname or None
У Python значення None, нуль і порожні послідовності є хибними у логічному контексті; оператор or повертає один зі своїх операндів, а не лише логічну істину чи хибу. Отже, порожній рядок буде замінено на None. Офіційна документація Python, «Truth Value Testing» і «Boolean Operations»
Дія. Рецензент порівнює три випадки на старому й новому коді та читає контракт профілю:
| Вхідне значення | Старий код | Запропонований код |
|---|---|---|
| «Марія» | «Марія» | «Марія» |
| порожній рядок | порожній рядок | None |
| відсутнє значення None | порожній рядок | None |
У серіалізованій відповіді None стає значенням null, а не порожнім рядком. Споживач API використовує порожній рядок як позначку «ім’я ще не задане» й окремо обробляє null як «дані неповні». Це означає, що змінився контракт відповіді. Команда відхиляє зміну як «чистий рефакторинг» і просить агента повернути форму, що зберігає відмінність:
nickname = user.nickname if user.nickname is not None else ""
Спостережуваний результат. Поточний контракт лишається незмінним: наявне ім’я повертається як є, порожнє залишається порожнім, а відсутнє значення перетворюється на порожній рядок. Після окремого погодження власник продукту може вирішити нормалізувати відсутність до null, але тоді змінюють специфікацію, перевірки й споживачів як окрему сумісну зміну.
Чому це не рефакторинг. Скорочення рядка непомітно злило два різні входи в один і змінило зовнішнє значення відповіді. Тести, написані лише після зміни й засновані на новому коді, могли б «підтвердити» саме помилку агента. Спершу потрібні наявний контракт, поведінка старої версії або погоджене рішення власника.
Межі. Це навчальний приклад; він показує семантику Python і можливий контракт, але не описує наявний API. У Python клас також може змінити власне логічне значення через спеціальні методи, тому не можна загалом вважати всі значення, крім None, істинними. Перевіряйте правила конкретної мови та конкретний контракт.
Випадок 3. Одноразове переписування інтеграції проти поступової заміни
Ситуація. У описаному Фаулером випадку організація замінювала старе інтеграційне проміжне програмне забезпечення, яке передавало події продуктів магазину. Одноразовий перехід означав би, що всі продукти мають змінити шлях у той самий час. Команда не мала доказів, що всі приховані споживачі й звіти поводяться так само після повної заміни.
Дія. Команда створила маршрутизатор подій як прозорий структурний шар: спершу він передавав повідомлення далі без зміни спостережуваної поведінки. Потім команда додала маршрутизацію за типом продукту й окремими продуктами та поступово збільшувала частку, яку обслуговувала нова система. Це реальний приклад поступової заміни з опису Ієна Картрайта, Роба Горна та Джеймса Льюїса «Patterns of Legacy Displacement»; він не є рецептом, за яким кожну систему треба перетворити на набір сервісів. Cartwright, Horn і Lewis, «Patterns of Legacy Displacement»
Спостережуваний результат і неочікуване відкриття. Невдовзі після перевірки нової реалізації у робочому середовищі команда виявила, що важливі управлінські звіти не сходяться. База проміжного програмного забезпечення зі станом довгих транзакцій копіювалася до сховища даних; пакетні завдання, збережені процедури й подання звітів використовували ці дані. Команда додала перехоплення повідомлень про продаж і додаткові сумісники, які записували потрібні дані у сховище. Автори називають їх частиною тимчасової архітектури й пишуть, що їх приберуть, коли це стане можливим; стаття не стверджує, що видалення вже відбулося. Такий підхід мав знижувати ризик, але коштував додаткової роботи — це був свідомий компроміс, а не безкоштовне спрощення.
Як порівняти варіанти. Одноразове переписування коротше лише на діаграмі плану: до повного ввімкнення всі споживачі, звіти, потоки даних і відновлення мають бути підтверджені. Команда порівнює на контрольних наборах старий і новий маршрути: однакову кількість подій для кожного продукту, порядок, помилки, повторну доставку та дані, з яких будуються звіти; перед переключенням також перевіряє, що старий маршрут ще доступний для відновлення. Повернення потребуватиме швидкого зворотного переключення й сумісності стану. Поступовий перехід довше підтримує два шляхи та потребує маршрутизатора, моніторингу, критеріїв переключення й дати прибирання перехідного коду. Перед кожним розширенням команда звіряє лічильники подій, помилки й важливі звіти для вже переведеного сегмента; повернення стосується маршруту конкретної групи продуктів, якщо дані не вимагають окремого відновлення. Такий крок дає докази на кожному етапі й обмежує обсяг одночасної зміни. Якщо система мала, залежності відомі, а повний еквівалентний прогін і відновлення перевірені, одноразове перемикання може бути прийнятним. Якщо межі приховані або дані неможливо миттєво повернути, потрібен поетапний план.
Чому це важливо для теми рефакторингу. Перший маршрутизатор створив структурну межу зі збереженням поведінки; поступове переведення потоків стало міграцією. Стаття розрізняє цю структурну зміну й подальшу міграцію, а додаткові сумісники називає тимчасовою архітектурою, яку планували прибрати, коли це стане можливим. Джерело не підтверджує, що це видалення відбулося.
Межі. Це опис конкретного досвіду, опублікований авторами статті, а не універсальний звіт із кількісно контрольованим порівнянням переписування та міграції. Він показує реальний тип прихованої залежності й рішення команди; у власному проєкті масштаби, дані й вартість перехідного шару треба встановити окремо.
67.7. Приклад зі змінами в реальному репозиторії
У вересні 2025 року злитий запит на зміну (pull request, PR) №55732 у Rails прибрав повторні визначення трьох допоміжних методів у тестах завдань бази даних: один метод був визначений шість разів, другий — тричі, третій — п’ять разів. Запит перемістив ці визначення в спільний модуль DatabaseTasksHelper. Його опис назвав покращенням читабельності й повторного використання; у ньому зафіксовано, що змінювали тестові допоміжні засоби. Rails, PR №55732, 22 вересня 2025 року
Цей приклад показує, як обмежений рефакторинг можна відрізнити за diff: усунуто дублювання тестового коду в одному файлі, а не переписано виконуваний код застосунку. Сам факт злиття та проходження трьох перевірок на GitHub підтверджує історію цього запиту, але не доводить поведінку всього Rails чи якість тестів у кожному випадку. Початковий запит №55713 містив і оптимізацію запису схем, і прибирання допоміжних методів у тестах. Його опис прямо каже, що повне прибирання помічників винесли в окремий запит №55732, щоб він злився першим; це й сталося: №55732 злили 22 вересня, а №55713 — 25 вересня 2025 року. Після цього №55713 зосереджувався на уникненні повторного запису тієї самої схеми й перевірках цього шляху. Автор повідомив про приблизно 40% швидше створення схеми на одному великому застосунку, із застереженням, що результат залежить від розміру схеми. Це вже оптимізація виконання з власним тестом і вимірюванням, а не доказ чистого рефакторингу. Історія двох запитів показує, як суміжні цілі можна розділити на окремі перевірювані зміни.
67.8. Коли не починати рефакторинг зараз
Відкласти структурну зміну — це іноді відповідальне рішення. Не починайте її, якщо:
- невідомо, хто є споживачем поведінки або який контракт чинний;
- проблема ґрунтується лише на вподобанні автора, а вимірної майбутньої користі немає;
- немає способу перевірити потрібні інваріанти, а невідомі наслідки можуть торкнутися даних, дозволів чи грошей;
- критичний випуск уже не має вікна для перевірки й безпечного відновлення;
- «маленьке прибирання» приховує нову поведінку, зміну API чи архітектурну міграцію без погодження;
- пропозиція розширилася до повного переписування, хоча заявлена проблема локальна;
- новий перехідний адаптер не має власника, умови видалення або способу відновлення;
- неможливо пояснити, що саме стає дешевшим після зміни.
Така пауза не має перетворюватися на мовчазне накопичення боргу. Зафіксуйте невідоме, відповідального за перевірку та умову повернення: наприклад, наступна зміна цієї ділянки, поява відомого споживача або погоджене вікно випуску. Якщо зміна потрібна для нового запиту, її слід обґрунтувати в межах цього запиту, а не автоматично розширювати заради майбутньої гіпотези.
67.9. Як ставити межі агенту
Завдання на рефакторинг для агента має описувати контракт і доказ, а не лише бажану форму коду. До запиту включіть:
- проблему та ділянку, якої вона стосується;
- інваріанти, які мають лишитися незмінними;
- позитивні й негативні сценарії, зокрема помилки та межові значення;
- дозволений обсяг файлів чи компонентів;
- поведінкові зміни, які заборонено робити в межах завдання;
- перевірки та інші докази, які потрібні;
- що агент має показати у своєму звіті: точний diff, змінені файли, результат перевірок і невідомі місця.
Просіть агента спершу знайти виклики та контракт, тоді запропонувати обмежений план. Не дозволяйте йому в одному заході об’єднати форматування, оновлення залежності, виправлення дефекту й рефакторинг, якщо ви не погодили кожну мету. Перегляньте справжній diff після останньої зміни агента: точкова відповідь може бути застарілою, якщо код після неї змінено.
Рецензент може поставити чотири прямі запитання:
- Що стало структурно простішим і для якого типового майбутнього завдання?
- Який контракт був чинним до зміни, і хто його підтвердив?
- Які докази перевіряють позитивні, негативні та граничні сценарії?
- Чи змінилася хоч одна зовнішня відповідь, дозвіл, подія, дані або характеристика, на яку покладається споживач?
Якщо на четверте запитання відповідь «так», зміна не обов’язково погана; просто назвіть її, погодьте контракт і додайте міграційний план. Якщо відповідь «не знаю», це відкритий ризик, а не доказ збереження поведінки.
Джерела та подальше читання
Посилання, дати публікації та поточні сторінки перевірено 25 вересня 2026 року. Описані синтетичні приклади 1 і 2 є навчальними сценаріями; приклади 3 і 4 посилаються на опубліковані першоджерела.
- Martin Fowler, «Definition of Refactoring», 1 вересня 2004 року — визначення, що пов’язує внутрішню структуру зі збереженням спостережуваної поведінки.
- Martin Fowler, «Workflows of Refactoring», 2014 рік — розділення структурних кроків і додавання функцій, економічне обґрунтування та довготривалі процеси.
- Martin Fowler, «Branch By Abstraction», 2014 рік — техніка поетапної заміни реалізації через спільну межу.
- Michael Feathers, викладений Alberto Savoia, «Working Effectively With Characterization Tests», 9 березня 2007 року — спостереження й фіксація поточної поведінки та відмінність між фактом і правильністю.
- Google Testing Blog, «TotT: Refactoring Tests in the Red», 26 квітня 2007 року — перевірка того, що зміна тестового коду не прибрала дієві твердження.
- Neal Ford, Mark Richards, Pramod Sadalage та Zhamak Dehghani, Software Architecture: The Hard Parts, 2021 рік — контекстний аналіз модульності, зв’язності, міграцій і архітектурних компромісів; сторінку видавця перевірено 10 жовтня 2026 року.
- Frederick P. Brooks Jr., «No Silver Bullet: Essence and Accidents of Software Engineering», технічний звіт Університету Північної Кароліни, вересень 1986 року — першоджерело розрізнення сутнісної та випадкової складності.
- Frederick P. Brooks Jr., [The Mythical Man-Month, глава 5 «The Second-System Effect»](https://www.oreilly.com/library/view/mythical-man-month-the/0201835959/ch05.xhtml), видання 1995 року — опис ризику накопичити відкладені ідеї в надмірно амбітній наступній системі.
- Ian Cartwright, Rob Horn і James Lewis, «Patterns of Legacy Displacement», 2021 рік, зі змінами сторінки до 2024 року — реальний досвід поетапної заміни й неочікуваної залежності управлінських звітів від старого сховища.
- Команда Rails, PR №55732, злитий 22 вересня 2025 року — конкретний diff структурного рефакторингу тестових допоміжних засобів; три перевірки PR пройшли, але це не є доказом поведінки всього застосунку.
- Команда Rails, PR №55713, злитий 25 вересня 2025 року — окрема зміна зі зменшенням дублювання запису схем, тестами на один запис для однакового шляху, нормалізацією шляхів і авторським вимірюванням швидкодії; наводиться як приклад окремої цілі оптимізації, не як чистий рефакторинг.
- Python Software Foundation, документація Python: «Built-in Types — Truth Value Testing», актуальна сторінка документації — семантика None, порожніх значень і логічного оператора or для другого навчального випадку.
- OpenAI, «Mastering remote engineering work from your phone», 23 червня 2026 року — опис перегляду diff та коментарів до змін у Codex; тут використаний лише для пояснення інструментального процесу, а не як доказ правильності коду.