Friday, September 2, 2011

Implementando Bons Métodos em C#

Regras para construir bons métodos:
  • 1a regra: métodos devem ser pequenas
  • 2a regra: métodos devem ser menores ainda
  • Até 30 linhas. (Sugestão: deve caber na tela)
  • Métodos devem fazer somente uma tarefa mas devem fazer isso bem.
Quanto menor o código, melhor
  • Antes: O método ObterLogradouros esta longo demais:
public virtual IList ObterLogradouros()
{
  IList lstLogradouros = new ArrayList();
  foreach(Logradouro logradouro in this.relLogradouros)
  {
    if (logradouro.GetType() != typeof(Logradouro))
    {
      throw new SystemException("LogradouroVitoria se associa apenas com BairroVitoria.");			
    }
    // Verifica se o logradouro ja foi adicionado na lista
    bool bAdicionado = false;
    for (int i=0; i < lstLogradouros.Count; i++)
    {
      if (((Logradouro)lstLogradouros[i]).Id == logradouro.Id)
      {
        bAdicionado = true; break;
      }					
    }			
    // Se nao foi adicionado na lista
    if (! bAdicionado)
    {
      lstLogradouros.Add(logradouro);
    }
  }
  return lstLogradouros;
}
  • Depois: o método ObterLogradouro se resume a menos linhas:
public virtual IList ObterLogradouros()
{
  IList lstLogradouros = new List();
  foreach(Logradouro logradouro in this.relLogradouros)
  {
    EhLogradouroValidoParaBairro(logradouro);				
    if (!lstLogradouros.Contains(logradouro))
    {
      lstLogradouros.Add(logradouro);
    }				
  }
  return lstLogradouros;
}

Método deve se concentrar em apenas uma tarefa:
  • Antes: Método faz várias tarefas:
public void autenticarLogin(Sessao sessao,string strSenha,
                 Pessoa pessoaSelecionada,Sistema sistema)
{
  if(strSenha == string.Empty)
  {
    throw new ApplicationException
         ("Favor preencher o campo senha!");
  }
  if (pessoaSelecionada == null)
  {
    throw new ApplicationException
      ("Pessoa inexistente para o documento informado.");
  }
  Usuario usuarioPessoaSelecionada = DAOFactory.ObterDAOFactory()
    .ObterDAO(sessao).ObterUsuario(pessoaSelecionada);
  if(usuarioPessoaSelecionada == null)
  {					
    string strErro = string.Empty;
    strErro += "A pessoa informada não possui permissão ";
    strErro += "para nenhum sistema!";			
      throw new 
    ApplicationException(strErro);
  }
	if(!usuarioPessoaSelecionada.eAtivo())
  {					
    throw new 
      ApplicationException("O usuário informado está desativado!");
  }				 
  Projeto projeto = sistema.ObterProjeto();	
  if(!DAOFactory.ObterDAOFactory().ObterDAO(sessao)
    .ValidarSenhaProjeto(strSenha,projeto,usuarioPessoaSelecionada))
  {					 
    throw new ApplicationException("Senha inválida!");
  }			            
  AutenticarUsuario(usuarioPessoaSelecionada);			
}   
  • Depois: Método se concentra em apenas uma tarefa:
public void AutenticarUsuario(Sessao sessao,string strSenha,
                 Pessoa pessoaSelecionada,Sistema sistema)
{  
  Usuario usuario = ObterUsuario(sessao,pessoaSelecionada,sistema);
  ValidarSenha(sessao,usuario,senha,sistema);			
}


Deve haver o mesmo nível de abstração para cada linha do método
  • Antes: Níveis de abstração diferentes (checagem de strings)
public void AlteracaoObservacaoHistorico(
            Sessao sessao, string strObservacao, Historico historico)
{
  if ((strObservacao == null) && (strObservacao == string.Empty))
  {
    throw new ApplicationException("Observação do histórico deve ser informada.");
  }
  historico.AtribuirObservacao(strObservacao);
  sessao.BeginTransaction();
  DAOFactory.ObterDAOFactory().Salvar(sessao,ref historico);
  sessao.CommitTransaction();
}
  • Depois: Código com mesmo nível de abstração
public void AlteracaoObservacaoHistorico(
            Sessao sessao, string strObservacao, Historico historico)
{
  historico.AtribuirObservacaoComChecagem(strObservacao);
  sessao.BeginTransaction();
  DAOFactory.ObterDAOFactory().Salvar(sessao,ref historico);
  sessao.CommitTransaction();
}

Use nomes descritivos
Uma função com nome descritivo é melhor que um comentário descritivo.

  • Antes - código não muito claro auxiliado por comentários

// Verifica se a pessoa é um funcionário aposentado
if (funcionario.EstaAtivo()&&funcionario.ObterIdade()>65) 
{
  // Calculo o valor da sua aposentadoria
  valor = pessoa.CalculaValor();
} 

  • Depois - código claro com métodos descritivos

if (pessoa.EstaAposentado())
{
  valorAposentadoria = pessoa.CalculaValorAtualDaAposentadoria();      
}

Manter consistência com nomes de funções:


pessoa = DAO.Factory.ObterDAO().RecuperarPeloNome("Joelson");
pessoa = DAO.Factory.ObterDAO().RecuperarPeloCPF("08766563489");
pessoa = DAO.Factory.RecuperarPeloId(12);
pessoa = DAO.Factory.RecuperarPelaMatricula(524567);

Funções descritivas não surpreendem (negativamente) o programador

Quanto menos argumentos, melhor
  • O número ideal de argumentos para funções: 0 (ZERO), depois vem funções com 1 argumento, por último, funções com 2 argumentos
  • Funções com 3 ou mais argumentos devem ser evitadas
  • Quanto maior o número de argumentos, maior a possibilidade de confusão e erro

  • Antes: Método com muitos argumentos
public void DesenhaRetangulo(int xi,int yi,int xf,int yf,int red,int green,int blue)
{ ... }


  • Depois: Métodos com poucos argumentos

public void DesenhaRetangulo(Ponto cantoInicial,Ponto CantoFinal,RGB cor) { ... }


  • Depois: refuzindo mais ainda os argumentos

public void DesenhaRetangulo(BordaRetangulo borda,RGB cor) { ... }

Funções não devem ter efeito colateral inesperado
public class Tarefa
{
  private string _descricao;
  private DateTime _dataCriacao;
  public string Descricao
  {
    set 
    { 
      // Uma nova descrição é uma nova tarefa
      if (_descricao != value)
      {
        _dataCriacao = DateTime.Now;
      }
      _descricao = value;
    }
    get
    {
      return _descricao;
    }
  }
}

Argumentos de saída
  • Argumentos são naturalmente interpretado apenas como entrada
  • Argumentos de saída devem ser evitados, se a função tiver que
    mudar algo, então que mude o objeto que contém a mesma
Separação Comando - Consulta
As funções devem ser de comando ou de consulta mas nunca ambos logo um método deve retornar informações sobre um objeto ou mudar o seu estado mas ambas as tarefas frequentemente levam a confusão.

  • Antes: AtribuirDado é consulta e comando

if (pessoa.AtribuirDado("Nome","Pedro"))
{
  throw new ApplicationException("Atributo inválido");
}

  • Depois: Existe um método para consulta e outro para comando

if (pessoa.PossuiAtributo("Nome"))
{
  pessoa.AtribuirDados("Nome","Pedro");
}


Prefira exceções a retornar códigos de erro
Funções que retornam erros de código violam a regra de separação de comando e consulta pois estas fazem com que funções de comando sejam usados como funções de consulta. Finalmente, o maior problema é que o sistema que usa o método deve tratar o erro imediatamente no decorrer do fluxo.

  • Antes: Metodos retornam código de erro

if (funcionario.CalcularSalario() == EVENTO_OK)
{
  Salario salario = funcionario.ObterSalarioAtual();
  if (!salario.TransferirValorParaContaBancaria() == EVENTO_OK)
  {
    throw new ErroEventoException("Erro ao transferir valor conta"); 
  }
  if (!funcionario.Alegrar() == EVENTO_OK)
  {
    throw new ErroEventoExceptrion("Erro ao alegrar o funcionário");
  }
  if (!funcionario.EnviarEmailAgradecendo() == EVENTO_OK)
  {
    throw new ErroEventoExceptrion("Erro ao enviar email agradecendo");
  }  
}

  • Depois: Métodos disparam exceções que são capturadas separadamente

try
{
  funcionario.CalcularSalario();
  Salario salario = funcionario.ObterSalarioAtual();
  salario.TransferirValorParaContaBancaria();
  funcionario.Alegrar();
  funcionario.EnviarEmailAgradecendo();
}
catch(Exception exception)
{
  ...
}
finally
{
  ...
}


Não repita blocos de código (no copy-paste)
Código duplicado é a origem de vários males e por isso, vários padrões de projeto e práticas foram criados com o propósito de eliminar a duplicação. Entretanto, às vezes não é tão fácil identificar a repetição de código e essa repetição é uma omissão que abre a oportunidade para que erros aconteçam.