Франкенштейн у програмуванні — це код, зібраний із несумісних частин різних технологій, стилів та архітектур. За даними дослідження ThoughtWorks Technology Radar (2024), 28% великих проєктів містять ознаки синдрому Франкенштейна — архітектурної еклектики, що виникає за відсутності єдиного технічного бачення. За аналогією з романом Мері Шеллі, такий код працює, але його підтримка перетворюється на кошмар.
Головне
Франкенштейн (Frankenstein code, Frankenstein pattern) — це антипатерн, за якого програмна система збирається з частин, не призначених для спільної роботи. Як монстр Франкенштейна, такий код може функціонувати, але він потворний, непередбачуваний і небезпечний за найменших змін.
Термін прийшов із літератури: у романі Мері Шеллі «Франкенштейн, або Сучасний Прометей» (1818) учений створив живу істоту з фрагментів тіл різних померлих людей. У програмуванні аналогія точна — розробники беруть шматки різних фреймворків, бібліотек, мов і склеюють їх «на живу нитку», отримуючи робочий, але жахливий результат.
Відмінність Франкенштейна від макаронного коду — у масштабі та природі проблеми. Спагеті-код — це заплутана структура всередині одного технологічного стека. Франкенштейн — це еклектика на рівні архітектури: різні технології, несумісні парадигми, конфліктуючі підходи всередині однієї системи.
Мікросервісна архітектура допускає використання різних технологій для різних сервісів, але за умови чітких меж і стандартизованих протоколів взаємодії. Франкенштейн — це хаотичне змішування без меж: REST і GraphQL в одному контролері, дві ORM в одному модулі, SQL і NoSQL для однієї сутності.
Відсутність технічного лідера або архітектора — першопричина. Коли в проєкті немає людини, яка відповідає за цілісність архітектури, кожен розробник обирає інструменти «під себе». Один любить Spring, другий — Guice, третій — власний DI. Результат — архітектурний вінегрет.
Злиття проєктів — друга поширена причина. Дві команди розробляли свої модулі незалежно, використовуючи різні стеки. Коли модулі потрібно об'єднати в один застосунок, їх просто «склеюють» адаптерами та прошарками. Виходить Франкенштейн.
Корпоративні поглинання — третій сценарій. Компанія А купила компанію Б і хоче інтегрувати її продукт у свій. Замість переписування — склейка через API, спільні бази даних і милиці. За рік система перетворюється на монстра, якого ніхто не розуміє.
| Причина | Опис | Типовий результат |
|---|---|---|
| Немає архітектора | Кожен розробник обирає свій стек | 3 різні HTTP-клієнти в одному модулі |
| Злиття проєктів | Два продукти склеюються в один | Дві ORM, два способи логування |
| M&A | Поглинання компанії з її продуктом | Гібрид із різних архітектур і стилів |
| Експерименти | Впровадження нових технологій без стратегії | Java 8 + фічі Java 21 в одному файлі |
| Політичні рішення | Нав'язування технології зверху без урахування контексту | Enterprise-фреймворк для простого скрипта |
Досвідчені розробники, які хочуть випробувати нові технології в продакшні, часто стають джерелом Франкенштейна. Замість того щоб обмежити експерименти ізольованим модулем, вони впроваджують експериментальний код у критичну частину системи.
Класичний приклад — використання кількох ORM в одному застосунку. Частина модулів використовує Hibernate, частина — MyBatis, а частина — прямі JDBC-запити. Транзакції стають некерованими, кеш — неузгодженим, а новий розробник не знає, який підхід обрати для нової фічі.
Другий приклад — змішування архітектурних стилів. У контролері REST API трапляються виклики SOAP-сервісів, прямі SQL-запити, звернення до файлової системи та генерація HTML. Такий застосунок неможливо тестувати, розширювати чи документувати.
Третій приклад — технологічний стек, де Python використовується для бекенду, Node.js — для мікросервісу, C# — для десктопного клієнта, а Java — для Android-застосунку, при цьому вся бізнес-логіка розмазана між ними без чіткого розподілу відповідальності.
// франкенштейн — змішані стилі та технології
// callbacks, Promises та async/await разом
// callbacks
db.query("SELECT * FROM users", function(err, rows) {
if (err) handleError(err);
// Promise усередині callback
fetch("/api/data").then(function(data) {
// async/await усередині then
(async () => {
const result = await processData(data);
sendResponse(result);
})();
});
});
// чистий код — єдиний стиль async/await
async function getUserData(userId) {
const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
const data = await fetch("/api/data/" + userId);
return await processData(user, data);
}
Одна база даних використовується одночасно як SQL-реляційна (з нормалізацією) і як NoSQL-документо-орієнтована (з JSON-колонками). Частина запитів іде через ORM, частина — через збережені процедури, частина — через прямий SQL із коду. Схема БД не документована, міграції конфліктують.
Складність онбордингу — перший наслідок. Новий розробник має знати 5 мов, 3 фреймворки, 2 архітектурні стилі, щоб розуміти, як працює система. Онбординг затягується з тижнів до місяців. За даними LinkedIn (2023), проєкти з технологічною еклектикою втрачають нових співробітників у 2 рази частіше.
Непередбачуваність поведінки — другий наслідок. Зміна в Python-мікросервісі може несподівано зламати Java-модуль, бо вони використовують спільну БД без чітких контрактів. Налагодження таких проблем вимагає одночасного знання всіх технологій у стеку.
Безпека — третій наслідок. Кожна технологія в стеку вимагає своєї конфігурації безпеки, своїх патчів, свого моніторингу. Підтримувати безпеку на прийнятному рівні для 5–6 різнорідних технологій практично неможливо. Одна з них неминуче виявиться вразливою.
SonarQube може виміряти технічний борг, але не може виміряти «архітектурний борг» — несумісність компонентів. Цей борг проявляється не в попередженнях лінтера, а в неможливості додати нову функцію без зміни трьох різних модулів, написаних на різних технологіях.
Перший і головний крок — призначити архітектора або tech lead, відповідального за цілісність технологічного стека. Ця людина має вето на впровадження нових технологій без архітектурної рецензії. Не демократія, а відповідальне одноосібне рішення з ключових технологій.
Другий — впровадити процес Architecture Decision Record (ADR). Будь-яке значне архітектурне рішення (вибір БД, фреймворку, протоколу) документується у вигляді короткого тексту: контекст, розглянуті альтернативи, ухвалене рішення, наслідки. ADR зберігаються в репозиторії та доступні всій команді.
Третій — встановити принцип «одне завдання — один інструмент». Для HTTP-запитів — один клієнт. Для ORM — одна бібліотека. Для логування — один фреймворк. Винятки допускаються лише через ADR з обґрунтуванням. Якщо в проєкті вже є Axios — не додавайте fetch, якщо є SLF4J — не пишіть через System.out.
Експерименти допустимі, але в ізольованому середовищі. Виділіть модуль або сервіс, який можна переписати на нову технологію без впливу на решту системи. Якщо експеримент удався — стандартизуйте його через ADR. Якщо ні — видаліть без наслідків.
Інвентаризація — перший крок. Складіть повну карту технологічного стека: які фреймворки, бібліотеки, мови, протоколи використовуються, у яких модулях і для яких завдань. Ви побачите масштаб проблеми: дублювання інструментів, конфліктуючі технології, невикористовувані залежності.
Стандартизація — другий крок. Оберіть один інструмент для кожного завдання. Наприклад: тільки Hibernate для ORM, тільки SLF4J + Logback для логування, тільки REST для API. Задокументуйте стандарт в ADR. Почніть заміну з модулів, де еклектика приносить найбільше проблем.
Стратегія Parallel Run — третій крок. Старий і новий інструменти працюють паралельно, поки новий не доведе свою надійність. Наприклад, старий HTTP-клієнт і новий працюють одночасно, але новий — лише для частини запитів. Після періоду стабілізації старий видаляється.
// франкенштейн — три HTTP-підходи в одному проєкті
// Модуль A: OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);
// Модуль B: RestTemplate (Spring)
restTemplate.getForObject(url, String.class);
// Модуль C: java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
// єдиний підхід: RestTemplate для синхронного, WebClient для реактивного
@Autowired
private RestTemplate restTemplate;
public String callApi(String url) {
return restTemplate.getForObject(url, String.class);
}
Технічний лідер — головний інструмент боротьби з Франкенштейном. Не менеджер, не архітектор у вежі зі слонової кістки, а практикуючий розробник, який пише код, рев'юїть PR та ухвалює архітектурні рішення. Без такої людини проєкт неминуче скочується в технологічну еклектику.
RFC (Request for Comments) — процес, запозичений із Open Source-спільнот. Перед впровадженням будь-якої значущої технології автор пише RFC: проблема, пропоноване рішення, альтернативи, план впровадження. Команда обговорює, голосує, ухвалює або відхиляє. RFC створює прозорість і запобігає «тихим» архітектурним рішенням.
Технологічний радар (Technology Radar від ThoughtWorks) — інструмент категоризації технологій: Adopt, Trial, Assess, Hold. Команда регулярно переглядає радар і оновлює статуси. Це допомагає відрізняти «модне» від «корисного» та уникати впровадження неперевірених технологій у критичний код.
Найважливіша якість архітектури — узгодженість (consistency). Навіть не найкращий інструмент, який використовується у всьому проєкті, кращий, ніж найкращий інструмент, який використовується лише в одному модулі. Узгодженість знижує когнітивне навантаження, спрощує онбординг і робить код передбачуваним.
Часто задавані питання
Polyglot persistence — усвідомлене використання різних БД для різних завдань (PostgreSQL для транзакцій, Redis для кешу, Elasticsearch для пошуку). Франкенштейн — хаотичне змішування без стратегії. Різниця в наявності архітектурного рішення: polyglot — це план, Франкенштейн — його відсутність.
Так, і це часта проблема. Коли кожен мікросервіс використовує свою мову, свою БД, свій протокол і свій підхід до розгортання без централізованих стандартів — виходить розподілений Франкенштейн. Для мікросервісів важливі спільні стандарти: єдиний протокол (REST/gRPC), спільний формат логів, централізоване observability.
Не забороняйте — спрямовуйте. Запропонуйте автору RFC: опишіть, чому наявне рішення не підходить, які альтернативи розглянули, як будете мігрувати. Часто в процесі написання RFC розробник сам розуміє, що нова технологія не потрібна. Якщо RFC переконливий — впроваджуйте, але з планом та обмеженнями.
Спочатку інвентаризація, потім стандартизація. Не намагайтеся переписати все одразу. Виділіть один шар (наприклад, HTTP-клієнти або логування), оберіть єдиний інструмент, напишіть ADR і мігруйте поступово. Метод Strangler Fig — замінюйте старі компоненти новими по одному, без зупинки роботи застосунку.
Чим менше, тим краще. Ідеально — одна мова, один фреймворк, одна БД, один спосіб логування. Реалістично — 2–3 мови (за чіткого розподілу), 1–2 БД, 1–2 фреймворки. Кожна додаткова технологія збільшує когнітивне навантаження команди та вартість підтримки.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також