Resolver avisos de análise estática para condições sempre falsas.

Como resolver avisos de análise estática para condições sempre falsas e ramificações inalcançáveis

Seu analisador estático sinalizou uma condição como sempre falsa. O aviso será semelhante a um destes, dependendo da sua ferramenta:

  • SonarQube: “Expressões condicionais que são sempre verdadeiras ou falsas podem levar a um código inacessível” (RSPEC-2583)
  • IntelliJ / Inspectopedia: “O código não foi analisado porque a condição foi avaliada estaticamente como falsa”
  • Checkmarx / Fortificar: “Código inacessível, ramo morto detectado”
  • ESLint: “sem condição constante, condição constante inesperada”
  • Pilint: “W0125: Utilizando uma instrução condicional com um valor constante”

Todas essas afirmações indicam a mesma coisa: o analisador comprovou, em tempo de análise, sem executar o código, que um ramo específico de uma instrução condicional jamais poderá ser executado. A condição que protege esse ramo é sempre falsa. Tudo dentro desse ramo é código morto.

Este guia aborda as causas desses avisos, como identificar a categoria de cada aviso, a solução correta para cada categoria, quando a supressão é apropriada e o que as condições sempre falsas no código COBOL legado revelam sobre o escopo da migração.

Regras de negócio desatualizadas ainda presentes no seu código.

SMART TS XL Identifica ramificações do EVALUATE que fazem referência a códigos de produto, valores de status e tipos de cliente que não existem mais nos dados de produção.

SAIBA MAIS…

Como a análise estática detecta condições sempre falsas

Os analisadores estáticos modernos usam execução simbólica e análise de fluxo de dados, e não simples correspondência de padrões, para detectar condições sempre falsas. O analisador rastreia os valores possíveis de cada variável em todos os pontos do programa. Quando encontra uma condição, avalia se ela pode ser verdadeira em algum momento, dadas as restrições conhecidas sobre as variáveis.

Três mecanismos principais produzem detecções sempre falsas:

Execução simbólica. O analisador atribui valores simbólicos às variáveis ​​e rastreia como as operações restringem esses valores. Se a variável x está atribuído 0 na linha 3 e comparado x > 10 Na linha 7, sem nenhuma atribuição intermediária, a condição é comprovadamente falsa.

Restrições do sistema de tipos. Algumas condições são impossíveis dado o tipo da variável. Comparar uma referência de objeto não nula com nulo após uma asserção de não nulo, ou comparar um valor de enumeração com um caso fora de seu conjunto definido.

Simplificação da álgebra booleana. Condições compostas que se simplificam para false independentemente dos valores das variáveis: (a && !a), (x > 10 && x < 5), (!flag || false).

A regra RSPEC-2583 do SonarQube utiliza especificamente a execução simbólica: ela rastreia o estado das variáveis ​​através do grafo de fluxo de controle e sinaliza as condições em que o estado simbólico torna a condição comprovadamente falsa. O aviso do IntelliJ de que a condição foi avaliada estaticamente como falsa utiliza a mesma abordagem.

As quatro causas principais

Resolver corretamente um aviso sempre falso depende de identificar corretamente qual das quatro causas raiz se aplica. A solução difere para cada uma delas.

Causa raiz 1: Contradição lógica

A condição contém uma impossibilidade lógica, duas subcondições que não podem ser verdadeiras simultaneamente ou uma condição composta que se reduz a false por meio da álgebra booleana.

Java

// Example: mutually exclusive subconditions
int score = getScore();
if (score > 90 && score < 50) {  // Always false: no integer is both > 90 and < 50
    applyBonus();                 // Never executes
}

// Example: boolean tautology negation
boolean active = isActive();
if (active && !active) {  // Always false: a && !a = false
    sendNotification();   // Never executes
}

// Example: range check already guaranteed
if (value >= 0) {
    if (value < 0) {  // Always false: already know value >= 0
        handleNegative();  // Never executes
    }
}

Resolução: Corrija a lógica. Ou a condição contém um erro de digitação (o desenvolvedor quis dizer "erro de digitação"?). score > 90 || score < 50) ou o ramo deve ser removido. No exemplo de verificação de intervalo, o interno if É redundante e deve ser eliminado.

Causa raiz 2: Acúmulo de alterações nas regras de negócio

Essa é a causa mais significativa em bases de código de longa duração. Uma condição que era válida e significativa quando foi escrita tornou-se permanentemente falsa devido a alterações subsequentes no código ao seu redor, sem que ninguém removesse o ramo agora inativo.

Java

// 2018: processType could be A, B, or C
// 2023: type C was deprecated and removed from the system
// The processType field is now guaranteed to never equal "C"
// but nobody removed the branch that handled it

public void process(String processType) {
    if (processType.equals("A")) {
        handleTypeA();
    } else if (processType.equals("B")) {
        handleTypeB();
    } else if (processType.equals("C")) {  // Always false since 2023
        handleTypeC();                      // 47 lines of dead business logic
    }
}

python

# Python equivalent: accumulated rule changes
def calculate_rate(customer_tier: str) -> float:
    if customer_tier == "GOLD":
        return 0.05
    elif customer_tier == "SILVER":
        return 0.03
    elif customer_tier == "BRONZE":
        # BRONZE tier was eliminated in Q2 2021
        # All BRONZE customers were migrated to SILVER
        # This branch has been dead for 4+ years
        return 0.01  # Never executes
    return 0.0

Resolução: Verifique o contexto de negócios e, em seguida, remova completamente o branch inativo. A regra de negócios que tornava a condição possível foi alterada. O código que a implementava não é mais necessário. Documente a remoção na mensagem de commit: “Branch com taxa BRONZE removido, nível eliminado no 2º trimestre de 2021, todos os clientes migraram para o nível PRATA.”

Causa raiz 3: Programação defensiva para garantir a sustentabilidade futura

Algumas condições que são sempre falsas são intencionais; o desenvolvedor as escreveu sabendo que a condição não pode ser verdadeira no momento, como uma medida de segurança contra futuras alterações no código que possam torná-la verdadeira.

Java

// SonarQube RSPEC-2583 explicitly exempts these:
final boolean DEBUG_MODE = false;
if (DEBUG_MODE) {                // Always false -- intentional
    dumpDiagnostics();           // Not unreachable in intent, only in fact
}

// Type narrowing that appears redundant
String value = getValue();
if (value == null || value.isEmpty()) {
    return;
}
// At this point, static analysis knows value is non-null and non-empty
if (value == null) {  // Always false -- already checked above
    throw new IllegalStateException(); // Defensive guard
}

A especificação RSPEC-2583 do SonarQube define explicitamente uma exceção: "Quando a condição é um único valor booleano final" ou "quando a condição é literalmente verdadeira ou falsa", esses casos são tratados como intencionais e não são sinalizados.

Resolução: Se a condição for um sinalizador de depuração ou uma constante de tempo de compilação: mantenha-a e adicione uma anotação de supressão caso a ferramenta a sinalize. Se for uma verificação defensiva de nulo ou uma proteção de intervalo que pareça redundante, mas seja genuinamente útil: reestruture o código para tornar a proteção explícita em um nível diferente ou suprima-a com uma justificativa documentada.

Causa raiz 4: Falsos positivos da ferramenta devido à configuração de tempo de execução

Os analisadores estáticos não conseguem visualizar configurações de tempo de execução, variáveis ​​de ambiente, reflexão, processamento de anotações ou injeção de dependência. O código que sempre aparece como falso para o analisador pode ser acessível por meio de um mecanismo de tempo de execução que o analisador não consegue modelar.

Java

// IntelliJ / Inspectopedia note: "This may cause false-positives if any kind
// of code postprocessing is used, for example, if an annotation processor
// later replaces the method body with something useful."

@ConditionalOnProperty(name = "feature.experimental", havingValue = "true")
public class ExperimentalFeature {
    // This class may appear unreachable if the property is false in the
    // test configuration the analyzer uses -- but it IS reachable in prod
}

// Method contract that the analyzer cannot see
void run() {
    performAction();
    System.out.println("Done"); // IntelliJ flags as unreachable
}
@Contract("-> _") // Tells the analyzer: method does NOT always throw
static void performAction() { throw new AssertionError(); }

Resolução: Adicione a anotação de contrato de método apropriada (@Contract para IntelliJ, @NotNull/@Nullable Para Kotlin, asserções de tipo para TypeScript) fornecem ao analisador as informações de tempo de execução necessárias. Se a resolução baseada em anotações não estiver disponível, use o mecanismo de supressão da ferramenta com uma justificativa documentada.

COBOL: Onde as condições sempre falsas revelam décadas de lógica obsoleta

Em COBOL e sistemas mainframe legados, as condições sempre falsas têm um caráter diferente do que no código de aplicativos modernos, e um significado diferente para as equipes que os mantêm ou modernizam.

Os programas COBOL não são refatorados da mesma forma que o código moderno. Quando uma regra de negócio muda, um código tributário é revisado, um nível de produto é eliminado ou um requisito regulatório é atualizado, a prática comum é adicionar a nova lógica sem remover a antiga. O antigo bloco EVALUATE WHEN ou IF permanece no código-fonte, protegido por uma condição que nunca pode ser verdadeira, dado o estado atual dos dados que processa.

cobol

       EVALUATE WS-PRODUCT-TYPE
           WHEN 'STD'
               PERFORM CALC-STANDARD-RATE
           WHEN 'PRM'
               PERFORM CALC-PREMIUM-RATE
           WHEN 'BAS'
      *        BAS product type retired June 2009
      *        All BAS customers converted to STD
      *        This branch has been dead for 15+ years
               PERFORM CALC-BASIC-RATE
           WHEN 'LEG'
      *        LEG (legacy) type eliminated in 2001 system migration
      *        Should have been removed but was left as "just in case"
               PERFORM CALC-LEGACY-RATE
           WHEN OTHER
               PERFORM HANDLE-UNKNOWN-TYPE
       END-EVALUATE.

Os dois ramos mortos nesta AVALIAÇÃO, BAS e LEG, não são erros. São artefatos históricos: regras de negócio que eram válidas em momentos específicos da história do sistema e se tornaram permanentemente inacessíveis quando esses tipos de produto foram descontinuados. Ninguém as removeu porque ninguém tinha certeza se ainda seriam necessárias, e o custo de estar errado parecia maior do que o custo de deixá-las no lugar.

O que as condições sempre falsas em COBOL revelam:

Os ramos mortos representam lógica de negócios que não se aplica mais. PERFORM CALC-BASIC-RATE e PERFORM CALC-LEGACY-RATE Os alvos podem ser parágrafos ou seções inteiras de código COBOL, potencialmente centenas de linhas cada, que também estão inativos. A condição sempre falsa é o ponto de entrada para uma árvore de código inativo que se propaga através de relacionamentos PERFORM.

Em um programa COBOL de 100,000 linhas, ramificações mortas provenientes de regras de negócio obsoletas podem representar de 20 a 35% do código total. Essa porcentagem constitui escopo de migração que não precisa ser convertido, testado ou validado em um programa de modernização. Identificá-la antes do planejamento da migração resulta em uma redução direta do escopo, e a redução do escopo na fase de planejamento é a aplicação de maior valor da análise de condições sempre falsas.

Árvore de Decisão de Resolução

Always-false condition warning received
            │
            ▼
    Is this a compile-time constant
    or debug flag? (DEBUG = false, etc.)
            │
     Yes ───┼─── No
            │         │
    Intentional.      ▼
    Suppress or   Does the condition
    leave as-is.  contain a logic error?
                  (a > 10 && a < 5, etc.)
                        │
                 Yes ───┼─── No
                        │          │
                  Fix the        Has the condition
                  logic bug.     become false due to
                                 a past business rule
                                 change?
                                        │
                                 Yes ───┼─── No
                                        │          │
                              Verify,         Could a runtime
                              then remove     mechanism make
                              the dead        it reachable?
                              branch.               │
                                           Yes ──── No
                                                    │
                                             Add contract
                                             annotation.     Remove the
                                                             dead branch.

Supressão: Quando e Como

A supressão é apropriada quando a condição é genuinamente intencional e a ferramenta não possui as informações contratuais necessárias para entender o motivo. A supressão não é apropriada como um atalho para evitar investigar o verdadeiro significado do aviso.

Java (SonarQube):

Java

@SuppressWarnings("java:S2583")
if (DEBUG_MODE) {  // Intentional: compile-time debug flag
    dumpState();
}

Java (IntelliJ):

Java

//noinspection ConstantConditions
if (possiblyNull != null) {  // Analyzer cannot see the runtime injection
    process(possiblyNull);
}

Python (Pylint):

python

if False:  # pylint: disable=W0125
    # Intentional: placeholder for future implementation
    pass

C# (SonarQube):

csharp

#pragma warning disable S2589
if (IsDebugBuild) {  // Compile-time constant -- intentional
    LogDiagnostics();
}
#pragma warning restore S2589

COBOL (SMART TS XL / comentário de supressão personalizado):

cobol

      *>> SUPPRESS UNREACHABLE: BAS branch retained for audit trail
           WHEN 'BAS'
               PERFORM CALC-BASIC-RATE

Disciplina de supressão: Toda supressão deve ser acompanhada de um comentário que explique por que a condição é intencional, e não apenas que está sendo suprimida. Um comentário de supressão que diga: // suppress unreachable É pior do que nenhuma supressão, pois oculta o aviso sem explicar por que ele está errado. Uma supressão que diz // suppress: DEBUG_MODE is a compile-time constant, branch is retained for IDE visibility é aceitável.

Como SMART TS XL Identifica condições sempre falsas em escala de portfólio.

SMART TS XL'S análise de código estático Aplica análise de fluxo de dados e fluxo de controle em COBOL, JCL, Java, Python e todas as outras linguagens do ambiente para identificar condições que podem ser comprovadas estaticamente como sempre falsas, em escala de portfólio, em milhares de programas simultaneamente, e não um arquivo por vez em um ambiente de desenvolvimento integrado (IDE) de desenvolvedor.

Especificamente para portfólios COBOL, isso significa: cada cláusula EVALUATE WHEN e cada condição IF em cada programa é analisada em relação às restrições conhecidas dos dados que a alimentam. Condições que fazem referência a códigos de produto, indicadores de status ou códigos de tipo cujos conjuntos de valores válidos foram alterados ao longo do histórico do programa são sinalizadas como possíveis ramificações mortas. O resultado é um inventário de código morto: cada programa, cada ramificação morta e a contagem estimada de linhas de código morto alcançáveis ​​a partir de cada condição sempre falsa.

A análise de modernização de sistemas legados converte esse inventário em uma redução de escopo: cada ramificação obsoleta é excluída da especificação de migração antes do início de qualquer esforço de conversão. Um programa que aparenta ter 2,000 linhas de lógica de negócios pode ter 600 linhas de lógica obsoleta provenientes de tipos de produtos descontinuados, níveis de clientes eliminados e caminhos de cálculo descontinuados. Migrar 1,400 linhas em vez de 2,000 representa uma redução de 30% no escopo desse programa, e as ramificações obsoletas são a parte de maior risco do escopo a ser migrado, pois ninguém sabe qual era a sua função original.

A funcionalidade de busca corporativa torna o inventário de códigos sempre falsos consultável: encontre todos os programas COBOL que contêm uma cláusula EVALUATE WHEN referenciando um código de produto que não está no catálogo de produtos atual, todos os programas que verificam um valor de indicador de status que não existe no banco de dados desde um ano específico, todas as condições IF que referenciam um campo de estrutura de dados que foi reaproveitado desde que a condição foi escrita. Essas consultas produzem o inventário de código morto direcionado que uma revisão manual de código levaria meses para produzir em um grande portfólio COBOL.

O aviso geralmente está certo.

Na maioria dos casos, os avisos de análise estática para condições sempre falsas são resultados precisos. A condição é sempre falsa. O ramo nunca é executado. O código dentro dele está morto.

A resposta apropriada não é a supressão, mas sim a investigação. Qual causa raiz se aplica? Contradição lógica, alteração acumulada de regras de negócio, programação defensiva intencional ou falso positivo da ferramenta devido à configuração em tempo de execução? Cada uma delas possui uma resolução correta. Três das quatro resultam na remoção do código morto. Uma resulta na supressão com justificativa.

Para sistemas COBOL legados, a análise de condições sempre falsas em escala de portfólio não é primordialmente um exercício de qualidade de código, mas sim um exercício de redução de escopo. Os ramos mortos que se acumulam ao longo de décadas de alterações nas regras de negócio representam um esforço de migração que não deveria ser realizado. Encontrá-los antes que o plano de migração seja finalizado é a aplicação de maior valor da análise de condições sempre falsas para qualquer equipe que planeje um programa de modernização de COBOL.

O analisador está lhe dizendo algo verdadeiro. Ouça-o.