드모르간 법칙으로 앱 조건문 단순화하기

드모르간 법칙은 긴 조건문을 짧게 만드는 요령이 아니라, 부정의 범위를 바꾸면서 결과를 그대로 보존하는 등가 변환입니다. !(A && B)를 !A || !B로 바꿀 때 연산자와 각 항의 부정을 함께 바꿔야 합니다. 괄호 하나를 빼거나 항 하나의 !를 놓치면 문법은 통과해도 일부 입력에서만 다른 결과가 나옵니다.

2026년 10월 2일 Windows 10 x64와 Dart 3.13.4에서 2변수 4행, 3변수 8행을 전부 생성해 두 공식의 좌우를 비교했습니다. 단락 평가 순서를 기록하고, 괄호를 빠뜨린 식과 nullable bool도 별도 실행했습니다. 총 25개 동치 조건은 통과했고, !A && B와 !(A && B)는 4개 입력 중 2개에서 달랐습니다.

두 공식은 네 입력에서 모두 같은 값을 냈다

A B !(A && B) !A || !B !(A || B) !A && !B
false false true true true true
false true true true false false
true false true true false false
true true false false false false

세 항으로 확장한 !(A && B && C)와 !A || !B || !C, !(A || B || C)와 !A && !B && !C도 8개 입력을 모두 비교했습니다. 이 16개 비교와 2변수 8개, 단락 평가 결과 1개를 합한 25개 조건이 모두 통과했습니다.

Dart 드모르간 법칙 진리표와 괄호 누락 단락 평가 nullable bool을 직접 검증한 결과
2026년 10월 2일 Dart 3.13.4에서 2·3변수 진리표, 괄호 누락, 단락 평가와 nullable bool을 실행한 결과입니다.

실무에서 가장 위험한 오류는 괄호 범위가 달라지는 것

Dart 연산자 우선순위에서 접두 !는 &&보다 먼저 평가됩니다. 따라서 두 식은 다릅니다.

!a && b    // (!a) && b
!(a && b)  // a와 b의 AND 전체를 부정

실행 결과 두 식은 (false, false)와 (true, false)에서 서로 달랐습니다. “NOT을 안쪽으로 옮긴다”는 말만 기억하고 괄호를 없애면 이런 오류가 남습니다. 안전한 변환 순서는 다음과 같습니다.

  1. 바깥 !가 덮는 괄호의 시작과 끝을 먼저 표시합니다.
  2. 괄호 내부의 &&와 ||를 서로 바꿉니다.
  3. 각 항을 개별적으로 부정합니다.
  4. 가능한 모든 bool 조합이나 경계 사례를 테스트합니다.

로그인·권한 조건을 바꿀 때 원인 정보는 버리지 않는다

final mayEnter = isLoggedIn && hasPermission;

if (!mayEnter) {
  // 왜 막혔는지 여기서는 알 수 없음
}

드모르간을 적용하면 차단 조건은 !isLoggedIn || !hasPermission입니다. 그러나 하나의 bool 결과로 먼저 합쳐 버리면 로그인과 권한 중 무엇이 실패했는지 사라집니다. 사용자에게 다른 복구 행동을 안내해야 한다면 실패 이유를 순서대로 분기하는 편이 낫습니다.

if (!isLoggedIn) {
  return AccessDecision.loginRequired;
}
if (!hasPermission) {
  return AccessDecision.permissionRequired;
}
return AccessDecision.allowed;

등가 변환은 최종 true/false를 보존하지만 오류 코드, 분석 로그, 화면 안내까지 자동으로 보존하지는 않습니다. 조건을 단순화하면서 진단 가능성을 잃지 않았는지 따로 확인해야 합니다.

단락 평가는 원식과 변환식에서 같은 순서를 보였다

부수 효과가 있는 함수 호출을 피연산자로 넣고 어떤 항이 평가됐는지 기록했습니다.

!(probe('A', false) && probe('B', true))
!probe('A', false) || !probe('B', true)

두 식 모두 A만 기록하고 B는 호출하지 않았습니다. AND는 왼쪽이 false면 오른쪽을 평가하지 않고, 변환된 OR은 왼쪽 !A가 true이므로 오른쪽을 평가하지 않았습니다. 항의 순서를 유지한 정상적인 드모르간 변환은 이 사례에서 단락 평가도 보존했습니다.

그렇다고 부수 효과가 있는 조건을 권장하는 것은 아닙니다. 네트워크 요청, 카운터 증가, 상태 변경을 bool 함수 안에 숨기면 조건 재배치가 동작을 바꿀 수 있습니다. 조건 함수는 같은 입력에 같은 bool을 반환하도록 만들고, 효과가 필요한 작업은 분기문 본문으로 옮기는 편이 안전합니다.

nullable bool은 true·false 두 칸 진리표가 아니다

bool maySubmit(bool? accepted, bool valid) {
  return accepted && valid;
}

이 파일에 analyzer를 실행하자 종료 코드 3과 UNCHECKED_USE_OF_NULLABLE_VALUE가 나왔습니다. nullable 표현식은 그대로 조건에 사용할 수 없습니다. null을 false로 취급하는 것이 제품 규칙이라면 (accepted ?? false) && valid처럼 명시할 수 있습니다. 하지만 null이 “아직 선택하지 않음”이라면 거부와 합치지 말고 별도 상태로 유지해야 합니다.

입력 의미 권장 모델 이유
null도 false와 완전히 같음 value ?? false 두 상태를 합쳐도 정보 손실이 없음
미결정과 거부의 안내가 다름 enum 또는 sealed 상태 다음 행동을 구분해야 함
값이 반드시 있어야 함 입력 경계에서 검증 깊은 조건문까지 null을 전달하지 않음

OS 권한은 bool 하나보다 세 상태 분기가 안전하다

실험에서는 undetermined, denied, granted 세 입력을 switch로 분리했습니다. 진입 가능 결과는 앞의 두 상태에서 false, granted에서 true였지만 사용자 행동은 서로 다릅니다. 미결정이면 권한 요청을 보여 줄 수 있고, 거부면 설정 이동이나 제한 안내가 필요할 수 있습니다.

final decision = switch (permission) {
  Permission.undetermined => Decision.requestPermission,
  Permission.denied => Decision.openSettings,
  Permission.granted => Decision.continueToApp,
};

드모르간 법칙은 bool 식의 동치를 보장합니다. 플랫폼 상태를 bool로 축약하는 과정이 올바른지는 보장하지 않습니다. 앱 권한, 결제, 네트워크처럼 복구 행동이 여러 개인 영역에서는 상태 모델을 먼저 정한 뒤, 각 분기 안의 bool 조건에만 법칙을 적용합니다.

변환 테스트는 네 종류로 나누면 놓치기 어렵다

테스트 잡는 오류 이번 실행
전체 진리표 연산자·부정 항 누락 2변수 4행, 3변수 8행 통과
우선순위 비교 괄호 범위 변경 잘못된 식 2행 불일치 확인
평가 순서 기록 단락 평가와 부수 효과 양쪽 모두 A만 평가
상태 모델 테스트 null·미결정 상태 손실 nullable 분석 오류, 권한 3상태 분리

이 검증의 범위

  • Dart 3.13.4 Windows VM과 analyzer에서 순수 bool 표현식을 실행한 결과입니다.
  • 실제 Android·iOS 권한 API나 결제 SDK는 호출하지 않았습니다. 권한 예제는 상태 모델의 판단 기준입니다.
  • 컴파일러 최적화나 실행 속도는 측정하지 않았고, 드모르간 변환의 성능 우위를 주장하지 않습니다.
  • 피연산자가 예외를 던지거나 외부 상태를 변경하는 코드의 모든 경우를 검증하지 않았습니다.

인접한 조건문 주제를 구분해서 보기

이 글은 부정이 괄호 전체에 적용될 때의 등가 변환과 검증법만 다룹니다. if·switch·반복문의 기본 흐름은 Dart 조건문과 반복문 입력 검증 예제에서 확인할 수 있습니다. AND·OR 조합을 요구사항 표에서 설계하는 방법은 앱 조건문을 안전하게 만드는 불리언 대수로 분리했습니다.

실행 결과와 대조한 Dart 공식 문서