O objetivo deste artigo é focar nos conceitos e boas práticas no desenvolvimento de software. É um dever de todo programador(a) saber sobre os princípios e os conceitos que irei abordar neste artigo, e aplicar em seu dia a dia.
Quando utilizamos boas práticas em nossos projetos, todos os envolvidos entendem o código, não perde tempo fazendo correções ou procurando entender a regra de negócio, fica tudo mais simples e ganha-se muito em produtividade. Sem contar que crescemos como programador(a), nos tornamos mais críticos ao escrever um código, e criar possíveis soluções para um problema. Eu chamo isso de evolução.
Introdução
Quando desenvolvemos e projetamos um software, é bem natural encontrarmos algumas dificuldades no caminho, e é através de erros e acertos que começamos a evoluir. Durante o desenvolvimento de um software, nos deparamos com coisas que nos “empaca” literalmente. Seja na complexidade na regra de negócios, mudanças de requisitos, no desenvolvimento do código, ferramentas, enfim, seja qual for o motivo, isso gera um certo desconforto ou desânimo na hora. Isso é natural, embora demorou um tempo para eu poder aceitar essa ideia.
E nesse ciclo percebemos que alguns problemas são recorrentes. Na maioria das vezes, todos os problemas são recorrentes. E pensando neste caso, existe um padrão que podemos utilizar para solucionar alguns problemas que encontramos no desenvolvimento do software. Ou evitar que esses problemas apareçam, esses padrões são conhecidos como Design Patterns.
Neste artigo irei abordar estes padrões de projetos e boas práticas, e outros princípios que farão você desenvolvedor(a) ter uma outra visão da programação em si, ter um olhar mais crítico e se perguntar mais o por que das coisas. É o que me tornou hoje e que me motiva a escrever este artigo agora. É importante que você aplique estes conceitos na sua rotina como desenvolvedor(a), para que assim você perceba sua evolução, e melhore a maneira como escreve seu código. Eu errei muito e continuo errando. Mas foi através destes erros que pude evoluir. Tive que focar meus objetivos para continuar progredindo e evitar velhos erros.
SOLID
É um acrônimo dos cinco princípios básicos e boas práticas de programação orientada a objetos e design de código, que pode ser aplicada em qualquer linguagem de programação, por Robert Martin e Uncle Bob, por volta dos anos 2000.
Os princípios do SOLID devem ser aplicados para se obter os benefícios da orientação a objetos, tais como:
- Seja fácil de se manter, adaptar e se ajustar as alterações do escopo;
- Seja testável e de fácil entendimento;
- Seja extensível para alterações com o menor esforço necessário;
- Que forneça o máximo de reaproveitamento;
- Que permaneça o máximo de tempo possível em utilização
- Single Responsability – uma classe deve ter um e apenas um motivo para ser modificada. Uma classe ou um método delegate, ele deve ter apenas uma responsabilidade (um motivo) para ser modificado. Ex: uma classe Banco nela eu não posso validar informações de cliente, ou ter um objeto de acesso a banco de dados, pois não é de sua responsabilidade fazer esse tipo de validação ou ter informações que são impertinentes a mesma. Ou seja, não posso ter nenhuma propriedade nada que não seja referente a Banco.
Esta classe tem a responsabilidade de possuir os atributos objetos relacionados a um Banco, exemplo, nome do banco, agência, número etc. Para que possamos salvar informações do Banco, o certo a se fazer é criar uma classe BancoRepository. Já esta classe terá a responsabilidade de armazenar informações de acesso a dados relacionadas ao banco, ela serve para encapsular todas as ações e persistir informações, conexões etc que um banco precisará ter. Este é o conceito de responsabilidade única.
- Close Open Principle – entidades de software (classes, métodos, módulos, funções etc) devem estar abertas para extensão, mas fechadas para modificações.
Uma vez criada uma classe, cuja sua responsabilidade seja única, se há alguma alteração nela a ser feita, esta classe deve ser extendida e não modificada.
Porém, se a classe por exemplo Banco no decorrer do desenvolvimento teve novos atributos a serem adicionados, esta mesma classe está aberta para ser modificada. Este tipo de alteração é válido. Agora, imagina um outro caso onde, houve mudanças na regra de negócios e haverá um outro tipo de banco, que não seja especificamente esse banco que existente, que tenha outra funcionalidade porém atributos semelhantes, neste caso não iremos modificar a classe existente chamada Banco. É menos custoso criar uma outra classe chamada TipoBanco, não concordam? Se esta classe tiver atributos diferentes e tem uma responsabilidade diferente da classe banco, porque irei modificar a classe Banco? O conceito é esse, ela está fechada para modificação. Uma alteração destas pode afetar o sistema inteiro. Isto serve para um método também. - Liskov Substitution Principle – uma classe base deve poder ser substituído pela sua classe base derivada. Toda herança deve ser muito bem justificada, pois se uma herança não for bem justificada ou modelada, isso acaba violando os princípios de Liskov.
Exemplo: se formos usar a herança do modo que aprendemos na faculdade, neste exemplo teríamos uma classe chamda Mamíferos, e se pensarmos na seguinte forma, a classe Humanos ela herda de Mamíferos, logo cachorro herda de Mamífero, cavalo também herda de Mamífero etc. Ou seja, quando pensamos no conceito de É UM, acabamos fazendo uma herança não justificada, pois iremos herdar tudo que achamos fazer sentido neste exemplo de classe. Não é errado, mas também não é o certo. Isso gera uma grande confusão no conceito de herança. Um bom jeito de usar herança é usá-la de uma forma bem justificada, seria pensar desta forma: evitar herança por comodismo. O conceito É UM, não deve ser um guia de referência para a utilização do conceito de herança. Uma herança bem justificada é aquela herança que divide uma classe da outra, porém possui funcionalidades diferentes. Não ir muito além da classe base.
- Interface Segregation Principle – clientes não devem ser forçados a depender de métodos que não tem. Este princípio te conduz a não depender da interface ter uma interface específica para cada classe.
- Dependency Inversion Principle– módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.
- Inverter o controle é uma espécie de injeção de dependência, mas de um jeito diferente. Inversão de controle seria passar o poder da implementação para outra implementação. Dependa de uma abstração e não de uma implementação. Abstrair não significa usar uma classe abstrata, significa não implementar. Ou seja, fazer funcionar sem criar um acoplamento para aquilo.
- DI Lifecyle – conhecer o processo de ciclo de vida da criação de um objeto pelo container de injeção de dependência (DI), é fundamental para o desenvolvedor(a) tomar as melhores decisões baseadas no comportamento da aplicação.
Por quê utilizar os princípios SOLID?
Utilizando os princípios do SOLID é possível evitar problemas muito comum:
- Dificuldades na testabilidade criação de testes unitários;
- Código “macarrônico” se estrutura ou padrão difícil compreensão;
- Dificuldades em isolar funcionalidades;
- Duplicação de código, uma alteração precisa ser feita em N pontos;
- Fragilidade, o código quebra facilmente em vários pontos após uma mudança
- Não reaproveitamento, ou seja, nenhuma ou quase nenhuma funcionalidade pode ser reaproveitada para outros sistemas.
DDD
O propósito do (Domain Driven Design) é modelar uma aplicação complexa, dentro de padrões. E quando falamos modelagem, o principal foco do DDD é a comunicação, ou seja, é necessário que todos utilizem uma linguagem em comum.
O modelo aborda os seguintes os cinco principais itens:
- Linguagem Ubíqua
- Separação de Camadas
- Objetos de Valor
- Serviços
- Factories
- Repositório
Linguagem Ubíqua
No desenvolvimento de software, chamamos essa linguagem de linguagem ubíqua. Linguagem Ubíqua, pode ser: Vocabulário de todos os termos específicos como: nomes,verbos, adjetivos, jargões, apelidos, expressões e etc.
A linguagem ubíqua, deve ser compartilhada por todos os envolvidos em um projeto de software, usadas em todas as formas faladas e escritas na comunicação, ou seja, é a linguagem do negócio. Vejamos um exemplo: é muito comum pessoas que atuam no ramo do e-commerce estarem habituados com as palavras, remessa, pedidos, estorno, armazém, antifraude, etc, essa é a linguagem ubíqua e qualquer pessoa que atue nesta área, irá entender esses jargões.
Separação de Camadas
A separação das camadas e de suas responsabilidades, é algo que deve ser visto na arquitetura do projeto. Cada camada será responsável por apenas uma única coisa no projeto. Um software de negócio pode existir N camadas, mas deverá existir também uma camada de domínio, ou seja, a camada responsável pelas regras de negócio do sistema, o coração do sistema.
Objeto e Valor
São objetos conhecidos por seus atributos e geralmente são imutáveis. Existem muitos outros tipos de objetos que não vão precisar ter identidade própria, mas que possuem informações que nos ajudam a descrever o domínio, e em alguns casos podem compor entidades.
Serviços
Serviços não são camadas, não são entidades e nem são objetos de valor, mas sim um objeto de negócio. Vou usar de exemplo bem simples do Airbn. Todo mundo sabe que seu negócio é aluguel de imóvel ou hospitalidade, por assim dizer. Os associados podem usar o serviço para organizar ou oferecer uma hospedagem, logo teremos um HospedagemService ou AluguelService, pois fazem parte do domínio.
Factories
Factories ou Fábricas no português servem para criar objetos complexos.
“Há alguns casos em que uma criação e montagem de objeto corresponde a um marco significativo no domínio, como “abrir uma conta bancária”. Mas a criação e a montagem de objetos geralmente não têm significado no domínio… Para resolver esse problema, temos adicionar construções ao design de domínio que não sejam entidades, objetos de valor ou serviços (Eric Evans in Domain Driven Design).
Repositório
Com um modelo conseguimos:
- Abstrair a complexidade do negócio através de uma representação simplificada do modelo;
- O modelo é a comunicação usada pelo time, chamada de linguagem ubíqua;
O modelo é algo interativo, quando há alguma mudança na regra de negócio o código é refatorado. Sendo assim, o modelo se torna evolutivo sendo gradualmente enriquecido.
TDD
O TDD é um estilo de desenvolvimento de software ágil do método Extreme Programming (XP) [Beck 2000]. No TDD os desenvolvedores(a) utilizam testes para orientar no desenvolvimento do projeto, os testes de unidade ou de integração, tem como objetivo testar pequenas unidades do software, e através das falhas ou sucessos que julgam a mudança no desenvolvimento do projeto. Conforme visto na figura acima, temos os seguintes passos:
- Escreva um teste que falhe;
- Escreva um código para passar no teste;
- Elimine a redundância (refatorar)
Vantagens em se utilizar o TDD
- Reduz o acoplamento dos componentes de software sendo assim suscetível a mudanças;
- Otimização de automatização de testes;
- Melhor designer de código tornando o limpo e bem escrito para fácil manutenção e refatoração;
- Mais produtividade pelo foco na resolução do problema;
- Economização de tempo para o desenvolvimento, menos bugs para corrigir e menos trabalhos;
- Melhora o desenvolvimento do programador(a)
De acordo com o Eduardo Pires em seu curso de Arquitetura de Software, ele refere-se o TDD como um designer de código. “O foco do TDD não é apenas escrever testes, é você desenvolvedor(a), testar um código dirigido por um objetivo, e escrever este código com um melhor designer possível.” (Eduardo Pires – Arquitetura de Software part III).
A ideia do TDD é criar um código testável, porque se o seu projeto não estiver utilizando princípios SOLID, pouco irá conter injeção de dependência, separação de camadas e etc, e com isso, você não vai conseguir fazer os teste de unidade no seu código. Se o código nasce testável ele se torna um código bom.
Honestamente confesso que eu não sou praticante de TDD, mas estou começando a criar esse hábito. Não é fácil criar um código a partir dos testes, mas se começarmos com pequenos passos e começar a adquirir o hábito de criar testes de unidades, acredito eu que a partir deste ponto tudo será mais fácil. Para isso, é necessário começar por baby steps (passos de bebê).
Baby steps
Baby steps são (passos de bebê) é um termo que expressa em como o TDD deve ser feito, começando com pequenos passos. A ideia é implementar a aplicação pouco a pouco, para que pequena mudança possa ser refletida e assim ter um feedback melhor, coisas pequenas para se obter resultados pequenos mas conseguir um avanço grande.
Conclusão
Ao utilizar os padrões do DDD, temos um código mais organizado de fácil entendimento, ou seja, qualquer um que comece a codificar um projeto que utilize padrões do DDD, já consegue entender o negócio e a estrutura do projeto. Perderá menos tempo tentando compreender o código, as funcionalidades e o desenvolvimento do software para aquele negócio.
Referências
Domain-driven Design: Atacando as Complexidades no Coração do Software – Eric Evans 2016
Clean Code
Curso Arquitetura de Software – Eduardo Pires
https://www.eduardopires.net.br/2012/06/ddd-tdd-bdd/
https://arquiteturadotnet.wordpress.com/2010/06/29/objeto-de-valor/
https://medium.com/withbetterco/using-aggregates-and-factories-in-domain-driven-design-34e0dff220c3