들어가기
복잡한 로직을 설계할 때는 여러 컴포넌트들이 서로 어떻게 상호작용하는지,
그리고 특정 작업을 수행하는 데 드는 비용과 그 작업이 실패했을 때 다시 원래 상태로 되돌리는 비용을 고려해야 합니다.
그래서 비용이 적게 드는 컴포넌트에 대해 먼저 작업을 시도하고,
실패했을 경우에는 빠르게 원래 상태로 되돌리는 '롤백(rollback)' 방식을 사용하기도 합니다.
이 과정에서 Golang의 defer를 사용하면 어떻게 효율적으로 개선할 수 있는지에 대해 설명합니다.
defer
golang에서 defer는 함수가 종료될 때 실행되는 코드 블록을 정의하는데 사용됩니다.
그래서 주로 리소스 해제나 파일 닫기, 뮤텍스 잠금 해제 등에 쓰입니다.
func example() {
defer fmt.Println("이 메시지는 함수가 끝날 때 출력된다.")
fmt.Println("이 메시지는 함수가 실행될 때 바로 출력된다.")
}
간단한 예시 코드
아래 예시를 통해 어떻게 사용하는지 알아보겠습니다.
defer를 사용하지 않은 경우
아래처럼 에러를 처리하는 부분을 defer로 사용할 수 있습니다.
func simpleWithoutDefer() error {
err := setAComponent()
if err != nil {
return err
}
err = setBComponent()
if err != nil {
_ = fmt.Errorf("failed to set b, reason : %s", err)
rollbackAComponent()
return err
}
return nil
}
defer를 사용한 경우
func simpleWithDefer() (err error) {
err = setAComponent()
if err != nil {
return err
}
defer handleError1(err)
return setBComponent()
}
func handleError1(err error) {
if err != nil {
_ = fmt.Println("failed to set b, reason : ", err)
rollbackAComponent()
}
}
코드가 간단해지긴 했지만 굳이 저렇게 처리할 필요는 없어보입니다.
아래에서 더 복잡한 코드를 보겠습니다.
복잡한 예시 코드
gRPC라던가 REST API를 사용하는 경우에는 rollback이 필요한 상황에서 코드가 생각보다 복잡합니다.
단순 동작 실패 이외에 여러 가지 이유로 동작 거절이 발생할 수 있는데 그런 경우마다 롤백 코드를 넣어줘야됩니다.
defer를 사용하지 않은 경우
func complexWithoutDefer() error {
err := setAComponent()
if err != nil {
return nil
}
resp, err := setCComponent()
if err != nil {
fmt.Println("failed to set c, reason : ", err)
rollbackAComponent()
return err
}
if resp.status != 200 {
fmt.Println("failed to set c, reason : ", resp.message)
rollbackAComponent()
return errors.New(resp.message)
}
return nil
}
defer를 사용한 경우
defer를 사용하면 아래와 같이 에러 로깅과 rollback하는 부분을 따로 떼어 함수를 더 간단히 할 수 있다.
func complexWithDefer() (err error) {
err = setAComponent()
if err != nil {
return err
}
defer handleError2(err)
resp, err := setCComponent()
if err != nil {
return err
}
if resp.status != 200 {
err = errors.New(resp.message)
}
return err
}
func handleError2(err error) {
if err != nil {
fmt.Println("failed to set c, reason : ", err)
rollbackAComponent()
}
}
코드를 좀 더 설명하면 return type에 err라고 미리 변수 정의가 되는 것을 볼 수 있습니다.
따라서 함수 내에서 err := 로 변수 선언을 하지 않고 바로 대입을 하는 것을 확인할 수 있습니다.
또한 defer 실행이 setAComponent 이후에 이루어지는 것을 볼 수 있는데
setAComponent가 실패하면 rollback할 필요가 없기 떄문입니다.
정리
defer를 사용하면 위와 같이 반복되는 rollback 코드 등을 간단하게 만들 수 있습니다.
rollback 뿐만 아니라 단순히 에러 로깅을 하는 경우에도 유용하기 때문에 필요에 따라 defer를 활용해보도록 해보면 좋을 것 같습니다.
'학습 > go' 카테고리의 다른 글
Taskfile을 이용하여 go lang 빌드하기 (0) | 2024.11.11 |
---|