常に偽となる条件に対する静的解析警告を解決する

常に偽となる条件と到達不能な分岐に関する静的解析警告を解決する方法

静的解析ツールが、ある条件を常に偽であると検出しました。警告メッセージは、ツールによって以下のいずれかになります。

  • ソナーキューブ: 「常に真または偽となる条件式は、到達不能なコードにつながる可能性がある」 (RSPEC-2583)
  • IntelliJ / Inspectopedia: 「条件が静的に偽と評価されたため、コードは解析されません」
  • Checkmarx / Fortify: 「到達不能なコード、デッドブランチが検出されました」
  • ESLint: 「定数条件なし、予期せぬ定数条件」
  • パイリント: 「W0125:定数値を用いた条件文の使用」

これらは全て同じことを意味しています。つまり、アナライザーはコードを実行することなく、解析時に条件文の特定の分岐が決して実行されないことを証明したということです。その分岐を保護する条件は常に偽です。その分岐内のコードは全てデッドコードです。

このガイドでは、これらの警告が発生する原因、各警告がどのカテゴリに属する​​かを識別する方法、各カテゴリに対する適切な解決策、警告の抑制が適切な場合、および従来の COBOL コードにおける常に偽となる条件が移行範囲について何を示しているかについて説明します。

廃止されたビジネスルールがまだコードに残っている

SMART TS XL 本番データに存在しなくなった製品コード、ステータス値、および顧客タイプを参照しているEVALUATEブランチを特定します。

さらに詳しく…

静的解析が常に偽となる条件を検出する方法

最新の静的解析器は、単純なパターンマッチングではなく、記号実行とデータフロー解析を用いて、常に偽となる条件を検出します。解析器は、プログラム内のあらゆる時点で各変数の取りうる値を追跡します。条件式に到達すると、変数に対する既知の制約に基づいて、その条件が真となる可能性があるかどうかを評価します。

常に誤検出となる主なメカニズムは3つあります。

象徴的な処刑。 アナライザーは変数にシンボル値を割り当て、演算がそれらの値をどのように制約するかを追跡します。変数が x 割り当てられています 0 3行目を比較 x > 10 7行目に代入がない場合、条件は明らかに偽である。

型システムの制約。変数の型によっては、一部の条件は不可能です。例えば、nullでないアサーションの後にnullでないオブジェクト参照をnullと比較したり、列挙型値を定義済みセット外のケースと比較したりする場合などです。

ブール代数の簡略化。 複合条件を簡略化すると false 変数の値に関係なく: (a && !a), (x > 10 && x < 5), (!flag || false).

SonarQubeのルールRSPEC-2583は、シンボリック実行を具体的に利用しています。制御フローグラフを通して変数の状態を追跡し、シンボリック状態によって条件が明らかに偽となる場合にフラグを立てます。IntelliJの「条件が静的に偽と評価されました」という警告も、同じアプローチを採用しています。

4つの根本原因

常に誤った警告を正しく解決するには、4つの根本原因のうちどれが該当するかを正しく特定する必要があります。それぞれの原因によって解決策は異なります。

根本原因1:論理的矛盾

この条件には論理的に不可能なこと、同時に真になり得ない 2 つのサブ条件、または、以下になる複合条件が含まれています。 false ブール代数によって。

ジャワ

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

解像度: ロジックを修正してください。条件にタイプミスがあるか(開発者は score > 90 || score < 50) または、その分岐を削除する必要があります。範囲チェックの例では、内側の if 重複しているため削除すべきです。

根本原因2:累積した業務ルール変更

これは、長期間運用されているコードベースにおいて最も深刻な原因です。記述当時は有効で意味のある条件が、その後のコード変更によって恒久的に偽になってしまい、しかもその既に使われなくなったブランチが削除されないままになってしまうのです。

ジャワ

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

解決策:ビジネスコンテキストを確認し、不要になったブランチを完全に削除します。この条件を可能にしていたビジネスルールが変更されたため、それを実装していたコードは不要になりました。コミットメッセージに削除内容を記載してください。「BRONZE レートブランチを削除しました。ティアは 2021 年第 2 四半期に廃止され、すべてのお客様が SILVER に移行しました。」

根本原因3:将来を見据えた防御的プログラミング

常に偽となる条件の中には、意図的に記述されたものもあります。開発者は、その条件が現状では真になり得ないことを承知の上で、将来のコード変更によってその条件が真になる可能性に備えるための防御策として、そのような条件を記述したのです。

ジャワ

// 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 では、例外が明示的に規定されています。「条件が単一の最終的なブール値である場合」または「条件が文字通り true または false である場合」は、意図的なものとして扱われ、フラグは立てられません。

解決策:条件がデバッグフラグまたはコンパイル時定数である場合は、そのままにして、ツールがフラグを立てた場合は抑制注釈を追加します。冗長に思えるが実際には保護に役立つ防御的なヌルチェックまたは範囲ガードである場合は、コードを再構成して別のレベルで保護を明示的にするか、文書化された正当な理由に基づいて抑制します。

根本原因4:実行時構成によるツールの誤検出

静的解析ツールは、実行時構成、​​環境変数、リフレクション、アノテーション処理、依存性注入などを参照できません。解析ツールにとって常に偽に見えるコードでも、解析ツールではモデル化できない実行時メカニズムを介してアクセスできる可能性があります。

ジャワ

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

解像度: 適切なメソッド契約注釈を追加します(@Contract IntelliJ の場合、 @NotNull/@Nullable Kotlin の場合は型アサーション、TypeScript の場合は型アサーションを使用して、アナライザーが必要とするランタイム情報を取得します。アノテーションによる解決が利用できない場合は、ツールの抑制メカニズムを文書化された正当な理由とともに使用します。

COBOL:常に偽となる条件が、数十年にわたる時代遅れのロジックを明らかにする

COBOLや従来のメインフレームシステムでは、常に偽となる条件は、現代のアプリケーションコードとは異なる性質を持ち、それらを保守または近代化するチームにとって異なる意味合いを持つ。

COBOLプログラムは、現代のコードのようにリファクタリングされることはありません。ビジネスルールが変更されたり、税法が改訂されたり、製品階層が廃止されたり、規制要件が更新されたりしても、古いロジックを削除せずに新しいロジックを追加するのが一般的です。古いEVALUATE WHENまたはIF分岐はソースコード内に残り、処理対象のデータの現在の状態からして決して真になり得ない条件によって保護されています。

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.

この EVALUATE の 2 つの枯れた枝は、 BAS (NAIST) と LEGこれらはエラーではありません。これらは歴史的な遺物です。システムの歴史上の特定の時点では有効だったビジネスルールですが、該当する製品タイプが廃止された時点で、永久にアクセスできなくなりました。誰も削除しなかったのは、それらがまだ必要かどうか確信が持てず、誤っていた場合のコストが、そのままにしておくコストよりも高いと思われたからです。

COBOLにおける常に偽となる条件が明らかにするもの:

枯れた枝は、もはや適用されないビジネスロジックを表しています。 PERFORM CALC-BASIC-RATE (NAIST) と PERFORM CALC-LEGACY-RATE ターゲットとなるのは、それぞれ数百行にも及ぶ可能性のある、同様に不要なCOBOLコードの段落やセクション全体である場合がある。常に偽となる条件は、PERFORM関係を通じて伝播する不要なコードのツリーへの入り口となる。

100,000万行のCOBOLプログラムでは、廃止されたビジネスルールから派生したデッドブランチがコード全体の20~35%を占める場合があります。この割合は、近代化プログラムにおいて変換、テスト、検証の必要がない移行範囲です。移行計画の前にこれを特定することで、直接的な範囲削減につながり、計画段階での範囲削減は、常に偽となる条件分析の最も価値の高い活用法となります。

解決決定ツリー

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.

抑制:いつ、どのように

抑制は、その状態が真に意図的なものであり、ツールにその理由を理解するための契約情報を提供できない場合に適切です。警告の実際の意味を調査することを避けるための安易な手段として抑制を用いるのは適切ではありません。

Java (SonarQube):

ジャワ

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

Java (IntelliJ):

ジャワ

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

Python (Pylint):

パイソン

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

C# (SonarQube):

Cシャープ

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

COBOL(SMART TS XL / カスタム抑制コメント):

COBOL

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

抑制規律: すべての抑制には、単に抑制されているという事実だけでなく、なぜその状態が意図的なものなのかを説明するコメントが添えられるべきです。抑制コメントには、次のように記載する必要があります。 // suppress unreachable これは抑制しないよりも悪い。警告が間違っている理由を説明せずに警告を隠してしまう。 // suppress: DEBUG_MODE is a compile-time constant, branch is retained for IDE visibility 許容範囲です。

認定条件 SMART TS XL ポートフォリオ規模で常に偽となる条件を特定する

SMART TS XLさん 静的コード分析 COBOL、JCL、Java、Python、および環境内の他のすべての言語にわたってデータフローと制御フローの分析を適用し、開発者IDEで一度に1つのファイルではなく、ポートフォリオ規模で、数千のプログラムにわたって同時に、常に偽であることが静的に証明できる条件を特定します。

COBOLポートフォリオの場合、これは具体的には、すべてのプログラム内のすべてのEVALUATE WHEN句とすべてのIF条件が、入力データに対する既知の制約に基づいて分析されることを意味します。プログラムの履歴を通じて有効な値セットが変更された製品コード、ステータスフラグ、またはタイプコードを参照する条件は、潜在的なデッドブランチとしてフラグ付けされます。出力はデッドコードインベントリであり、すべてのプログラム、すべてのデッドブランチ、および常に偽となる各条件から到達可能なデッドコードの推定行数が含まれます。

レガシーシステムの近代化分析では、このインベントリをスコープの削減に変換します。つまり、移行作業を開始する前に、すべての不要な分岐を移行仕様から除外します。2,000行のビジネスロジックを持つように見えるプログラムでも、廃止された製品タイプ、削除された顧客階層、非推奨の計算パスなどから、600行の不要なビジネスロジックが含まれている可能性があります。2,000行ではなく1,400行を移行することで、そのプログラムのスコープは30%削減されます。また、不要な分岐は、そもそも何をするべきだったのか誰も知らないため、移行対象の中で最もリスクの高い部分となります。

エンタープライズ検索機能により、常に偽となるインベントリをクエリ可能にします。例えば、現在の製品カタログに存在しない製品コードを参照する EVALUATE WHEN 句を含むすべての COBOL プログラム、特定の年以降データベースに存在しないステータス フラグ値をチェックするすべてのプログラム、条件が記述されてから用途が変更されたデータ構造フィールドを参照するすべての IF 条件を検索できます。これらのクエリにより、大規模な COBOL ポートフォリオ全体で手動コード レビューを行うと数か月かかるような、対象を絞ったデッド コード インベントリが生成されます。

警告は大抵正しい

静的解析で常に偽となる条件に関する警告は、ほとんどの場合、正確な結果を示しています。その条件は常に偽であり、分岐は決して実行されません。その中のコードは無効です。

適切な対応は抑制ではなく、調査です。どの根本原因が該当するでしょうか?論理的な矛盾、累積的なビジネスルールの変更、意図的な防御的プログラミング、それとも実行時構成によるツールの誤検出でしょうか?それぞれに適切な解決策があります。4つのうち3つはデッドコードの削除につながり、残りの1つは正当な理由に基づく抑制につながります。

レガシーCOBOLシステムの場合、ポートフォリオ規模での常時偽条件分析は、主にコード品質向上のための作業ではなく、スコープ削減のための作業です。数十年にわたる業務ルール変更によって蓄積されたデッドブランチは、投入すべきではない移行作業を表しています。移行計画が確定する前にこれらを見つけることは、COBOL近代化プログラムを計画するあらゆるチームにとって、常時偽条件分析の最も価値の高い活用方法です。

分析器は正しいことを告げている。それに耳を傾けなさい。