Frankenstein na programação é código montado a partir de partes incompatíveis de diferentes tecnologias, estilos e arquiteturas. De acordo com a pesquisa ThoughtWorks Technology Radar (2024), 28% dos grandes projetos apresentam sinais da síndrome de Frankenstein — ecletismo arquitetônico que surge da falta de uma visão técnica unificada. Por analogia com o romance de Mary Shelley, esse código funciona, mas sua manutenção se torna um pesadelo.
Principais pontos
Frankenstein (código Frankenstein, padrão Frankenstein) é um antipadrão onde um sistema de software é montado a partir de partes não projetadas para trabalhar juntas. Como o monstro de Frankenstein, esse código pode funcionar, mas é feio, imprevisível e perigoso ao menor sinal de mudança.
O termo vem da literatura: no romance de Mary Shelley “Frankenstein, ou O Prometeu Moderno” (1818), um cientista criou um ser vivo a partir de fragmentos de corpos de diferentes mortos. Na programação, a analogia é exata — desenvolvedores pegam pedaços de diferentes frameworks, bibliotecas, linguagens e os colam “a vivo”, obtendo um resultado funcional, mas monstruoso.
A diferença entre Frankenstein e código espaguete está na escala e na natureza. Código espaguete é uma estrutura emaranhada dentro de uma única pilha tecnológica. Frankenstein é ecletismo no nível da arquitetura: diferentes tecnologias, paradigmas incompatíveis, abordagens conflitantes dentro do mesmo sistema.
A arquitetura de microsserviços permite o uso de diferentes tecnologias para diferentes serviços, mas com limites claros e protocolos de interação padronizados. Frankenstein é uma mistura caótica sem limites: REST e GraphQL no mesmo controlador, dois ORMs no mesmo módulo, SQL e NoSQL para a mesma entidade.
A falta de um líder técnico ou arquiteto é a causa raiz. Quando não há uma pessoa responsável pela integridade arquitetônica, cada desenvolvedor escolhe ferramentas “para si”. Um gosta de Spring, outro de Guice, um terceiro usa um DI personalizado. O resultado é uma salada mista arquitetônica.
A fusão de projetos é a segunda causa comum. Duas equipes desenvolveram seus módulos de forma independente, usando pilhas diferentes. Quando os módulos precisam ser combinados em um único aplicativo, eles são simplesmente “colados” com adaptadores e camadas intermediárias. O resultado é Frankenstein.
Aquisições corporativas são o terceiro cenário. A empresa A comprou a empresa B e quer integrar seu produto ao seu. Em vez de reescrever — colagem via APIs, bancos de dados compartilhados e remendos. Depois de um ano, o sistema se torna um monstro que ninguém entende.
| Causa | Descrição | Resultado típico |
|---|---|---|
| Sem arquiteto | Cada desenvolvedor escolhe sua própria pilha | 3 clientes HTTP diferentes em um módulo |
| Fusão de projetos | Dois produtos colados em um | Dois ORMs, duas formas de log |
| M&A | Aquisição de empresa com seu produto | Híbrido de diferentes arquiteturas e estilos |
| Experimentos | Introdução de novas tecnologias sem estratégia | Recursos Java 8 + Java 21 em um arquivo |
| Decisões políticas | Tecnologia imposta de cima sem contexto | Framework empresarial para um script simples |
Desenvolvedores experientes que querem experimentar novas tecnologias em produção frequentemente se tornam a fonte de Frankenstein. Em vez de limitar os experimentos a um módulo isolado, eles introduzem código experimental em partes críticas do sistema.
Um exemplo clássico é o uso de múltiplos ORMs em uma única aplicação. Alguns módulos usam Hibernate, alguns usam MyBatis, e alguns usam consultas JDBC diretas. As transações se tornam ingerenciáveis, o cache inconsistente, e um novo desenvolvedor não sabe qual abordagem usar para um novo recurso.
Um segundo exemplo é a mistura de estilos arquitetônicos. Em um controlador REST API, você encontra chamadas de serviço SOAP, consultas SQL diretas, acesso ao sistema de arquivos e geração de HTML. Tal aplicação é impossível de testar, estender ou documentar.
Um terceiro exemplo é uma pilha tecnológica onde Python é usado para o backend, Node.js para um microsserviço, C# para um cliente desktop e Java para um aplicativo Android, enquanto toda a lógica de negócios está espalhada entre eles sem uma clara separação de responsabilidades.
// frankenstein — estilos e tecnologias misturados
// callbacks, Promises e async/await combinados
// callbacks
db.query("SELECT * FROM users", function(err, rows) {
if (err) handleError(err);
// Promise dentro de callback
fetch("/api/data").then(function(data) {
// async/await dentro de then
(async () => {
const result = await processData(data);
sendResponse(result);
})();
});
});
// código limpo — estilo async/await unificado
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);
}
Um único banco de dados é usado simultaneamente como SQL relacional (com normalização) e como NoSQL orientado a documentos (com colunas JSON). Algumas consultas vão através de ORM, algumas através de procedimentos armazenados, algumas através de SQL direto do código. O esquema do BD não é documentado, as migrações entram em conflito.
A complexidade de integração é a primeira consequência. Um novo desenvolvedor deve conhecer 5 idiomas, 3 frameworks, 2 estilos arquitetônicos para entender como o sistema funciona. A integração se estende de semanas a meses. De acordo com o LinkedIn (2023), projetos com ecletismo tecnológico perdem novos funcionários 2 vezes mais frequentemente.
A imprevisibilidade do comportamento é a segunda consequência. Uma alteração no microsserviço Python pode quebrar inesperadamente o módulo Java porque eles compartilham um banco de dados sem contratos claros. Depurar tais problemas requer conhecimento simultâneo de todas as tecnologias na pilha.
A segurança é a terceira consequência. Cada tecnologia na pilha requer sua própria configuração de segurança, seus próprios patches, seu próprio monitoramento. Manter a segurança em um nível aceitável para 5-6 tecnologias heterogêneas é praticamente impossível. Uma delas inevitavelmente será vulnerável.
SonarQube pode medir a dívida técnica, mas não pode medir a “dívida arquitetônica” — incompatibilidade de componentes. Essa dívida se manifesta não em avisos de linter, mas na impossibilidade de adicionar um novo recurso sem modificar três módulos diferentes escritos em tecnologias diferentes.
O primeiro e principal passo é nomear um arquiteto ou tech lead responsável pela integridade da pilha tecnológica. Essa pessoa tem poder de veto sobre a introdução de novas tecnologias sem revisão arquitetônica. Não democracia, mas decisão individual responsável sobre tecnologias-chave.
O segundo passo é implementar o processo Architecture Decision Record (ADR). Qualquer decisão arquitetônica significativa (escolha de BD, framework, protocolo) é documentada como um texto curto: contexto, alternativas consideradas, decisão tomada, consequências. ADRs são armazenados no repositório e disponíveis para toda a equipe.
O terceiro passo é estabelecer o princípio de “uma tarefa — uma ferramenta”. Para requisições HTTP — um cliente. Para ORM — uma biblioteca. Para logging — um framework. Exceções são permitidas apenas através de ADR com justificativa. Se o projeto já tem Axios — não adicione fetch, se tem SLF4J — não escreva via System.out.
Experimentos são permitidos, mas em ambiente isolado. Aloque um módulo ou serviço que possa ser reescrito com uma nova tecnologia sem afetar o resto do sistema. Se o experimento for bem-sucedido — padronize-o via ADR. Se não — remova-o sem consequências.
Inventário — o primeiro passo. Crie um mapa completo da pilha tecnológica: quais frameworks, bibliotecas, linguagens, protocolos são usados, em quais módulos e para quais tarefas. Você verá a magnitude do problema: duplicação de ferramentas, tecnologias conflitantes, dependências não utilizadas.
Padronização — o segundo passo. Escolha uma ferramenta para cada tarefa. Por exemplo: apenas Hibernate para ORM, apenas SLF4J + Logback para logging, apenas REST para API. Documente o padrão em ADR. Comece a substituição pelos módulos onde o ecletismo causa mais problemas.
Estratégia Parallel Run — o terceiro passo. As ferramentas antiga e nova funcionam em paralelo até que a nova prove sua confiabilidade. Por exemplo, o cliente HTTP antigo e o novo funcionam simultaneamente, mas o novo só lida com parte das requisições. Após um período de estabilização, o antigo é removido.
// frankenstein — três abordagens HTTP em um projeto
// Módulo A: OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);
// Módulo B: RestTemplate (Spring)
restTemplate.getForObject(url, String.class);
// Módulo C: java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
// abordagem unificada: RestTemplate para síncrono, WebClient para reativo
@Autowired
private RestTemplate restTemplate;
public String callApi(String url) {
return restTemplate.getForObject(url, String.class);
}
Um líder técnico é a principal ferramenta no combate a Frankenstein. Não um gerente, não um arquiteto em uma torre de marfim, mas um desenvolvedor praticante que escreve código, revisa PRs e toma decisões arquitetônicas. Sem essa pessoa, o projeto inevitavelmente desliza para o ecletismo tecnológico.
RFC (Request for Comments) é um processo emprestado das comunidades de código aberto. Antes de introduzir qualquer tecnologia significativa, o autor escreve um RFC: problema, solução proposta, alternativas, plano de implementação. A equipe discute, vota, aceita ou rejeita. RFC cria transparência e evita decisões arquitetônicas “silenciosas”.
O Radar Tecnológico (ThoughtWorks Technology Radar) é uma ferramenta de categorização: Adopt, Trial, Assess, Hold. A equipe revisa regularmente o radar e atualiza os status. Isso ajuda a distinguir o “moderno” do “útil” e evitar a introdução de tecnologias não comprovadas em código crítico.
A qualidade mais importante da arquitetura é a consistência. Mesmo uma ferramenta não tão boa usada em todo o projeto é melhor do que a melhor ferramenta usada apenas em um módulo. A consistência reduz a carga cognitiva, simplifica a integração e torna o código previsível.
Perguntas frequentes
Polyglot persistence é o uso consciente de diferentes bancos de dados para diferentes tarefas (PostgreSQL para transações, Redis para cache, Elasticsearch para busca). Frankenstein é mistura caótica sem estratégia. A diferença está na presença de uma decisão arquitetônica: polyglot é um plano, Frankenstein é sua ausência.
Sim, e é um problema frequente. Quando cada microsserviço usa sua própria linguagem, seu próprio BD, seu próprio protocolo e sua própria abordagem de implantação sem padrões centralizados — você obtém um Frankenstein distribuído. Para microsserviços, padrões comuns são importantes: protocolo unificado (REST/gRPC), formato de log comum, observabilidade centralizada.
Não proíba — oriente. Sugira ao autor escrever um RFC: descreva por que a solução atual não é adequada, quais alternativas foram consideradas, como a migração será feita. Frequentemente, no processo de escrever um RFC, o próprio desenvolvedor percebe que a nova tecnologia não é necessária. Se o RFC for convincente — implemente, mas com um plano e limitações.
Primeiro, inventário, depois padronização. Não tente reescrever tudo de uma vez. Selecione uma camada (por exemplo, clientes HTTP ou logging), escolha uma ferramenta única, escreva um ADR e migre gradualmente. O padrão Strangler Fig — substitua componentes antigos por novos um a um sem parar a aplicação.
Quanto menos, melhor. Idealmente — uma linguagem, um framework, um banco de dados, um método de logging. Realisticamente — 2-3 linguagens (com separação clara), 1-2 bancos de dados, 1-2 frameworks. Cada tecnologia adicional aumenta a carga cognitiva da equipe e o custo de manutenção.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também