항상 거짓인 조건에 대한 정적 분석 경고 해결

항상 거짓인 조건과 도달할 수 없는 분기에 대한 정적 분석 경고를 해결하는 방법

정적 분석기가 특정 조건을 항상 거짓으로 표시했습니다. 사용하시는 도구에 따라 경고 메시지는 다음과 같습니다.

  • 소나큐브: "항상 참이거나 거짓인 조건식은 실행 불가능한 코드로 이어질 수 있습니다." (RSPEC-2583)
  • IntelliJ / Inspectopedia: "조건이 정적으로 거짓으로 평가되었기 때문에 코드가 분석되지 않습니다."
  • 체크마르크스/포티파이: "접근할 수 없는 코드, 사용되지 않는 분기가 감지되었습니다."
  • ESL린트: "상수 조건 없음, 예상치 못한 상수 조건"
  • 필린트: “W0125: 상수 값을 사용하는 조건문”

이 모든 내용은 동일한 의미를 전달합니다. 분석 도구는 코드를 실행하지 않고 분석 단계에서 조건문의 특정 분기가 절대 실행될 수 없음을 입증했습니다. 해당 분기를 제어하는 ​​조건은 항상 거짓입니다. 따라서 해당 분기 안의 모든 코드는 실행되지 않는 코드입니다.

이 가이드에서는 이러한 경고의 원인, 각 경고가 속하는 범주를 식별하는 방법, 각 범주에 대한 올바른 해결 방법, 경고를 억제해야 하는 경우, 그리고 기존 COBOL 코드에서 항상 거짓인 조건이 마이그레이션 범위에 대해 무엇을 알려주는지에 대해 설명합니다.

더 이상 사용되지 않는 비즈니스 규칙이 여전히 코드에 남아 있습니다.

SMART TS XL 제품 코드, 상태 값 및 고객 유형을 참조하는 EVALUATE 분기 중 운영 데이터에 더 이상 존재하지 않는 항목을 식별합니다.

자세히 알아보세요…

정적 분석을 통해 항상 거짓인 조건을 탐지하는 방법

최신 정적 분석기는 단순한 패턴 매칭이 아닌 기호 실행 및 데이터 흐름 분석을 사용하여 항상 거짓인 조건을 탐지합니다. 분석기는 프로그램의 모든 지점에서 각 변수의 가능한 값을 추적합니다. 조건문에 도달하면 변수에 대한 알려진 제약 조건 하에서 해당 조건이 참이 될 수 있는지 여부를 평가합니다.

항상 잘못된 탐지를 유발하는 세 가지 주요 메커니즘은 다음과 같습니다.

상징적 처형. 분석기는 변수에 기호 값을 할당하고 연산이 해당 값에 어떤 제약을 가하는지 추적합니다. 변수가 x 할당된다 0 3번째 줄과 비교했습니다. x > 10 7번째 줄에 중간에 할당이 없으면 해당 조건은 명백히 거짓입니다.

타입 시스템 제약 조건. 변수의 타입 때문에 일부 조건은 불가능합니다. 예를 들어, null이 아님을 확인한 후 null이 아닌 객체 참조를 null과 비교하거나, 열거형 값을 정의된 집합 이외의 경우와 비교하는 경우입니다.

부울 대수 간소화. 단순화되는 복합 조건 false 변수 값과 관계없이: (a && !a), (x > 10 && x < 5), (!flag || false).

SonarQube의 규칙 RSPEC-2583은 특히 기호 실행을 사용합니다. 즉, 제어 흐름 그래프를 통해 변수의 상태를 추적하고 기호 상태가 조건을 명백히 거짓으로 만드는 경우 해당 조건을 표시합니다. IntelliJ의 "조건이 정적으로 거짓으로 평가됨" 경고도 동일한 접근 방식을 사용합니다.

네 가지 근본 원인

항상 잘못된 경고를 올바르게 해결하려면 네 가지 근본 원인 중 어떤 원인이 해당하는지 정확하게 파악해야 합니다. 해결 방법은 원인에 따라 다릅니다.

근본 원인 1: 논리적 모순

해당 조건에는 논리적 불가능성, 동시에 참일 수 없는 두 개의 하위 조건, 또는 로 축소되는 복합 조건이 포함되어 있습니다. 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
    }
}

Resolution : 논리를 수정하세요. 조건에 오타가 있거나 (개발자가 의도한 바가 틀렸을 수 있음) 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은 다음과 같은 예외를 명시적으로 규정합니다. "조건이 단일 final 부울 값인 경우" 또는 "조건이 문자 그대로 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(); }

Resolution : 적절한 메서드 계약 어노테이션을 추가하세요(@Contract IntelliJ용, @NotNull/@Nullable Kotlin의 경우 타입 어설션, TypeScript의 경우 타입 어설션을 사용하여 분석기에 필요한 런타임 정보를 제공합니다. 어노테이션 기반 해결이 불가능한 경우, 문서화된 근거와 함께 도구의 억제 메커니즘을 사용합니다.

COBOL: 항상 거짓인 조건이 수십 년간 묵살된 논리를 드러내는 곳

COBOL 및 기존 메인프레임 시스템에서 항상 거짓인 조건은 최신 애플리케이션 코드에서와는 다른 특성을 가지며, 이를 유지 관리하거나 현대화하는 팀에게 다른 의미를 갖습니다.

COBOL 프로그램은 최신 코드처럼 리팩토링되지 않습니다. 비즈니스 규칙이 변경되거나, 세법이 개정되거나, 제품 등급이 삭제되거나, 규제 요건이 업데이트될 때, 기존 로직을 제거하지 않고 새로운 로직을 추가하는 것이 일반적입니다. 따라서 기존의 EVALUATE WHEN 또는 IF 분기문은 소스 코드에 그대로 남아 있으며, 처리하는 데이터의 현재 상태를 고려할 때 결코 참이 될 수 없는 조건에 의해 보호됩니다.

코볼

       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.

이 평가에서 죽은 두 가지, BAS LEG이러한 규칙들은 오류가 아닙니다. 그것들은 역사적 유물입니다. 시스템 역사의 특정 시점에는 유효했지만 해당 제품 유형이 단종되면서 영구적으로 사용할 수 없게 된 비즈니스 규칙들입니다. 아무도 그것들을 제거하지 않았는데, 그 이유는 그것들이 여전히 필요할지 확신할 수 없었고, 잘못 판단했을 때의 비용이 그대로 두는 비용보다 더 커 보였기 때문입니다.

COBOL에서 항상 거짓인 조건이 드러내는 것:

사용되지 않는 분기는 더 이상 적용되지 않는 비즈니스 로직을 나타냅니다. PERFORM CALC-BASIC-RATE 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.

억제: 언제, 어떻게

경고를 억제하는 것은 해당 상황이 진정으로 의도적이고 도구에 계약 정보를 제공하여 그 이유를 이해할 수 없는 경우에 적절합니다. 하지만 경고의 실제 의미를 조사하는 것을 피하기 위한 지름길로 경고를 억제하는 것은 적절하지 않습니다.

자바(SonarQube):

자바

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

자바(IntelliJ):

자바

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

파이썬(Pylint):

파이썬

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

C# (SonarQube):

날카로운

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

코볼(SMART TS XL (사용자 지정 억제 주석):

코볼

      *>> 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에서 파일 하나씩 분석하는 것이 아니라 항상 거짓임을 정적으로 증명할 수 있는 조건을 식별합니다.

특히 COBOL 포트폴리오의 경우, 모든 프로그램의 모든 EVALUATE WHEN 절과 모든 IF 조건은 입력되는 데이터에 대한 알려진 제약 조건과 비교하여 분석됩니다. 프로그램 실행 과정에서 유효 값 집합이 변경된 제품 코드, 상태 플래그 또는 유형 코드를 참조하는 조건은 잠재적인 데드 분기로 표시됩니다. 출력 결과는 데드 코드 목록으로, 모든 프로그램, 모든 데드 분기, 그리고 항상 거짓인 각 조건에서 도달 가능한 데드 코드의 예상 줄 수를 보여줍니다.

레거시 현대화 분석을 통해 이러한 인벤토리를 범위 축소로 변환합니다. 즉, 모든 비활성 분기 코드는 변환 작업이 시작되기 전에 마이그레이션 사양에서 제외됩니다. 2,000줄의 비즈니스 로직이 있는 것처럼 보이는 프로그램에는 단종된 제품 유형, 삭제된 고객 등급, 더 이상 사용되지 않는 계산 경로 등으로 인해 600줄의 비활성 비즈니스 로직이 있을 수 있습니다. 2,000줄 대신 1,400줄만 마이그레이션하면 해당 프로그램의 범위가 30% 감소하며, 비활성 분기 코드는 원래 어떤 기능을 수행하도록 설계되었는지 아무도 모르기 때문에 마이그레이션 범위에서 가장 위험 부담이 큰 부분입니다.

엔터프라이즈 검색 기능을 통해 항상 거짓으로 표시되는 인벤토리를 쿼리할 수 있습니다. 예를 들어, 현재 제품 카탈로그에 없는 제품 코드를 참조하는 EVALUATE WHEN 절을 포함하는 모든 COBOL 프로그램, 특정 연도 이후 데이터베이스에 존재하지 않는 상태 플래그 값을 확인하는 모든 프로그램, 조건이 작성된 이후 용도가 변경된 데이터 구조 필드를 참조하는 모든 IF 조건을 찾을 수 있습니다. 이러한 쿼리를 통해 대규모 COBOL 포트폴리오 전반에 걸쳐 수동 코드 검토로 몇 달이 걸릴 작업을 효율적으로 수행할 수 있습니다.

경고는 대개 옳다

정적 분석에서 항상 거짓인 조건에 대한 경고는 대부분 정확한 결과입니다. 해당 조건은 항상 거짓이므로 분기는 실행되지 않습니다. 따라서 분기 안의 코드는 사용되지 않습니다.

적절한 대응은 억제가 아니라 조사입니다. 근본 원인은 무엇일까요? 논리적 모순, 누적된 비즈니스 규칙 변경, 의도적인 방어적 프로그래밍, 또는 런타임 구성으로 인한 도구의 오탐지일까요? 각각에 대한 올바른 해결책이 있습니다. 네 가지 중 세 가지는 사용되지 않는 코드를 제거하는 것으로 귀결되고, 나머지 한 가지는 정당한 이유를 제시하여 억제하는 것으로 귀결됩니다.

기존 COBOL 시스템의 경우, 포트폴리오 규모의 상시 거짓 조건 분석은 주로 코드 품질 개선을 위한 작업이 아니라, 작업 범위 축소를 ​​위한 작업입니다. 수십 년에 걸친 비즈니스 규칙 변경으로 누적된 사용되지 않는 브랜치는 마이그레이션에 투입해서는 안 될 노력을 의미합니다. 마이그레이션 계획이 확정되기 전에 이러한 사용되지 않는 브랜치를 찾아내는 것이 COBOL 현대화 프로그램을 계획하는 모든 팀에게 있어 상시 거짓 조건 분석의 가장 중요한 활용 사례입니다.

분석기가 당신에게 진실을 말하고 있습니다. 귀 기울여 들으세요.