[pt-BR] De design de software ruim a uma arquitetura de software ruim
Tags: architecture, oop
Published: 2022-04-28 18:59:59

Um design ruim leva a uma arquitetura ruim. Obviamente, o inverso também é verdade, a falta de uma arquitetura bem pensada muitas vezes leva a pontos de atrito, principalmente quando se fala em integração; some isto a um time grande e com certeza surgirão problemas a nível de código.

De qualquer forma, muito antes de nos aventurarmos com arquitetura de software, atuamos - e batemos muito a cabeça - como desenvolvedores. Se você trabalha em um ambiente que respeita minimamente sua capacidade como indivíduo, você, como desenvolvedor, pensa rotineiramente na estrutura do seu código e implementa da forma que achar melhor. É claro que não estamos falando que não haverá padrões de codificação ou práticas como code reviews e afins, mas você é responsável por pensar; as vezes você tem uma boa ideia e as vezes nem tanto.

Desenvolvedores não são - ou não deveriam ser - robôs. Pensar é uma parte extremamente importante no desenvolvimento de um desenvolvedor e isso provavelmente se aplica a qualquer profissional, na verdade. Acredito que a maneira mais eficiente de desenvolvimento profissional é permitir que o desenvolvedor seja criativo e bata a cabeça; não é fácil pensar em qual a melhor forma de solucionar um problema. Eu sempre busco permitir que o desenvolvedor, principalmente aqueles que não tem tanta experiência, implemente suas ideias para depois discutirmos os motivos por trás daquela ideia e chegarmos juntos a conclusão do por que ela é ou não adequada.

Seja como for, o enfoque aqui não é o processo. Apenas reitero que enquanto nem sonhamos com arquitetura de software, já somos responsáveis pelo design do software. Enquanto ser um bom desenvolvedor não significa que a pessoa é ou será um bom arquiteto, entender como estruturar bem o código de uma aplicação certamente é de grande valia. 

É aí que o problema entra; não importa quantos anos de experiência eu tenha ou quantas vezes eu realize este tipo de atividade, pensar no design do código de uma aplicação é, para mim, sempre uma tarefa difícil. E, para quem discorda, existem duas possibilidades: ou você é simplesmente muito bom nisto ou o seu conceito de um bom design é bem diferente do meu.

Se você está no primeiro grupo, sorte sua! Se você se enquadra no segundo, divergências são normais e isto não me torna mais certo ou mais errado que você - também deixo claro que analiso tudo sobre o prisma de orientação a objetos e não me meto a falar sobre programação funcional, por exemplo.

Há alguns dias atrás, vi uma pequena discussão no linkedin sobre a seguinte ilustração: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ioi6xu7kobvgjd9su7qs.jpeg)

Todos os posts, com exceção de um, concordavam com a ideia apresentada pela imagem. Segundo à auto intitulação de cada participante, diversos níveis de senioridade estavam de acordo, desde júniores até tech leads. É claro que, em efeito prático, geralmente quem se manifesta está de acordo com a ideia e o faz como forma de apoio. De qualquer forma, é irrelevante a porcentagem de pessoas que concorda ou discorda, o ponto chave é que concordar com isso não, não está diretamente ligado à experiência. 

Bom... já eu acho esta ilustração, no mínimo, absurda, e devo dizer que, em certo ponto, é até difícil argumentar do por quê. Traduzindo em miúdos, o que a ilustração diz, pra mim, é: "padrões, oo e abstração são ruins e artefatos desnecessariamente complexos, programe de forma simples".

Só que, para mim, existe uma falha fundamental nesta afirmação. O que a ilustração chama de "código super simples", eu chamo de "código simplista". Sempre busque a simplicidade, mas nunca seja simplista. Simples significa sermos capazes de filtrar a complexidade desnecessária enquanto simplista significa que ignoramos fatores fundamentais do objetivo geral.

Não é necessário muito aprofundamento para verificar que a ilustração é demasiadamente falha e, efetivamente, simplista. Ela ignora todo e qualquer objetivo de uma aplicação, design ou arquitetura para realizar uma afirmação genérica de que quem tem anos de experiência, não usa OO. Admitidamente, OO não é o paradigma correto para toda e qualquer situação, mas isto não tem relação absolutamente nenhuma com a experiência.

Frases como "eu posso precisar disto depois" ou "isto é o que os especialistas fazem" são desqualificações baratas e sem conteúdo, pois são enviesadas na ideia de que abstrações são criadas, comumente, por capricho. 

Fato é que mudanças são comuns no mundo de desenvolvimento de software e as abstrações são ferramentas excelentes para isolar complexidade. Padrões de projeto, então, não vale nem adentrar na discussão: são efetivamente ferramentas que comprovadamente resolvem problemas e não um modismo.

Também me questiono sobre o que o autor considera como OO, tendo em vista que a esmagadora maioria dos serviços web Java, nada tem de orientação a objetos. A percepção que eu tenho, porém, é de que nossos entendimentos sobre o que é um código OO não estejam tão distantes assim.

A utilização correta da orientação a objetos permite que uma aplicação acomode mudança gradual e cresça de forma sustentável. Infelizmente, é mais comum do que gostaríamos casos onde um serviço nasce e, dentro de alguns (ou vários) meses, problemas do tipo acontecem: código bagunçado; complexidade de se realizar uma mudança; tempo desnecessariamente grande para fazer se fazer uma mudança, em tese, simples; etc. O interessante é que, na maior parte dos casos que já vivenciei, as aplicações eram "simples". 

Sempre que vamos implementar uma mudança ou funcionalidade, sempre temos que avaliar a aplicação como um todo e o que a inserção do novo código acarreta. Uma inclusão de funcionalidade nunca é apenas isso. É fácil desenvolver um pedaço de código muito focado em entregar o comportamento, mas a verdade é que uma mudança tem impactos muito mais profundos sobre a estrutura do código. É nessa hora que devemos avaliar se nossas estruturas são adequadas para o software e pode ser que, neste momento, seja necessário a criação de abstrações, mudança em estruturas existentes, etc. Isto é ser simples, é saber qual o nível de complexidade necessário para se atingir um objetivo. Ser simples é difícil, ser simplista é fácil.

Ademais, comumente ignoramos aquilo que chamo de "código de suporte". Uma domínio não existe no vácuo, ele precisa de uma lógica adequada que o sustente. O código de suporte não é análogo ao conceito de "pure fabrication", ainda mais se você considera que um serviço spring é a implementação exemplo de "pure fabrication". Uma comparação mais justa, talvez, seja pensar no padrão chassi de microservices. De qualquer forma, o código estrutural não necessariamente suporta apenas funcionalidades comuns (logging, tracing, etc).

O resultado destes problemas de design são problemas na arquitetura. A dificuldade de criar um código OO é, muitas vezes, responsável pela criação de uma quantidade enorme e desnecessária de microservices. Uma vez que não conseguimos separar a complexidade através de abstrações, separamos a complexidade fisicamente em serviços na busca do baixo acoplamento. Para mim, é um esforço equivocado: isto tende, na verdade, a exacerbar o acoplamento de forma mascarada; faz com que haja uma explosão de serviços cada vez menores e menos relevantes e cria serviços ruins, como "entity traps". 

Há quem seja fã de nanoservices e não descordo que façam sentido para um determinado contexto; entendo que podem ser componentes muito úteis, principalmente quando pensamos numa arquitetura com particionamento técnico. O meu ponto aqui é que um design ruim cria uma explosão de serviços desnecessária.

Implementar mudanças, ou melhor, criar um código OO que suporte mudanças de forma adequada é tão difícil que, certa vez, ouvi alguém argumentar que microservices são feitos para serem jogados fora. Devo admitir que, neste caso, eu sequer consigo racionalizar sobre uma possível razão para uma afirmação desta. Não me entra na cabeça a ideia de um serviço descartável, um serviço que é recriado quando algo sai do plano ou quando uma mudança é suficientemente grande. Não que isso não aconteça, mas certamente não é essa mentalidade que norteia o design de uma aplicação. Novamente, o que vemos é a criação de serviços sem um nível adequado de avaliação e o desprezo do todo.

E qual o resultado dessa arquitetura guiada pelo design ruim? Ai que surgem os famosos monólitos distribuídos. Peças que dependem excessivamente umas das outras, funcionalidade distribuídas sem um fundamento guia e dificuldade de manutenção e operação.