Programação Por Equipe DevBras

Capa do artigo: If dentro de if: como achatar a função com retorno antecipado e condição com nome

If dentro de if: como achatar a função com retorno antecipado e condição com nome

Transforme uma pirâmide de if e else em uma função plana: trate os casos inválidos primeiro, dê nome às condições e deixe o caminho feliz legível no fim.

Sexta, 16h, chega um pedido simples: "pedidos acima de 30 kg agora podem ir por transportadora". Você abre a função liberarEnvio e encontra cinco níveis de if, cada um com seu else lá embaixo, e a linha que realmente agenda a coleta indentada até o meio da tela. Para mudar uma regra, você precisa contar chaves. Ninguém mexe nisso sem medo, e o medo é justificado.

A resposta curta: trate os casos inválidos primeiro e saia da função logo (retorno antecipado, também chamado de cláusula de guarda), dê nome às condições que não se explicam sozinhas e deixe o caminho feliz no fim, sem indentação. A lógica continua a mesma; o que muda é que cada regra vira uma linha que você lê de cima para baixo.

Por que o código cresce para o lado

Ninguém escreve uma pirâmide de propósito. A função nasce com um if que confere se o pedido existe. Depois alguém precisa checar se está pago e coloca o teste dentro do primeiro, porque "só faz sentido se o pedido existe". Depois o endereço, depois o peso, depois o estoque. Cada regra nova entra no nível mais fundo, e cada else fica a dezenas de linhas do if que o abriu.

O custo aparece na leitura. Para saber por que a função devolveu "sem endereço", você precisa reconstruir mentalmente quatro condições aninhadas. Para adicionar uma regra, precisa achar o nível certo. E o caminho principal, aquilo que a função existe para fazer, fica enterrado no ponto mais indentado.

A função de exemplo, antes

Vamos usar a mesma função do começo ao fim: a liberação de envio de uma loja virtual, em PHP.

function liberarEnvio(?array $pedido): string
{
    if ($pedido !== null) {
        if ($pedido['status'] === 'pago') {
            if (!empty($pedido['endereco']['cep']) && !empty($pedido['endereco']['numero'])) {
                if ($pedido['peso_kg'] <= 30) {
                    if ($pedido['estoque_reservado']) {
                        agendarColeta($pedido);
                        return 'liberado';
                    } else {
                        return 'sem estoque';
                    }
                } else {
                    return 'peso acima do limite';
                }
            } else {
                return 'endereço incompleto';
            }
        } else {
            return 'aguardando pagamento';
        }
    } else {
        return 'pedido inexistente';
    }
}

Ela funciona. Mas o par de cada else está longe, o número 30 não diz o que é, e a condição do endereço exige ler os dois lados do && para entender a intenção.

Como achatar a função passo a passo

  1. Escreva testes antes de mexer. Um caso para cada retorno possível: seis casos aqui. Refatorar sem teste é reescrever no escuro.
  2. Inverta a condição mais externa e retorne cedo: se o pedido é nulo, devolva "pedido inexistente" e acabou.
  3. Repita para cada nível, de fora para dentro. Cada else distante vira um if curto no topo.
  4. Apague os else que vêm depois de um return. Se o ramo anterior já saiu da função, o else só adiciona indentação.
  5. Dê nome às condições que precisam de mais de uma leitura, extraindo pequenas funções ou variáveis.
  6. Troque números soltos por constantes com nome.
  7. Rode os testes de novo. Os seis casos precisam devolver exatamente o mesmo texto.

A mesma função, depois

const PESO_MAXIMO_KG = 30;

function liberarEnvio(?array $pedido): string
{
    if ($pedido === null) {
        return 'pedido inexistente';
    }
    if (!pedidoPago($pedido)) {
        return 'aguardando pagamento';
    }
    if (!enderecoCompleto($pedido)) {
        return 'endereço incompleto';
    }
    if (pesoAcimaDoLimite($pedido)) {
        return 'peso acima do limite';
    }
    if (!$pedido['estoque_reservado']) {
        return 'sem estoque';
    }

    agendarColeta($pedido);
    return 'liberado';
}

function pedidoPago(array $pedido): bool
{
    return $pedido['status'] === 'pago';
}

function enderecoCompleto(array $pedido): bool
{
    return !empty($pedido['endereco']['cep']) && !empty($pedido['endereco']['numero']);
}

function pesoAcimaDoLimite(array $pedido): bool
{
    return $pedido['peso_kg'] > PESO_MAXIMO_KG;
}

Agora a função principal se lê como uma lista de regras, na ordem em que são conferidas. O caminho feliz está nas duas últimas linhas, sem indentação. E o pedido de sexta à tarde vira uma mudança localizada: a regra de peso está num lugar só, com nome, e você decide ali se acima de 30 kg o retorno muda para "enviar por transportadora".

Repare que não foi preciso extrair tudo. A checagem de estoque ficou inline porque $pedido['estoque_reservado'] já diz o que é. Nome serve para esconder detalhe que atrapalha a leitura, não para criar uma função por linha.

Os testes que garantem que nada mudou

Para cada retorno, monte um pedido que falha só naquela regra: nulo; status "aguardando"; sem número no endereço; 31 kg; sem estoque reservado; e um pedido válido. Rode os seis antes e depois. Se usar PHPUnit, um data provider com esses seis casos deixa o teste curto. Na versão válida, confira também que agendarColeta foi chamada, substituindo-a por um dublê no teste.

Quando o retorno antecipado não é a resposta

Cláusula de guarda funciona quando há um caminho principal e vários motivos para desistir dele. Quando os ramos são caminhos igualmente importantes, como calcular frete por região ou formatar um documento por tipo, achatar com return esconde a simetria. Aí outras ferramentas leem melhor.

  • Dois ramos curtos e equivalentes: um if com else é honesto e claro.
  • Mapear um valor para outro: no PHP 8, a expressão match compara com igualdade estrita e devolve um valor, sem cair de um caso para o outro.
  • Cada ramo é um processo inteiro: cada um vira uma função própria, e o if só escolhe qual chamar.

O post sobre if, else e switch compara essas opções com mais calma. O critério de fundo é o mesmo: quem lê pela segunda vez precisa entender a regra sem contar chaves.

Erros comuns

  • Refatorar sem teste: uma condição invertida errada muda o comportamento e só aparece quando um cliente reclama.
  • Inverter condição com dupla negação: if (!naoEstaPago) é mais difícil de ler que a pirâmide original; nomeie no positivo.
  • Manter else depois de return "para ficar completo": a indentação volta e a função parece ter mais caminhos do que tem.
  • Extrair uma função para cada linha: a leitura passa a exigir pular entre dez arquivos para entender uma regra simples.
  • Mudar a ordem das guardas sem pensar: se a regra de pagamento vier depois da de estoque, a mensagem que o cliente recebe muda.
  • Deixar número mágico na condição: o 30 aparece em três lugares e só dois são atualizados.

Checklist

  • Existe um teste para cada retorno possível da função.
  • Casos inválidos são tratados no topo, com retorno imediato.
  • Nenhum else vem logo depois de um bloco que termina em return.
  • Condições compostas têm nome que diz a intenção, no positivo.
  • Limites numéricos estão em constantes com nome.
  • O caminho principal está no fim da função, sem indentação extra.
  • Os testes passam com os mesmos retornos de antes da mudança.

Abra a função mais temida do seu projeto: o caminho feliz dela cabe na largura da tela, ou você precisa rolar para o lado para descobrir o que ela faz quando tudo dá certo?

Avalie este artigo

Referência: http://www.tiexpert.net/programacao/web/php/if-else.php