Aikido

Evitar atribuições em condicionais: prevenir erros ocultos

Legibilidade

Regra
Não colocar atribuições dentro de condicionais. 
Misturar atribuição e condição lógica faz com que o código propenso a erros
e mais difícil de compreender. Separar tarefas de lógicas lógicas. 

Suportadas linguagens suportadas:** JavaScript, TypeScript, Python, PHP

Introdução

Os operadores de atribuição dentro de declarações condicionais são uma fonte comum de erros que os compiladores e linters muitas vezes não detectam. O erro clássico é usar = (atribuição) em vez de == ou === (comparação) em uma instrução if, mas o problema é mais profundo. Mesmo atribuições intencionais em condicionais criam código que é difícil de ler, revisar e depurar. Quando a atribuição e a avaliação ocorrem na mesma linha, os leitores devem analisar mentalmente qual operação tem precedência e qual valor está realmente sendo testado.

Porque é importante

Porque é importante

Introdução de erros: Um erro tipográfico que altera === para = não causa um erro de sintaxe, apenas altera silenciosamente o comportamento. A condição é avaliada para o valor atribuído (verdadeiro/falso), não para o resultado da comparação.

Legibilidade do código: Os leitores esperam que as condicionais testem valores, não os modifiquem. Quando ambos acontecem simultaneamente, os mantenedores devem rastrear quais variáveis estão sendo modificadas e quando.

Exemplos de código

Não conforme:

function processUser(userData) {
    if (user = userData.user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    if (result = value.match(/^\d{3}-\d{2}-\d{4}$/)) {
        return result[0];
    }
    return false;
}

Por que está errado: As atribuições dentro das condicionais não deixam claro se isso é intencional ou um erro de digitação. O primeiro exemplo pode ser um erro onde === foi intencional, e o segundo mistura correspondência de regex com atribuição, tornando o fluxo de código difícil de seguir.

Conformidade:

function processUser(userData) {
    const user = userData.user;
    if (user) {
        console.log(`Processing user: ${user.name}`);
        return user.id;
    }
    return null;
}

function validateInput(value) {
    const result = value.match(/^\d{3}-\d{2}-\d{4}$/);
    if (result) {
        return result[0];
    }
    return false;
}

Porque é que isto é importante: Separar a atribuição da condicional torna a intenção muito clara. Os leitores vêem imediatamente que utilizador é extraído primeiro e depois testado. O resultado da correspondência regex é capturado e depois avaliado. Sem ambiguidade, sem sobrecarga cognitiva e erros de digitação como = vs === tornam-se evidentes.

Conclusão

Manter as atribuições separadas das condicionais é uma regra simples que evita toda uma classe de erros. A sobrecarga cognitiva de analisar operações combinadas supera qualquer benefício de brevidade percebido. Um código claro e explícito em que a atribuição e a avaliação são operações distintas melhora a legibilidade, reduz os erros e torna a revisão do código mais eficaz.

FAQs

Tem perguntas?

E nos casos em que a atribuição em condicionais é idiomática, como a leitura de ficheiros?

Mesmo em linguagens em que while (line = file.readline()) é comum, as melhores práticas modernas favorecem a separação explícita. Em JavaScript, use protocolos iteradores: for (const line of fileLines). Em Python 3.8+, o operador de morsa := torna a intenção explícita quando a atribuição em condicionais é genuinamente necessária, mas mesmo assim, considere se declarações separadas seriam mais claras.

Existem implicações em termos de desempenho na separação entre atribuição e condicionais?

Não. Os motores JavaScript modernos optimizam ambos os padrões de forma idêntica. A separação adiciona uma declaração de variável, que tem custo zero em tempo de execução após a compilação. Qualquer diferença de desempenho percebida é insignificante em comparação com os benefícios de prevenção de erros e legibilidade. Escreva primeiro um código claro, optimize apenas quando a análise de perfil identificar os verdadeiros estrangulamentos.

Como é que lido com padrões como if ((match = regex.exec(str)) !== null)?

Divida-o em duas declarações: const match = regex.exec(str); if (match !== null). Ou melhor, use alternativas modernas: const match = str.match(regex); if (match). A verificação explícita de nulo torna-se redundante porque match() devolve nulo em caso de falha, o que é falso. A clareza melhora e a intenção torna-se óbvia.

E as atribuições que são intencionalmente utilizadas para o seu valor de retorno?

Intencional não significa boa prática. O código que se baseia na atribuição de valores de retorno em condicionais cria riscos de manutenção. Futuros editores podem "corrigir" o que parece ser um erro de digitação. Se tiver mesmo de utilizar este padrão, adicione um comentário a explicar porquê, mas reconsidere se o código pode ser reestruturado de forma mais clara.

Esta regra aplica-se aos operadores ternários?

Sim. Evite const x = (y = getValue()) ? y : defaultValue. Isto é ainda mais difícil de ler do que nas declarações if. Use: const y = getValue(); const x = y ? y : defaultValue. Ou melhor, use nullish coalescing: const x = getValue() ?? defaultValue. Os operadores modernos existem especificamente para evitar estes padrões incómodos.

Como é que os linters e as ferramentas de análise estática lidam com este padrão?

A maioria dos linters modernos sinalizam atribuição em condicionais por padrão ou via configuração. Eles normalmente requerem parênteses extras if ((x = y)) para sinalizar a atribuição intencional, mas isso é um cheiro de código. É melhor desabilitar a exceção do linter e corrigir o código corretamente. As ferramentas de análise estática podem detetar esses padrões durante a CI/CD, impedindo-os de chegar à produção.

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.