Esta publicação está disponível em vídeo, ampliada e revisada, no canal da EximiaCo.
Com frequência, em nossas consultorias, encontramos códigos que fazem uso excessivo de tipos primitivos.
public class Employee { public string Id { get; set; } public string Name { get; set; } public string Cpf { get; set; } public decimal Salary { get; set; } }
No exemplo acima, usou-se string
para representar pelo menos três tipos diferentes de dados. Essa abordagem “anêmica” acaba criando dificuldades, por exemplo, para implantação de validações.
public class Employee { public string Id { get; set; } public string Name { get; set; } string _cpf; public string Cpf { get { return _cpf; } set { // .. validation Logic _cpf = value; } } public decimal Salary { get; set; } }
Uma saída ingênua comum é implantar o código de validação nos Setters das propriedades. O problema, é que essa abordagem leva a duplicação de código e repetição desnecessária de testes.
public class Employee { public string Id { get; set; } public string Name { get; set; } string _cpf; public string Cpf { get { return _cpf; } set { if (Utils.CheckCpf(value)) { throw new ArgumentException(“...”); } _cpf = value; } } public decimal Salary { get; set; } }
Como aprimoramento da solução, muitos times acabam desenvolvendo bibliotecas de uso comum (com nomes criativos como Comum, Utils, …). Entretanto, é importante observar que essas bibliotecas acabam se afastando do paradigma orientado a objetos.
Nossa recomendação é desenvolver um sistema de tipos mais rico que se afaste das primitivas.
public struct Cpf { private readonly string _value; private Cpf(string _value) { _value = value; } public static Cpf Parse(string value) { if (TryParse(value, out var result)) { return result; } throw new ArgumentException(...); } public static bool TryParse(string value, out Cpf cpf) { //.. validation Logic } public static implicit operator Cpf(string value) => Parse(value); }
Esse tipo, por estar implementado como struct
, tem custo mínimo para o runtime. Além disso, torna as implementações que o utilizam mais limpas (por não ter de explicitar a validação) e mais expressivas (pelo tipo do dado).
Observe, também, que o conversor implícito mantem esse tipo compatível com a primitiva mais próxima (no caso, string
).
Seus projetos fazem uso obsessivo dos tipos primitivos? O que acha da abordagem que estamos propondo?
Parabéns pelo Post! Conteúdo incrível.
Muitas vezes achamos que sabemos tudo e que não podemos mais nos surpreender.
São pequenos detalhes como esse que fazem muita diferença.
Achei interessante a abordagem. E como fica a implementação da classe CPF na classe Employee? E se esta classe fosse uma classe de domínio no EF?
Elemar.
Gostei muito da implementação, mas tenho uma duvida. A muito tempo atrás fiz um sistema que necessitava de muita performance e optei por utilizar structs, e por serem “value type” eu tive problemas quando precisa que fosse tratadas como “reference type” (não sei se me entendeu). Num sistema multi thread esse tipo de implementação não dispenderia muito tempo de desenvolvimento pensando no fato que eu teria que tratar a aplicação toda como value type? E o fato de que os métodos são static’s não acarretaria em race condition num sistema multi thread?
Em resumo eu aprecio muito esse tipo te implementação.
Obrigado por compartilhar.
Ótima consideração, Elemar! Gosto da abordagem proposta. Coincidentemente, estou utilizando bastante em meu atual projeto.
Fiquei com uma dúvida, no entanto.
O implicit operator do exemplo lança, ainda que indiretamente, uma exceção.
A documentação afirma que: “Predefined C# implicit conversions always succeed and never throw an exception. User-defined implicit conversions should behave in that way as well.” [https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/user-defined-conversion-operators]
A abordagem estaria de acordo utilizando-se um explicit operator.
Seria muito ruim chamar o método Parse já no construtor?
Sei que também não é muito indicado, mas no caso de um tipo, meu raciocínio é o seguinte: “se essa string _value não representar um CPF válido, nem continuamos a conversa”.
Muito obrigado por compartilhar conteúdo de qualidade e inspirador!
Nesse caso tu poderia usar uma static factory ao invés do construtor e validar antes de criar o objeto de fato. 🙂