segunda-feira, 21 de janeiro de 2013

ArgumentNullExceptions com Expressions

Introdução

Quando escrevemos código reaproveitável, criamos funções e/ou classes que recebem parâmetros. Na maioria das vezes esses parâmetros são fundamentais na implementação da lógica dentro de uma classe ou método, e o programador mais experiente irá, de forma defensiva, verificar se um determinado parâmetro recebido é válido antes de prosseguir com a lógica, e caso contrário, informar ao cliente (que está consumido o código) do erro cometido com a exceção correta.

Exemplo fictício

Vamos supor que temos uma classe chamada Formulario. Essa classe, quando instanciada, recebe em seu construtor uma instância de outra classe chamada Registro, que representa um registro no banco de dados, veja o código abaixo:

/// <summary>
/// Representa um registro de um banco de dados
/// </summary>
public class Registro {
public void Salvar() {
}
}
/// <summary>
/// Representa um formulário que altera um registro
/// </summary>
public class Formulario {
readonly Registro _registro;
public Formulario(Registro registro) {
_registro = registro;
}
/// <summary>
/// Grava o registro quando o usuário clica em um botão
/// </summary>
public void GravarClick() {
_registro.Salvar();
}
}
view raw parte1.cs hosted with ❤ by GitHub

Note que o formulário recebe um registro, e quando o usuário clicar no botão Gravar, o registro por si mesmo irá ser persistido no banco de dados. Para consumir esse código, basta fazer a seguinte chamada:

var registro = new Registro();
var formulario = new Formulario(registro);
view raw parte3.cs hosted with ❤ by GitHub
Até aí tudo bem, mas a linguagem permite que façamos o seguinte código, sem erros de compilação:
var formulario = new Formulario(null);
view raw parte4.cs hosted with ❤ by GitHub

O erro só será visto em tempo de execução, pois quando o usuário clicar em Gravar, o valor de "registro" é nulo e consequentemente o .NET irá lançar uma exceção não tratada e a aplicação provavelmente irá parar de funcionar.

Adicionando ArgumentNullException

É uma boa prática verificar se um parâmetro é nulo antes de sair consumindo o valor passado por referência.
public class Formulario {
readonly Registro _registro;
public Formulario(Registro registro) {
if (registro == null)
throw new ArgumentNullException("registro");
_registro = registro;
}
}
view raw formulario1.cs hosted with ❤ by GitHub
Desta maneira, destacamos a importância do valor do parâmetro na classe, e que sem um valor válido, lançaremos uma exception informando o nome do argumento inválido. Vale ressaltar que classe ArgumentNullException recebe uma string que representa o nome do argumento que estamos tratando e esse valor deve ser passado de forma correta. Assim, se o cliente que estiver consumindo esse código tratar a excessão dentro de um bloco Try/Catch, ele terá essa informação de forma precisa.

Evitando código tedioso

Imagine agora um método e que contenha 4 parâmetros, todos eles obrigatórios. Teríamos o trabalho tedioso de escrever 4 vezes a mesma expressão, além do trabalho de digitar na mão o nome dos argumentos ao lançar a exception.
Para automatizar essa tarefa, criei uma classe chamada Requires, contendo um método chamado That, para utilizar da seguinte maneira:

public class Formulario {
readonly Registro _registro;
public Formulario(Registro registro) {
Requires.That(() => registro != null);
_registro = registro;
}
// ...
}
view raw formulario2.cs hosted with ❤ by GitHub
O método That recebe uma Lambda Expression que representa uma função que retorna verdadeiro ou falso. Desejamos que o parâmetro "registro" não seja nulo. Caso o valor seja nulo, o método lança automaticamente para nós um ArgumentNullException com o nome do parâmetro corretamente, sem nos preocuparmos em digitar isso na mão, facilitando a nossa vida.
A classe requires ficou conforme o código abaixo:

using System;
using System.Linq.Expressions;
namespace MyNamespace {
public static class Requires {
/// <summary>
/// Compiles the given boolean expression and executes the resulting delegate.
/// If returns False, throws an ArgumentNullException with the correct parameter name
/// </summary>
/// <param name="verifiableExpresson"></param>
public static void That(Expression<Func<bool>> verifiableExpresson) {
var binaryExpression = (BinaryExpression)verifiableExpresson.Body;
var memberExpression = (MemberExpression)binaryExpression.Left;
var paramName = memberExpression.Member.Name;
var compiledExpression = verifiableExpresson.Compile();
if (!compiledExpression.Invoke())
throw new ArgumentNullException(paramName);
}
}
}
view raw Requires.cs hosted with ❤ by GitHub

Outro exemplo com Extension Method

Extension Methods recebem pelo menos um parâmetro (this), e devemos verificar se possuem valores. É muito fácil passar valores nulos para ExtensionMethods, basta fazer um Cast implícito.

No exemplo abaixo, possuímos uma classe Cliente que implementa a interface IEntidade.

/// <summary>
/// Representa o Estado atual da entidade
/// </summary>
public enum EEstado {
Editando,
Inserindo
}
/// <summary>
/// Interface comum para entidades que possuem Estado
/// </summary>
public interface IEntidade {
EEstado Estado { get; set; }
}
public class Cliente : IEntidade {
public string Nome { get; set; }
public EEstado Estado { get; set; }
}
public static class Extensoes {
public static void AlteraEstado(this IEntidade entidade, EEstado estado) {
entidade.Estado = estado;
}
}
view raw exemplo2.cs hosted with ❤ by GitHub
Como no caso anterior, podemos facilmente escrever um programa que consome essas classes chamando o extension method criado:
public static class Program {
public static void Main() {
var cliente = new Cliente();
cliente.AlteraEstado(EEstado.Inserindo); //utiliza o extension method
}
}
view raw program1.cs hosted with ❤ by GitHub
No ExtensionMethod "AlteraEstado", não verificamos se passamos um valor nulo (entidade). Se escrevermos o seguinte código, a aplicação compilará normalmente, e só veremos o problema em tempo de execução, pois o cast que foi feito retornará nulo:
public static class Program {
public static void Main() {
var cliente = new object();
//consome o extension method, passando um valor nulo (object não implementa IEntidade)
(cliente as IEntidade).AlteraEstado(EEstado.Inserindo);
}
}
view raw program2.cs hosted with ❤ by GitHub
O Extension Method AlteraEstado deve ser modificado para ficar assim:
public static class Extensoes {
public static void AlteraEstado(this IEntidade entidade, EEstado estado) {
Requires.That(() => entidade != null);
entidade.Estado = estado;
}
}
view raw AlteraEstado.cs hosted with ❤ by GitHub
Dessa forma o programador será informado corretamente sobre um erro de argumento nulo, e terá oportunidade de fazer correções antes de entregar ao cliente. =)

Nenhum comentário:

Postar um comentário