C#  -  Usando Guard Clauses


 Hoje veremos o conceito de Guard e como usar Guard Clauses para otimizar o seu código.

O termo "guard" é frequentemente usado para se referir a uma técnica de programação que verifica condições de pré-condição em um método ou função.


Também conhecido como "guard clauses" ou "guard conditions", esse padrão de programação tem como objetivo verificar se as condições necessárias para a execução correta de um método são atendidas e, caso contrário, retornar ou lançar uma exceção antes de executar o restante do código.

Seguindo as diretrizes de programação defensiva e o design de sistemas fail-fast, um método deve sempre validar sua entrada.

Nota : O termo "fail-fast" é uma abordagem de design de sistemas que enfatiza a detecção rápida e o tratamento imediato de falhas ou erros.

O código que valida as entradas do seu método é chamado de Guard Clause ou cláusulas de Guarda, e, isso torna seu código mais compreensível e protege você de bugs e comportamentos inesperados.

Suponha que temos o código a seguir:

void ValidaAcesso()
{
    if (Conexao)
    {
        if (Login)
        {
            if (Admin)
            {
                PainelAdmin();
            }
            else
            {
                Console.WriteLine("Acesso restrito ao Admin");
            }
        }
        else
        {
            Console.WriteLine("Precisa fazer Login");
        }
    }
    else
    {
        Console.WriteLine("Sem conexão...");
    }
}

Embora o código seja simples temos uma aninhamento de if/else que o torna difícil de ler. Além disso e um valor null for informado  isso não esta sendo previsto e teremos um erro que vai quebrar o código.

Podemos melhora este código usando as Guard Clause separando as condições e invertendo a lógica da seguinte forma:

void ValidaAcesso()
{
    if (!Conexao)
    {
        Console.WriteLine("Sem conexão...");
        return;
    }
    if (!Login)
    {
        Console.WriteLine("Precisa fazer Login");
        return;
    }
    if (!Admin)
    {
        Console.WriteLine("Acesso restrito ao Admin");
        return;
    }
    PainelAdmin();
}

O código agora ficou mais claro e fácil de entender classes ao uso das cláusulas de guarda.

Vejamos outro exemplo:

public bool ValidaUrl(string url)
{
    return Uri.TryCreate(url, UriKind.Absolute, out Uri result) &&
           (result.Scheme == "http" || result.Scheme == "https");
}

Neste código se passarmos null como o parâmetro url, o método retornará false. À primeira vista, isso pode parecer correto, já que null é de fato uma URL inválida. No entanto, essa não foi nossa intenção e não consideramos null um valor de entrada válido.

Assim, poderíamos reescrever o método como:

public bool ValidaUrl(string url)
{
    if (url == null)
        throw new ArgumentNullException(nameof(url));

    return Uri.TryCreate(url, UriKind.Absolute, out Uri result) &&
           (result.Scheme == "http" || result.Scheme == "https");
}

 

A expressão if que foi introduzida é uma Guard Clauses ou Cláusulas de Guarda e agora um valor null esta sendo previsto e tratado.

No entanto, podemos melhorar o código para não ficar repetido o código em nossos métodos ferindo assim o princípio DRY - Don´t repeate yourself. Para isso podemos criar uma classe estática e concentrar nela as validações.

public static class GuardClauses
{
    public static void IsNotNull(object argumentValue, string argumentName)
    {
        if (argumentValue is null)
            throw new ArgumentNullException(argumentName);
    }
}

Agora o código ficaria assim:

public bool ValidaUrl(string url)
{
    
GuardClauses.IsNotNull(url, nameof(url));

    return Uri.TryCreate(url, UriKind.Absolute, out Uri result) &&
           (result.Scheme == "http" || result.Scheme == "https");
}

No uso das cláusulas de guarda a lógica das condições normalmente é invertida e, dependendo da complexidade da condição, será muito complexo entender o que está sendo avaliado naquela condição.

Por isso é uma boa prática extrair a lógica das condições em pequenas funções que permitem maior legibilidade do código e, claro, encontrar bugs neles, já que a responsabilidade de avaliar a condição está sendo delegada a uma função específica.

Assim podemos criar mais métodos em nossa classe GuardClauses para atender outros critérios.

public static class GuardClauses
{
    public static void IsNaoNull(object valorArgumento, string nomeArgumento)
    {
        if (valorArgumento is null)
            throw new ArgumentNullException(nomeArgumento);
    }

    public static void IsNaoNullOuVazio(string valorArgumento, string nomeArgumento)
    {
        if (string.IsNullOrEmpty(valorArgumento))
            throw new ArgumentNullException(nomeArgumento);
    }

    public static void IsNaoZero(int valorArgumento, string nomeArgumento)
    {
        if (valorArgumento == 0)
            throw new ArgumentException($"Argumento '{nomeArgumento}' não pode ser zero");
    }

    public static void IsMenorQue(int valorMaximo, int valorArgumento, string nomeArgumento)
    {
        if (valorArgumento >= valorMaximo)
            throw new ArgumentException($"Argumento '{nomeArgumento}' não pode exceder '{valorMaximo}'");
    }

    public static void IsMaiorQue(int valorMinimo, int valorArgumento, string nomeArgumento)
    {
        if (valorArgumento <= valorMinimo)
            throw new ArgumentException($"Argumento '{nomeArgumento}' não pode ser menor que '{valorMinimo}'");
    }
}

Existem muitas técnicas para melhorar a qualidade do código. A coisa mais importante a aprender ao aplicar técnicas de refatoração é que elas devem ser focadas em dois pontos :

  1. Desacoplar o código - Com isso estamos permitindo que pequenas alterações não causem grandes alterações encadeadas em todo o projeto de software;
     
  2. Tornar o código mais legível - É muito importante entender que a maior parte do tempo de seu trabalho é baseado na leitura de código, e provavelmente código escrito por outro desenvolvedor. Assim gastar tempo tentando entender lógicas elementares de um código difícil de ler tem um custo muito alto;

O uso de guard clauses pode ajudar a melhorar a legibilidade do código, evitando a necessidade de aninhamento excessivo de blocos "if" e tornando as pré-condições mais claras e explícitas.

E estamos conversados...

'Tendo sido, pois, justificados pela fé, temos paz com Deus, por nosso Senhor Jesus Cristo;'
Romanos 5:1

Referências:


José Carlos Macoratti