Olá pessoal, como estamos?
Exceptions são muito importantes. Elas funcionam como alertas de que algo não está bem. Lançamos exceptions, por exemplo, quando:
- um método é chamado com um parâmetro inválido;
- há uma tentativa de colocar um objeto em estado inválido.
Há muitos posts e artigos que mostram como deve ser a captura e tratamento eficiente de Exceptions (código cliente). Infelizmente, há bem pouco material disponível sobre formas eficientes de lançar exceptions. O post de hoje é dedicado a esse tema.
O problema
Lançar Exceptions é a opção natural para alertar um comportamento não-OK de parte do sistema.
É obrigação de todo objeto garantir que seu estado permaneça válido (consistente). Mais que isso, é obrigação de todo objeto denunciar “objetos irresponsáveis” que tentem o colocar em estado inválido.
Lançar exceptions também é a caminho mais comum para tornar evidente que um método foi evocado com um parâmetro inadequado. Sendo radical, um método deve rejeitar qualquer chamada inválida e tornar evidente que fez seu trabalho devido a entradas inválidas.
public void DoSomething(object a)
{
if (a == null)
throw new ArgumentNullException("a");
// ...
}
Ao lançar Exceptions, estamos dando a oportunidade para que os programadores de códigos clientes possam fazer correções rapidamente e evitar comportamentos inesperados ou indesejados.
Entretanto, lançar um exception não é uma decisão fácil. Sempre precisamos considerar:
- Que tipo de exception devo lançar?
- Que mensagem fornecer?
- Como tratar sua localização (idioma e cultura)?
- Como garantir uma escolha coerente de tipos Exceptions e mensagens em todo o sistema?
O código que apresentei acima mostra a opção de um programador para lançar um Exception. Ela foi uma escolha adequada? Considere o seguinte bloco de código:
public void DoSomething(string a)
{
if (String.IsNullOrEmpty(a))
throw new ArgumentException("Parameter a cannot be Null nor Empty");
// ...
}
Observe que uma mesma Exception é lançada para o valor null or empty. Querendo ou não, temos um caso claro de comportamento inconsistente.
As questões que apresentei são apenas algumas das dúvidas que precisam ser resolvidas claramente para todos o time antes que qualquer código seja escrito. Mas o que queremos?
- Garantir um padrão coerente na escolha de tipos de exceptions. Por exemplo, se um parâmetro for passado nulo, que sempre seja lançada uma ArgumentNullException (nunca ArgumentException);
- Garantir um padrão coerente nas mensagens associadas às instâncias das exceptions;
- Facilitar a localização (cultura e idioma) das mensagens associadas às exceptions;
- Possibilitar adequação das Exceptions para diferentes cenários de uso, possibilitando, inclusive, a alteração do tipo de Exception que está sendo lançado sem adicionar complexidade ao código origem;
- Permitir que o time se concentre no negócio no lugar de aspectos técnicos.
A solução (uma proposta, pelo menos)
Criar um componente que centralize o lançamento de Exceptions para todo o sistema. Esse componente deve permitir ao desenvolver se concentrar no negócio. Observe os exemplos apresentados acima reapresentados com o conceito que defendo hoje:
public void DoSomething(object a)
{
Throw.IfArgumentNull(a, "a");
// ...
}
Repare como o código já fica mais expressivo e mais limpo. Repare que retiramos do programdor a responsabilidade de saber que Exception será disparada. Há um foco muito mais claro no negócio. Vamos ao outro exemplo:
public void DoSomething(string a)
{
Throw.IfArgumentNullOrEmpty(a, "a");
// ...
}
Mais uma vez, perceba como o código ficou mais limpo. Além disso, tiramos do desenvolvedor a carga de escolher a exception que será lançada. Centralizar o lançamento de exceptions garantiu consistência. Mais que isso, retiramos do desenvolvedor a responsabilidade de ter que escrever a mensagem associada a exception.
Implementação dos métodos de verificação/consistência
Quero começar o passeio de nossa implementação mostrando o código fonte associado a IfArgumentNull. Observe:
[DebuggerStepThrough]
public static void IfArgumentNull(
object argument,
string argumentName,
Func< Exception, Exception > modifier = null
)
{
if (argument != null) return;
Throw.Now(
new ArgumentNullException(argumentName),
modifier
);
}
Primeiro, observe como transferimos a criação da Exception para o interior desse método. No lugar de lançar a exception aqui chamo ainda um outro método. Observe também que decorei o método com o atributo DebuggerStepThrough, assim faço com que o método não seja considerado pela depuração passo-a-passo.
Observe agora o método IfArgumentNullOrEmpty:
[DebuggerStepThrough]
public static void IfArgumentNullOrEmpty(
string argument,
string argumentName,
Func<Exception, Exception> modifier = null
)
{
Throw.IfArgumentNull(argument, argumentName, modifier);
if (!String.IsNullOrEmpty(argument)) return;
Throw.Now(
new ArgumentException(
string.Format(
CultureInfo.CurrentUICulture,
ThrowResources.ArgumentNullOrEmpty,
argumentName)
),
modifier
);
}
Aqui, temos um exemplo bacana. Nosso IfArgumentNullOrEmpty lança um ArgumentNullException caso o argumento esteja nulo e um ArgumentException caso o argumento esteja Empty.
Por favor, observe como também garantimos a padronização da mensagem associada a exception, uma vez que estamos construindo a mensagem no interior do método. Perceba como é prático transportar a string para um resource, facilitando a localização.
Modificando o “comportamento” do sistema com Modifiers
Nosso mecanismo para lançamento de Exceptions pode ser ajustado através da passagem de um Modifier como argumento (opcional). O que esse “Modifier” faz é capturar a Exception que seria lançada inicialmente, realizar algum processamento e devolver uma exception transformada (ou a original, conforme a regra do negócio).
Essa inteligência permite que que, por exemplo, possamos utilizar uma “infraestrutura” de exceptions conforme contexto. Por exemplo, gerando uma “FaultException” no WCF.
Observe a implementação do método central Now que “opera” essa computação:
public static Func<Exception, Exception> GlobalModifier { get; set; }
[DebuggerStepThrough]
public static void Now(
Exception exception,
Func<Exception, Exception> modifier = null
)
{
if (exception == null)
throw new InvalidOperationException(ThrowResources.ExceptionCannotBeNull);
Exception e = exception;
if (modifier != null)
e = modifier(exception) ?? exception;
if (GlobalModifier != null)
e = GlobalModifier(e) ?? exception;
throw e;
}
O que fizemos? Definimos uma propriedade para um “modifier global” e, antes de finalmente realizar o throw, submetemos a exception as duas modificações. Com alguma criatividade, podemos incorporar, inclusive, um mecanismo de log para as exceptions.
Bacana!
Por hoje, era isso.
![]()






Denis Ferrari
19/04/2011
Muito bom post Elemar!
Perguntinha básica: Como tratar os argumentos inválidos da classe que trata os argumentos inválidos? rs…
Abraços!
elemarjr
19/04/2011
Da forma como fiz no método Now
Waldyr Felix
19/04/2011
Muito bom o post Elemar.
Também gostei bastante da solução, mas para esse caso em especifico ficaria mais feliz em ver algo do tipo:
[VerifyIfArgumentIsNull(a)]
public void DoSomething(string a)…
Decorando desta forma acredito que deixe o código ainda mais limpo, o que vc acha?
[]s
elemarjr
19/04/2011
Sim, deixaria mais limpo. Mas, para isso, precisaria alterar o compilador. Se fosse em Boo…
Douglas Aguiar
19/04/2011
Muito legal!!!
Rola uma lib pro NuGet hein…
elemarjr
19/04/2011
Projeto ainda pequeno. Quando ganhar corpo, com certeza.
tisciencia
19/04/2011
Bacana Elemar.
Também sou a favor de tratar das mesmas “coisas” em um único ponto.
A idéia do [Waldyr Felix] poderia ser implementada em F# tbm.
Abraços!
Paulo Marco
20/04/2011
Muito bom post, parabéns. E quanto ao Exception Handling do Enterprise Library você acha uma boa opção para o controle de exceptions ?
Wallison Santos
20/04/2011
Olá Elemar excelente o post.
Gostaria de saber como seria a implementação de um
IfFileNotFound(string filePath, Func modifier = null). Se eu entendi bem eu teria que chamar oThrow.IfArgumentNullOrEmptydentro do antes de verificar se o arquivo realmente existe. Correto?elemarjr
22/04/2011
Eu faria exatamente isso. Até porque seria uma forma mais inteligente de informar o dev “cliente” que o código dele está passando null.