Do caos ao equilíbrio: como transformar um código legado em algo renovado e limpo

Deixe seu código mais limpo e organizado! Descubra estratégias criativas para migrar seu código legado para uma arquitetura limpa e eficiente.

Do caos ao equilíbrio: como transformar um código legado em algo renovado e limpo
Photo by Christophe Hautier / Unsplash

Uma boa arquitetura de software faz a diferença entre um sistema que é um prazer de se trabalhar e um sistema que só de olhar o código já causa dor de cabeça.

Alinhando expectativas


A ideia deste artigo não é te dizer a exata forma de fazer a migração de seu projeto para uma super bela e escalável arquitetura. Infelizmente, já adianto que não existe uma fórmula mágica que permita ter o sistema X rodando em um dia e o sistema Y-plus no ar no dia seguinte.

Quando pensamos em software atualmente, não falamos apenas de linhas de códigos que são compiladas e executadas a fim de obter o resultado esperado por parte do desenvolvedor. Em minha opinião (talvez radical), o software, juntamente com a análise de requisitos, políticas, regras de negócios, arquitetura de software, implementação, testes, implantação e manutenção, é o núcleo de um negócio.

Portanto, como desenvolvedores, temos o dever de construir softwares que, durante o seu ciclo de vida, se mantenham coesos, com o menor custo de mão de obra necessária para manutenção e que, acima de tudo, se paguem, mantendo sempre suas características funcionais e não funcionais aderentes ao negócio.

Pensando nos pontos destacados acima, quais desafios temos quando pensamos em um software legado e, provavelmente, mal planejado?

  • Custo 💰: Softwares legados, na maioria das vezes, são caros de se manter, talvez por serem executados em uma linguagem sem suporte, uma versão muito antiga ou até por forçar a empresa a manter hardwares específicos para a sobrevivência do software.
  • Falta de suporte 👨‍💻: Conforme o software envelhece, torna-se mais difícil encontrar suporte para linguagens em versões antigas. O custo de equipes especializadas aumenta.
  • Segurança 🔓: O software legado pode conter vulnerabilidades que não foram abordadas em releases anteriores, o que torna o software um alvo fácil de hackers.

Em paralelo, temos softwares que são desenvolvidos com base nos princípios de arquitetura e código limpo. Esses softwares, em teoria, são capazes de mitigar e minimizar os problemas e desafios acima listados. Já falamos um pouco sobre código limpo em uma série de artigos no Vórtx Lab:

Clean Architecture, front-end e o caos
Quando falamos sobre padrões de arquitetura no front-end, existem diferentes opiniões sobre quais padrões aplicar, quando aplicar e se padrões já reconhecidos no mundo de back-end, deveriam fazer parte do contexto do front. Um dos padrões que costuma ser discutido é o famoso Clean Architecture. Muit…
A clareza do código limpo
Como escrever códigos limpos e eficientes?Escrever código limpo é semelhante a andar de bicicleta: podemos tentar aprenderapenas estudando toda a mecânica para se andar de bicicleta, como gravidade,atrito, momento angular, centro de massa e assim por diante. Dado toda teoria eestudo, vamos concl…
Aprofundando em “Code Smells” e refatoração de código
Tem algo que não “cheira bem” no seu código? Humm, isso pode ser um sintoma de um problema mais profundo, de um possível bug, complexidade ou vulnerabilidades na sua aplicação. Ela pode não estar rodando em perfeitas condições… “Code smells” são sinais indicativos de que um pedaço de código pode

A ideia de arquitetura limpa nos traz um conjunto de princípios e práticas que nos auxiliam e nos guiam rumo ao desenvolvimento de software de maneira escalável, flexível, testável e fáceis de manter.

O artigo a seguir foi baseado em valiosos insights retirados do livro Arquitetura Limpa (2017).

Para migrar um código legado para uma estrutura de arquitetura limpa, é importante separarmos estratégias em etapas.

Etapa 1: Análise do projeto legado.

Essa etapa é muito importante, a simples tomada de decisão baseada em uma errada interpretação de como o software funciona pode gerar consequências negativas, e pior, tornar o software ainda mais rígido e complexo do que realmente é.

Portanto, deve ser realizada uma análise minuciosa do projeto em que será feito a migração/atualização, procure o máximo de documentação possível do projeto, tente entender a arquitetura e como foi projetada, como as partes do software se relacionam e se comunicam e qual é o objetivo do sistema em questão.

Etapa 2: Estabelecer um plano para a migração do projeto

O segundo passo consiste em traçar um plano para a reestruturação do projeto, o plano deve estar alinhado com os stakeholders e a equipe de desenvolvedores (caso exista) e deve ser apresentado cada etapa da reestruturação, integrações, testes e depuração, precursores da conclusão do objetivo.

Vou apresentar um exemplo prático de migração.

Vamos supor que temos um sistema de pagamentos. No início do desenvolvimento, após a análise do código legado e camadas definidas, separe as principais regras de negócio e escolha uma para começar a desenvolver o novo software, faça essa regra funcionar e suba o novo software. Essa funcionalidade será a rota /pagamento/efetua-pix, sempre que uma requisição for enviada para o software antigo, usaremos um proxy reverse para redirecionar para o novo sistema. A medida em que funcionalidades forem sendo desenvolvidas, faça os inputs de proxy reverse no sistema. Aos poucos, o seu software legado irá diminuindo de tamanho até que o sistema esteja completamente migrado.

Etapa 3: Alterações incrementais

É importante que haja um ambiente de homologação, no qual as alterações incrementais e evolutivas possam ser testadas antes de serem implementadas em produção. Muitas alterações de uma vez, provavelmente irão gerar interrupções no sistema, gerando problemas nas métricas de evolução do desenvolvimento.

Mantenha uma linha contínua de entrega (continuous delivery), assim os usuários poderão acessar as novas funcionalidades ou melhor performance de uma funcionalidade já existente de forma mais rápida. Mas lembre-se de sempre testar e validar adequadamente cada implementação no software.

Além disso, sempre dê visibilidade dos resultados evolutivos de cada etapa alcançada, isso irá trazer visibilidade aos stakeholders e desenvolvedores sobre a evolução e mudanças da nova estrutura do software.

Etapa 4: Teste!

Não trate testes como detalhe, invista tempo em testes durante o desenvolvimento, e como sugestão particular, invista tempo em testes antes de começar desenvolver seu software, não escreva nenhum código de produção antes de escrever um teste que falhou pela falta deste mesmo código (o assunto de TDD fica para um próximo artigo).

"O código de teste é tão importante quando o código de produção. Não é um cidadão de segunda classe. Exige pensamento, design e cuidado. Precisa ser mantido tão clean quanto o código de produção" - Robert Martin, autor dos livros Clean Code e Clean Architeture
Entenda um pouco mais sobre a importância de testes e aprenda como os deixar o mais clean possível neste artigo: https://lab.vortx.com.br/test-code-matters-deixe-os-clean/

Um dos princípios da arquitetura limpa é você ser capaz de desenvolver softwares de boa qualidade e testáveis. Os testes permitem cobertura de cenários em casos de usos, entidades, repositórios, entre diversas outras partes de seu código. Com eles você é capaz de setar e manter o mínimo de qualidade da sua aplicação, além de dar mais agilidade na evolução do software, uma vez que quando uma manutenção ou release for desenvolvida, os testes podem rapidamente apontar algo que não está de acordo com os requisitos mínimos de qualidade.

Etapa 5: Foco nas políticas e regras de negócio.

O software pode ser dividido em duas partes, políticas e detalhes. O elemento político engloba todas as regras e procedimentos de negócios. A política é onde está o verdadeiro valor do sistema, é onde está o dinheiro.

Os detalhes são itens necessários para que os seres humanos, outros sistemas, e programadores se comuniquem com a política, mas que não devem ter influência alguma com o core da sua aplicação. Por ora, esqueça das demais camadas da sua aplicação, que não seja o domínio. Não importa onde o sistema será rodado, se será uma aplicação web, se usará REST, GraphQL. Acredite em mim, eu sei o que é pensar em um software e a primeira coisa que vem a sua cabeça são as tabelas, relacionamentos, FK, PK, se será SQL Server, PostgreSQL, mongoDB, MySql... Enfim, todos os possíveis componentes derivados das tecnologias citadas anteriormente são detalhes, ao início do desenvolvimento os detalhes devem ser ao máximo postergados.

Portanto, comece pela regra de negócio e a trate como o item primordial da aplicação fazendo com que os detalhes não influenciem e se tornem irrelevantes.

Etapa 6:  Olhe para os orquestradores da sua regra de negócio, Use Cases!

Existem regras cruciais de negócio, que são o nível mais alto de uma aplicação e muitas vezes chamadas de entidades. Existem também as regras de negócios especificas da aplicação, que definem a maneira como e quando as regras cruciais serão invocadas. Use Cases assim geram lucro para a empresa, mas só fazem sentido em um ambiente de sistema automatizado.

Apesar de ser o orquestrador e receber a entrada do usuário e definir a saída que será gerada ao usuário, os Use Cases devem ser indiferentes a camadas externas. Os dados devem chegar na maneira correta para ser manipulada pelo componente e gerará uma resposta que, se necessário, será tratada de alguma forma.

Um exemplo:

Seguindo a linha de um software de pagamentos. Imagine que temos as entidades cartãoDeCredito e Fatura, além disso, temos uma rota
/pagamento/cartao-de-credito. Claramente, essa rota manipulará as entidades acima e todas as suas validações. Para isso, não podemos simplesmente instanciá-las em nossa camada de apresentação, devemos ter um orquestrador que irá manipular toda a transação, ou seja, receber os dados -> validar as regras da entidade -> salvar a transação -> atualizar os dados da fatura. Esse orquestrador será o use case EfetuaPagamentoCartaoDeCredito

Mas lembre-se: componentes de nível superior não dependem de componentes de nível inferior. UseCases devem orquestrar entidades, mas entidades não devem fazer ideia de quem está implementando e como está implementando.

Veja bem, se até aqui você não faz ideia do que seja alguns conceitos apresentados, acredito que aqui você irá se encontrar. E de quebra ainda aprenderá um pouco sobre o HerbsJs, um conjunto de bibliotecas como foco na camada de domínio de sua aplicação.

Etapa 7:  Desacople e aponte para dentro.

Desacoplamento de componentes de software poderia facilmente ser um tema para um artigo apartado, como não é o caso, serei breve.

Desacople seus componentes de maneira que componentes de alto nível, não deem a mínima para componentes de baixo nível, um exemplo:

Se temos um usecase em um projeto legado que atualmente faz as chamadas diretamente ao banco, o usecase depende do banco, quando na verdade deveríamos ter uma interface que dependesse do banco e do usecase.

https://imgur.com/gaLfNQ4

Repare na seta apontando para o núcleo da aplicação, ela não necessariamente está representando o fluxo de dados, mas sim as dependências. O desacoplamento nos permite isolar camadas de software de maneira que cada camada seja testável, mutável e manutenível sem quebrar outras partes do software.

Uma breve dica que ajuda o desacoplamento do domínio em particular é a postergação da preocupação com camadas externas, tornando o código mais flexível a entradas e saídas de dados de acordo com as funcionalidades requeridas.

Etapa 8: Segregue.

Mas ok, todo esse papo de camadas e de desacoplamento de camadas... Como eu defino os limites de cada camada? Por incrível que pareça, quem terá essa resposta será você, de acordo com a análise feita do software. Faça um pente fino ao olhar o software, crie em sua cabeça, pegue um papel e anote... “Essa minha classe está inteiramente ou quase inteiramente ligada a rotas da API”, provavelmente isso deverá estar segregado em uma camada de apresentação. “Com certeza essa minha classe está definindo a entidade Agência do negócio”, ótimo, você acabou de encontrar algo para compor a sua camada de domínio.

O que eu posso dizer, que firmemente Robert C. Martin prega em seu livro é:

Um componente criado para ser fácil de mudar não deve conter outros componentes que dependam dele, isso torna o componente difícil de mudar. Pense em responsabilidade única, políticas ou detalhes que mudam pela mesma razão deve estar contida no mesmo nível e no mesmo componente, já as que mudam por razões diferentes devem estar em níveis diferentes e em componentes distintos.

Portanto caro leitor, quais resultados podemos obter e quais os problemas que a migração do software legado para uma arquitetura limpa resolve?

  • 1- Separação de preocupações: A prática de separar preocupações é um importante tema dentro da arquitetura limpa. Quando separamos responsabilidades e delimitamos limites entre componentes e camadas, tornamos o software mais fácil de entender e trazemos a possibilidade de alterações independentes em componentes, onde não afetaram o restante do sistema
  • 2- Desacoplamento: Fazendo um link com a solução apresentada acima, o desacoplamento consiste em reorganizarmos e reduzirmos ao máximo as dependências entre componentes. Uma das premissas da arquitetura limpa é manter nossa camada de negócio longe de conhecer e ser afetada por fatores externos, como bancos de dados, frameworks, web, etc.
  • 3- Testabilidade: Com a separação de componentes e o desacoplamento entre eles, a arquitetura limpa nos permite uma melhor construção de testes automatizados e testes unitários. Testes são imprescindíveis no desenvolvimento de software.
  • 4- Escalabilidade: Talvez um dos pontos mais importantes, pois reúne tudo o que a arquitetura limpa e seus princípios nos fornecem. Uma vez que o software desenvolvido é testável, desacoplado e coeso, minimizamos o recurso humano necessário para desenvolvimento e manutenção do software e consequentemente se torna mais fácil (e principalmente mais barato) a evolução do software sem reduzir o nível de produtividade da equipe ao longo do tempo.
  • 5- Performance: A performance em muitas aplicações é o principal indicador de qualidade do software. Migrando um projeto legado para arquitetura limpa, temos a melhor gestão de performance, descomplicando evoluções que agregam nesse quesito.
  • 6- Isolamento de problemas: Como mencionado algumas vezes anteriormente, a arquitetura limpa permite que segreguemos cada camada da aplicação, de maneira que componentes de baixo nível dependam de componentes de alto nível (NUNCA, nunca ao contrário), isso nos traz a flexibilidade de isolar problemas e dar manutenção em pontos específicos da aplicação sem que o restante do software desabe como peças de dominó.

A arquitetura de software é o conjunto de ideias, discussões e decisões que nos permitem construir software com um longo ciclo de vida, que minimize ao máximo o recurso humano para manutenção e evolução, de maneira escalável, testável e rentável.

Neste artigo, além de insights de como migrar de forma segura seu software legado para uma estrutura de arquitetura limpa podemos notar pontos importantes de uma aplicação bem estruturada e as principais vantagens de um software construído com a preocupação de uma boa arquitetura.

MARTIN, Robert C. Arquitetura Limpa: O Guia do Artesão para Estrutura e Design de Software. Porto Alegre: Bookman, 2017.