Princípios SOLID: as cinco regras de ouro da OOP
Os princípios SOLID são cinco práticas e diretrizes para um código limpo, fácil de manter e flexível na programação baseada em objetos. A aplicação e o cumprimento desses princípios facilitam a compreensão do design de software durante longos períodos de desenvolvimento. Dessa forma, não só é possível escrever um código melhor, mas também manter melhor o código existente.
O que são os princípios SOLID?
Um bom código-fonte começa com regras, paradigmas de programação e um estilo de programação adequado para obter um código eficiente e limpo. É exatamente isso que garantem os cinco princípios SOLID, cunhados por Robert C. Martin, Bertrand Meyer e Barbara Liskov. Seguir certos princípios na programação orientada a objetos (OOP) com linguagens como Python ou Java não só fará com que escreva melhor o código, mas também obterá uma manutenção mais eficiente do mesmo, um design de software sustentável e flexível e maior segurança a longo prazo.
SOLID refere-se, por um lado, à base sólida de desenvolvimento para todos aqueles que querem aprender a programar. Por outro lado, o acrónimo é composto pelas primeiras letras dos cinco princípios (por Michael Feathers):
- Princípio da Responsabilidade Única: um objeto deve ter um único motivo para mudar, ou seja, uma única responsabilidade.
- Princípio Aberto-Fechado: as classes devem estar abertas para extensão, mas fechadas para modificação.
- Princípio de Substituição de Liskov: as subclasses devem ser substituíveis pelas suas classes base sem afetar o comportamento do programa.
- Princípio da Segregação de Interface: os clientes não devem ser obrigados a depender de interfaces que não utilizam.
- Princípio da inversão de dependência: os módulos de alto nível não devem depender de módulos de baixo nível, mas sim de abstrações. As abstrações não devem depender de detalhes, mas sim os detalhes devem depender das abstrações.
Quais são as vantagens dos princípios SOLID?
Sem regras, surge o caos, afirmação que se torna evidente na programação. Mesmo pequenos erros, imprecisões e omissões podem tornar o código-fonte “inutilizável” a longo prazo. Não é preciso muito para isso: basta ter classes complexas que dificultam a implementação ou subclasses que carecem de certas propriedades das suas superclasses. Os princípios SOLID garantem que seja necessária a menor quantidade possível de reparação de código através da refatoração.
As vantagens de aplicar os princípios SOLID incluem:
- Clareza, limpeza e beleza: o software e os códigos são mais fáceis de entender, mais compreensíveis, mais eficazes e, em definitiva, mais agradáveis à vista.
- Facilidade de manutenção: a estrutura clara e organizada facilita a manutenção tanto do código novo quanto do que foi crescendo ao longo do tempo, mesmo que haja várias partes interessadas.
- Personalizável, expansível e reutilizável: melhor legibilidade, redução de complexidades e responsabilidades e diminuição das dependências de classes tornam o código mais fácil de editar, personalizar, expandir por meio de interfaces e reutilizar com flexibilidade.
- Menos propenso a erros: um código limpo com uma estrutura simples ajuda a que as alterações numa parte não afetem indiretamente outras áreas ou funções.
- Mais seguro e fiável: com menos ou nenhuma vulnerabilidade, incompatibilidade ou erro, a funcionalidade e a fiabilidade dos sistemas melhoram, assim como a segurança.
Os princípios SOLID num relance
Os princípios SOLID estão entre as regras de ouro da boa programação e, portanto, devem ser familiares a qualquer pessoa que se dedique à programação baseada em objetos. Apresentamos os cinco princípios em detalhe.
SRP: Princípio da Responsabilidade Única
A definição original, segundo Robert C. Martin em “Agile Software Development: Principles, Patterns and Practices”, estabelece:
“Nunca deve haver mais do que um motivo para mudar uma aula”
O SRP estabelece que apenas uma responsabilidade deve ser atribuída a cada classe do OOP. Portanto, deve haver apenas um motivo para realizar alterações na classe. Ao contrário do que se costuma entender, isso não significa que uma classe ou módulo só possa ter uma tarefa. No entanto, ele deve assumir a responsabilidade apenas por tarefas específicas que, idealmente, não se sobreponham a outras áreas.
A sobreposição de responsabilidades, como fornecer funções para diferentes áreas comerciais, pode ter um impacto na funcionalidade da classe quando são feitas alterações numa dessas áreas. Ter mais do que uma responsabilidade e demasiadas dependências pode resultar numa alteração numa área provocar várias alterações adicionais ou erros no código.
Portanto, o SRP tem como objetivo desenvolver módulos coerentes responsáveis por uma tarefa claramente compreensível ou por objetos claramente definidos. A estrutura claramente definida com dependências reduzidas e implementações desacopladas permite realizar alterações e modularizações posteriores de maneira mais fácil, rápida e sem complicações.
As classes, também conhecidas como tipos de objetos, são o elemento central na programação orientada a objetos (OOP). Podem ser entendidas como um plano com atributos para construir objetos reais semelhantes em objetos de software. Por isso, as classes, também conhecidas como módulos, são frequentemente comparadas a tipos de ficheiros.
OCP: Princípio de Abertura e Fecho (Open Closed Principle)
Bertrand Meyer e Robert C. Martin, em “Object Oriented Software Construction”, afirmam o seguinte sobre o OCP:
“As entidades de software (classes, módulos, funções, etc.) devem estar abertas para ampliações e fechadas para modificações”
O OCP garante que não seja necessário reescrever o software no seu núcleo para aplicar alterações. Se forem necessárias modificações profundas no código, existe o risco de ocorrerem erros subtis e falhas no código. Um código bem estruturado deve poder ser dotado de interfaces através das quais possa ser ampliado com funções adicionais. Aqui, a palavra-chave é herança de classes.
Novas funcionalidades e extensões com funções e métodos claramente novos que precisam ser implementados podem ser simplesmente acoplados a uma superclasse na forma de subclasses através de uma interface. Desta forma, não é necessário “retocar” o código escrito e estável. Isso simplifica a manutenção e a conservação dos programas e torna a reutilização de elementos de código estáveis muito mais eficiente graças às interfaces.
LSP: Princípio de Substituição de Liskov
Barbara H. Liskov e Jeannette M. Wing, em “Behavioral Subtyping Using Invariants and Constraints”, dizem sobre o LSP:
“Se q (x) é uma propriedade do objeto x do tipo T, então q (y) deve ser válida para todos os objetos do tipo S, onde S é um subtipo de T”.
O que parece enigmático à primeira vista é fácil de entender: as subclasses vinculadas ou estendidas devem funcionar como suas superclasses ou classes base. Isso significa que cada subclasse deve conservar as propriedades da sua respectiva superclasse por meio da herança e que essas propriedades não devem ser modificadas na subclasse. Em princípio, elas devem ser substituíveis. As superclasses, por outro lado, podem ser modificadas por meio de alterações.
O exemplo clássico do retângulo e do quadrado de Robert C. Martin serve para explicar isso. Nas aulas de geometria, reconhece-se que todo quadrado é um retângulo, mas nem todo retângulo é um quadrado. Em vez disso, um quadrado partilha a propriedade «lados em ângulo reto» com a superclasse dos retângulos, mas tem a propriedade adicional «lados de igual comprimento».
Na programação é diferente. Neste caso, a suposição de que classes semelhantes ou aparentemente idênticas estão relacionadas entre si ou dependem umas das outras leva a erros, mal-entendidos e código pouco claro. Por isso, na programação, a classe «retângulo» não é um quadrado e a classe «quadrado» não é um retângulo. Ambas estão «desacopladas» e implementadas separadamente. Sem uma conexão integrada entre as classes, um mal-entendido não pode causar erros entre as classes. Isso aumenta a segurança e a estabilidade ao substituir implementações em subclasses ou superclasses sem repercussões.
ISP: Princípio de Segregação de Interfaces
Em “The Interface Segregation Principle”, Robert C. Martin define o ISP da seguinte forma:
“Os clientes não devem ser obrigados a depender de interfaces que não utilizam”
O ISP estabelece que os utilizadores não devem ser obrigados a utilizar interfaces de que não necessitam. Por outras palavras: para fornecer aos clientes as funções de determinadas classes, são adaptadas novas interfaces mais pequenas a requisitos específicos. Desta forma, evita-se que as interfaces se tornem demasiado grandes e que se criem fortes dependências entre classes. A vantagem: um software com classes desacopladas e várias interfaces pequenas adaptadas a requisitos específicos é mais fácil de manter.
DIP: Princípio da Inversão de Dependência
De acordo com Robert C. Martin em “The Dependency Inversion Principle”, o quinto e último elemento dos princípios SOLID é o seguinte:
“A. Os módulos de alto nível não devem depender dos de baixo nível. Ambos devem depender de abstrações. B. As abstrações não devem depender dos detalhes. Os detalhes devem depender das abstrações”.
O DIP garante que as funções e dependências específicas nos níveis do código-fonte sejam baseadas em interfaces abstratas e não entre si. Para explicar: as arquiteturas de software podem ser divididas, em linhas gerais, em níveis superiores de utilizador e níveis abstratos inferiores ou mais profundos. Logicamente, é de supor que a base abstrata determina o comportamento dos níveis superiores. No entanto, o DIP considera que isso é propenso a erros, uma vez que os níveis superiores dependem dos níveis inferiores.
Em vez de ligar os níveis superiores aos inferiores, as classes dos níveis altos e baixos devem depender de interfaces abstratas intermediárias. As interfaces recuperam dos níveis inferiores as funcionalidades necessárias nos níveis superiores e as colocam à sua disposição. Dessa forma, evita-se uma “hierarquia de baixo para cima” de dependências, que pode dar origem a erros no código com o passar do tempo. Isso facilita a reutilização dos módulos e permite introduzir alterações nas classes inferiores sem afetar os níveis superiores.
O que acontece em caso de incumprimento dos princípios SOLID?
Um código bonito e fácil de ler que facilite ao máximo a manutenção deve ser o objetivo de todo desenvolvimento de software. No entanto, se diretrizes como os princípios SOLID forem esquecidas, o código envelhece muito mal devido a vulnerabilidades, redundâncias, erros acumulados e dependências excessivas. Na pior das hipóteses, o código torna-se inutilizável com o tempo. Este é um problema importante no desenvolvimento ágil de software, uma vez que geralmente há muitas pessoas a trabalhar em código complexo.
As consequências de um código descuidado ou manutenção deficiente incluem:
- Code Smell: as fraquezas herdadas de um código pouco limpo, conhecidas como code smell ou “fedor do código”, podem causar falhas de funcionamento e programas incompatíveis.
- Code Rot: se não for realizada a manutenção ou reparação por meio de refatoração ou uma revisão de código dispendiosa, o código pode “apodrecer” metaforicamente e perder completamente a sua funcionalidade. Outro nome para um código ilegível e confuso é “código espaguete”.
- Vulnerabilidades de segurança: além de falhas, manutenção complicada ou incompatibilidades, também existem riscos de segurança, que podem dar ao malware a oportunidade de explorar vulnerabilidades ou ataques de dia zero.
Quem desenvolveu os princípios SOLID?
A origem dos princípios SOLID encontra-se em vários princípios introduzidos pela primeira vez por Robert C. Martin (“Uncle Bob”), um dos iniciadores da programação ágil, no seu ensaio “Design Principles and Design Patterns” em 2000. O conjunto de cinco princípios SOLID foi cunhado por Robert C. Martin, Bertrand Meyer e Barbara Liskov. O acrónimo cativante formado pelas cinco letras iniciais dos princípios foi divulgado por Michael Feathers, que reordenou cinco dos princípios essenciais.
Que princípios de programação semelhantes existem?
No desenvolvimento de software, os princípios representam diretrizes e recomendações de ação gerais ou muito específicas, semelhantes aos princípios SOLID, que oferecem um conjunto de princípios para o paradigma da programação orientada a objetos. Outros princípios de programação para código limpo incluem:
- Princípio DRY (Don’t repeat yourself) para funções com uma representação única e exclusiva.
- Princípio KISS (Keep it simple, stupid) para um código o mais simples possível. b4e8ee6481d2df0eff54c00d070a0811