Votre analyseur statique a signalé une condition comme étant toujours fausse. L'avertissement ressemble à l'un de ces exemples, selon votre outil :
- SonarQube : « Les expressions conditionnelles qui sont toujours vraies ou fausses peuvent conduire à du code inaccessible. » (RSPEC-2583)
- IntelliJ / Inspectopedia : « Le code n'est pas analysé car la condition est statiquement évaluée comme fausse. »
- Checkmarx / Fortify : « Code inaccessible, branche morte détectée »
- ESLint : « absence de condition constante, condition constante inattendue »
- Pylint : « W0125 : Utilisation d'une instruction conditionnelle avec une valeur constante »
Tout cela revient au même : l’analyseur a prouvé, lors de l’analyse et sans exécuter le code, qu’une branche spécifique d’une instruction conditionnelle ne peut jamais être exécutée. La condition qui protège cette branche est toujours fausse. Tout le code à l’intérieur de cette branche est mort.
Ce guide explique les causes de ces avertissements, comment identifier la catégorie à laquelle appartient chaque avertissement, la solution appropriée pour chaque catégorie, quand la suppression est appropriée et ce que les conditions toujours fausses dans le code COBOL hérité révèlent sur la portée de la migration.
Des règles métier obsolètes sont toujours présentes dans votre code.
SMART TS XL identifie les branches EVALUATE faisant référence à des codes produits, des valeurs d'état et des types de clients qui n'existent plus dans les données de production.
EN SAVOIR PLUS…Comment l'analyse statique détecte les conditions toujours fausses
Les analyseurs statiques modernes utilisent l'exécution symbolique et l'analyse du flux de données, et non une simple recherche de motifs, pour détecter les conditions toujours fausses. L'analyseur suit les valeurs possibles de chaque variable à chaque étape du programme. Lorsqu'il rencontre une condition, il évalue si celle-ci peut être vraie compte tenu des contraintes connues sur les variables.
Trois mécanismes principaux produisent une détection toujours fausse :
Exécution symbolique. L'analyseur attribue des valeurs symboliques aux variables et suit la manière dont les opérations contraignent ces valeurs. Si la variable x est assigné 0 à la ligne 3 et comparé x > 10 Sur la ligne 7, sans affectation intermédiaire, la condition est manifestement fausse.
Contraintes liées au système de types. Certaines conditions sont impossibles compte tenu du type de la variable. Par exemple, comparer une référence d'objet non nulle à null après une assertion de non-nullité, ou comparer une valeur d'énumération à un cas hors de son ensemble défini.
Simplification de l'algèbre booléenne. Conditions composées qui se simplifient en false indépendamment des valeurs des variables : (a && !a), (x > 10 && x < 5), (!flag || false).
La règle RSPEC-2583 de SonarQube utilise spécifiquement l'exécution symbolique : elle suit l'état des variables à travers le graphe de flux de contrôle et signale les conditions pour lesquelles l'état symbolique rend la condition manifestement fausse. L'avertissement « condition statiquement évaluée comme fausse » d'IntelliJ utilise la même approche.
Les quatre causes profondes
La résolution correcte d'une alerte toujours fausse dépend de l'identification précise de la cause première parmi les quatre possibles. La solution diffère pour chacune.
Cause première : Contradiction logique
La condition contient une impossibilité logique, deux sous-conditions qui ne peuvent être vraies simultanément, ou une condition composée qui se réduit à false par l'algèbre booléenne.
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
}
}
Résolution: Corrigez la logique. Soit la condition contient une faute de frappe (le développeur voulait dire score > 90 || score < 50) ou la branche doit être supprimée. Dans l'exemple de vérification de plage, l'interne if est redondant et devrait être supprimé.
Cause première 2 : Accumulation de modifications des règles métier
Il s'agit de la cause la plus fréquente dans les bases de code à longue durée de vie. Une condition valide et pertinente lors de sa création est devenue définitivement fausse suite à des modifications ultérieures du code environnant, sans que personne n'ait supprimé la branche désormais obsolète.
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
Résolution : Vérifiez le contexte métier, puis supprimez complètement la branche obsolète. La règle métier qui rendait la condition possible a changé. Le code qui l’implémentait n’est plus nécessaire. Documentez la suppression dans le message de commit : « Suppression de la branche BRONZE, niveau éliminé au deuxième trimestre 2021, tous les clients sont passés au niveau SILVER. »
Cause racine 3 : Programmation défensive pour pérenniser les problèmes
Certaines conditions toujours fausses sont intentionnelles ; le développeur les a écrites en sachant que la condition ne peut pas être vraie actuellement, comme mesure de protection contre de futures modifications du code qui pourraient la rendre vraie.
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
}
La spécification RSPEC-2583 de SonarQube prévoit explicitement une exception : « Lorsque la condition est un booléen final unique » ou « lorsque la condition est littéralement vraie ou fausse », ces cas sont considérés comme intentionnels et ne sont pas signalés.
Résolution : Si la condition est un indicateur de débogage ou une constante de compilation : conservez-la et ajoutez une annotation de suppression si l’outil la signale. S’il s’agit d’une vérification de valeur nulle ou d’une protection de plage défensive qui semble redondante mais qui est réellement protectrice : restructurez le code pour rendre la protection explicite à un autre niveau, ou supprimez-la en fournissant une justification documentée.
Cause racine 4 : Faux positifs de l’outil dus à la configuration d’exécution
Les analyseurs statiques ne peuvent pas accéder à la configuration d'exécution, aux variables d'environnement, à la réflexion, au traitement des annotations ni à l'injection de dépendances. Du code qui apparaît toujours faux à l'analyseur peut être accessible via un mécanisme d'exécution que l'analyseur ne peut pas modéliser.
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(); }
Résolution: Ajoutez l'annotation de contrat de méthode appropriée (@Contract pour IntelliJ, @NotNull/@Nullable Pour Kotlin, utilisez des assertions de type ; pour TypeScript, utilisez des assertions de type afin de fournir à l’analyseur les informations d’exécution nécessaires. Si la résolution par annotations n’est pas disponible, utilisez le mécanisme de suppression de l’outil en le justifiant par une documentation.
COBOL : Quand des conditions toujours fausses révèlent des décennies de logique obsolète
En COBOL et dans les anciens systèmes mainframe, les conditions toujours fausses ont un caractère différent de celui du code d'application moderne, et une signification différente pour les équipes qui les maintiennent ou les modernisent.
Les programmes COBOL ne sont pas remaniés comme le code moderne. Lorsqu'une règle métier change, qu'un code fiscal est révisé, qu'une gamme de produits est supprimée ou qu'une exigence réglementaire est mise à jour, la pratique courante consiste à ajouter la nouvelle logique sans supprimer l'ancienne. L'ancienne branche EVALUATE WHEN ou IF reste donc présente dans le code source, protégée par une condition qui ne peut jamais être vraie compte tenu de l'état actuel des données qu'elle traite.
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.
Les deux branches mortes dans cette ÉVALUATION, BAS et LEGCe ne sont pas des erreurs. Ce sont des vestiges historiques : des règles métier valides à des moments précis de l’histoire du système et devenues définitivement inaccessibles lorsque les types de produits correspondants ont été abandonnés. Personne ne les a supprimées car personne n’était certain de leur utilité future, et le risque d’erreur semblait plus élevé que celui de les laisser en place.
Que révèlent les conditions toujours fausses en COBOL :
Les branches mortes représentent une logique métier qui n'est plus applicable. PERFORM CALC-BASIC-RATE et PERFORM CALC-LEGACY-RATE Les cibles peuvent être des paragraphes ou des sections entières de code COBOL, pouvant atteindre plusieurs centaines de lignes chacune, qui sont également mortes. La condition toujours fausse est le point d'entrée d'une arborescence de code mort qui se propage via les relations PERFORM.
Dans un programme COBOL de 100 000 lignes, les branches mortes issues de règles métier obsolètes peuvent représenter 20 à 35 % du code total. Ce pourcentage correspond au périmètre de migration qui n'a pas besoin d'être converti, testé ni validé dans le cadre d'un programme de modernisation. L'identifier avant la planification de la migration permet une réduction directe du périmètre, et cette réduction dès la phase de planification constitue l'application la plus pertinente de l'analyse des conditions toujours fausses.
L'arbre de décision de résolution
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.
Suppression : quand et comment
La suppression de l'alerte est justifiée lorsque la situation est réellement intentionnelle et que l'outil ne peut pas accéder aux informations contractuelles nécessaires pour en comprendre la raison. En revanche, elle n'est pas appropriée comme raccourci pour éviter d'analyser la signification de l'avertissement.
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) :
tranchant
#pragma warning disable S2589
if (IsDebugBuild) { // Compile-time constant -- intentional
LogDiagnostics();
}
#pragma warning restore S2589
COBOL (SMART TS XL / commentaire de suppression personnalisé) :
Cobol
*>> SUPPRESS UNREACHABLE: BAS branch retained for audit trail
WHEN 'BAS'
PERFORM CALC-BASIC-RATE
Discipline de répression : Chaque suppression doit être accompagnée d'un commentaire expliquant pourquoi cette suppression est intentionnelle, et non pas simplement qu'elle est effectuée. Un commentaire de suppression qui dit
// suppress unreachableest pire que l'absence de suppression, car cela masque l'avertissement sans expliquer pourquoi il est erroné. Une suppression qui dit// suppress: DEBUG_MODE is a compile-time constant, branch is retained for IDE visibilityest acceptable.
Comment SMART TS XL Identifie les conditions toujours fausses à l'échelle du portefeuille
SMART TS XL's analyse de code statique applique l'analyse des flux de données et des flux de contrôle à travers COBOL, JCL, Java, Python et tous les autres langages de l'environnement pour identifier les conditions qui peuvent être statiquement prouvées comme toujours fausses, à l'échelle du portefeuille, sur des milliers de programmes simultanément, et non un fichier à la fois dans un EDI de développeur.
Pour les portefeuilles COBOL en particulier, cela signifie que chaque clause EVALUATE WHEN et chaque condition IF de chaque programme est analysée au regard des contraintes connues sur les données qui y entrent. Les conditions faisant référence à des codes de produit, des indicateurs d'état ou des codes de type dont les ensembles de valeurs valides ont évolué au cours de l'historique du programme sont signalées comme des branches potentiellement mortes. Le résultat est un inventaire du code mort : chaque programme, chaque branche morte et le nombre estimé de lignes de code mort accessibles depuis chaque condition toujours fausse.
L' analyse de modernisation des systèmes existants permet de réduire le périmètre : chaque branche obsolète est exclue du cahier des charges de migration avant toute opération de conversion. Un programme comportant apparemment 2 000 lignes de logique métier peut en contenir 600, obsolètes, provenant de types de produits abandonnés, de niveaux clients supprimés et de chemins de calcul dépréciés. Migrer 1 400 lignes au lieu de 2 000 représente une réduction de périmètre de 30 % pour ce programme. Les branches obsolètes constituent la partie la plus risquée du périmètre de migration, car leur fonction initiale est inconnue.
La fonction de recherche d'entreprise permet d'interroger l'inventaire des codes morts : elle permet de trouver tous les programmes COBOL contenant une clause EVALUATE WHEN faisant référence à un code produit absent du catalogue actuel, tous les programmes vérifiant une valeur d'indicateur d'état qui n'a pas été enregistrée dans la base de données depuis une certaine année, et toutes les conditions IF faisant référence à un champ de structure de données réaffecté depuis leur écriture. Ces requêtes génèrent l'inventaire ciblé des codes morts, un travail qui prendrait des mois à réaliser manuellement sur un vaste portefeuille de programmes COBOL.
L'avertissement est généralement justifié.
Les avertissements d'analyse statique concernant les conditions toujours fausses sont, dans la plupart des cas, exacts. La condition est toujours fausse. La branche n'est jamais exécutée. Le code qu'elle contient est inactif.
La réponse appropriée n'est pas la suppression, mais l'investigation. Quelle est la cause profonde ? Contradiction logique, modification cumulative des règles métier, programmation défensive intentionnelle ou faux positif d'un outil de configuration d'exécution ? Chaque cas a une solution. Trois des quatre causes entraînent la suppression du code mort. La quatrième justifie sa suppression.
Pour les systèmes COBOL existants, l'analyse des conditions toujours fausses à l'échelle du portefeuille n'est pas avant tout un exercice de qualité du code, mais plutôt une démarche de réduction de la portée. Les branches mortes qui s'accumulent au fil des décennies de modifications des règles métier représentent un effort de migration inutile. Les identifier avant que le plan de migration ne soit figé constitue l'application la plus précieuse de l'analyse des conditions toujours fausses pour toute équipe planifiant un programme de modernisation COBOL.
L'analyseur vous dit quelque chose de vrai. Écoutez-le.


