Recentemente passei a desenvolver códigos mais defensivos com o intuito de trazer maior estabilidade aos frameworks dos quais participo. Me propus a sempre validar os argumentos passados aos métodos antes de executá-los. As vantagens dessa abordagem são as seguintes:

  • Diminuir ocorrências da exceção NullReferenceException
  • Aumentar legibilidade do código pois você não irá codificar ifs de validação no corpo do método
  • Aumenta a consistência dos seus testes unitários

Para notificar o cliente de que uma operação ilegal ocorreu nós temos duas opções:

  1. Lançar uma exceção descrevendo o erro
  2. Utilizar o comando Debug.Assert

As duas opções têm comportamentos parecidos mas na prática são bastante diferentes. Se optarmos por lançar uma exceção para toda inconsistência nos argumentos, o cliente a utilizar a classe terá um grande número de preocupações extras pois ele terá que capturar cada uma das exceções. E com base em alguns livros que li no passado, nós não devemos controlar o fluxo da nossa aplicação através de exceções.

Já o uso de Debug.Trace, tem um comportamento interessante. Ele funciona da seguinte maneira:

Dada uma função Debug.Assert([expressão condicional], [mensagem livre]) se a expresão condicional for falsa ela irá exibir uma mensagem de alerta no desktop do usuário exibindo a StackTrace e opções para:

  • Ir para a linha de erro no código fonte
  • Interromper a execução do aplicativo
  • Ignorar a mensagem

O comportamento acima pode ser interessante ao desenvolvedor, já em ambientes de produção uma mensagem de alerta no servidor pode ter consequências ruins se ela ficar se repetindo levando até ao website cair. Dessa forma, devemos ser cuidadosos e devemos sempre usar dlls compiladas em release pois elas automaticamente ignoram os códigos Debug.Assert.

Agora que você conheceu as duas abordagens vamos explorar um pouco mais sobre as vantagens desse código:

  • A grande maioria das exceções NullReferenceException, são causadas por argumentos inválidos nas funções. E seguindo a Lei de Murphy, essas exceções ocorrem sempre depois de você ter feito algo importante como: mandar um e-mail, apagar um arquivo ou atualizar um registro no banco. O nosso objetivo é evitar que nosso código seja interrompido no meio do caminho, e podemos diminuir muito as chances não permitindo que ele seja executado logo no início do método caso os argumentos sejam inválidos.
  • Antigamente eu validava o estado de uma variável imediatamente antes de usá-la. E isso inseria uma série de ifs no corpo do método. O que dificultava o entendimento de alguns códigos. Agora eu valido logo no início do método.
  • Ao realizar testes unitários você verá que com esse tratamentos os seus casos de testes ficam bem mais fáceis.

Já vimos então quais opções de tratamentos nós temos e o porque de usá-las. Vamos agora decidir quando usar cada uma, em todos os seus métodos públicos utilize um código semelhante a esse:

/// <summary>
/// Altera a pergunta e resposta secretas
/// </summary>
/// <param name="password">Senha atual
/// <param name="newPasswordQuestion">Nova pergunta secreta
/// <param name="newPasswordAnswer">Nova resposta secreta
/// <returns>True se a pergunta secreta foi alterada</returns>
[PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
public bool ChangePasswordQuestionAndAnswer(string password, string newPasswordQuestion,
                                            string newPasswordAnswer)
{
    #region debug
 
    if (string.IsNullOrEmpty(password))
        throw new ArgumentNullException("password", Messages.EmptyPassword);
 
    if (string.IsNullOrEmpty(password))
        throw new ArgumentNullException("newPasswordQuestion", Messages.EmptyPasswordQuestion);
 
    if (string.IsNullOrEmpty(newPasswordAnswer))
        throw new ArgumentNullException("newPasswordAnswer", Messages.EmptyPasswordAnswer);
 
    #endregion
 
    ...
}

Já nós métodos private, internal ou protected utilize códigos assim:

/// <summary>
/// Verifica se a reposta secreta é válida
/// </summary>
/// <param name="passwordAnswer">Resposta secreta
/// <returns>True se for válido</returns>
private bool ValidatePasswordAnswer(string passwordAnswer)
{
    #region debug
 
    Debug.Assert(!string.IsNullOrEmpty(PasswordQuestion));
    Debug.Assert(!string.IsNullOrEmpty(passwordAnswer));
 
    #endregion
 
    ...
}

Por experiência própria os métodos publicos não deviam executar Debug.Assert. Fica aí a dica aos desenvolvedores de frameworks e bibliotecas. Coloque sua dúvida nos comentários.