DEV Community

Denis Augusto
Denis Augusto

Posted on

Early return: o jeito de matar aquele if aninhado que ninguém lê

Seu código tem forma de seta e ninguém quer admitir

Você já abriu um método, olhou pro lado direito da tela e viu aquilo? A indentação vai crescendo, crescendo, o código forma uma seta apontando pro canto, e lá no fundo, uns cinco níveis pra dentro, mora a linha que realmente importa.

A gente até tem nome carinhoso pra isso: arrow code (código seta). Ou, pros mais dramáticos, "a pirâmide da perdição".

O pior é que quase sempre esse método começou pequeno. Um if inocente. Aí veio uma validação, mais um caso, uma exceção pra tratar... e quando você percebe, precisa rolar o olho por seis linhas de fechamento de chave } só pra descobrir a qual if cada uma pertence.

O problema na prática

Olha esse método de processar um pedido. Parece inofensivo, mas repara na indentação:

public function processar(Order $order)
{
    if ($order->isPago()) {
        if ($order->itens->isNotEmpty()) {
            if ($order->cliente->estaAtivo()) {
                if (! $order->foiEnviado()) {
                    // finalmente, o código de verdade
                    $this->enviar($order);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    return false;
}
Enter fullscreen mode Exit fullscreen mode

Pra chegar na única linha que interessa ($this->enviar($order)), seu olho precisou atravessar quatro if e ainda torcer pra não confundir qual else fecha qual bloco. E se amanhã aparecer mais uma regra? Mais um nível pra dentro. A seta cresce.

Esse código não é difícil porque o problema é difícil. Ele é difícil porque tá escrito de trás pra frente.

O conceito: verifica o que dá errado primeiro

Early return (ou "cláusula de guarda", guard clause) é a ideia de tratar os casos de saída logo no começo do método e sair fora na hora. Em vez de aninhar o "caminho feliz" lá no fundo de um monte de if, você inverte a lógica:

"Não tá pago? Cai fora. Sem itens? Cai fora. Cliente inativo? Cai fora. Passou por tudo? Então executa."

O caminho feliz fica no nível principal do método, sem indentação nenhuma. As exceções ficam no topo, cada uma resolvida e esquecida. Você lê de cima pra baixo, uma condição de cada vez, sem precisar segurar nada na cabeça.

A solução: mesmo método, agora legível

public function processar(Order $order): bool
{
    if (! $order->isPago()) {
        return false;
    }

    if ($order->itens->isEmpty()) {
        return false;
    }

    if (! $order->cliente->estaAtivo()) {
        return false;
    }

    if ($order->foiEnviado()) {
        return false;
    }

    // o caminho feliz, no nível principal, sem seta nenhuma
    $this->enviar($order);

    return true;
}
Enter fullscreen mode Exit fullscreen mode

Zero else. Zero aninhamento. Cada if é uma pergunta simples: "tem algum motivo pra parar aqui?". Se tiver, para. Se chegou no fim, é porque passou por todos os filtros e agora pode fazer o trabalho de verdade com a consciência tranquila.

E o de bônus: adicionar uma regra nova amanhã é colar mais um bloco de guarda no meio. Não mexe na estrutura, não empurra nada pra dentro.

Como usar na prática

Lançando exceção em vez de retornar — funciona igualzinho, e é ótimo em Services e Actions:

public function reembolsar(Order $order): void
{
    if (! $order->isPago()) {
        throw new OrderException('Não dá pra reembolsar um pedido não pago.');
    }

    if ($order->foiReembolsado()) {
        throw new OrderException('Esse pedido já foi reembolsado.');
    }

    $this->gateway->refund($order);
}
Enter fullscreen mode Exit fullscreen mode

Saindo cedo dentro de um loop com continue — mesma filosofia, evita o if gigante envolvendo o corpo inteiro:

foreach ($usuarios as $usuario) {
    if ($usuario->optOut) {
        continue; // pula esse e segue
    }

    $this->notificar($usuario);
}
Enter fullscreen mode Exit fullscreen mode

No Blade também, com @continue e @break, pra não aninhar a view inteira num @if.

A pegadinha: não é "return em todo canto"

Aqui vem o aviso, porque tem sempre alguém que lê isso e sai espalhando return no meio de qualquer função só pra parecer moderno.

Early return brilha quando as saídas são cláusulas de guarda de verdade: validações, pré-condições, casos de borda que impedem o fluxo principal. Aí ele reduz complexidade.

O que ele não é: desculpa pra ter dez return espalhados no meio da lógica principal, cada um saindo de um lugar diferente, sem ninguém entender por onde o método realmente termina. Isso não é early return, é fuga.

A régua é simples: os return de guarda ficam agrupados no topo, tratando o que dá errado. O return do caminho feliz fica sozinho no fim. Se seus returns estão espalhados feito confete no meio do código, você trocou um problema por outro.

Bônus: e agora?

Abre o arquivo com o método mais bagunçado do seu projeto — você já sabe qual é. Conta quantos níveis de indentação ele tem. Passou de dois ou três? Provavelmente dá pra inverter os if do topo em cláusulas de guarda e achatar aquilo em cinco minutos.

Uma dica: comece pelos else. Todo else que existe só pra retornar algo é um forte candidato a virar um early return e sumir.

Antes de você fechar a aba

Legibilidade não é frescura de quem gosta de código bonito. É você economizando o tempo do seu eu do futuro (ou do coitado que vai dar manutenção depois) na hora de entender o que aquele método faz.

E você, é time "trata o erro primeiro e cai fora" ou ainda tem carinho pela pirâmide de if? Conta nos comentários — e se conhece alguém que ama um else aninhado, marca lá. 😅


Top comments (0)