Франкенщайн в програмирането е код, сглобен от несъвместими части на различни технологии, стилове и архитектури. Според проучването 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 в callbacks
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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също