Костурець (англ. workaround, kludge, hotfix) — це тимчасове або неоптимальне рішення проблеми в коді, яке працює, але порушує принципи чистої архітектури, читабельності або продуктивності. Костурці неминучі в реальній розробці: дедлайни, несумісність версій, legacy-код і недокументована поведінка фреймворків змушують розробників іти на компроміси. За даними Martin Fowler (2025), ключова відмінність виправданого костурця від технічного боргу — наявність плану з його усунення та явне маркування в коді.
Головне
Костурець — це сленгова назва програмного рішення, яке функціонально коректне, але технічно неоптимальне. Такий код працює, проходить тестування і навіть потрапляє в продакшн, але його читання викликає бажання переписати все з нуля. В англомовному середовищі використовуються терміни workaround, kludge (kluge), hack або quick-and-dirty fix.
Термін прийшов із побутової метафори: якщо зламалася ніжка стільця, можна примотати її скотчем — стілець знову стоїть, але рішення тимчасове і негарне. У програмуванні те саме: баг фіксується хардкодом, таймаутом-костурцем або обходом через недокументоване API. Код компілюється, застосунок не падає, але рішення не можна назвати якісним.
Важлива відмінність: баг — це коли код не працює, костурець — коли код працює, але неправильно спроєктований. Костурець — це завжди усвідомлений вибір розробника: «Я знаю, що це негарно, але прямо зараз це вирішує проблему».
За оцінкою Stripe (2024), розробники витрачають у середньому 17 годин на тиждень на роботу з технічним боргом і костурцями — майже половину робочого часу. Це пряма втрата продуктивності команди.
Перша і головна причина — дедлайн. Коли до релізу залишається день, а критичний баг ще не виправлений, команда обирає швидке рішення замість правильного. Хардкод значення, вимкнення перевірки, додавання sleep() — класичні приклади дедлайн-костурців. Досвідчений розробник завжди маркує такі місця TODO або FIXME.
Друга причина — несумісність API. Стороння бібліотека або фреймворк поводиться не так, як описано в документації. Фреймворк не експортує потрібний клас, метод позначений як deprecated, а альтернативи немає. Розробник змушений використовувати рефлексію, внутрішнє API або обхідний шлях. У Java це може бути доступ через setAccessible(true), у Swift — @objc і performSelector.
Третя причина — legacy-код. Розробник успадковує проєкт, написаний 5–10 років тому на застарілій версії фреймворку. Переписувати весь модуль немає часу і бюджету, тому новий функціонал «приклеюється» до старого коду через костурці. Поступово таких нашарувань стає так багато, що модуль перетворюється на «big ball of mud».
Четверта причина — відсутність тестів. Рефакторинг без тестів небезпечний: зміна архітектури може зламати працюючу функціональність. Коли тестів немає, розробник воліє додати костурець поверх працюючого коду, ніж ризикувати стабільністю. За даними Google Testing Blog (2024), команди без тестів у 3 рази частіше використовують workaround-рішення.
Класифікація костурців допомагає команді розуміти, з яким типом технічного боргу вона має справу, і обирати правильну стратегію усунення. Розглянемо основні види.
Хардкод — найпоширеніший тип. Замість конфігурації, ресурсу або параметра використовується жорстко задане значення в коді. Приклад: захардкожений URL сервера, таймаут 5 секунд, розмір шрифту 16pt. Хардкод робить код немасштабованим і потребує перекомпіляції при будь-якій зміні.
Copy-paste — дублювання ділянки коду з невеликими змінами замість виділення спільної логіки. Класичний симптом: у проєкті є 3 схожі методи, які відрізняються одним рядком. Copy-paste пришвидшує написання коду на момент задачі, але в 10 разів уповільнює його підтримку в майбутньому — правку потрібно вносити в 3 місця замість одного.
Try-catch-пустушка — блок catch, який нічого не робить або тільки логує помилку без обробки. Такий костурець «глушить» виняток, але не вирішує його причину. Застосунок продовжує працювати, але дані можуть бути пошкоджені, а користувач — не отримати зворотний зв'язок.
Сон у коді — Thread.sleep(500) або DispatchQueue.main.asyncAfter для очікування, коли має бути подія або колбек. Такий код ненадійний: на повільному пристрої 500 мс може не вистачити, на швидкому — пауза буде зайвою. Використовуйте CountDownLatch, Semaphore або async/await із правильними таймінгами.
Прапорці сумісності — if-else-каскади, що перевіряють версію ОС, модель пристрою або наявність фічі. Коли прапорців стає більше 3–4, код перетворюється на спагеті. Рішення — Strategy pattern або Feature Flags через конфігурацію.
Багато розробників плутають костурець і технічний борг. Різниця — у масштабі та усвідомленості. Костурець — це локальне, конкретне рішення (один метод, один клас). Технічний борг — це системна проблема, що зачіпає архітектуру модуля або всього застосунку.
Метафора Ward Cunningham (творця терміна Technical Debt): технічний борг — це як взяти кредит у банку. Ви берете гроші зараз, щоб швидше добудувати будинок, але потім виплачуєте відсотки. Костурець — це забити цвях молотком замість шуруповерта: робота виконується, але менш ефективно.
Один костурець не створює технічний борг. Але 50 костурців в одному модулі = архітектурний борг. Тому правило команди: кожен костурець фіксується в code review або task tracker, і команда регулярно (раз у спринт) рев'ювить накопичені workaround-рішення.
За досвідом Spotify Engineering (2023), команди, які ведуть облік костурців у коді (через спеціальний лейбл TODO або custom annotation), скорочують час на рефакторинг на 30% — тому що не витрачають години на пошук проблемних місць.
Перший крок — інвентаризація. Шукайте в кодовій базі ключові слова: TODO, FIXME, HACK, WORKAROUND, KLUDGE. Сучасні IDE підсвічують їх окремим кольором. GitHub також відображає TODO в інтерфейсі Pull Request. Складіть список усіх костурців із пріоритетом.
Другий крок — пріоритезація. Не всі костурці потрібно фіксити негайно. Пріоритет = частота змін у файлі × критичність. Якщо файл змінюється 2 рази на рік, костурець може почекати. Якщо модуль зачіпається в кожному спринті — костурець потрібно фіксити в першу чергу.
Третій крок — рефакторинг із тестами. Ніколи не рефакторьте костурець без тестів. Напишіть спочатку тест, який перевіряє поточну поведінку (з костурцем), потім рефакторьте, потім переконайтеся, що тест проходить. Без цього рефакторинг костурця може зламати функціональність, заради якої він був написаний.
// До: обхідний шлях із жорстко закодованим URL
fun getApiUrl(): String {
return "https://api.example.com/v2"
}
// Після: конфігурація через BuildConfig
fun getApiUrl(): String {
return BuildConfig.API_BASE_URL
}
Четвертий крок — автоматизація. Налаштуйте лінтер, який забороняє певні патерни-костурці. Наприклад, Detekt для Kotlin може перевіряти відсутність Thread.sleep() у production-коді, ESLint — забороняти console.log у проєкті. Це запобігає появі нових костурців того ж типу.
Незважаючи на негативне забарвлення терміна, костурець може бути виправданим рішенням. Головна умова: костурець — тимчасовий, явно маркований і має план заміни. У продакшн-коді кожного великого проєкту є сотні виправданих костурців.
Ситуація 1: hotfix у продакшні. Критичний баг падає в усіх користувачів. Команді потрібне виправлення протягом години. Правильний підхід: чинимо баг будь-яким способом, деплоїмо хотфікс. Потім, наступного дня, пишемо правильне рішення і закриваємо задачу. Хотфікс — це виправданий костурець, якщо він живе не більше 48 годин.
Ситуація 2: очікування виходу нової версії бібліотеки. Фреймворк містить баг, який виправлений у master, але реліз вийде через 2 тижні. Замість того щоб писати складний обхідний код, команда додає workaround із позначкою «REMOVE after library 3.2». Коли виходить 3.2, workaround видаляється.
Ситуація 3: запуск стартапу або MVP. На стадії MVP важливіша швидкість, ніж архітектура. Костурці на старті — це нормально. Проблема виникає, коли стартап не перетворюється на продукт, а костурці залишаються. Рекомендація: після раунду funding виділіть спринт на погашення критичного технічного боргу.
Головний принцип: «Легасі — це чужий код без тестів» (Michael Feathers). Якщо костурець покритий тестом і явно задокументований — він керований. Якщо він висить без коментарів 2 роки в забутому модулі — це вже не костурець, а архітектурна проблема.
Часті запитання
Баг — код не працює як очікується. Костурець — код працює, але написаний неоптимально. Костурець — завжди усвідомлене рішення розробника, баг — зазвичай неусвідомлена помилка.
Використовуйте // TODO: refactor — ... або кастомну анотацію @Workaround із полями: причина, дата, відповідальний, deadline видалення. Уникайте голого // HACK без пояснень.
Якщо модуль не змінюється і костурець стабільний — не потрібно. Рефакторинг без причини збільшує ризик регресії. Фіксіть тільки ті костурці, які заважають додавати новий функціонал.
Порівняйте час: «Зараз ми витрачаємо 4 години на ручне тестування через ці костурці. Рефакторинг займе 8 годин і скоротить час до 30 хвилин. Окупність — 2 спринти». Говоріть мовою швидкості та грошей, а не чистої архітектури.
Шукайте TODO, FIXME, HACK, WORKAROUND через grep по проєкту. Аналізуйте методи довші за 100 рядків і класи з більш ніж 5 залежностями. Використовуйте лінтери з кастомними правилами для автоматичного детекту.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також