Assert 래퍼 깔끔하게 쓰기
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");
}
arg가 null일 때 스택은 이렇게 찍힌다:
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 콤보는 사실상 필수.
제대로 대우받는 개발자 | 부족한 컴공지식 배우기 | MIT급 컴공인강
최저임금으로 고통받는 일회성 프로그래머는 그만! POCU 아카데미가 올해 연봉협상을 책임지겠습니다!