[Pesquisar este blog]

segunda-feira, 3 de abril de 2017

Os Princípios SOLID (parte I)

Parte I | Parte II | Parte III | Parte IV | Parte V | Parte VI
O que esperamos de um software? Com certeza, valor. Neste artigo vou falar um pouco sobre a importância do projeto de software, de como esta atividade agrega valor, e também sobre alguns critérios que podem ser utilizados para a avaliação do projeto, finalizando com uma visão geral sobre princípios importantes no uso da orientação a objetos, denominados SOLID (os quais pretendo retomar com detalhe nos próximos posts).
 

O que esperar do software

Os usuários sempre esperam obter alguma espécie de valor do software em uso. Esta noção de valor é determinada pela capacidade do software em: auxiliar o usuário a fazer algo melhor ou mais rápido ou ambos; o que significa aumentar a produtividade do usuário e, com isso, aumentar ganhos (de tempo, de qualidade ou financeiros) e reduzir também retrabalho e desperdícios (que representa reduzir custos, que também é outra forma de aumentar os ganhos).
Assim, o valor do software decorre dos ganhos que seu uso pode proporcionar. Para que o software possa, de fato, possibilitar ganhos, as necessidades do cliente, vistas em seu contexto, devem ser entendidas com clareza. Mas isto não basta.

Projeto de software

Dado um problema para o qual se pretende obter uma solução por meio de um software, é necessário realizar sua análise e, depois, o projeto da solução desejadas.
A análise se ocupa do entendimento do (domínio do) problema e pretende determinar o que deve ser feito para solucioná-lo. Para tanto a etapa de análise constrói um modelo conceitual da solução determinada.
O propósito do projeto é determinar como construir uma solução para o modelo definido pela análise, ou seja, seu objetivo é definir como tal modelo pode ser construído (ou programado, no caso do desenvolvimento de software).
A principal diferença entre a análise e o projeto é que a primeira envolve contato e a discussão com o cliente sobre a construção da solução conceitual. Já o projeto engloba detalhes relativos ao trabalho do programador, que em geral, não são de interesse do cliente.
Deve ficar claro que precisamos tanto de um trabalho de análise, como de projeto, bem feitos. É bastante típico que seja desejado que a análise e o projeto de software sejam realizados de maneira rápida, produzindo um software que atenda plenamente as necessidades de seus clientes, além de ser robusto, seguro e interoperável.
Outra maneira de observar a questão é procurar as características indesejáveis de um projeto de software, ou seja, aquilo que identifica um projeto ruim. Um projeto ruim é algo que, inevitavelmente leva ao software rot.

Software ROT

A expressão software rot, algo como "software podre", é semelhante aos termos code rot, bit rot, software erosion, software decay. Todas as expressões mencionadas descrevem a percepção do usuário sobre a deterioração da performance ou da funcionalidade do software ao longo do tempo, que progressivamente reduz sua utilidade, até que este se torne obsoleto ou inutilizável. Mas como isso pode ser verdade se um programa de computador não sofre qualquer tipo de envelhecimento físico? A questão aqui não é física, mas contextual.
Uma das principais causas do envelhecimento de um software é a evolução do seu contexto de utilização. Um programa pode ser bem projetado e implementado, atendendo plenamente as necessidades de seus usuários no momento em que é introduzido no mercado. A percepção nesta situação é o programa atende ou excede as expectativas existentes. Mas à medida que em que é usado, é bastante natural que os usuários percebam novas possibilidades para sua utilização, nem todas sendo atendidas pelo projeto inicial. Agora a percepção do usuário é que o software, mesmo que ainda bom e útil, poderia ser melhor. Esta piora da percepção é um sinal de envelhecimento.
Quanto maior o tempo, maior será a deterioração percebida em função da evolução das condições de uso do software. Produtos concorrentes que exibam diferenciais, além de defeitos observados no produto, acelerarão o processo de percepção de deterioração, quando, de fato, o software continua exatamente o mesmo.
A manutenção contínua do software, no sentido de corrigir-se os defeitos percebidos e, principalmente, incorporação de novas funcionalidades evolutivas, possibilitarão restaurar, no todo ou em parte, a percepção inicial de qualidade.
Sendo assim, a ausência ou insuficiência de manutenção causa a degradação do software, não em decorrência de envelhecimento real, mas sim da alterações nos requisitos de uso deste software.
Outro sentido de software rot, e das expressões semelhantes, se refere a implementação de aplicações que, por conta de deficiências no projeto, na documentação e na própria manutenção, se tornam uma massa desorganizada de código improvisado que os desenvolvedores tem dificuldade crescente em manter.

Projetos de Software::critérios de avaliação

Existem quatro critérios importantes para avaliar a qualidade de um projeto de software:
  • Rigidez;
  • Fragilidade;
  • Imobilidade; e
  • Viscosidade.
Critério da Rigidez
Este critério representa a imprevisibilidade do impacto relativo a uma mudança. Em um projeto rígido, toda e qualquer alteração causa uma sucessão de mudanças em módulos dependentes, produzindo um efeito dominó ou cascata, que representa a propagação indesejada da alteração efetuada. Assim, uma tarefa, em tese, simples, torna-se um trabalho hercúleo e, com isto os custos das alterações são imprevisíveis, bem como o tempo necessário para sua realização.

Critério da Fragilidade
Dizemos que um software é frágil quando tende a apresentar problemas em muitos pontos em cada mudança. Ou seja, as alterações efetuadas em um ponto do software acabam por causar problemas em outros pontos não relacionados conceitualmente. Assim, em cada reparo o software apresenta novos problemas não previstos, novamente com implicações negativas no custo e no tempo da manutenção.

Critério da Imobilidade
Em função de como um software é projetado, pode se tornar muito difícil, ou quase impossível, reutilizar partes do software em outros projetos, caracterizando a imobilidade (as parte de um software não podem ser movidas para outros). Desta maneira, mesmo os módulos úteis exibem muitas dependências com outras partes do software, dificultando ou impedindo seu reuso em outros projetos. Nesta situação, o custo de reescrever (refazer) acaba sendo menor do que o risco de separação das partes em função da imobilidade + rigidez + fragilidade.

Critério da Viscosidade
Também existem situações onde as deficiências no projeto ou em sua documentação, acabam estimulando a realização de pequenos consertos improvisados. Tais remendos (patches) são mais baratos de implementar do que os reparos maiores que proporcionariam soluções mais efetivas no projeto como um todo. Assim, a preservação consistente do projeto vai se tornando cada vez mais difícil de idealizar e implementar, tornando-o viscoso.

A razão central de todos esses problemas é o projeto mal feito, ingênuo, apressado, improvisado ou incompleto.

Bons projetos não são rígidos, possibilitando alteração que não se propagam no código; não são frágeis, garantindo que mudanças em um ponto não façam surgir problemas em módulos não correlatos; nem impõe a imobilidade; pois suas partes podem ser reutilizadas em outros projetos; muito menos viscosos; favorecendo que a manutenção seja realizada de maneira adequada. Então, bons projetos requerem conhecimento técnico, dedicação e, tão importante quanto isso, que práticas ruins sejam evitadas, enquanto práticas boas sejam adotadas.

Projeto de Software::características desejadas

Existem algumas características de são desejadas em qualquer projeto de software, independentemente de seu tamanho ou complexidade. Aqui podemos destacar dois conceitos centrais na construção de software: coesão e acoplamento. Além destes, também se deseja a componibilidade, a independência de contexto e a facilidade para teste.

Projeto de Software::características desejadas::coesão
O termo coesão (cohesion) foi definido por Tom DeMarco em 1979 e afirma que:
Cada módulo deve possuir apenas uma responsabilidade.

Isso implica em dividir as diversas responsabilidades identificadas na análise e no projeto em módulos distintos, de maneira que cada um possua apenas as características e funcionalidades estritamente necessárias à sua responsabilidade.
Num projeto orientado a objetos isto nos faria pensar sobre:
  • Quão bem os métodos e atributos de uma classe se articulam para definir seus propósitos?
  • Como uma classe se articula com as outras, sem descaracterizar seus próprios objetivos?
Assim, idealmente no projeto OO, cada classe deve possuir apenas uma responsabilidade, ficando claro seu próprio papel e propósito.
As implicações da coesão em um projeto são boas, pois:

  •  As alterações necessárias se concentram em módulos específicos;
  •  Torna-se mais fácil reutilizar aquilo que tem funcionalidade claramente delimitada.
Como é provável que a coesão absoluta seja difícil de obter, deseja-se que seja a maior ou mais alta possível, pois mais benefícios trará para o projeto durante sua implementação e também durante sua manutenção.

Projeto de Software::características desejadas::acoplamento
Esta outra característica se relaciona às dependências entre os módulos (classes num projeto OO) de um sistema. Deseja-se que cada módulo possua o menor número possível de dependências com outros módulos, ou seja, que os módulos sejam o mais independentes possíveis e, desta maneira, o acoplamento seja pequeno ou baixo.
As implicações do baixo acoplamento são:
·      Concentração das alterações em módulo específicos;
·      Menor a propagação das alterações efetuadas; e
·      Maior a chance de reaproveitamento (reuso) dos módulos.
No entanto, deve-se observar que o acoplamento não é um mal em si, pois  algum grau de acoplamento é inerente a criação de um sistema. Se um módulo não depende de nenhum outro, temos que seu acoplamento é zero. Mas se todos os módulos são assim, não existe um sistema, mas um amontoado, provavelmente inútil, de classes.
Isto significa que o acoplamento também é a extensão com que duas ou mais partes de um sistema estão relacionadas para criar mais valor do que partes individuais. Um grau adequado de acoplamento permite criar um sistema funcional, compreensível e manutenível.

Projeto::características desejadas::componibilidade
Deseja-se que os módulos de um projeto sejam componíveis, ou seja, que possam ser combinados de maneiras diferentes, produzindo funcionalidades distintas. Esta característica implica em:
·      Acrescentar novos módulos e, com isso, novas funcionalidades;
·      Modificar, no sentido de corrigir ou melhorar, os módulos existentes; e
·      Também facilitar o teste de cada componente.
O grande valor da componibilidade é o reuso das partes elaboradas para um sistema na construção de outros sistemas.

Projeto::características desejadas::contexto
Além do interesse em criar componentes que possam ser rearranjados e reutilizados, também se deseja que tal reuso possa ocorrer em contextos diferentes, ou seja, para auxiliar na solução de problemas existentes em outros domínios.
A independência de contexto possibilita:
·      Reutilizar os componentes do projeto para outros propósitos, em domínios diferentes;
·      Reuso ampliado, o que é um grande benefício.

Projeto de Software::avaliação das características desejadas
Observando-se em conjunto as características boas e ruins dos projetos, nota-se que estão correlacionadas. Muito da rigidez, da fragilidade, da imobilidade e da viscosidade são decorrentes da multiplicidade de papeis dos módulos existentes (i.e., de sua baixa coesão) e também da interdependência excessiva entre suas partes (i.e., de seu alto acoplamento). Tudo decorrência de projetos descuidados nesse sentido.
No projeto orientado a objetos (OO Design) as classes tem papel fundamental, pois são os elementos construtivos dos sistemas. Como modificações no escopo de qualquer sistema são inevitáveis com o passar do tempo, classes bem projetadas permitirão:

  • Codificação mais rápida;
  • Teste mais abrangente; e
  • Manutenção facilitada.
Para projetar boas classes e, com isso, construir bons sistemas, existem aspectos importantes que podem e devem ser observados, como indicam os princípios SOLID.

Princípios SOLID

Em 1995 Robert C. Martin (também conhecido como Uncle Bob) propôs um amplo conjunto de princípios de projeto e programação orientada a objetos que, posteriormente, tornaram-se conhecidos como SOLID, um acrônimo criado por Michael Feathers para designar os primeiros cinco dos princípios propostos.
A intenção destes princípios é orientar o programador na construção de sistemas mais fáceis de manter e ampliar com o passar do tempo, pois sem isso os softwares envelhecem e são percebidos como deteriorados.
Os princípios enunciados por Martin constituem um guia para refatoração do código, e também se enquadram nas estratégias ágeis e na abordagem adaptativa de construção de software.

O acrônimo SOLID é formado por:
  • Single Responsability Principle (Parte II)
  • Open-Closed Principle (Parte III)
  • Liskov`s Substitution Principle (Parte IV)
  • Interface Segregation Principle (Parte V)
  • Dependency Inversion Principle (Parte VI)
Estes cinco princípios podem ser, resumidamente, entendidos como segue.

O Single Responsability Principle ou Princípio da Responsabilidade Única afirma que uma classe deve ter uma, e apenas uma, razão para mudar. Assim, cada classe deve representar apenas um ator ou entidade, devendo ser escrita, modificada e mantida para apenas um propósito específico.

O Open Closed Principle ou Princípio Aberto-Fechado indica que os componentes de um software devem ser abertos para extensão e fechados para modificação. Isto significa que as classes e interfaces devem ser pensadas para permitir que sejam estendidas (pela mecanismo da herança), mas não para modificações nelas próprias.

O Liskov’s Substitution Principle ou Princípio da Substituição de Liskov determina que quaisquer tipos derivados (subclasses ou classes derivadas) devam ser completamente substituíveis por seus tipos base, de modo que tais substituições sejam transparentes e não causem qualquer erro.

O Interface Segregation Principle ou Princípio da Segregação de Interfaces requer que os clientes de módulos de software não devam ser forçados a
implementar métodos desnecessários que não sejam usados, reduzindo a burocracia .

O Dependency Inversion Principle ou Princípio da Inversão de Dependência estabelece a conveniência de um módulo depender de outros módulos mais abstratos, ao invés de elementos concretos (de substituição mais complexa).

A aplicação destes cinco princípios permite melhorar, e muito, a qualidade do software construído para qualquer projeto e, por isso, cada um merece mais atenção. Mas isto fica para os próximos posts!

Para Saber Mais

  • LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
  • MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
  • MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
  • MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
  • MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
  • PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
  • SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.