Assert 래퍼 깔끔하게 쓰기

김포프 2025-08-26

C# 개발하다 보면 Debug.Assert()Debug.Fail() 같은 디버깅용 API를 자주 쓰게 된다. 그런데 프로젝트 전체에서 직접 호출하다 보면 불편할 때가 많지. 그래서 보통은 이를 감싸는 래퍼 함수를 만들어 전역적으로 쓰곤 한다.

예를 들어:

DBG_CHECK(x != null, "x is null!");

왜 굳이 래퍼를 쓰는가?

  • DBG_CHECK() 같은 헬퍼를 using static으로 글로벌하게 걸어두면 타이핑이 편하다.
  • 전부 대문자라서 코드 속에서 눈에도 잘 띈다. → 로직 코드가 아니라 디버그 전용 장치라는 걸 금방 알아볼 수 있어서, 훑어보다가 쉽게 넘기기도 좋다.
  • 내부에 추가적인 강제 로직을 몰래(?) 집어넣을 수도 있다.

즉, 반복적으로 쓰는 Debug.Assert() 호출을 보기 좋고 쓰기 편하게 정리하는 셈이다.

[Conditional("DEBUG")]를 붙이는 이유

이 래퍼 함수는 당연히 DEBUG 모드에서만 호출되길 원한다. C#에서는 [Conditional("DEBUG")] 어트리뷰트를 붙이면, 컴파일러가 릴리스 빌드에서 호출 자체를 제거한다.

[Conditional("DEBUG")]
public static void DBG_CHECK(bool condition, string? message = null)
{
    if (!condition)
    {
        Debug.Fail(message);
    }
}
  • 릴리스 빌드에선 IL 코드조차 남지 않는다.
  • #if DEBUG ... #endif 블록을 매번 감쌀 필요도 없다.
  • 성능/보안 측면에서 릴리스에 assert가 남으면 절대 안 되기 때문에 반드시 필요하다.

귀찮은 점: 스택 트레이스 문제

문제는 이 함수를 쓰다 Debug.Assert()가 히트하면, 스택의 맨 위가 항상 DBG_CHECK() 같은 래퍼 함수라는 점이다.

예를 들어:

void Foo()
{
    Bar(null);
}

void Bar(object? arg)
{
    DBG_CHECK(arg != null, "arg is null");
}

argnull일 때 스택은 이렇게 찍힌다:

DBG_CHECK()
Bar()
Foo()

내가 보고 싶은 건 Bar()인데, 디버거는 항상 DBG_CHECK() 안에서 멈춰버린다. 그래서 매번 콜 스택을 뒤돌려 봐야 하는 불편함이 있다.

해결책: [DebuggerHidden]

이때 쓸 수 있는 게 바로 [DebuggerHidden] 어트리뷰트다.

[Conditional("DEBUG")]
[DebuggerHidden]
public static void DBG_CHECK(bool condition, string? message = null)
{
    if (!condition)
    {
        Debug.Fail(message);
    }
}
  • DebuggerHidden은 디버거에게 이 함수 내부는 숨기라고 지시한다.
  • 즉, 이 함수 안에서 브레이크가 걸리면 호출자 코드 위치로 곧장 점프한다.
  • Visual Studio에서는 아주 깔끔하게 잘 동작한다.

실제 동작 예시

Bar() 함수에서 DBG_CHECK()가 실패하면 디버거는 이렇게 보여준다:

Bar()
Foo()

DBG_CHECK() 프레임이 스택에서 사라진 것처럼 보이므로, 원하는 호출 지점에서 바로 디버깅을 시작할 수 있다.

추가 디테일

  • DebuggerHidden은 .NET Framework 2.0 이상, .NET Core, .NET 5~9까지 전부 지원된다.
  • Visual Studio 2019, 2022에서 정상 동작 확인. (JetBrains Rider도 비슷하지만 IDE별로 차이는 있을 수 있다.)
  • 단점: DebuggerHidden이 붙은 함수 내부에는 브레이크포인트를 찍을 수 없다. 하지만 assert 래퍼라면 전혀 문제 없음.

정리

  • Debug.Assert() 대신 DBG_CHECK() 같은 래퍼를 만들면 편하고 눈에 잘 띄며 코드 구분성도 좋아진다.
  • [Conditional("DEBUG")]를 붙여야 릴리스 빌드에서 안전하다.
  • 하지만 항상 스택 최상단에 래퍼 함수가 찍히는 게 불편한데, [DebuggerHidden]을 함께 붙이면 호출자 쪽으로 바로 디버깅 포커스가 옮겨진다.
  • Visual Studio 기준 아주 깔끔하게 동작한다.

👉 결론: Assert 래퍼 만들 땐 Conditional("DEBUG") + DebuggerHidden 콤보는 사실상 필수.

img

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

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