과정을 즐기자

Golang 에서의 예외 처리 본문

Go

Golang 에서의 예외 처리

320Hwany 2025. 2. 16. 16:53

이번 글에서는 Golang에서의 예외 처리 방법에 대해 알아보려고 합니다.

Java Spring에 익숙한 개발자가 Golang을 배우게 되면서 느낀점을 위주로 작성해보려고 합니다.

 

📕 익숙한 Java Spring 방식의 예외 처리

Java에서는 Checked Exception, Unchecked Exception이 있습니다.

 

TIL/java/예외처리.md at main · 320Hwany/TIL

Today I Learned. Contribute to 320Hwany/TIL development by creating an account on GitHub.

github.com

 

간단하게 요약하면 개발하면서 발생하는 예외들을 처리할 때 최근에는 Unchecked Exception으로 처리하는 경향이 많은데

이유는 Checked Exception을 사용하면 예외를 반드시 외부로 던져야 하기 때문입니다.

즉, 레이어드 아키텍쳐에서 생각을 해본다면 Repository에서 발생한 예외를 Service, Controller 등 상위 계층까지 전파가 됩니다.

이로 인해 불필요한 의존 관계가 발생한다는 문제가 생깁니다.

 

따라서 기존에 개발을 해올 때는 커스텀 예외에 대해서 모두 Unchecked Exception으로 만든 후 해당 메소드 안에서

throw 하는 구조로 만들었습니다.

이렇게 발생한 예외를 Spring에서는 Controller Advice를 이용하여 공통 예외 처리를 해주어서 throw로 던진 예외를

처리할 수 있도록 하였습니다.

@Slf4j
@RestControllerAdvice
public class ApiRestControllerAdvice {

    /**
     * 커스텀 예외 처리
     */

    // 400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BadRequestException.class)
    public ErrorResponse handleException(final BadRequestException e) {
        log.info("BadRequestException={}", e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 401
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(UnauthorizedException.class)
    public ErrorResponse handleException(final UnauthorizedException e) {
        log.info("UnauthorizedException Exception={}", e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 403
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(ForbiddenException.class)
    public ErrorResponse handleException(final ForbiddenException e) {
        log.info("ForbiddenException Exception={}", e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 404
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NotFoundException.class)
    public ErrorResponse handleException(final NotFoundException e) {
        log.info("NotFoundException Exception={}", e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }
 	
    ...
}

 

이렇게 개발을 해오는 것이 익숙하였고 딱히 별다른 문제없이 당연(?)하다고 생각해왔습니다.

하지만 Golang에서의 예외 처리를 접하면서 조금 다른 시각을 가지게 되었습니다.

 

📘 Golang 에서는 공통 예외 처리가 없다?

Golang에서는 위 Java Spring 예시와 같이 공통 예외 처리를 해주는 방법이 따로 없습니다.

Repository에서 발생한 예외는 Service, Controller 상위 레이어까지 전달한 다음에 상위 레이어에서 예외 처리를 해주도록 합니다.

 

왜 이러한 방식으로 처리를 할까요? Golang의 철학이라고 생각합니다.

Golang은 개발자에게 많은 자유도를 주면서 실제 코드의 내부 동작을 개발자가 파악하기 쉽도록 하는 것에 집중한다고 생각합니다.

 

Repository

func (r *memberRepository) GetById(memberId int) (*entity.Member, error) {
	var member entity.Member

	if err := r.db.First(&member, memberId).Error; err != nil {
		return nil, err
	}

	return &member, nil
}

 

Service

func (s *memberService) GetById(memberId int) (*entity.Member, error) {
    return s.memberRepository.GetById(memberId)
}

 

Controller

func (c *memberController) GetMember(w http.ResponseWriter, r *http.Request) {
    memberId, isFail := validator.ValidatePathVariable(w, r, "/members/")

    if isFail {
       return
    }

    member, err := c.memberService.GetById(memberId)

    if validator.ValidateGormError(w, r, err) {
       return
    }

    memberResponse := dto.ToMemberResponse(member)
    json.NewEncoder(w).Encode(memberResponse)
}

 

이렇게 Controller의 코드만 보고도 이 코드를 실행하면서 내부적으로 어떠한 에러가 발생할 수 있는지를 한 번에 확인할 수 있습니다.

여기에 Controller가 너무 길어져 가독성이 떨어지는 문제가 있는 것 같아 validator를 만들어서 예외처리를 해주도록 하였습니다.

 

📚  정리

코드가 시작하는 지점에서 어떤 에러가 발생할 수 있는지를 미리 확인할 수 있는 점은 Golang에서의 장점이라고 생각합니다.

하지만 Java Spring 방식으로 공통 예외 처리를 하는 방식이 조금 더 가독성이 있다고 생각합니다.

 

정답은 없다고 생각합니다. 각 언어의 철학이 있는 것이고 Golang은 개발자에게 자유도를 주면서 내부 동작을 쉽게 

확인하도록 해주는 언어입니다. DI와 같은 자주 사용하는 패턴도 프레임워크에서 지원해주는 것을 쓰지 않고

직접 구현해서 사용하는 경우가 많은 것을 보니 굳이 이런 것도 직접 구현해야 하나 싶은 생각이 들면서도

내부 동작에 대한 개발자의 이해도가 올라간다는 건 또 좋다는 생각도 듭니다. 개발에 정답은 없는 것 같습니다...!

'Go' 카테고리의 다른 글

Goroutine 과 Java virtual thread 비교해보기  (1) 2024.12.20