IFinResponseWithError
요구사항 R3: Pipeline이 실패 시 에러 정보에 접근할 수 있어야 합니다. 지금까지 IFinResponse로 성공/실패를 확인하고, IFinResponseFactory로 실패 응답을 생성할 수 있게 되었지만, Pipeline에서 에러 정보에 접근하려면 어떻게 해야 할까요? 이 장에서는 IFinResponseWithError 인터페이스를 도입하여, Fail 케이스에서만 에러에 접근할 수 있는 타입 안전한 패턴을 설계합니다.
IFinResponseWithError ← 에러 접근 인터페이스 (이번 장)├── Error: Error Fail에서만 구현학습 목표
섹션 제목: “학습 목표”이 장을 완료하면 다음을 할 수 있습니다:
IFinResponseWithError가 Fail에서만 구현되는 이유를 설명할 수 있습니다- 패턴 매칭으로 에러에 안전하게 접근하는 코드를 작성할 수 있습니다
- Succ에서 에러 접근이 타입 시스템에 의해 방지되는 원리를 이해할 수 있습니다
- 인터페이스 분리가 타입 안전성을 강화하는 방식을 설명할 수 있습니다
핵심 개념
섹션 제목: “핵심 개념”1. IFinResponseWithError 인터페이스
섹션 제목: “1. IFinResponseWithError 인터페이스”IFinResponseWithError는 Error 속성을 제공하는 별도의 인터페이스입니다. Fail 케이스에서만 이 인터페이스를 구현하여, Succ 케이스에서 에러에 접근하는 것을 원천적으로 방지합니다.
public interface IFinResponseWithError{ Error Error { get; }}2. Fail에서만 구현
섹션 제목: “2. Fail에서만 구현”Discriminated Union의 Fail 레코드만 IFinResponseWithError를 구현합니다. Succ는 이 인터페이스를 구현하지 않으므로, 에러 접근이 타입 시스템에 의해 차단됩니다.
public abstract record ErrorAccessResponse<A> : IFinResponse{ public sealed record Succ(A Value) : ErrorAccessResponse<A> { // IFinResponseWithError를 구현하지 않음! }
public sealed record Fail(Error Error) : ErrorAccessResponse<A>, IFinResponseWithError { // Fail만 IFinResponseWithError 구현 }}3. 패턴 매칭으로 에러 접근
섹션 제목: “3. 패턴 매칭으로 에러 접근”Pipeline에서는 is IFinResponseWithError 패턴 매칭을 사용하여 에러에 안전하게 접근합니다. Succ인 경우 패턴 매칭이 실패하므로, 에러 접근이 자연스럽게 방지됩니다.
public static string LogResponse<TResponse>(TResponse response) where TResponse : IFinResponse{ if (response.IsSucc) return "Success";
// 패턴 매칭으로 에러 접근 - Fail에서만 IFinResponseWithError 구현 if (response is IFinResponseWithError failResponse) return $"Fail: {failResponse.Error}";
return "Fail: unknown error";}4. 왜 별도 인터페이스인가?
섹션 제목: “4. 왜 별도 인터페이스인가?”Error 속성을 IFinResponse에 직접 추가하면, Succ 케이스에서도 Error에 접근할 수 있게 되어 런타임 예외 위험이 생깁니다. 별도 인터페이스로 분리하면 컴파일 타임에 안전성을 보장할 수 있습니다.
FAQ
섹션 제목: “FAQ”Q1: Error 속성을 IFinResponse에 직접 추가하면 왜 안 되나요?
섹션 제목: “Q1: Error 속성을 IFinResponse에 직접 추가하면 왜 안 되나요?”A: IFinResponse에 Error를 추가하면 성공(Succ) 케이스에서도 Error에 접근할 수 있게 됩니다. Succ에는 에러가 없으므로 null 반환이나 예외 발생 등 런타임 위험이 생깁니다. 별도 인터페이스로 분리하면 Fail에서만 구현하여 타입 시스템이 안전성을 보장합니다.
Q2: is IFinResponseWithError 패턴 매칭은 리플렉션과 다른가요?
섹션 제목: “Q2: is IFinResponseWithError 패턴 매칭은 리플렉션과 다른가요?”A: 완전히 다릅니다. is 패턴 매칭은 CLR의 타입 시스템이 수행하는 네이티브 타입 검사로, 리플렉션(GetType().GetProperty())보다 수십 배 빠릅니다. 또한 fail.Error 접근은 컴파일 타임에 검증되므로 프로퍼티 이름 오타 위험도 없습니다.
Q3: Fail에서만 IFinResponseWithError를 구현하는 패턴은 다른 곳에서도 사용되나요?
섹션 제목: “Q3: Fail에서만 IFinResponseWithError를 구현하는 패턴은 다른 곳에서도 사용되나요?”A: 네. 이 패턴은 케이스별 인터페이스 구현이라는 일반적인 설계 기법입니다. 예를 들어 HTTP 응답에서 에러 본문은 4xx/5xx 응답에만 존재하므로, 에러 본문 인터페이스를 에러 응답 케이스에서만 구현하는 것과 동일한 원리입니다.
프로젝트 구조
섹션 제목: “프로젝트 구조”04-IFinResponseWithError/├── FinResponseWithError/│ ├── FinResponseWithError.csproj│ ├── Interfaces.cs│ ├── ErrorAccessResponse.cs│ ├── LoggingPipelineExample.cs│ └── Program.cs├── FinResponseWithError.Tests.Unit/│ ├── FinResponseWithError.Tests.Unit.csproj│ ├── xunit.runner.json│ └── FinResponseWithErrorTests.cs└── README.md실행 방법
섹션 제목: “실행 방법”# 프로그램 실행dotnet run --project FinResponseWithError
# 테스트 실행dotnet test --project FinResponseWithError.Tests.Unit에러 접근까지 해결했으니, 이제 R1~R4 모든 요구사항을 하나의 타입으로 통합합니다. FinResponse<A> Discriminated Union으로 Match, Map, Bind, 암시적 변환까지 구현합니다.