Engenharia reversa e recuperação de arquitetura

TL;DR

Ler o código (nota 06) e escavar o histórico (nota 07) te dá fragmentos — trechos que você entende um a um. Mas o sistema não cabe na sua cabeça: você precisa de um mapa formal, e ninguém tem um que seja verdadeiro. Engenharia reversa é o ato de reconstruir esse mapa a partir do artefato: extrair automaticamente o grafo de dependências e a estrutura real (análise estática), e confrontá-la com a arquitetura que dizem que o sistema tem. A ferramenta-chave dessa etapa é o reflexion model (Murphy & Notkin): você desenha o mapa que esperava, a ferramenta extrai o mapa que existe, e a diferença entre os dois — a erosão arquitetural (Perry & Wolf) — é exatamente onde mora o risco. Você não mapeia tudo: mapeia guiado por hipótese, top-down encontra bottom-up. É a fase em que a teoria perdida deixa de ser intuição e vira um artefato que você pode mostrar ao cliente.

Você já passou três semanas no sistema do cliente. Leu os módulos que mais assustavam, rodou o pickaxe nos trechos estranhos, montou um modelo mental decente de pedaços do sistema. Aí o CTO te convida para uma reunião de due diligence e faz a pergunta que você temia: “Me desenha a arquitetura disso aqui.” Você congela. Não porque não entende as partes — entende. Mas porque o sistema tem 340 mil linhas, 1.200 classes, e o que você tem na cabeça são ilhas desconexas. Ninguém consegue segurar 340 mil linhas na memória de trabalho (a nota 06 explicou por quê: sete itens, lembra?). Você precisa de um mapa — e o único diagrama que existe, aquele PowerPoint de 2019 pendurado na parede do time, você já desconfia que é ficção.

Esta nota abre a fase Adepto do galho. A fase Iniciado te deu orientação: entender o sistema peça por peça. Agora o salto é de outra natureza — elevar fragmentos a estrutura. Deixar de dizer “esse arquivo faz X” e passar a dizer “o sistema tem estas camadas, com estas dependências, e aqui a arquitetura pretendida foi violada”. Isso é engenharia reversa: reconstruir o mapa que ninguém documentou, e — mais importante — descobrir onde o mapa oficial mente.

O problema: você tem fragmentos, precisa de um mapa

Reengenharia começa com um paradoxo cruel. Para mudar o sistema com segurança, você precisa entendê-lo como um todo. Mas entender um todo grande é justamente o que a mente humana não faz — e é por isso que o sistema virou legado em primeiro lugar (a teoria se perdeu porque ninguém mais a segurava inteira).

A leitura de código resolve o local: você mergulha num método e o entende. A engenharia reversa resolve o global: ela produz uma representação do sistema em escala que cabe numa folha, num grafo, numa matriz — algo que sua memória de trabalho consegue manipular. A diferença é a mesma entre conhecer cada rua de um bairro andando por elas e ter o mapa da cidade. As duas coisas são necessárias; a segunda não emerge sozinha da primeira.

O problema em uma frase: ler código te dá as ruas; engenharia reversa te dá o mapa — e sem o mapa você não consegue nem descrever o sistema ao cliente, nem decidir com segurança onde intervir.

Os dois braços da engenharia reversa

Reconstruir o mapa tem dois movimentos que se completam. Um é automático e ascendente (bottom-up): deixar as ferramentas extraírem a estrutura que está de fato no código. O outro é humano e descendente (top-down): trazer a hipótese do que o sistema deveria ser e confrontá-la com o extraído. O ouro está no encontro dos dois.

Braço 1 — Extração: o que o código realmente diz

A máquina lê o código muito mais rápido e honestamente que você. Análise estática percorre o código-fonte (sem executá-lo) e responde perguntas estruturais: quem chama quem, quem importa quem, quais classes dependem de quais. O resultado é um grafo de dependências — nós são módulos/pacotes/classes, arestas são “usa/importa/chama”. Esse grafo é o esqueleto do sistema, e ele revela padrões que a leitura linha a linha nunca mostraria:

  • Módulos-deus (god modules): um nó com dezenas de arestas entrando e saindo. É o coração do acoplamento — mexer nele reverbera por todo o sistema.
  • Ciclos de dependência: A depende de B que depende de A. Ciclos são o cimento que impede decomposição: você não consegue isolar, testar ou extrair nada dentro de um ciclo sem arrastar o resto.
  • Camadas fantasma: o grafo mostra que a “camada de domínio” na verdade importa a camada de infraestrutura, invertendo a dependência que a arquitetura pretendia.

Braço 2 — Validação: onde a arquitetura pretendida foi traída

O grafo cru é grande e ruidoso demais para ler direto. É aqui que entra a ideia mais poderosa da recuperação de arquitetura: o reflexion model, de Gail Murphy e David Notkin (1995). O procedimento é enganosamente simples e você o executa em horas, não semanas:

  1. Desenhe o mapa que você espera. Um diagrama de caixas-e-setas de alto nível: “UI → Serviços → Repositórios → Banco”, umas 5 a 15 caixas. É a sua hipótese da arquitetura — a teoria que você acredita que o sistema tem.
  2. Mapeie as caixas para o código. Diga à ferramenta quais arquivos/pacotes pertencem a cada caixa (ex.: com.cliente.web.* → caixa “UI”).
  3. Deixe a ferramenta computar o reflexion model. Ela extrai o grafo real e o sobrepõe à sua hipótese, classificando cada relação em três tipos:

graph LR
    H["Seu mapa esperado<br/>(hipótese / teoria)"] --> C{"Reflexion<br/>model"}
    S["Grafo extraído<br/>do código real"] --> C
    C --> CV["CONVERGÊNCIA<br/>esperei e existe ✓"]
    C --> DV["DIVERGÊNCIA<br/>NÃO esperei, mas existe ⚠"]
    C --> AB["AUSÊNCIA<br/>esperei, mas NÃO existe ⚠"]
    DV --> R["Onde a arquitetura<br/>foi erodida"]
    AB --> R
    style CV fill:#7ED321,color:#000
    style DV fill:#F5A623,color:#000
    style AB fill:#F5A623,color:#000
    style R fill:#D0021B,color:#fff
  • Convergências (verde): você esperava a dependência e ela existe. Sua teoria estava certa ali.
  • Divergências (âmbar): existe uma dependência que você não esperava — a UI falando direto com o banco, o módulo de relatórios importando o de pagamento. Cada divergência é uma decisão perdida, um ponto onde a arquitetura real traiu a pretendida.
  • Ausências (âmbar): você esperava uma dependência que não existe — sinal de que sua hipótese estava errada, ou de que uma camada morreu sem que ninguém notasse.

Murphy e Notkin validaram isso reconstruindo o subsistema de memória virtual do NetBSD — 250 mil linhas de C — em poucas horas, um mapa global que nenhum engenheiro tinha inteiro na cabeça. O poder do reflexion model é que ele não te pede para ler tudo: ele te dá uma visão global aproximada e aponta exatamente os pontos onde vale a pena mergulhar.

Erosão e desvio: por que o diagrama na parede sempre mente

Aquele PowerPoint de 2019 na parede não é uma mentira maldosa — é o resultado inevitável de um processo que Dewayne Perry e Alexander Wolf batizaram em 1992. Toda arquitetura sofre dois males ao longo do tempo:

  • Desvio arquitetural (architectural drift): decisões vão sendo tomadas sem malícia, mas sem aderência à visão original, simplesmente porque ninguém a conhece ou a impõe. A estrutura vai deslizando.
  • Erosão arquitetural (architectural erosion): violações diretas da arquitetura pretendida — aquele atalho da UI ao banco — que se acumulam e enfraquecem a estrutura, como infiltrações numa viga.

O diagrama oficial congela a intenção de um momento passado; o código continua se movendo. A distância entre os dois só cresce. Por isso o consultor experiente nunca confia no diagrama que lhe mostram — ele o trata como uma hipótese a ser testada (exatamente o input do reflexion model), não como um fato. O reflexion model é, no fundo, um medidor de erosão: quanto mais divergências, mais o sistema se afastou da própria teoria — e mais fundo a teoria se perdeu.

O procedimento de mapeamento: top-down encontra bottom-up

O livro Object-Oriented Reengineering Patterns (Demeyer, Ducasse & Nierstrasz) organiza a engenharia reversa como uma escalada do First Contact (nota 05) até o entendimento detalhado dos subsistemas críticos. A lição central: você não mapeia o sistema inteiro — isso é impossível e inútil. Você mapeia guiado por objetivo, num vaivém entre dois sentidos:


graph TD
    TD["TOP-DOWN<br/>hipótese de arquitetura<br/>(o que eu espero)"] --> M["Ponto de encontro:<br/>o reflexion model"]
    BU["BOTTOM-UP<br/>grafo extraído + métricas<br/>(o que existe)"] --> M
    M --> D["Divergências guiam<br/>onde ler a fundo"]
    D --> RE["Refina a hipótese"]
    RE -.itera.-> TD
    style M fill:#4A90D9,color:#fff
    style D fill:#F5A623,color:#000

Você começa com uma hipótese grosseira (top-down), extrai a estrutura real (bottom-up), e usa as divergências para decidir onde investir sua leitura cara de código. Cada mergulho refina a hipótese, que refina o próximo mapa. É iterativo e barato: em vez de ler 340 mil linhas, você lê as 3 mil que o mapa apontou como o coração do risco.

Ferramentas por stack

Você não precisa das ferramentas de pesquisa dos anos 90 — hoje há opções maduras e gratuitas em quase todo ecossistema. O que importa é ter uma que extraia o grafo e, de preferência, uma que valide contra uma arquitetura declarada.

StackExtrair grafo / métricasValidar arquitetura (reflexion-like)
Java/JVMjdeps (vem no JDK), Structure101, SonargraphArchUnit (regras de camada como teste), Sonargraph
.NETNDepend, Visual Studio dep. diagramNDepend (regras CQLinq), ArchUnitNET
JavaScript/TSmadge, dependency-cruiserdependency-cruiser (regras forbidden), eslint-plugin-boundaries
Pythonpydeps, import-linter grafoimport-linter (contratos de camada), tach
Poliglota / geralCodeScene, Understand, Structure101, cloc (tamanho)Sonargraph, Structure101

A virada moderna: arquitetura como teste automatizado

Ferramentas como ArchUnit (Java), import-linter (Python) e dependency-cruiser (JS) fazem algo que Murphy e Notkin só sonhavam: transformam o reflexion model num teste executável. Você declara “a camada de domínio não pode depender da de infraestrutura” como código, e o CI quebra a build na próxima violação. Isso não só mede a erosão — estanca o sangramento. É a ponte da engenharia reversa (esta nota) para a rede de segurança que vem depois: uma vez recuperada, a arquitetura vira um invariante protegido, não mais um PowerPoint que ninguém lê.

Assista: Unit Test Your Java Architecture With ArchUnit

Canal: JCON (Roland Weisleder) | Duração: ~43min | Idioma: EN

A nota já explica que o ArchUnit transforma o reflexion model num teste executável; este talk mostra como isso funciona no dia a dia, ao vivo, com regras de nomeação, camadas e ciclos. O ganho maior para quem trabalha com legado é o mecanismo de freezing: ao introduzir o ArchUnit num sistema com centenas de violações acumuladas, você “congela” o estado atual num arquivo de exceções conhecidas — os testes ficam verdes de novo, mas qualquer violação nova quebra o build. É o equivalente ao strangler fig aplicado a dívida arquitetural: você não para tudo para corrigir 500 violações de uma vez, mas também não deixa a erosão continuar sem ser notada. Trecho de destaque [34:01]: “The third thing is the freezing of ArchUnit… we acknowledge that these violations exist, but we can’t fix them right away — and we have a clean state and known state of violations. But if we would try to add a new violation, the test will fail.”

🎬 Assistir no YouTube

Design Structure Matrix: o grafo que vira matriz

Quando o grafo fica grande demais para os olhos, a Design Structure Matrix (DSM) o comprime numa matriz quadrada N×N: módulos nas linhas e colunas, uma marca na célula (i,j) quando i depende de j. A leitura é poderosa:

  • Marcas abaixo da diagonal principal = dependências “para trás” = ciclos. Uma matriz bem arquitetada é quase triangular (tudo acima da diagonal), com camadas empilhadas limpamente.
  • Blocos densos ao longo da diagonal revelam módulos naturalmente coesos — candidatos a subsistema.
  • Uma coluna densa = um módulo do qual todo mundo depende (utilitário ou god module).

A DSM é o instrumento que torna o acoplamento visível em escala — e prepara o terreno para decidir onde cortar quando chegar a hora de quebrar dependências (nota 12).

Casos práticos

Cenário 1: due diligence — o “núcleo cíclico” que matou a estimativa

Um fundo te contrata para avaliar o risco técnico de uma aquisição: 12 semanas para dizer se o sistema do alvo é modularizável ou uma bola de lama. Em vez de ler por meses, você roda jdeps no primeiro dia e gera a DSM. O padrão salta aos olhos: 40% das classes estão num único componente fortemente conexo — um emaranhado onde tudo depende de tudo, sem uma ordem topológica possível. Você mostra a matriz ao fundo: “vejam esse bloco abaixo da diagonal; não dá para extrair nenhum serviço daqui sem arrastar o resto”. Aquilo muda a negociação — o custo de modernização que o vendedor estimava em 3 meses é, na verdade, um projeto de reescrita de núcleo. O grafo disse em um dia o que a leitura não diria em três meses, e o disse numa linguagem que um não-técnico entende: a forma da matriz.

Cenário 2: resgate — a divergência que explicava os incidentes

Um cliente sofre incidentes recorrentes: toda mudança na tela de relatórios derruba o processamento de pagamentos, dois módulos que “não têm nada a ver”. O diagrama na parede mostra Relatórios e Pagamentos como caixas separadas, sem seta entre elas. Você não confia: desenha esse mapa como hipótese, mapeia os pacotes e roda o reflexion model. Aparece uma divergência gritante — Relatórios importa diretamente uma classe interna de Pagamentos para reaproveitar um cálculo de taxa. A seta que “não deveria existir” existe, e é exatamente o canal por onde o acoplamento propaga a quebra. O diagrama oficial mentia por omissão; o reflexion model expôs a erosão em minutos. Agora você tem alvo: quebrar aquela dependência específica (nota 12) resolve a classe inteira de incidentes.

Armadilhas comuns

Confiar no diagrama que te entregam

O que acontece: você recebe o diagrama de arquitetura do time, o toma como verdade, e planeja a intervenção em cima dele — para descobrir em produção que a realidade é outra. Por quê: todo diagrama sofre desvio e erosão (Perry & Wolf) desde o dia em que foi desenhado; ele registra a intenção de um momento, não o código de hoje. Como evitar: trate o diagrama como hipótese, nunca como fato. É o input do reflexion model — o mapa esperado que você vai confrontar com o extraído, não copiar.

Tentar mapear o sistema inteiro (ferver o oceano)

O que acontece: você decide gerar o grafo completo das 1.200 classes, produz um “prato de espaguete” ilegível com milhares de setas, e não consegue tirar conclusão nenhuma. Por quê: o grafo cru total tem baixíssima razão sinal/ruído; a estrutura relevante se perde no volume. Engenharia reversa não é catalogar tudo, é responder uma pergunta. Como evitar: parta de um objetivo (a divergência que explica um incidente, o subsistema que você vai mudar) e mapeie guiado por hipótese. Deixe as divergências dirigirem sua atenção; suba o nível de abstração (pacotes, não classes) até o mapa caber numa folha.

Tomar o mapa estático como a verdade completa

O que acontece: você conclui que dois módulos são independentes porque não há dependência estática entre eles — e ignora que eles se acoplam por reflexão, injeção de dependência, eventos ou config em runtime. Por quê: análise estática enxerga o que está escrito explicitamente; acoplamentos dinâmicos (reflection, DI, mensageria, chamadas via string) são invisíveis a ela. Como evitar: cruze o mapa estático com outras fontes — o acoplamento temporal do histórico (nota 09: arquivos que mudam juntos mesmo sem dependência estática) e, se preciso, tracing dinâmico em runtime. O mapa estático é o primeiro esboço, não a palavra final.

Como explicar em inglês

Quando te perguntarem, em entrevista, como você entende a arquitetura de um sistema grande que ninguém documentou:

“Reading code gives you fragments; it doesn’t give you the whole. So I reverse-engineer the map. I run static analysis to extract the real dependency graph — that alone exposes god modules and dependency cycles you’d never see reading line by line. But the most useful technique is the reflexion model, from Murphy and Notkin: I draw the architecture I expect, map the boxes to the actual packages, and let a tool compute where my model converges with the code and where it diverges. The divergences are the gold — that’s the architectural erosion, the shortcuts that betrayed the intended design. I never trust the diagram on the wall; I treat it as a hypothesis to test, because every architecture drifts from its own documentation. And with tools like ArchUnit, I can turn the recovered architecture into an automated test so the erosion stops there.”

PTEN
engenharia reversareverse engineering
recuperação de arquiteturaarchitecture recovery / reconstruction
grafo de dependênciasdependency graph
análise estáticastatic analysis
reflexion modelreflexion model
convergência / divergência / ausênciaconvergence / divergence / absence
erosão / desvio arquiteturalarchitectural erosion / drift
módulo-deusgod module
ciclo de dependênciadependency cycle
ferver o oceanoto boil the ocean
matriz de estrutura de dependênciasDesign Structure Matrix (DSM)

O que vem a seguir

Você saiu de fragmentos para um mapa formal: sabe as camadas reais, onde a arquitetura foi erodida, e onde o acoplamento mora. Mas o mapa estático te diz a forma do risco, não a sua intensidade nem sua evolução. Duas classes podem estar no mesmo emaranhado, mas uma muda toda semana e a outra não é tocada há três anos — o risco real não é o mesmo. Para medir isso, você cruza a estrutura de agora com o tempo do histórico (nota 07): é a forense quantitativa da próxima nota.

Fontes

  • Gail Murphy & David NotkinSoftware Reflexion Models: Bridging the Gap between Source and High-Level Models (FSE, 1995) — o método canônico de confrontar a arquitetura esperada com a extraída; convergências, divergências e ausências; o caso NetBSD em poucas horas.
  • Serge Demeyer, Stéphane Ducasse & Oscar NierstraszObject-Oriented Reengineering Patterns (livre) — o catálogo de padrões da engenharia reversa: do First Contact ao mapeamento de subsistemas críticos, guiado por hipótese.
  • Dewayne Perry & Alexander WolfFoundations for the Study of Software Architecture (1992) — a origem dos conceitos de erosão e desvio arquitetural: por que o diagrama sempre se afasta do código.
  • Michael FeathersWorking Effectively with Legacy Code (2004) — a recuperação de estrutura como pré-condição para intervir com segurança; a ponte para seams.
  • Adam TornhillSoftware Design X-Rays (2018) — a leitura de dependências e acoplamento em escala, que a nota 09 aprofunda no eixo do tempo.

Veja também