É comum ver em nosso dia a dia ao desenvolver software tipos primitivos. Mas muitas vezes usamos string, int, bool entre outros tipos de forma desnecessária. O que vamos aprender neste artigo é exatamente como evitar o uso de tipos primitivos e criar classes especializadas. Esse assunto é muito abordado em livros, como Domain Driven Design e também citado por Martin Fowler.

Principais Tipos de dados primitivos em Csharp

Abaixo segue uma lista dos tipos primitivos:

  • int: Números inteiros - Tamanho 32 bits.
  • long: Números Inteiros (Intervalo Maior) - 64 bits.
  • float: Números de ponto flutuante - 32 bits.
  • double: Número de ponto flutuante de precisão dupla - 64 bits.
  • decimal: Valores Monetários - 128 bits.
  • string: Sequência de caracteres. - 16 bits por Caractere.
  • char: Caractere único - 16 bits.
  • bool: Booleano - 8 bits.

O que é Obsessão por Tipos Primitivos?

Obsessão primitiva pode ser considerado um code smell e um anti-pattern onde o desenvolvedor tenta usar primitivos para modelos de domínio. Isso significa que o desenvolvedor está criando um software sem pensar, e acaba apenas criando classes e atribuindo tipos como string e int sem antes pensar se isso realmente faz sentido. A melhor maneira de entender é com um exemplo:

code1.png

No código acima existem três tipos primitivos: string, int e decimal. Sabemos que um CPF precisa ser validado para que a entidade tenha aquele campo salvo com formato válido e não apenas um texto qualquer seja salvo. Então podemos fazer uma validação nessa classe. Essa validação depende muito da regra de negócio e como o domínio será modelado. O exemplo na imagem é simples e está sendo utilizado para deixar o mais didático possível:

code1.png

Temos uma validação. Mas existe um problema. E se mais tarde a aplicação necessite de uma outra classe que precisa utilizar o Cpf também? Vamos ficar replicando o mesmo método de ValidarCPF() para outra classes? Isso não seria muito inteligente e até mesmo fere as boas práticas de desenvolvimento de software. Então aqui vemos que propriedade (atributo) começa agora a ter responsabilidades únicas, que devem ser passadas para uma classe especializada e que possui suas próprias validações. Então agora temos uma classe CPF:

code.png

Primeiro verificamos e retornamos no método apenas números. Depois, se o tamanho do CPF for maior que 11 dígitos será retornado false e será lançada uma ArgumentException que passamos no construtor da classe. Existem outras validações que poderiam ser aplicadas a classe, mas como objetivo é apenas mostrar como podemos evitar em certas ocasiões o uso excessivo de tipos primitivos, esse exemplo já é o suficiente.

Vale destacar que acabamos de criar um Objeto de Valor (Value Object). Mas o que é um objeto de valor?

São objetos sem identidade conceitual e que dão característica a algum outro objeto (no caso classe Funcionário). Em geral, estamos interessados no que eles fazem e não em quem eles são. Eles agregam valor a entidade principal, que em nosso exemplo é a classe funcionário. Mas o que isso realmente significa? Se revisar o código, antes o atributo CPF era um tipo string. Isso é muito ruim pois assim permitimos que o usuário nos envie qualquer tipo de texto, sendo que na verdade podemos apenas receber números, pois o CPF é constituído apenas de números. Então se não existisse um objeto que agregasse valor para esse atributo na classe funcionário, um CPF inválido poderia facilmente ser persistido na base.

Veja como a classe funcionário foi refatorada:

code4.png

Substituímos o que era um tipo primitivo string , por um tipo CPF que tem suas próprias validações. Assim se outra classe no futuro necessitar de um CPF já temos uma classe especializada para ele. Ou seja agregamos valor a classe funcionário utilizando o objeto CPF.

Mas temos também a propriedade Email. Será que ela se encaixa no mesmo caso do CPF? Sim! Se você permitir que o usuário crie um email qualquer e nem mesmo validar se ele contém a estrutura correta de um email, isso pode causar problemas no futuro. Por isso podemos também criar um Value Object para Email :

code7.png

Veja como está a classe Funcionário após a refatoração:

code5.png

Novamente podemos perceber que temos mais um Value Object, que agrega valor a entidade Funcionário. Podemos ir além e criar um objeto de valor para Name, e atribuir um método que nos retorna name e fullName sem a necessidade de declarar dois tipos primitivos dentro da classe Funcionário.

Benefícios de se evitar a Obsessão Primitiva

  • Evita a duplicação de código ajudando na melhor manutenção do Software, seguindo o princípio de design DRY (Don Don't Repeat Yourself).

  • Auxilia a construir um modelo de domínio adequado com objetos que contém suas próprias validações e apenas tem uma única responsabilidade segundo seu próprio contexto, seguindo o princípio de responsabilidade única (SRP).

  • Software com tipos mais fortes, agora é impossível atribuir erroneamente um CPF ou Email com qualquer tipo de texto, o que resultaria em uma falha grave de persistência de dados.

  • Evitar o uso de Primitive Obsession nos ajuda a seguir qualquer Padrão de Design, incluindo Design Orientado a Domínio (DDD).

  • Outro desenvolvedor ao criar uma nova classe com outras responsabilidades, poderá utilizar esse Value Object, em vez de simplesmente passar tipos primitivos. Assim novamente ele está agregando validações a entidade principal e modelando cada vez mais seu domínio.

Conclusão

Vimos como podemos melhorar cada vez mais o nosso Software e como desenvolvemos. Se quiser saber mais sobre o assunto, recomendo a leitura do livro:

Fico por aqui neste post, agradeço muito por ler até aqui! Até o próximo! 😉

Referência:

Refactoring adaptive model