S.O.L.I.D Princípios da boa engenharia de software
Os princípios S.O.L.I.D. surgiram como uma resposta evolutiva à degradação estrutural do software (software rot). Essa degradação — que faz com que sistemas utilizados diariamente por milhares de pessoas possam manifestar falhas não intencionais — ocorre devido à má aplicação da engenharia de software no seu desenvolvimento. Tais falhas podem levar a desastres catastróficos que, por sua vez, levam a sociedade a exigir um monitoramento constante dos softwares que operam no nosso quotidiano.
Quando o termo 'apodrecimento de software' (software rot) é utilizado, geralmente nos referimos à má aplicação de padrões de design e engenharia, o que leva à criação não intencional de alto acoplamento, confusão e negligência. Esse cenário cria um efeito cascata: as más práticas acabam sendo replicadas pelos desenvolvedores envolvidos no projeto, formando o que conhecemos como uma grande 'bola de lama' (Big Ball of Mud). O resultado final é um custo de manutenção tão elevado que o software perde sua função de ativo estratégico para o negócio, tornando-se apenas uma fonte constante de gastos.
Além disso, esse cenário traz problemas relacionados à arquitetura do software que impactam diretamente o dia a dia e o comportamento da equipe:
- Rigidez: É a tendência de um sistema resistir a mudanças devido à existência de um alto acoplamento entre seus módulos. Nesse cenário, qualquer mudança de negócio ou correção dentro de um único módulo desencadeia um efeito em cascata, exigindo alterações em diversos outros componentes dependentes.
- Fragilidade: É a tendência de um sistema apresentar falhas de maneiras inesperadas e inexplicáveis após uma simples alteração. Nesse cenário, modificações pontuais resultam na quebra ou no mau funcionamento de outros módulos do sistema que, conceitualmente, não aparentavam possuir qualquer relação ou dependência com a área que foi alterada.
- Imobilidade: É a tendência de um sistema ser incapaz de permitir a reusabilidade de suas partes. Nesse cenário, os módulos existentes estão tão acoplados uns aos outros que extraí-los da solução atual para reaproveitá-los em outro contexto se torna inviável, dado o peso e a complexidade das dependências que eles arrastam consigo.
O maior causador desses problemas é a construção de um software cuja estrutura possui um emaranhado entre os módulos existentes, ou seja, um alto acoplamento. A melhor maneira de contornar esse problema é por meio da aplicação rigorosa de princípios que guiam o bom design de software, os princípios S.O.L.I.D.
Estes princípios nos ajudam a construir firewalls. Firewalls que impedem a propagação de mudanças, que bloqueiam a interdependência entre os módulos e impedem que o sistema se torne inter-relacionado. O principal mecanismo ou princípio que utilizamos por trás disso é o princípio da inversão de dependência.
A execução de um software sempre possui um fluxo lógico a ser seguido; geralmente, este fluxo possui duas principais dependências: as dependências de código, aquelas cuja instrução de importação é necessária a determinado código, e as dependências de tempo de execução, aquelas que, para que o fluxo seja feito, dependem da execução de uma ou mais códigos não relacionados de forma direta.
Antes da adoção do design orientado a objetos, as arquiteturas de software baseavam-se predominantemente no design procedural. Neste estilo arquitetural, as dependências do fluxo de execução e as dependências de código caminham na mesma direção, ou seja, são exatamente as mesmas. Para que uma função de alto nível pudesse ser executada de forma correta, ela dependia diretamente da importação do código de níveis mais baixos. Alterar esses códigos base impactava direta ou indiretamente a função como um todo, levando à famosa Big Ball of Mud e aos problemas de arquitetura descritos anteriormente.
A partir dos anos 1970, a maneira como desenvolvemos software mudou. Com o surgimento do design orientado a objetos, as dependências de código e as do fluxo de execução não precisam mais caminhar obrigatoriamente na mesma direção (de forma paralela). Elas podem apontar para direções opostas. Isso se tornou possível graças à capacidade de representar abstrações dentro dos nossos sistemas. Podemos, por exemplo, ter um módulo contendo nossas regras de negócio (alto nível) que precisa salvar informações em um banco de dados (baixo nível). Em vez de importar o módulo do banco diretamente — o que engessaria o sistema —, a nossa regra de negócio interage apenas com uma abstração, como uma Interface. O módulo do banco de dados é quem precisa importar e implementar essa Interface.
Assim, enquanto o fluxo de execução continua fluindo de cima para baixo (a regra de negócio chama a ação de salvar no banco quando o sistema roda), a dependência de código aponta na direção oposta (o módulo do banco depende da interface que pertence à regra de negócio). É essa inversão que ergue o "firewall" arquitetural, garantindo que alterações na infraestrutura ou no banco de dados não quebrem o núcleo do nosso sistema.
Em suma, a inversão da dependência de código em relação ao fluxo de execução é a ferramenta mecânica que nos permite erguer esses firewalls e proteger a nossa arquitetura. No entanto, o verdadeiro desafio do desenvolvedor no dia a dia é saber responder a perguntas mais táticas: Onde exatamente devo colocar esses firewalls? Como devo separar os meus módulos para que a abstração faça sentido? Quando uma classe está acumulando responsabilidades demais?
É exatamente para responder a essas perguntas e guiar a correta criação dessas fronteiras que os princípios S.O.L.I.D. foram consolidados.



