어디서 예외를 `catch`하고 로그를 내며 에러 처리해야 하는가 - 호출 계층의 경계와 책무를 실무용으로 정리

· · 예외 처리, 로그, 에러 처리, 설계, C# / .NET

1. 먼저 결론

  • 원칙은 깊은 계층에서 넓게 catch하지 않는 것입니다. catch하는 장소는 실패 단위를 정의할 수 있는 경계에 모읍니다.
  • 로그는 1개의 실패에 대해 1개의 주요 로그를 기본으로 합니다. 각 계층에서 같은 예외를 계속 Error하면 읽는 쪽이 곤란해집니다.
  • 가장 깊은 계층의 책무는 뒷정리, 국소 롤백, 예외의 번역, 필요하면 한정적 retry입니다. 재throw한다면 보통은 거기서 주요 로그를 내지 않습니다.
  • 화면 조작, HTTP 요청, 1건의 작업, 1건의 메시지 처리 같은 처리 경계가 가장 자연스러운 주요 로그 지점이 되기 쉽습니다.
  • 상정 내 실패는 그 유스케이스의 단위로 결과화합니다. 반드시 전부를 예외로서 위까지 계속 던질 필요는 없습니다.
  • AppDomain.UnhandledException, WPF의 DispatcherUnhandledException, WinForms의 ThreadException, ASP.NET Core의 예외 핸들러, 호스트의 최종 예외 처리는 회복 포인트라기보다 최후의 기록 지점입니다.
  • 사용자 취소나 셧다운에 의한 OperationCanceledException은 보통 Error 취급하지 않습니다.
  • 망설여지면 다음 순서로 봅니다.
    1. 이 장소에서 정말 판단할 수 있는가
    2. 실패한 단위가 여기서 알 수 있는가
    3. 여기서 상태를 되돌릴 수 있는가, 재생성할 수 있는가
    4. 여기서 로그하면 같은 예외를 위에서도 로그하지 않는가

요컨대 catch할 수 있는 장소가 아니라, 책임을 지고 판단할 수 있는 장소에서 받는 것이 기본입니다.

2. catch와 로그와 에러 처리는 별개

2.1. catch하는 것

catch는 예외를 한 번 받아서 처리의 흐름을 바꾸는 것입니다. 다만 그 자체는 회복이 아닙니다.

예를 들어, 하위 메서드에서 예외를 받아도,

  • 무엇을 사용자에게 보여야 할지 모른다
  • 그 실패로 화면 전체를 멈춰야 할지, 이번 조작만 실패여도 좋은지 모른다
  • 그 request나 job을 계속해도 되는지 모른다

라면, 그 장소는 catch의 적지가 아닌 경우가 많습니다.

2.2. 로그를 내는 것

로그는 「예외가 일어났다」라는 사실뿐만 아니라, 어느 작업이 실패했는지를 나중에 쫓기 위한 기록입니다.

그래서 좋은 로그 지점에는 대개 다음 중 하나가 있습니다.

  • requestId / traceId
  • userId
  • orderId / fileId / batchId
  • 몇 번째 입력인지
  • 어느 화면 조작인지
  • 어느 큐・어느 메시지인지

깊은 helper나 공통 함수는 기술 상세는 알고 있어도 이 문맥을 가지고 있지 않은 경우가 많습니다. 그래서 기술 상세를 알고 있는 장소운용 문맥을 알고 있는 장소는 종종 별개입니다.

2.3. 에러 처리하는 것

여기서 말하는 에러 처리는 예를 들어 다음입니다.

  • 화면에 에러 메시지를 낸다
  • HTTP에서는 4xx / 5xx를 반환한다
  • 1건만 실패로 하고 다음 건으로 진행한다
  • 그 subsystem을 재초기화한다
  • 프로세스를 종료하고 재기동에 맡긴다
  • 리소스를 해방하고 안전하게 빠진다

즉, 호출원이나 사용자로부터 본 실패의 형태를 정하는 것입니다.

2.4. 예외를 번역하는 것

실무에서는 catch와 「처리한다」 사이에 또 하나 중요한 일이 있습니다. 그것이 번역입니다.

예를 들어,

  • HttpRequestException
  • IOException
  • JsonException
  • DB 드라이버 고유의 예외
  • vendor SDK 고유의 예외

를 그대로 UI나 Controller로 새게 하면 상위 계층이 하위 구현의 사정을 알기 시작합니다.

그래서 경계면에서는,

  • 「결제 서비스에 접속할 수 없었다」
  • 「CSV 형식이 깨져 있었다」
  • 「저장처에 쓸 수 없었다」
  • 「장치 응답이 부정이었다」

처럼 그 계층에서 의미 있는 실패로 변환합니다.

여기서 중요한 것은 번역과 로그는 같지 않다는 점입니다. 번역해서 위로 던질 뿐이라면, 보통은 주요 로그까지는 내지 않습니다.

3. 먼저 보는 판단표

먼저 다음 표에서 큰 방침을 결정하면 정리하기 쉽습니다.

장소 기본 방침 주요 로그 주된 책무
helper / utility / private method 원칙적으로 넓게 catch하지 않는다 내지 않는다 finally에 의한 뒷정리, 국소 롤백, 필요 최소한의 문맥 추가
Repository / Gateway / SDK 래퍼 구체 예외만 받는다 통상 내지 않는다 예외 번역, 한정적 retry, 접속이나 핸들 파기
Application Service / UseCase 상정 내 실패를 결과화한다 삼킨다면 여기서 필요에 따라 실패 단위의 정의, 부분 실패화, 유스케이스 단위의 판단
UI / Controller / API / Job / Message 경계 상정 외 예외의 주요 수용구 여기가 주요 로그가 되기 쉽다 사용자용 응답, HTTP 응답, 다음 건 계속, abort 판단
미처리 예외 핸들러 / 호스트 최종 경계 누락 방지의 최후의 보루 Critical 최종 기록, flush, dump, 종료・재기동 도선

다음 그림으로 하면 대략 이렇습니다.

flowchart TD
    A["예외가 일어났다"] --> B{"이 장소에서 retry / 결과화 / 계속 가부를 정할 수 있는가?"}
    B -- "아니오" --> C["원칙 catch하지 않고 위로 보낸다"]
    B -- "예" --> D{"여기는 계층의 경계인가?"}
    D -- "아니오" --> E["국소 cleanup만"]
    D -- "예" --> F["필요하면 의미 있는 예외로 번역"]
    E --> G{"여기서 실패 단위와 운용 문맥을 알 수 있는가?"}
    F --> G
    G -- "아니오" --> H["주요 로그는 내지 않고 상위로 보낸다"]
    G -- "예" --> I["주요 로그를 1회 내고 응답을 정한다"]
    I --> J["필요하면 종료 / 재초기화 / 다음 건 계속"]

이 그림의 포인트는 2개입니다.

  1. catch의 첫 이유는 회복이나 cleanup이며, 로그가 아니다
  2. 로그의 첫 이유는 운용 문맥이 갖춰진 것이며, 예외를 발견한 것이 아니다

4. 호출 계층의 어디서 무엇을 할까

4.1. 가장 깊은 helper / utility / private method

여기서는 원칙적으로 넓게 받지 않는 것이 기본입니다.

예를 들어, 문자열 변환, 파스, 계산, 내부 정형, 공통 helper와 같은 장소는,

  • 어느 화면 조작이었는지
  • 어느 request였는지
  • 이번만 실패여도 좋은지
  • 화면 전체를 닫아야 하는지

를 판단할 수 없습니다.

이 계층에서 해도 좋은 것은 주로 다음입니다.

  • finally에서의 resource 해방
  • 도중까지 망친 국소 상태의 롤백
  • 예외 메시지로의 최소한의 문맥 추가
  • 보다 적절한 예외형으로의 치환
  • 재사용 불가능해진 객체의 파기

반대로 피하고 싶은 것은 다음입니다.

  • catch (Exception)해서 null / false / 빈 배열을 반환한다
  • 여기서 MessageBox를 낸다
  • 여기서 Error 로그를 낸 후 재throw한다
  • 되돌릴 수 없는데 「일단 계속한다」

특히 위험한 것은 도중까지 자신의 상태를 고쳐 쓴 후에 실패했는데, 그대로 계속 사용하는 패턴입니다. 이 경우는 그 자리에서 되돌릴 수 있다면 되돌리고, 되돌릴 수 없다면 파기 전제로 하든지의 어느 쪽입니다.

4.2. 외부 I/O 경계: Repository / Gateway / SDK 래퍼

여기는 catch하는 이유가 분명한 계층입니다.

왜냐하면 여기서는 아래 계층의 구현 사정이 표면에 나오기 때문입니다.

  • DB 드라이버 예외
  • HTTP 통신 예외
  • 파일 I/O 예외
  • COM / P/Invoke / vendor SDK의 고유 예외
  • 파스 라이브러리나 시리얼라이저의 예외

이 계층에서 하는 일은 대략 다음 4가지입니다.

  1. 구체 예외를 받는다 넓은 Exception이 아니라 의미 있는 구체 예외를 받습니다.

  2. 의미 있는 실패로 번역한다 상위 계층이 하위의 사정을 직접 알지 않아도 되도록 합니다.

  3. 국소적으로 retry한다면 여기서 한다 다만 조건은 엄격한 편입니다.
    • 일시적 실패라고 알고 있다
    • 멱등성이 있다
    • 상한 횟수와 대기 방식이 정해져 있다
    • 실패 시의 최종 거동이 명확하다 이 4가지가 갖춰졌을 때만입니다.
  4. 망가진 접속이나 핸들을 버린다 「다음도 같은 객체로 계속한다」보다 「접속을 다시 만든다」쪽이 안전한 경우는 많습니다.

여기서의 로그 방침은 다음과 같이 생각하면 흔들리기 어렵습니다.

  • 위로 재throw한다면 보통은 주요 로그를 내지 않는다
  • 여기서 예외를 삼키고 결과로 바꾼다면 그 시점에서 필요한 로그나 메트릭을 낸다
  • retry 중의 각 시행은 Debug / Information / Warning의 범위로 다루고, 최종 실패만을 강하게 기록한다

이 계층은 번역하는 장소이며, 통상 최종 판단하는 장소가 아닙니다.

4.3. Application Service / UseCase

여기는 「이번 작업을 어떻게 실패시킬까」를 정하는 계층입니다.

예를 들어,

  • 저장 처리
  • 주문 확정
  • CSV 취입
  • 배치 1건분의 처리
  • 메시지 1건분의 반영

과 같은 유스케이스로서 정리된 단위가 여기에 있습니다.

이 계층에서는 다음과 같은 판단이 가능합니다.

  • validation 에러는 이번만 실패
  • NotFound는 404 상당
  • 업무 규칙 위반은 사용자 수정 대기
  • CSV의 1행 부정은 Warning으로 계속
  • 외부 서비스 일시 장해는 처리 전체를 실패
  • 도중 성과를 파기하고 처음부터 다시

즉, 실패 단위를 결정할 수 있는 장소입니다.

이 계층이 어울리는 것은 예를 들어 다음입니다.

  • 상정 내 실패를 Result나 실패 DTO의 형태로 한다
  • 부분 실패를 집계한다
  • 몇 건까지 실패를 허용하고 계속할지 결정한다
  • 에러 코드나 사용자용 메시지 키로 변환한다

반대로, 이 계층에서 해서는 안 되는 것은 UI 표시나 HTTP 응답 본문의 조립을 너무 들여오는 것입니다. 여기서는 유스케이스로서의 의미까지를 결정하고, 최종적인 보이는 방식은 경계 측에 맡기는 편이 분리하기 쉽습니다.

4.4. UI / HTTP / Job / Message의 경계

여기가 많은 앱에서 주요 로그 지점이 되기 쉽습니다.

예를 들어 다음입니다.

  • WinForms / WPF의 「저장」 버튼 누름 1회
  • ASP.NET Core의 HTTP request 1개
  • worker의 메시지 1건
  • 배치의 입력 1건
  • 스케줄 실행 작업 1회

이 장소는 다음을 알고 있습니다.

  • 무엇의 조작이었는지
  • 누구의 조작이었는지
  • 몇 건째였는지
  • 어느 request / batch / message였는지
  • 실패하면 사용자나 호출원에 무엇을 반환할지

그래서,

  • 상정 외 예외를 여기서 한데 받는다
  • 문맥을 붙여 주요 로그를 1회 낸다
  • 에러 다이얼로그, HTTP 500, Problem Details, 작업 실패, 다음 건 계속 등으로 변환한다

는 역할을 가지기 쉽습니다.

이 계층에서 중요한 것은 넓게 받는 것 자체가 아니라, 넓게 받은 후에 무엇을 반환할지가 정의되어 있는 것입니다.

예를 들어 batch나 queue에서는 다음 2단계로 나누면 정리하기 쉽습니다.

  • 1건 경계에서 받는다 1건만 실패로 하고 다음으로 진행할지 정한다
  • 부모 루프에서는 넓게 쥐어 뭉개지 않는다 부모 루프가 죽으면 프로세스 전체의 재기동에 기울인다

「1건씩 실패시키고 계속」과 「부모 루프가 상정 외 예외로 떨어져도 말없이 산다」는 전혀 다릅니다.

4.5. 최후의 미처리 예외 핸들러

여기는 최후의 보루입니다. 마법의 회복 포인트가 아닙니다.

대표적으로 다음이 있습니다.

  • AppDomain.UnhandledException
  • WPF의 Application.DispatcherUnhandledException
  • WinForms의 Application.ThreadException
  • ASP.NET Core의 예외 처리 미들웨어나 핸들러
  • Generic Host / worker / BackgroundService의 최종 예외 처리

이 계층의 주된 책무는 다음입니다.

  • 최종 로그
  • flush
  • dump 채취 도선
  • 세션 정보나 직전 문맥의 대피
  • 종료 코드나 재기동 도선의 정비

반대로, 여기에 너무 기대지 않는 편이 좋은 것도 있습니다.

  • 여기까지 온 시점에서 위의 설계 누락인 경우가 많다
  • 이미 상태가 망가져 있을 가능성이 있다
  • 락 보유 중일 수도 있어, 무거운 처리는 위험하다
  • 외견상 계속할 수 있어도, 계속해서 안전하다고 한정할 수 없다

.NET 주변에서 짚어두고 싶은 실무상의 주의도 있습니다.

  • AppDomain.UnhandledException미처리 예외의 통지와 기록을 위한 이벤트입니다. 이후에 회복 처리를 너무 포함시키는 것은 위험합니다.
  • WPF의 DispatcherUnhandledException에서는 Handled = true로 해서 외견상 계속하는 길이 있지만, 회복 가능한지 여부의 판단이 먼저입니다.
  • WinForms의 ThreadException도 거기서 대처한 후에 애플리케이션이 불명 상태가 될 가능성이 있습니다.
  • ASP.NET Core의 예외 처리 미들웨어는 후속 예외를 받을 수 있도록 파이프라인의 이른 단계에 둘 필요가 있습니다.
  • BackgroundService의 미처리 예외는 .NET 6 이후에서는 로그되고 기본으로 호스트 정지에 기울입니다. 부모 루프에서 전부 쥐어 뭉개는 것보다 정지시키고 재기동 전략에 태우는 편이 안전한 경우가 있습니다.

특히 데스크톱 앱에서는 「미처리 예외를 주워서 계속한다」 길이 존재합니다. 다만 계속할 수 있는 것계속해도 좋은 것은 별개입니다.

4.6. 1개의 호출 계층으로 보기

예를 들어 다음과 같은 흐름을 생각합니다.

flowchart LR
    A["UI / Controller / Job 경계"] --> B["Application Service / UseCase"]
    B --> C["Domain / 업무 로직"]
    C --> D["Repository / Gateway / SDK wrapper"]
    D --> E["DB / HTTP / File / Vendor SDK"]

이때, 역할은 대략 다음과 같이 나뉩니다.

저장 버튼 → SaveOrderUseCasePaymentGateway → HTTP

  • PaymentGateway
    • 통신 실패나 응답 형식 이상을 받는다
    • 「결제 서비스 접속 실패」 「결제 서비스 응답 부정」으로 번역한다
    • retry한다면 여기서 조건부로 한다
    • 재throw한다면 보통 주요 로그하지 않는다
  • SaveOrderUseCase
    • 결제 거부와 같은 상정 내 실패를 결과로 바꾼다
    • 「이번 주문 확정만 실패」로서 다룬다
    • 실패 결과를 UI나 API에 반환하기 쉬운 형태로 한다
  • UI 버튼 핸들러 / Controller
    • 상정 외 예외를 한데 받는다
    • orderId, userId, requestId를 붙여 주요 로그한다
    • 다이얼로그 표시나 500 / 503 응답으로 변환한다
  • 미처리 예외 핸들러
    • 거기까지 샌 것만 기록한다
    • dump나 최종 flush를 한다
    • 회복이 아니라 종료 도선을 우선한다

이 분할 방식으로 하면 기술 상세는 아래에서 닫고, 운용 문맥은 위에서 붙이고, 판단은 경계에서 한다는 형태가 됩니다.

5. 상정 내 실패와 상정 외 예외를 나눈다

이 테마에서 가장 중요한 것은 전부를 같은 「예외」로서 다루지 않는 것입니다.

먼저 다음과 같이 나누면 정리하기 쉽습니다.

실패의 종류 먼저 다루는 장소 전형적인 취급
validation 불비 UseCase / request 경계 입력 에러로서 반환
NotFound / Conflict UseCase / Controller 404 / 409나 화면 메시지
사용자 취소 / 셧다운 조작 경계 취소 취급. 보통 Error로 하지 않는다
CSV의 1행 부정 1행 경계 Warning으로 기록하고 다음으로 진행
일시적 timeout으로 최종적으로 실패 I/O 경계~request 경계 retry 후에 실패로서 반환
NullReferenceException, 전제 붕괴 request / job 경계 주요 로그하고 실패 응답
AccessViolationException, 심각한 OutOfMemoryException, native 경계 파괴 냄새 최종 경계 Critical로서 종료 쪽

상정 내 실패는 설계로 먼저 정할 수 있는 실패입니다. 상정 외 예외는 이후에도 상태를 신뢰해도 좋을지 수상한 실패입니다.

이 2개를 나누는 것만으로도 다음 사고가 줄어듭니다.

  • NotFound를 매번 Error로 한다
  • 사용자 취소를 장해 취급한다
  • 정말로 위험한 전제 붕괴를 「이번만 실패」로 흘려버린다

6. 로그는 어디서 몇 번 내야 할까

로그의 설계에서는 catch의 위치보다 누가 주요 로그를 낼지를 먼저 정하는 편이 중요합니다.

기본 룰은 다음과 같습니다.

  1. 1개의 실패에 대해 주된 Error / Critical 로그는 1회
  2. 하위 계층은 필요하면 번역과 문맥 추가를 한다
  3. 상위의 경계는 실패 단위와 운용 문맥을 붙여 주요 로그를 낸다
  4. 그 자리에서 삼키는 계층만이 그 삼킨 실패의 기록 책임을 가진다
  5. 상정 내 실패는 매번 Error로 하지 않는다
  6. OperationCanceledException은 보통의 장해 로그에서 나눈다

로그 지점을 대략 표로 하면 다음과 같이 됩니다.

상황 주로 로그하는 장소 레벨의 기준 보충
validation 에러 request / use case 경계 Information 또는 로그 없음 장해가 아니라 계약상의 실패
사용자 취소 / shutdown 조작 경계 Debug / Information 보통 Error로 하지 않는다
retry 중의 일시 실패 retry를 가지는 계층 Debug / Warning 최종 실패 전은 너무 떠들썩하게 하지 않는다
retry 다 써서 실패 request / job 경계, 또는 그 자리에서 삼키는 계층 Warning / Error 실패 단위를 붙여 기록
1행만 부정으로 계속 item 경계 Warning fileId, rowNumber를 붙인다
request 전체를 떨어뜨리는 상정 외 예외 request / UI / job 경계 Error requestId, userId, entityId를 붙인다
프로세스 종료급 미처리 예외 경계 Critical flush, dump, 재기동 도선

실무에서는 다음 중복 로그가 꽤 많습니다.

  • Repository가 Error
  • Service가 같은 예외를 Error
  • Controller가 또 Error
  • 최후의 미처리 예외 핸들러에서도 Critical

이러면 1회의 장해로 같은 스택 트레이스가 몇 줄이나 늘어섭니다. 읽는 쪽에서 원하는 것은 같은 stack trace의 4줄이 아니라, 1줄의 주요 로그와, 필요하면 소수의 보조 로그입니다.

바꿔 말하면 로그는 1회, 문맥은 필요한 만큼이 기본입니다.

7. 자주 있는 NG

7.1. 깊은 계층에서 catch (Exception)해서 null / false를 반환한다

이것은 원인의 정보를 떨어뜨리기 쉽습니다. 게다가 호출 측은 「정말로 데이터가 없었는지」 「도중에 망가졌는지」 구별할 수 없게 됩니다.

7.2. 각 계층에서 Error 로그한 후 재throw한다

가장 많은 중복 로그의 원인입니다.

  • 하위 계층은 번역만
  • 상위 경계가 주요 로그

라는 분담으로 하면 꽤 줄일 수 있습니다.

C#에서 재throw한다면 스택 트레이스를 망가뜨리지 않도록 throw;를 사용하는 것이 기본입니다.

7.3. 라이브러리 계층이나 공통 부품이 UI를 직접 낸다

공통 부품이 MessageBox를 내거나 HTTP 응답 본문을 직접 정하거나 하면, 재사용성도 책무 분리도 무너집니다. 하위 계층은 의미 있는 실패를 반환하는 것까지에 모으는 편이 안전합니다.

7.4. OperationCanceledException을 장해로서 Error 로그한다

취소는 제어 플로우의 일부입니다. 매번 Error로 하면 정말의 장해가 파묻힙니다.

7.5. 외부 부작용이 있는데 안이하게 retry한다

메일 송신, 과금, 장치 커맨드, 파일 이동과 같이 같은 조작을 한 번 더 하면 사고나는 것은 많습니다. retry는 일시적 실패멱등성 양쪽이 보일 때만입니다.

7.6. 최후의 미처리 예외 핸들러에서 뭐든지 회복하려고 한다

여기는 최후의 보험입니다. 설계의 중심에 두는 장소가 아닙니다.

회복 전략은 그 앞의 계층, 즉 request / job / subsystem의 경계에 두는 편이 안전합니다.

8. 리뷰 시의 체크리스트

예외 처리의 리뷰에서는 다음을 순서대로 보면 정리하기 쉽습니다.

  • catch무엇을 판단하기 위해 있는가를 1문으로 말할 수 있는가
  • 이 장소에서 retry / 결과화 / 계속 가부 / 사용자 응답을 정말로 정할 수 있는가
  • 여기서 로그하면 위의 계층에서도 같은 실패를 Error하지 않는가
  • 하위 구현 고유의 예외를 경계에서 의미 있는 실패로 번역하고 있는가
  • 도중에 망가진 상태를 여기서 되돌릴 수 있는가. 되돌릴 수 없다면 파기 전제로 되어 있는가
  • OperationCanceledException을 보통의 장해에서 나누고 있는가
  • item 단위 계속인가, request 단위 실패인가, process 종료인가가 명확한가
  • 최후의 미처리 예외 핸들러에, 회복이 아니라 기록을 기대하고 있는가
  • 로그에 requestId / userId / batchId / fileId / rowNumber 등 실패 단위의 문맥이 실려 있는가
  • 「상정 내 실패」와 「전제 붕괴」를 같은 취급으로 하고 있지 않은가

이 체크리스트에서 특히 효과 있는 것은 「이 catch는 무엇을 정하고 있는가」를 매번 말로 하는 것입니다. 여기를 말할 수 없는 catch는 대개 불필요하거나 장소가 너무 깊습니다.

9. 대강의 사용 구분

마지막으로 꽤 짧게 정리하면 다음 표입니다.

장면 catch 로그 에러 처리
helper / utility 원칙 하지 않는다 하지 않는다 하지 않는다
Repository / Gateway / SDK 래퍼 구체 예외만 받는다 통상 주요 로그하지 않는다 번역, 국소 retry, 접속 파기
UseCase / Application Service 상정 내 실패를 받는다 삼킨다면 필요에 따라 결과화, 부분 실패화
UI / Controller / request / item / job 경계 상정 외 예외를 넓게 받는다 주요 로그 응답, 메시지, 계속 / abort
미처리 예외 핸들러 샌 것만 Critical 최종 기록, 종료 도선

망설였을 때는 먼저 다음만으로 충분합니다.

  1. 깊은 계층에서는 넓게 쥐지 않는다
  2. 경계에서 받는다
  3. 주요 로그는 1회
  4. 삼키는 계층이 책임을 진다
  5. 최후의 미처리 예외는 기록과 종료 도선

10. 정리

예외 처리는 「어디서든 catch할 수 있으니까 어디서든 catch한다」는 이야기가 아닙니다.

보는 순서로서는 대략 다음으로 충분합니다.

  1. 이 장소에서 정말 판단할 수 있는가
  2. 여기서 실패 단위를 알 수 있는가
  3. 여기서 상태를 되돌릴 수 있는가, 재생성할 수 있는가
  4. 여기서 로그하면 중복하지 않는가
  5. 여기는 회복 지점인가, 아니면 최후의 기록 지점인가

이 순서로 보면 호출 계층의 정리는 꽤 쉬워집니다.

특히 중요한 것은 다음 3가지입니다.

  • 깊은 계층은 주로 번역과 cleanup
  • 경계는 주로 판단과 주요 로그
  • 최후의 미처리 예외 핸들러는 주로 기록과 종료 도선

바꿔 말하면, 예외는 경계에서 받고, 문맥을 붙이고, 회복할 수 있는 장소에서만 처리하는 것이 기본입니다.

이것이 정해지면 코드 리뷰에서도 장해 조사에서도 꽤 흔들리기 어려워집니다.

11. 참고 자료

12. 관련 기사

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

Windows 앱이 프로그램 실수에 의한 예외로 떨어져도 확실히 로그를 남기려면 - in-process에 걸지 않는 설계와 WER / 최종 로그 / 감시 프로세스의 베스트 프랙티스

Windows 앱이 예상 밖의 예외로 떨어져도 원인을 추적할 수 있도록, 통상 로그·최종 크래시 마커·WER LocalDumps·감시 프로세스를 어떻게 조합할지를 실무 관점에서 정리하고, in-process 핸들러에 의존하지 않는 설계의 베스트 ...

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기