Os prin­cí­pios SOLID são cinco práticas e di­re­tri­zes para um código limpo, fácil de manter e flexível na pro­gra­ma­ção baseada em objetos. A aplicação e o cum­pri­mento desses prin­cí­pios facilitam a com­pre­en­são do design de software durante longos períodos de de­sen­vol­vi­mento. 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 prin­cí­pios SOLID?

Um bom código-fonte começa com regras, pa­ra­dig­mas de pro­gra­ma­ção e um estilo de pro­gra­ma­ção adequado para obter um código eficiente e limpo. É exa­ta­mente isso que garantem os cinco prin­cí­pios SOLID, cunhados por Robert C. Martin, Bertrand Meyer e Barbara Liskov. Seguir certos prin­cí­pios na pro­gra­ma­ção orientada a objetos (OOP) com lin­gua­gens como Python ou Java não só fará com que escreva melhor o código, mas também obterá uma ma­nu­ten­ção mais eficiente do mesmo, um design de software sus­ten­tá­vel e flexível e maior segurança a longo prazo.

SOLID refere-se, por um lado, à base sólida de de­sen­vol­vi­mento para todos aqueles que querem aprender a programar. Por outro lado, o acrónimo é composto pelas primeiras letras dos cinco prin­cí­pios (por Michael Feathers):

  • Princípio da Res­pon­sa­bi­li­dade Única: um objeto deve ter um único motivo para mudar, ou seja, uma única res­pon­sa­bi­li­dade.
  • Princípio Aberto-Fechado: as classes devem estar abertas para extensão, mas fechadas para mo­di­fi­ca­ção.
  • Princípio de Subs­ti­tui­ção de Liskov: as sub­clas­ses devem ser subs­ti­tuí­veis pelas suas classes base sem afetar o com­por­ta­mento do programa.
  • Princípio da Se­gre­ga­ção de Interface: os clientes não devem ser obrigados a depender de in­ter­fa­ces que não utilizam.
  • Princípio da inversão de de­pen­dên­cia: os módulos de alto nível não devem depender de módulos de baixo nível, mas sim de abs­tra­ções. As abs­tra­ções não devem depender de detalhes, mas sim os detalhes devem depender das abs­tra­ções.

Quais são as vantagens dos prin­cí­pios SOLID?

Sem regras, surge o caos, afirmação que se torna evidente na pro­gra­ma­ção. Mesmo pequenos erros, im­pre­ci­sões e omissões podem tornar o código-fonte “inu­ti­li­zá­vel” a longo prazo. Não é preciso muito para isso: basta ter classes complexas que di­fi­cul­tam a im­ple­men­ta­ção ou sub­clas­ses que carecem de certas pro­pri­e­da­des das suas su­per­clas­ses. Os prin­cí­pios SOLID garantem que seja ne­ces­sá­ria a menor quan­ti­dade possível de reparação de código através da re­fa­to­ra­ção.

As vantagens de aplicar os prin­cí­pios SOLID incluem:

  • Clareza, limpeza e beleza: o software e os códigos são mais fáceis de entender, mais com­pre­en­sí­veis, mais eficazes e, em de­fi­ni­tiva, mais agra­dá­veis à vista.
  • Fa­ci­li­dade de ma­nu­ten­ção: a estrutura clara e or­ga­ni­zada facilita a ma­nu­ten­ção tanto do código novo quanto do que foi crescendo ao longo do tempo, mesmo que haja várias partes in­te­res­sa­das.
  • Per­so­na­li­zá­vel, ex­pan­sí­vel e reu­ti­li­zá­vel: melhor le­gi­bi­li­dade, redução de com­ple­xi­da­des e res­pon­sa­bi­li­da­des e di­mi­nui­ção das de­pen­dên­cias de classes tornam o código mais fácil de editar, per­so­na­li­zar, expandir por meio de in­ter­fa­ces e reu­ti­li­zar com fle­xi­bi­li­dade.
  • Menos propenso a erros: um código limpo com uma estrutura simples ajuda a que as al­te­ra­ções numa parte não afetem in­di­re­ta­mente outras áreas ou funções.
  • Mais seguro e fiável: com menos ou nenhuma vul­ne­ra­bi­li­dade, in­com­pa­ti­bi­li­dade ou erro, a fun­ci­o­na­li­dade e a fi­a­bi­li­dade dos sistemas melhoram, assim como a segurança.

Os prin­cí­pios SOLID num relance

Os prin­cí­pios SOLID estão entre as regras de ouro da boa pro­gra­ma­ção e, portanto, devem ser fa­mi­li­a­res a qualquer pessoa que se dedique à pro­gra­ma­ção baseada em objetos. Apre­sen­ta­mos os cinco prin­cí­pios em detalhe.

SRP: Princípio da Res­pon­sa­bi­li­dade Única

A definição original, segundo Robert C. Martin em “Agile Software De­ve­lop­ment: Prin­ci­ples, Patterns and Practices”, es­ta­be­lece:

Citação

“Nunca deve haver mais do que um motivo para mudar uma aula”

O SRP es­ta­be­lece que apenas uma res­pon­sa­bi­li­dade deve ser atribuída a cada classe do OOP. Portanto, deve haver apenas um motivo para realizar al­te­ra­çõ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 res­pon­sa­bi­li­dade apenas por tarefas es­pe­cí­fi­cas que, ide­al­mente, não se so­bre­po­nham a outras áreas.

A so­bre­po­si­ção de res­pon­sa­bi­li­da­des, como fornecer funções para di­fe­ren­tes áreas co­mer­ci­ais, pode ter um impacto na fun­ci­o­na­li­dade da classe quando são feitas al­te­ra­ções numa dessas áreas. Ter mais do que uma res­pon­sa­bi­li­dade e de­ma­si­a­das de­pen­dên­cias pode resultar numa alteração numa área provocar várias al­te­ra­ções adi­ci­o­nais ou erros no código.

Portanto, o SRP tem como objetivo de­sen­vol­ver módulos coerentes res­pon­sá­veis por uma tarefa cla­ra­mente com­pre­en­sí­vel ou por objetos cla­ra­mente definidos. A estrutura cla­ra­mente definida com de­pen­dên­cias reduzidas e im­ple­men­ta­ções de­sa­co­pla­das permite realizar al­te­ra­ções e mo­du­la­ri­za­ções pos­te­ri­o­res de maneira mais fácil, rápida e sem com­pli­ca­ções.

Nota

As classes, também co­nhe­ci­das como tipos de objetos, são o elemento central na pro­gra­ma­ção orientada a objetos (OOP). Podem ser en­ten­di­das como um plano com atributos para construir objetos reais se­me­lhan­tes em objetos de software. Por isso, as classes, também co­nhe­ci­das como módulos, são fre­quen­te­mente com­pa­ra­das a tipos de ficheiros.

OCP: Princípio de Abertura e Fecho (Open Closed Principle)

Bertrand Meyer e Robert C. Martin, em “Object Oriented Software Cons­truc­tion”, afirmam o seguinte sobre o OCP:

Citação

“As entidades de software (classes, módulos, funções, etc.) devem estar abertas para am­pli­a­ções e fechadas para mo­di­fi­ca­ções”

O OCP garante que não seja ne­ces­sá­rio re­es­cre­ver o software no seu núcleo para aplicar al­te­ra­ções. Se forem ne­ces­sá­rias mo­di­fi­ca­ções profundas no código, existe o risco de ocorrerem erros subtis e falhas no código. Um código bem es­tru­tu­rado deve poder ser dotado de in­ter­fa­ces através das quais possa ser ampliado com funções adi­ci­o­nais. Aqui, a palavra-chave é herança de classes.

Novas fun­ci­o­na­li­da­des e extensões com funções e métodos cla­ra­mente novos que precisam ser im­ple­men­ta­dos podem ser sim­ples­mente acoplados a uma su­per­classe na forma de sub­clas­ses através de uma interface. Desta forma, não é ne­ces­sá­rio “retocar” o código escrito e estável. Isso sim­pli­fica a ma­nu­ten­ção e a con­ser­va­ção dos programas e torna a reu­ti­li­za­ção de elementos de código estáveis muito mais eficiente graças às in­ter­fa­ces.

LSP: Princípio de Subs­ti­tui­ção de Liskov

Barbara H. Liskov e Jeannette M. Wing, em “Beha­vi­o­ral Subtyping Using In­va­ri­ants and Cons­traints”, dizem sobre o LSP:

Citação

“Se q (x) é uma pro­pri­e­dade 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 enig­má­tico à primeira vista é fácil de entender: as sub­clas­ses vin­cu­la­das ou es­ten­di­das devem funcionar como suas su­per­clas­ses ou classes base. Isso significa que cada subclasse deve conservar as pro­pri­e­da­des da sua res­pec­tiva su­per­classe por meio da herança e que essas pro­pri­e­da­des não devem ser mo­di­fi­ca­das na subclasse. Em princípio, elas devem ser subs­ti­tuí­veis. As su­per­clas­ses, por outro lado, podem ser mo­di­fi­ca­das por meio de al­te­ra­çõ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 pro­pri­e­dade «lados em ângulo reto» com a su­per­classe dos re­tân­gu­los, mas tem a pro­pri­e­dade adicional «lados de igual com­pri­mento».

Na pro­gra­ma­ção é diferente. Neste caso, a suposição de que classes se­me­lhan­tes ou apa­ren­te­mente idênticas estão re­la­ci­o­na­das entre si ou dependem umas das outras leva a erros, mal-en­ten­di­dos e código pouco claro. Por isso, na pro­gra­ma­ção, a classe «retângulo» não é um quadrado e a classe «quadrado» não é um retângulo. Ambas estão «de­sa­co­pla­das» e im­ple­men­ta­das se­pa­ra­da­mente. 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 es­ta­bi­li­dade ao subs­ti­tuir im­ple­men­ta­ções em sub­clas­ses ou su­per­clas­ses sem re­per­cus­sões.

ISP: Princípio de Se­gre­ga­ção de In­ter­fa­ces

Em “The Interface Se­gre­ga­tion Principle”, Robert C. Martin define o ISP da seguinte forma:

Citação

“Os clientes não devem ser obrigados a depender de in­ter­fa­ces que não utilizam”

O ISP es­ta­be­lece que os uti­li­za­do­res não devem ser obrigados a utilizar in­ter­fa­ces de que não ne­ces­si­tam. Por outras palavras: para fornecer aos clientes as funções de de­ter­mi­na­das classes, são adaptadas novas in­ter­fa­ces mais pequenas a re­qui­si­tos es­pe­cí­fi­cos. Desta forma, evita-se que as in­ter­fa­ces se tornem demasiado grandes e que se criem fortes de­pen­dên­cias entre classes. A vantagem: um software com classes de­sa­co­pla­das e várias in­ter­fa­ces pequenas adaptadas a re­qui­si­tos es­pe­cí­fi­cos é mais fácil de manter.

DIP: Princípio da Inversão de De­pen­dên­cia

De acordo com Robert C. Martin em “The De­pen­dency Inversion Principle”, o quinto e último elemento dos prin­cí­pios SOLID é o seguinte:

Citação

“A. Os módulos de alto nível não devem depender dos de baixo nível. Ambos devem depender de abs­tra­ções. B. As abs­tra­ções não devem depender dos detalhes. Os detalhes devem depender das abs­tra­ções”.

O DIP garante que as funções e de­pen­dên­cias es­pe­cí­fi­cas nos níveis do código-fonte sejam baseadas em in­ter­fa­ces abstratas e não entre si. Para explicar: as ar­qui­te­tu­ras de software podem ser divididas, em linhas gerais, em níveis su­pe­ri­o­res de uti­li­za­dor e níveis abstratos in­fe­ri­o­res ou mais profundos. Lo­gi­ca­mente, é de supor que a base abstrata determina o com­por­ta­mento dos níveis su­pe­ri­o­res. No entanto, o DIP considera que isso é propenso a erros, uma vez que os níveis su­pe­ri­o­res dependem dos níveis in­fe­ri­o­res.

Em vez de ligar os níveis su­pe­ri­o­res aos in­fe­ri­o­res, as classes dos níveis altos e baixos devem depender de in­ter­fa­ces abstratas in­ter­me­diá­rias. As in­ter­fa­ces recuperam dos níveis in­fe­ri­o­res as fun­ci­o­na­li­da­des ne­ces­sá­rias nos níveis su­pe­ri­o­res e as colocam à sua dis­po­si­ção. Dessa forma, evita-se uma “hi­e­rar­quia de baixo para cima” de de­pen­dên­cias, que pode dar origem a erros no código com o passar do tempo. Isso facilita a reu­ti­li­za­ção dos módulos e permite in­tro­du­zir al­te­ra­ções nas classes in­fe­ri­o­res sem afetar os níveis su­pe­ri­o­res.

O que acontece em caso de in­cum­pri­mento dos prin­cí­pios SOLID?

Um código bonito e fácil de ler que facilite ao máximo a ma­nu­ten­ção deve ser o objetivo de todo de­sen­vol­vi­mento de software. No entanto, se di­re­tri­zes como os prin­cí­pios SOLID forem es­que­ci­das, o código envelhece muito mal devido a vul­ne­ra­bi­li­da­des, re­dun­dân­cias, erros acu­mu­la­dos e de­pen­dên­cias ex­ces­si­vas. Na pior das hipóteses, o código torna-se inu­ti­li­zá­vel com o tempo. Este é um problema im­por­tante no de­sen­vol­vi­mento ágil de software, uma vez que ge­ral­mente há muitas pessoas a trabalhar em código complexo.

As con­sequên­cias de um código des­cui­dado ou ma­nu­ten­ção de­fi­ci­ente incluem:

  • Code Smell: as fraquezas herdadas de um código pouco limpo, co­nhe­ci­das como code smell ou “fedor do código”, podem causar falhas de fun­ci­o­na­mento e programas in­com­pa­tí­veis.
  • Code Rot: se não for realizada a ma­nu­ten­ção ou reparação por meio de re­fa­to­ra­ção ou uma revisão de código dis­pen­di­osa, o código pode “apodrecer” me­ta­fo­ri­ca­mente e perder com­ple­ta­mente a sua fun­ci­o­na­li­dade. Outro nome para um código ilegível e confuso é “código espaguete”.
  • Vul­ne­ra­bi­li­da­des de segurança: além de falhas, ma­nu­ten­ção com­pli­cada ou in­com­pa­ti­bi­li­da­des, também existem riscos de segurança, que podem dar ao malware a opor­tu­ni­dade de explorar vul­ne­ra­bi­li­da­des ou ataques de dia zero.

Quem de­sen­vol­veu os prin­cí­pios SOLID?

A origem dos prin­cí­pios SOLID encontra-se em vários prin­cí­pios in­tro­du­zi­dos pela primeira vez por Robert C. Martin (“Uncle Bob”), um dos ini­ci­a­do­res da pro­gra­ma­ção ágil, no seu ensaio “Design Prin­ci­ples and Design Patterns” em 2000. O conjunto de cinco prin­cí­pios SOLID foi cunhado por Robert C. Martin, Bertrand Meyer e Barbara Liskov. O acrónimo cativante formado pelas cinco letras iniciais dos prin­cí­pios foi divulgado por Michael Feathers, que reordenou cinco dos prin­cí­pios es­sen­ci­ais.

Que prin­cí­pios de pro­gra­ma­ção se­me­lhan­tes existem?

No de­sen­vol­vi­mento de software, os prin­cí­pios re­pre­sen­tam di­re­tri­zes e re­co­men­da­ções de ação gerais ou muito es­pe­cí­fi­cas, se­me­lhan­tes aos prin­cí­pios SOLID, que oferecem um conjunto de prin­cí­pios para o paradigma da pro­gra­ma­ção orientada a objetos. Outros prin­cí­pios de pro­gra­ma­ção para código limpo incluem:

  • Princípio DRY (Don’t repeat yourself) para funções com uma re­pre­sen­ta­ção única e exclusiva.
  • Princípio KISS (Keep it simple, stupid) para um código o mais simples possível. b4e8ee6481d2df0eff54c00d070a0811
Ir para o menu principal