Åtgärda statiska analysvarningar för alltid-falska villkor

Hur man åtgärdar statiska analysvarningar för alltid-falska villkor och oåtkomliga grenar

Din statiska analysator har flaggat ett tillstånd som alltid falskt. Varningen ser ut som en av dessa, beroende på ditt verktyg:

  • SonarQube: "Villkorliga uttryck som alltid är sanna eller falska kan leda till oåtkomlig kod" (RSPEC-2583)
  • IntelliJ / Inspectopedia: "Koden analyseras inte eftersom villkoret statiskt utvärderas som falskt"
  • Checkmark / Förstärk: "Oåtkomlig kod, död gren upptäckt"
  • ESLint: "inget-konstant-villkor, oväntat konstant villkor"
  • Pylint: "W0125: Användning av en villkorlig sats med ett konstant värde"

Alla dessa säger samma sak: analysatorn har bevisat, vid analystillfället, utan att köra koden, att en specifik gren av en villkorlig sats aldrig kan köras. Villkoret som skyddar den grenen är alltid falskt. Allt inuti den grenen är död kod.

Den här guiden beskriver vad som orsakar dessa varningar, hur man identifierar vilken kategori varje varning faller under, rätt lösning för varje kategori, när undertryckning är lämpligt och vad alltid-falska villkor i äldre COBOL-kod avslöjar om migreringens omfattning.

Pensionerade affärsregler finns fortfarande i din kod

SMART TS XL identifierar UTVÄRDERA-grenar som refererar till produktkoder, statusvärden och kundtyper som inte längre finns i produktionsdata.

TA REDA PÅ MER…

Hur statisk analys upptäcker alltid-falska villkor

Moderna statiska analysatorer använder symbolisk exekvering och dataflödesanalys, inte enkel mönstermatchning, för att upptäcka villkor som alltid är falska. Analysatorn spårar de möjliga värdena för varje variabel vid varje punkt i programmet. När den når ett villkor utvärderar den om villkoret någonsin kan vara sant givet de kända begränsningarna för variablerna.

Tre primära mekanismer producerar alltid-falsk detektion:

Symbolisk avrättning. Analysatorn tilldelar symboliska värden till variabler och spårar hur operationer begränsar dessa värden. Om variabel x tilldelas 0 på rad 3 och jämfört x > 10 på rad 7 utan mellanliggande tilldelning är villkoret bevisligen falskt.

Typsystembegränsningar. Vissa villkor är omöjliga givet variabelns typ. Jämföra en objektreferens som inte är null med null efter en icke-null-påstående, eller jämföra ett enum-värde med ett fall utanför dess definierade mängd.

Boolesk algebraförenkling. Sammansatta villkor som förenklar till false oavsett variabelvärden: (a && !a), (x > 10 && x < 5), (!flag || false).

SonarQubes regel RSPEC-2583 använder specifikt symbolisk exekvering: den spårar variablernas tillstånd genom kontrollflödesgrafen och flaggar villkor där det symboliska tillståndet gör villkoret bevisbart falskt. IntelliJs varning om att "villkoret statiskt utvärderas som falskt" använder samma metod.

De fyra grundorsakerna

Att korrekt åtgärda en alltid-falsk varning beror på att korrekt identifiera vilken av de fyra grundorsakerna som gäller. Lösningen skiljer sig åt för varje.

Grundorsak 1: Logisk motsägelse

Villkoret innehåller en logisk omöjlighet, två undervillkor som inte båda kan vara sanna samtidigt, eller ett sammansatt villkor som reduceras till false genom boolesk algebra.

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
    }
}

Upplösning: Åtgärda logiken. Antingen innehåller villkoret ett stavfel (utvecklaren menade det) score > 90 || score < 50) eller så bör grenen tas bort. I exemplet med intervallkontrollen är den inre if är överflödig och bör tas bort.

Grundorsak 2: Ackumulerade ändringar av affärsregler

Detta är den viktigaste orsaken till långlivade kodbaser. Ett villkor som var giltigt och meningsfullt när det skrevs har blivit permanent falskt på grund av efterföljande ändringar i koden runt det, utan att någon har tagit bort den nu döda grenen.

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
    }
}

pytonorm

# 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

Lösning: Verifiera affärskontexten och ta sedan bort den döda grenen helt. Affärsregeln som möjliggjorde villkoret har ändrats. Koden som implementerade den behövs inte längre. Dokumentera borttagningen i commit-meddelandet: ”Borttagen BRONZE-gradgren, nivå eliminerad Q2 2021, alla kunder migrerade till SILVER.”

Grundorsak 3: Defensiv programmering för framtidssäkring

Vissa villkor som alltid är falska är avsiktliga, utvecklaren skrev dem med vetskapen om att villkoret för närvarande inte kan vara sant, som ett skydd mot framtida kodändringar som kan göra dem sanna.

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
}

SonarQube RSPEC-2583 definierar uttryckligen ett undantag: "När villkoret är en enda slutlig booleskt värde" eller "när villkoret är bokstavligen sant eller falskt", behandlas dessa som avsiktliga och flaggas inte.

Lösning: Om villkoret är en felsökningsflagga eller kompileringstidskonstant: lämna den och lägg till en undertryckningsannotering om verktyget flaggar den. Om det är en defensiv nullkontroll eller intervallskydd som känns redundant men är genuint skyddande: omstrukturera koden för att göra skyddet explicit på en annan nivå, eller undertryck med en dokumenterad motivering.

Rotorsak 4: Verktygets falska positiva resultat från körtidskonfigurationen

Statiska analysatorer kan inte se runtime-konfiguration, miljövariabler, reflektion, annoteringsbearbetning eller beroendeinjektion. Kod som verkar vara alltid falsk för analysatorn kan nås via en runtime-mekanism som analysatorn inte kan modellera.

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(); }

Upplösning: Lägg till lämplig metodkontraktsannotering (@Contract för IntelliJ, @NotNull/@Nullable För Kotlin, skriv assertions (för TypeScript) för att ge analysatorn den körtidsinformation den behöver. Om annoteringsbaserad upplösning inte är tillgänglig, använd verktygets undertryckningsmekanism med en dokumenterad motivering.

COBOL: Där alltid-falska villkor avslöjar årtionden av död logik

I COBOL och äldre stordatorsystem har alltid-falska villkor en annan karaktär än i modern applikationskod, och en annan betydelse för de team som underhåller eller moderniserar dem.

COBOL-program omstruktureras inte på samma sätt som modern kod. När en affärsregel ändras, en skattekod revideras, en produktnivå elimineras eller ett regelkrav uppdateras, är det vanligt att lägga till den nya logiken utan att ta bort den gamla. Den gamla grenen EVALUATE WHEN eller IF finns kvar i källkoden, skyddad av ett villkor som aldrig kan vara sant med tanke på det aktuella tillståndet för de data den bearbetar.

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.

De två döda grenarna i denna UTVÄRDERING, BAS och LEG, är inte fel. De är historiska artefakter: affärsregler som var giltiga vid specifika tidpunkter i systemets historia och blev permanent oåtkomliga när dessa produkttyper togs ur bruk. Ingen tog bort dem eftersom ingen var säker på om de fortfarande behövdes, och kostnaden för att ha fel verkade högre än kostnaden för att låta dem vara kvar.

Vad alltid-falska villkor i COBOL avslöjar:

De döda grenarna representerar affärslogik som inte längre gäller. PERFORM CALC-BASIC-RATE och PERFORM CALC-LEGACY-RATE Mål kan själva vara hela stycken eller avsnitt av COBOL-kod, potentiellt hundratals rader vardera, som på liknande sätt är döda. Villkoret "alltid falskt" är ingångspunkten till ett träd av död kod som fortplantar sig genom PERFORM-relationer.

I ett COBOL-program med 100 000 rader kan döda grenar från pensionerade affärsregler stå för 20–35 % av den totala koden. Den procentandelen är migreringsomfång som inte behöver konverteras, testas eller valideras i ett moderniseringsprogram. Att identifiera det före migreringsplanering ger en direkt omfångsreduktion, och omfångsreduktion i planeringsstadiet är den mest värdefulla tillämpningen av alltid-falskt tillståndsanalys.

Beslutsträdet för resolutionen

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.

Undertryckande: När och hur

Undertryckning är lämpligt när villkoret är genuint avsiktligt och verktyget inte kan få kontraktsinformationen för att förstå varför. Undertryckning är inte lämpligt som en genväg för att undvika att undersöka vad varningen faktiskt betyder.

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):

pytonorm

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 / anpassad undertryckningskommentar):

cobol

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

Disciplinär undertryckande: Varje undertryckning bör åtföljas av en kommentar som förklarar varför villkoret är avsiktligt, inte bara att det undertrycks. En undertryckningskommentar som säger // suppress unreachable är värre än ingen undertryckning, den döljer varningen utan att förklara varför varningen är fel. En undertryckning som säger // suppress: DEBUG_MODE is a compile-time constant, branch is retained for IDE visibility är acceptabelt.

Hur SMART TS XL Identifierar alltid-falska villkor på portföljskala

SMART TS XLÄr statisk kodanalys tillämpar dataflödes- och kontrollflödesanalys över COBOL, JCL, Java, Python och alla andra språk i miljön för att identifiera villkor som statiskt kan bevisas som alltid-falska, i portföljskala, över tusentals program samtidigt, inte en fil i taget i en utvecklar-IDE.

För COBOL-portföljer specifikt betyder detta: varje EVALUATE WHEN-klausul och varje IF-villkor i varje program analyseras mot de kända begränsningarna för de data som flödar in i det. Villkor som refererar till produktkoder, statusflaggor eller typkoder vars giltiga värdeuppsättningar har ändrats under programmets historik flaggas som potentiella döda grenar. Utdata är en inventering av död kod: varje program, varje död gren och det uppskattade radantalet död kod som kan nås från varje alltid-falskt villkor.

Analysen av den äldre moderniseringen omvandlar detta inventarium till en minskning av omfattningen: varje död gren exkluderas från migreringsspecifikationen innan någon konvertering påbörjas. Ett program som verkar ha 2 000 rader affärslogik kan ha 600 rader död affärslogik från pensionerade produkttyper, eliminerade kundnivåer och föråldrade beräkningsvägar. Att migrera 1 400 rader istället för 2 000 rader är en minskning av omfattningen med 30 % för det programmet, och de döda grenarna är den delen av omfattningen som har högst risk att migrera, eftersom ingen vet vad de skulle göra från första början.

Företagssökningsfunktionen gör det möjligt att söka efter den alltid-falska inventeringen: hitta alla COBOL-program som innehåller en EVALUATE WHEN-klausul som refererar till en produktkod som inte finns i den aktuella produktkatalogen, alla program som söker efter ett statusflaggvärde som inte har funnits i databasen sedan ett visst år, alla IF-villkor som refererar till ett datastrukturfält som har använts om sedan villkoret skrevs. Dessa frågor producerar den riktade inventeringen av död kod som manuell kodgranskning skulle ta månader att producera över en stor COBOL-portfölj.

Varningen är oftast rätt

Statiska analysvarningar för villkor som alltid är falska är i de flesta fall korrekta resultat. Villkoret är alltid falskt. Grenen körs aldrig. Koden inuti den är död.

Det lämpliga svaret är inte undertryckning, utan undersökning. Vilken grundorsak gäller? Logisk motsägelse, ackumulerad ändring av affärsregeln, avsiktlig defensiv programmering eller falskt positivt resultat från verktygskonfigurationen? Var och en har en korrekt lösning. Tre av fyra resulterar i att den döda koden tas bort. Ett resulterar i undertryckning med motivering.

För äldre COBOL-system är analys av alltid-falska villkor i portföljskala inte primärt en övning i kodkvalitet, utan en övning i att minska omfattningen. De döda grenar som ackumuleras under årtionden av affärsregeländringar representerar migreringsinsatser som inte bör göras. Att hitta dem innan migreringsplanen är låst är den mest värdefulla tillämpningen av analys av alltid-falska villkor för alla team som planerar ett COBOL-moderniseringsprogram.

Analysatorn säger dig något sant. Lyssna på det.