Go 1.20에서 추가된 에러 처리 개선 사항

배경

마이크로서비스 간 통신 로직을 작성하다가 여러 서비스의 에러를 동시에 수집해야 하는 상황이 생겼다. 기존에는 직접 구조체를 만들어 처리했는데, Go 1.20에서 이를 표준 라이브러리로 지원한다는 소식을 접했다.

errors.Join 사용

여러 에러를 하나로 합칠 수 있게 되었다.

err1 := fetchUserService()
err2 := fetchOrderService()
err3 := fetchInventoryService()

if err := errors.Join(err1, err2, err3); err != nil {
    log.Printf("multiple services failed: %v", err)
    return err
}

fmt.Errorf의 다중 wrapping

%w를 여러 번 사용할 수 있게 되었다.

if err1 != nil && err2 != nil {
    return fmt.Errorf("failed: %w and %w", err1, err2)
}

errors.Is, errors.As 동작

errors.Join으로 합쳐진 에러는 errors.Iserrors.As로 개별 에러를 확인할 수 있다.

err := errors.Join(io.EOF, context.Canceled)

if errors.Is(err, io.EOF) {
    // true
}

if errors.Is(err, context.Canceled) {
    // true
}

실제 적용 사례

배치 작업에서 일부 실패가 있어도 전체를 중단하지 않고, 마지막에 모든 에러를 반환하는 패턴에 적용했다.

var errs []error

for _, item := range items {
    if err := processItem(item); err != nil {
        errs = append(errs, err)
        continue
    }
}

if len(errs) > 0 {
    return errors.Join(errs...)
}

기존에 직접 구현했던 multierror 타입을 제거하고 표준 라이브러리로 대체할 수 있었다. 코드가 간결해지고 팀원들도 이해하기 쉬워졌다.