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

May 12, 2025 - 11:07
 0
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:

  1. 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 (>).
  2. 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!