에러 처리의 책임

윤종성·2025년 5월 8일
1

좋은 질문이야.
**"에러를 어디서 책임져야 하느냐"**는 아래 기준에 따라 결정하는 게 바람직해:


✅ 기본 원칙: 에러에 대한 처리 책임은 의미 있는 대응이 가능한 계층에서

처리 위치책임 대상권장 상황
함수 내부 (호출된 쪽)로컬 로직해당 함수만의 예외 처리/복구/대체 로직이 있을 때
호출자 (상위)전역 흐름에러를 로깅하거나, 사용자에게 에러 메시지를 보여주거나, 트랜잭션을 롤백할 필요가 있을 때

💡 판단 기준: 아래 질문들을 해봐

  1. 이 함수는 에러가 날 수 있다는 걸 자연스럽게 예상할 수 있는가?

    • 예: 외부 API 호출, DB 쿼리 등
    • → 이럴 땐 호출자가 책임지는 게 더 자연스러움 (try/catch를 호출 쪽에서)
  2. 에러가 났을 때 이 함수 내에서 복구 가능하거나 의미 있는 fallback이 있는가?

    • 예: 파일이 없으면 기본 설정으로 대체
    • → 함수 내부에서 try/catch로 잡는 게 맞음
  3. 에러가 발생했을 때 사용자에게 메시지를 보여주거나 로그로 남겨야 하는가?

    • → 호출자(서비스 레이어, 컨트롤러 등)에서 책임져야 함

🔍 예시

1. 내부에서 의미 있는 대체 로직이 있을 경우

async function getUser(id: string) {
  try {
    return await db.findUserById(id);
  } catch {
    return null;  // 없는 경우 null 처리
  }
}

호출자가 이 함수가 null을 반환할 수 있음을 예상해야 함


2. 서비스에서 의미 있는 대응을 해야 할 경우

async function createUser(data: UserDTO) {
  const user = await this.userRepo.save(data);  // 여기서 실패 가능
  this.logger.log(`User ${user.id} created`);
}

이럴 땐 save() 내부에서는 await 없이 에러를 리턴하고, createUser()에서 try/catch로 전체 흐름 관리하는 게 좋음.


🧭 NestJS 관점 예시

  • Service 계층에서는 return foo()로 넘기고
  • Controller 계층에서 await + try/catch 혹은 ExceptionFilter로 처리
@Controller()
export class UserController {
  @Post()
  async create(@Body() dto: CreateUserDto) {
    try {
      return await this.userService.create(dto);
    } catch (e) {
      throw new InternalServerErrorException('유저 생성 실패');
    }
  }
}

✅ 결론

  • 에러 처리는 의미 있는 대응이 가능한 계층이 책임져야 한다
  • 단순히 return await을 쓰느냐 마느냐보다,
    “이 에러를 여기서 감싸고 처리할 의미가 있는가?” 가 중요
  • 라이브러리/서비스 함수는 에러를 최대한 드러내고, 최종 처리 책임은 상위 계층으로 넘기는 게 일반적인 패턴

profile
알을 깬 개발자

0개의 댓글