3 dicas para melhorar seu código orientado a objetos
Introdução Um dos maiores desejos dos desenvolvedores é escrever um código correto e manutenível. Ao longo dos anos, a comunidade juntou um conjunto de ideias, heurísticas e padrões de solução a problemas recorrentes. Este texto tem como objetivo mostrar algumas dicas para tornar seu código mais testável, claro e coeso. Lógica sobre o estado de objetos Digamos que você está escrevendo um sistema de biblioteca e que em determinados momentos é necessário saber o estado de um empréstimo para a execução de determinada lógica. Poderíamos fazer algo do tipo: if(emprestimo.DataDeDevolução > DateTime.Now) { EnviarEmailDeAtraso(); } Será que teria algum problema nisso? Podemos pensar em alguns: E se essa lógica for necessária em outras partes do sistema? Pensando nas bases da orientação a objetos, comportamentos devem estar pertos dos dados que utilizam. Logo, a coesão das partes do sistema está prejudicada. Além da repetição de código desnecessária, risco de cometer algum erro como usar um maior ou igual (≥) ao invés de um maior (>). Como seriam os testes? Por enquanto, não é possível testar esse estado isoladamente, o que pode causar redundância de testes em outras classes. Caso seja uma necessidade, também não é possível testar com outras datas, apenas DateTime.Now. A partir dessas informações, como poderíamos melhorar esse código? Uma sugestão: if(emprestimo.ExpiradoEm(DateTime.Now)) { EnviarEmailDeAtraso(); } public bool ExpiradoEm(DateTime dateTime) { return emprestimo.DataDeDevolução > dateTime; } ou até: public bool Expirou() { return emprestimo.DataDeDevolução > DateTime.Now; } Lógica sobre o estado de uma lista Continuando no contexto de empréstimo de livros, digamos que precisamos saber algumas informações sobre os empréstimos de determinado usuário: Quantos empréstimos estão abertos? Quais empréstimos expiram nessa semana? O usuário pegou algum livro com um título específico? Poderíamos fazer algo semelhante a: public class Usuario { //Essa lista é inicializada no construtor public List Emprestimos; public Usuario() { Emprestimos = new List(); } } Usuario user = new Usuario(); //Neste momento a lista de empréstimos possui n elementos. //Quantos empréstimos estão abertos? int quantidadeDeEmprestimosAbertos = user .Emprestimos .Count(x => !x.Expirou()); //Quais empréstimos expiram nessa semana? DateTime daquia6dias = DateTime.Now.AddDays(6); DateTime daquia7dias = DateTime.Now.AddDays(7); List = emprestimosQueExpiramEssaSemana = user .Emprestimos .Where(x => !x.ExpiradoEm(daquia6dias) && x.ExpiradoEm(daquia7dias)) .ToList(); //O usuário pegou algum livro com um título específico? bool jaPegouEsseLivro = user .Emprestimos .Any(x => string.Equals(x.Titulo, "Refatoração")); Quais problemas identificamos no código acima? Os mesmos problemas que vimos anteriormente, mas focados em uma coleção. No final das contas, a lista de empréstimos também é tem um sentido coeso pro domínio. Seguindo as ideias já comentadas, uma sugestão de solução seria: public class TodosEmprestimos { private List _emprestimos; public int NumeroDeAbertos() { return _emprestimos.Count(x => !x.Expirou()); } public List EmprestimosQueExpiramEm(DateTime expiracao) { DateTime umDiaAntesDaExpiracao = expiracao.AddDays(-1); return _emprestimos .Where(x => !x.ExpiradoEm(umDiaAntesDaExpiracao) && x.ExpiradoEm(expiracao)) .ToList(); } public bool PegouLivro(string titulo) { bool PegouEsseLivro = _emprestimos.Any(x => string.Equals(x.Titulo, titulo)); } } public class Usuario { public TodosEmprestimos Emprestimos; public int NumeroDeEmprestimosAbertos() { return Emprestimos.NumeroDeAbertos(); } public List EmprestimosQueExpiramEmUmaSemana() { DateTime daquia7dias = DateTime.Now.AddDays(7); return EmprestimosQueExpiramEm(daquia7dias); } public bool PodePegarEsseLivro(string titulo) { return !Emprestimos.PegouEsseLivro(titulo); } } Ganhamos coesão, reusabilidade e testabilidade. Outro ponto: se quiséssemos impor uma estrutura de dados específica pra coleção por uma demanda de negócio, o código estaria alinhado as necessidades do mundo real. Por exemplo, se precisássemos que os empréstimos estivessem ordenados pelo tempo, o encapsulamento como uma classe permitiria mais facilmente. Tipo primitivo com significado para o domínio A última dica vai mostrar como trazer o domínio para mais perto do código. Muito provavelmente, nosso sistema de biblioteca precisa guardar o CPF do usuário. Como o documento transita pelo sistema? Um campo string na classe usuário? A partir do que já comentamos, quais os prováveis problemas dessa abordagem? public Usuario { public string CPF {get; pr

Introdução
Um dos maiores desejos dos desenvolvedores é escrever um código correto e manutenível. Ao longo dos anos, a comunidade juntou um conjunto de ideias, heurísticas e padrões de solução a problemas recorrentes. Este texto tem como objetivo mostrar algumas dicas para tornar seu código mais testável, claro e coeso.
Lógica sobre o estado de objetos
Digamos que você está escrevendo um sistema de biblioteca e que em determinados momentos é necessário saber o estado de um empréstimo para a execução de determinada lógica. Poderíamos fazer algo do tipo:
if(emprestimo.DataDeDevolução > DateTime.Now)
{
EnviarEmailDeAtraso();
}
Será que teria algum problema nisso? Podemos pensar em alguns:
- E se essa lógica for necessária em outras partes do sistema? Pensando nas bases da orientação a objetos, comportamentos devem estar pertos dos dados que utilizam. Logo, a coesão das partes do sistema está prejudicada. Além da repetição de código desnecessária, risco de cometer algum erro como usar um maior ou igual (≥) ao invés de um maior (>).
- Como seriam os testes? Por enquanto, não é possível testar esse estado isoladamente, o que pode causar redundância de testes em outras classes. Caso seja uma necessidade, também não é possível testar com outras datas, apenas DateTime.Now.
A partir dessas informações, como poderíamos melhorar esse código? Uma sugestão:
if(emprestimo.ExpiradoEm(DateTime.Now))
{
EnviarEmailDeAtraso();
}
public bool ExpiradoEm(DateTime dateTime)
{
return emprestimo.DataDeDevolução > dateTime;
}
ou até:
public bool Expirou()
{
return emprestimo.DataDeDevolução > DateTime.Now;
}
Lógica sobre o estado de uma lista
Continuando no contexto de empréstimo de livros, digamos que precisamos saber algumas informações sobre os empréstimos de determinado usuário:
- Quantos empréstimos estão abertos?
- Quais empréstimos expiram nessa semana?
- O usuário pegou algum livro com um título específico?
Poderíamos fazer algo semelhante a:
public class Usuario
{
//Essa lista é inicializada no construtor
public List<Emprestimo> Emprestimos;
public Usuario()
{
Emprestimos = new List<Emprestimo>();
}
}
Usuario user = new Usuario();
//Neste momento a lista de empréstimos possui n elementos.
//Quantos empréstimos estão abertos?
int quantidadeDeEmprestimosAbertos =
user
.Emprestimos
.Count(x => !x.Expirou());
//Quais empréstimos expiram nessa semana?
DateTime daquia6dias = DateTime.Now.AddDays(6);
DateTime daquia7dias = DateTime.Now.AddDays(7);
List<Emprestimo> = emprestimosQueExpiramEssaSemana =
user
.Emprestimos
.Where(x => !x.ExpiradoEm(daquia6dias) && x.ExpiradoEm(daquia7dias))
.ToList();
//O usuário pegou algum livro com um título específico?
bool jaPegouEsseLivro =
user
.Emprestimos
.Any(x => string.Equals(x.Titulo, "Refatoração"));
Quais problemas identificamos no código acima? Os mesmos problemas que vimos anteriormente, mas focados em uma coleção. No final das contas, a lista de empréstimos também é tem um sentido coeso pro domínio. Seguindo as ideias já comentadas, uma sugestão de solução seria:
public class TodosEmprestimos
{
private List<Emprestimo> _emprestimos;
public int NumeroDeAbertos()
{
return _emprestimos.Count(x => !x.Expirou());
}
public List<Emprestimo> EmprestimosQueExpiramEm(DateTime expiracao)
{
DateTime umDiaAntesDaExpiracao = expiracao.AddDays(-1);
return _emprestimos
.Where(x => !x.ExpiradoEm(umDiaAntesDaExpiracao) && x.ExpiradoEm(expiracao))
.ToList();
}
public bool PegouLivro(string titulo)
{
bool PegouEsseLivro = _emprestimos.Any(x => string.Equals(x.Titulo, titulo));
}
}
public class Usuario
{
public TodosEmprestimos Emprestimos;
public int NumeroDeEmprestimosAbertos()
{
return Emprestimos.NumeroDeAbertos();
}
public List<Emprestimo> EmprestimosQueExpiramEmUmaSemana()
{
DateTime daquia7dias = DateTime.Now.AddDays(7);
return EmprestimosQueExpiramEm(daquia7dias);
}
public bool PodePegarEsseLivro(string titulo)
{
return !Emprestimos.PegouEsseLivro(titulo);
}
}
Ganhamos coesão, reusabilidade e testabilidade. Outro ponto: se quiséssemos impor uma estrutura de dados específica pra coleção por uma demanda de negócio, o código estaria alinhado as necessidades do mundo real. Por exemplo, se precisássemos que os empréstimos estivessem ordenados pelo tempo, o encapsulamento como uma classe permitiria mais facilmente.
Tipo primitivo com significado para o domínio
A última dica vai mostrar como trazer o domínio para mais perto do código. Muito provavelmente, nosso sistema de biblioteca precisa guardar o CPF do usuário. Como o documento transita pelo sistema? Um campo string na classe usuário? A partir do que já comentamos, quais os prováveis problemas dessa abordagem?
public Usuario
{
public string CPF {get; private set;}
}
Possível resposta: Dificuldade de validação, menos focalização pros testes, menos coesão, etc.
Como podemos melhorar isso? Se criarmos uma classe para representar esse conceito, podemos ter certas vantagens.
public CPF
{
private string cpf;
private CPF(string cpfValido)
{
cpf = cpfValido;
}
public static CPF Create(string cpf)
{
//aqui estaria o algoritmo de validação
if(isValid(cpf))
{
return CPF(cpf);
}
throw new ArgumentException("CPF informado inválido");
}
public string EmTextoLimpo()
{
return cpf;
}
}
public User
{
public CPF cpf {get; set;}
}
Agora com uma classe, podemos fazer diversos testes para o algoritmo aqui nessa classe, a validação está isolada e, por fim, aumentamos a coesão do conceito de CPF. O ponto de atenção a se levar em conta é a facilidade de verificar tal comportamento em tempo de execução com o que a linguagem/framework já disponibiliza, ou seja, se é muito fácil fazer essa verificação usando algum recurso nativo talvez não valha a pena criar um nova classe, cabe a análise.
Conclusão
A programação orientada a objetos nos traz a possibilidade de criar códigos coesos, modulares e encapsulados. Contudo, isso não vem de graça, é necessária a utilização das boas práticas e de uma reflexão se estamos fazendo nosso código OO da melhor forma. Este texto foi inspirado e baseado no módulo de heurísticas para orientação objetos da Jornada Dev+Eficiente. Por fim, avalie se as dicas que passei fazem sentido para seu código. Obrigado!