내가 만든 방어 유틸 함수 5종 세트

C# 기본 제공 Debug.Assert()만으로는 부족했다.
운영 중 발생할 수 있는 가정 위반, 내부 버그, 즉시 대응이 필요한 크리티컬 상황
한 체계로 다루고 싶었다.

그래서 아래 다섯 가지 유틸을 만들었다.

global using static POCU.Core.Assertion.Check;

이렇게 전역으로 불러와서 전부 대문자로 사용한다.
로직 코드 중간에서도 눈에 확 띄어서 "이건 방어 코드다"라는 걸 한눈에 알 수 있다.

🧱 공통 규칙

모든 함수의 첫 번째 인자는 반드시 bool 표현식 이다.

CHECK911_THROW(user != null, "User should not be null");
  • true → 아무 일도 일어나지 않는다.
  • false → 각 함수에 맞는 방식으로 로그·알림·예외가 발생한다.

즉, if (!condition) { ... }을 매번 쓰지 않아도 된다.
표현식 하나로 "이게 깨지면 뭔가 잘못된 거다"를 명확히 선언할 수 있다.

📊 함수 요약

함수 목적 행동 요약 운영팀 대응 예외
VERIFY "그럴 리 없겠지" 가정 관찰 로그만 남기고 계속 실행 덜 바쁠 때 확인
DBG_CHECK 디버그 전용 어서션 Debug 빌드에서만 중단
CHECK912 내부 문제 감지 (비긴급) 알림 + 그래프 집계 1~2일 내 확인/수정
CHECK911 즉시 대응 필요 알림 + 그래프 집계 즉시 확인/수정
CHECK911_THROW 트랜잭션 보호용 알림 + 그래프 집계 + 예외 발생 즉시 확인

🔔 모든 CHECK 계열은 회사 메신저로 노티가 오고,
발생 횟수는 자동 집계되어 그래프로 시각화된다.

🧩 VERIFY — 장기 가정 검증용 센서

VERIFY는 단순한 로그가 아니다.
"현재는 절대 일어나지 않을 거라 생각하는 상황"을 장기적으로 관찰하기 위한 센서다.

VERIFY(order.TotalPrice >= 0, "Order total should never be negative");
  • 지금은 이게 절대 깨질 리 없다고 생각한다.
  • 하지만 혹시 모른다. 1년, 2년 동안 한 번이라도 깨질 수도 있다.
  • 그때 로그가 오면, "우리의 가정이 잘못됐구나"를 알 수 있다.
  • 반대로 몇 년간 한 번도 안 깨지면 그 라인은 제거해도 된다.

이건 테스트로 잡을 수 없는 실운영 환경의 가정 검증이다.
운영 중 일시적인 엣지케이스나 외부 시스템의 불규칙성을 감시하기에 적합하다.

💡 또 하나의 꼼수

가끔 이런 경우도 있다.

오래된 코드인데, "절대 일어나지 않을 조건"이라 삭제하고 싶지만 확신이 안 설 때가 있다.

이럴 땐 다음처럼 처리한다:

// TODO(delete): delete after 2028-12-31
VERIFY(someOldFlag == false, "Old flag still being used?");
  • 일단 VERIFY를 걸어둔다.
  • 운영 중 1년 정도 지켜본다.
  • 한 번도 깨지지 않으면, 해당 코드를 안전하게 삭제한다.

이 패턴은 "실운영 환경에서 살아있는 테스트" 같은 역할을 한다. 수많은 라이브 유저가 테스트해주는 자동 테스트 시스템 완성! 최고다, 헤헤 😎 실제로 코드 정리 주기를 시스템적으로 관리하기가 훨씬 쉬워진다.

🧪 DBG_CHECK — 디버그 전용 강력 어서션

DBG_CHECK(buffer.Length == expectedSize, "Unexpected buffer size");
  • Debug 빌드에서만 실행된다.
  • Release 빌드에서는 완전히 제거되어 성능 부담이 없다.
  • "개발 중엔 절대 이 조건이 깨지면 안 된다"를 강하게 보장할 때 쓴다.

✅ 테스트 중 빠르게 터트려야 하는 조건에는 DBG_CHECK.
✅ 운영 중 관찰해야 하는 조건에는 VERIFY.

🔍 CHECK912 — 내부 문제지만 긴급 아님

CHECK912(userCache.Count > 0, "User cache is unexpectedly empty");
  • 내부 버그 가능성이 높지만 당장 서비스에 영향은 없다.
  • 메신저 알림이 오고, 그래프에 집계된다.
  • 운영팀은 "하루나 이틀 뒤에 봐도 되는 이슈"로 분류한다.

예시:

  • 캐시 불일치
  • 일시적인 네트워크 재시도 성공
  • 데이터 통계의 경계값 초과

🚨 CHECK911 — 즉시 대응이 필요한 크리티컬

CHECK911(paymentResponse.IsValid, "Payment gateway returned invalid data");
  • 데이터 손실, 보안, 고객 영향 같은 즉시 대응이 필요한 문제다.
  • 메신저로 바로 알림이 전송되고,
    그래프 집계운영 훅(예: 세이프모드 전환)이 트리거된다.
  • 하지만 예외는 던지지 않는다.
    → 워커나 파이프라인을 멈추지 않고 운영팀이 실시간 대응할 수 있게 한다.

💣 CHECK911_THROW — 트랜잭션 보호용

CHECK911_THROW(invoice != null, "Invoice must exist before commit");
  • CHECK911과 동일한 알림을 보내지만, 즉시 예외를 던져 트랜잭션을 중단한다.
  • 잘못된 상태가 DB나 외부 API로 퍼지는 걸 방지하기 위한 안전장치다.
  • 보통 다음과 같은 곳에서 사용된다:

예시:

CHECK911_THROW(user != null, "User not found");
CHECK911_THROW(balance >= 0, "Negative balance detected");

트랜잭션 내부, 커밋 직전, 외부 시스템 호출 직후 같은
"사이드이펙트가 퍼지기 전에 막아야 하는 시점"에 쓴다.

⚙️ 내부 동작 개요

모든 함수는 내부적으로 같은 베이스 로직을 공유한다:

  1. bool expression 평가
  2. false면 → 공통 로거 호출
  3. 로거는 다음을 수행
    • 로그 작성 (파일 / 콘솔 / Sentry 등)
    • 메신저로 노티 전송
    • 메트릭 집계 (그래프용)
  4. *_THROW 계열은 마지막에 throw 수행

즉, 같은 알림 인프라 위에서 심각도만 다르게 분류된다.

🧩 마무리

이 다섯 가지 함수는 결국 한 가지 목적을 가진다.
"로직과 방어선을 분리하고, 발생 시점에 따라 적절히 대응하기."

  • VERIFY: 장기 가정 검증 센서
  • DBG_CHECK: 디버그 전용 강한 어서션
  • CHECK912: 내부 문제 감지 (비긴급)
  • CHECK911: 즉시 대응 필요 (운영 크리티컬)
  • CHECK911_THROW: 즉시 대응 + 트랜잭션 중단

이제 로직 코드 한가운데서 이렇게 쓰면 된다:

CHECK911_THROW(totalPrice >= 0, "Negative total price detected");

그 한 줄이면 끝이다.
읽는 사람은 "이건 단순 로직이 아니라, 안전선을 세운 부분"임을 바로 알 수 있다.

img

제대로 대우받는 개발자 | 부족한 컴공지식 배우기 | MIT급 컴공인강

최저임금으로 고통받는 일회성 프로그래머는 그만! POCU 아카데미가 올해 연봉협상을 책임지겠습니다!