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
- Escreva testes antes de mexer. Um caso para cada retorno possível: seis casos aqui. Refatorar sem teste é reescrever no escuro.
- Inverta a condição mais externa e retorne cedo: se o pedido é nulo, devolva "pedido inexistente" e acabou.
- Repita para cada nível, de fora para dentro. Cada
elsedistante vira umifcurto no topo. - Apague os
elseque vêm depois de umreturn. Se o ramo anterior já saiu da função, oelsesó adiciona indentação. - Dê nome às condições que precisam de mais de uma leitura, extraindo pequenas funções ou variáveis.
- Troque números soltos por constantes com nome.
- 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
ifcomelseé honesto e claro. - Mapear um valor para outro: no PHP 8, a expressão
matchcompara 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
ifsó 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
elsedepois dereturn"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
elsevem logo depois de um bloco que termina emreturn. - 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?
Referência: http://www.tiexpert.net/programacao/web/php/if-else.php