studybook
  • Introduction
  • 실무 하며 깨닫는 부분 정리
    • 옵션에 대해서
    • 코드 작성의 순서
    • 자바 프로그램에 문제가 생겼다면
    • 장애 대처법
  • Logstash, Beats 정리
  • Zookeeper 정리
  • Message Queue 정리
    • RabbitMQ 삽질
  • Java 관련 정리
    • Java Primitive Wrapper class
    • Java NIO
    • Java8 Double colon operator
    • Effective Java
      • 4장
      • 5장
      • 6장 - Enum, Annotation
      • 7장 - Method
      • 8장 - 프로그래밍 일반
      • 9장 - Exception
    • Java8 Lambda expression
    • JDBC
    • Linux에서 WatchService 이상동작
  • Spring 관련 정리
    • Spring Bean init, destroy 순서
    • Spring Async Controller
    • Spring Executable jar 웹 개발 및 배포
    • Spring Boot Font 배포 에러
    • Spring AOP
      • Spring AOP로 모든 Request 로그 남기기
    • Spring Cache
    • Spring Cloud
      • Consul로 spring 설정 관리하기
    • Spring Test
      • Spring Test DirtiesContext
      • Spring Test MockBean, SpyBean
      • Spring Test Dynamic @Scheduled
    • Spring JDBC
    • Spring Validation
    • Spring Transaction Management
      • Spring with JTA 삽질
    • Spring에서 효율적으로 Static resource 관리하기
    • Zuul을 사용해서 Spring Reverse proxy 만들기
    • Spring Security
    • 스프링 어노테이션이 안 먹힐 때 의심해볼만한 것
    • Spring Data
    • Spring Webflux
      • Tobi 강연
  • 코드 리팩토링
    • 한번에 하나씩
  • 지속적 통합 (CI)
    • Jenkins pipeline 삽질기
  • Log Aggregator 정리
    • Flume 테스트
    • Fluentd 테스트
  • Web Socket 정리
  • Akka
    • Actor 모델
    • Supervision
  • IE 8 대응 정리
  • 함수형 프로그래밍
    • 모나드
  • Netty
    • Netty 기본 예제
    • Netty 주요 특징
    • Netty 부트스트랩
    • Netty 채널 파이프라인, 코덱
    • Netty 이벤트 모델
    • Netty 바이트 버퍼
  • 스칼라 관련 정리
    • Maven으로 컴파일하기
    • Scala def 괄호 여부의 차이
    • 스칼라 function, method 차이점
    • ScalaTest와 Spring 연동하기
    • Programming in Scala
  • J2S 컨퍼런스
  • Android
    • 테스트
    • NDK
  • DDOS
  • HTTP
  • HttpClient
  • Container
    • Image 개요
    • cri-o
    • kata containers
    • Open Container Initiative Image
    • Buildkit
  • Github pages
  • Static Website
  • Webhook
  • Service Discovery Tools
    • Etcd
    • Eureka
    • Consul
      • ACL
    • 비교
  • React
    • JSX
    • React Element
    • Components, Props
    • State, Lifecycle
    • Handling Event
    • Flux
  • Vagrant
    • SSH 접속
  • Linux
    • Systemd
    • Alternatives
  • Messaging protocols
    • XMPP
    • AMQP
  • Windows
    • Windows10 내장 우분투에 ssh 클라이언트로 접속하기
    • Windows10 Hyper-V와 Virtual Box가 충돌을 일으켰을 때
    • Hyper-V 기반 docker에서 Shared Drives 설정 실패할 때
    • 윈도우 개발환경 설정
    • Docker desktop 없이 docker 환경 세팅하기
    • UWP 앱을 항상 관리자권한으로 실행하는 바로가기 만들기
  • Spring camp 2017
    • Project Reactive
    • 이벤트 소싱
    • CQRS
  • Spring webflux
  • 리액티브 프로그래밍
  • Linux Settings
    • 홈서버 백업 및 복구기
    • 홈서버 트러블슈팅
  • Kubernetes
    • k3s 설치 및 삽질
    • pod resources
    • Argo workflow
    • 트러블 슈팅
      • Kubernetes namespace의 phase가 Terminating에서 멈춰있을 때
    • 쿠버네티스 마스터
    • Knative
    • Knative Pipeline
    • Aggrerated API server
    • Accessing the API
      • Authenticating
  • Sonarqube
  • HTTP/2
  • Go
    • Go Module
    • Go dependency injection
    • Go Error handling
    • Go in Action
      • 3장 패키지
      • 4장 배열, 슬라이스, 맵
      • 5장 GO의 타입 시스템
      • 6장 동시성
      • 7장 동시성 패턴
      • 8장 표준 라이브러리
      • 9장 테스트와 벤치마킹
    • Go Channel 사용법
  • Cloud Native
Powered by GitBook
On this page
  • 참고
  • 일반적 사용법 (Go way)
  • Custom error 만들어서 쓰기
  • error 모아서 처리하기
  1. Go

Go Error handling

PreviousGo dependency injectionNextGo in Action

Last updated 6 years ago

참고

일반적 사용법 (Go way)

result, err := someFunction()
if err != nil {
  // handle err
}

//or

if _, err := someFunction(); err != nil {
  // handle err
}

go에서 권장하는 방법이고 가장 많이 쓰게 될 방법.

문제는 위와 같은 코드가 너무. 너무 많이 나온다는 것. 죄다 중복 코드인데;

거기에다가 에러 인스턴스에서 얻을 수 있는 데이터가 오직 에러 메세지 뿐임. 스택 트레이스를 보려면 panic 을 쓰던지 아니면 직접 구현해야되는 것도 짜증난다.

Custom error 만들어서 쓰기

type error interface {
  Error() string
}

error는 이렇게 엄청 단순한 인터페이스다. 따라서 이것만 가지고 적절하게 에러핸들링을 하는건 거의 불가능에 가까우므로 사용자가 직접 구현한 error struct를 만들어서 쓰는게 낫다. 내지는 `github.com/juju/errors` 같은 라이브러리를 쓰던지.

type MyError struct {
    Cause MyError
    Msg string
}
func (m *MyError) Error() string {
    return fmt.Sprintf("%s: %s", m.Msg, m.Cause.Error())
}

그런데 이렇게 하면 또 약간 귀찮은 부분이 생긴다. 일반적으로 함수가 리턴할 때는 error 인터페이스 상태로 리턴하기 때문에 캐스팅을 해줘야되는 것. 이 땐 if else 문을 쓰는게 마음 편하다.

result, err := SomeFunc()
if mErr, ok := err.(*MyError); ok {
    // mErr을 사용해서 에러 핸들링
} else {
    msg := fmt.Sprintf("unknown error: %s", err)
    mErr = &MyError{Msg: msg}
    // mErr을 사용해서 에러 핸들링
}

error 모아서 처리하기

코드 중복도 줄일 겸 로직도 알아보기 편하게 할 겸 겸사겸사 모아서 처리하는 법을 찾아보게 되었다. 무엇보다 스프링 부트에서 @AdviceController 나 @ExceptionHandler 를 엄청 편하게 썼던 기억을 잊을 수가 없었다.

defer, panic, recover

defer, panic, recover을 사용하는 방법. 이 세개는 모두 go built-in 함수.

  • defer: defer로 어떤 함수를 지정해놓으면 defer가 선언된 스코프가 끝나고 난 다음 실행하게 됨. 한 스코프에 여러 defer 함수를 넣을 수 있으며 여러개를 넣어놓아도 모두 실행한 다음 끝남. 보통 Close() 처럼 clean 로직이 필요할때 사용

  • panic: panic을 호출하면 프로그램의 일반적인 흐름이 즉시 멈추고 panicking이라고 부르는 상태로 바뀜. 이 상태에서는 함수의 콜스택을 하나하나 거슬러 올라가면서 모든 defer 함수들을 호출하고 root까지 가면 프로그램이 강제종료됨

  • recover: defer 함수 안에서만 사용. recover를 호출하게 되면 panicking에서 다시 원래 프로그램 흐름으로 돌아오게 됨. 이 때, panic에 패러미터로 넘겼던 interface를 반환함.

요약하면 모든 error가 발생할 때마다 panic을 호출하고, error를 모아서 처리할 곳에다가 defer 및 recover를 등록해두는 것. 이렇게 하면 어디서 언제 error가 발생하든 recover로 모두 모이게 된다.

func HandleHttpRequest(request *Request) *Response {
    defer func() {
        if r := recover(); r != nil {
            if err, ok := r.(error); ok {
                // 에러 타입에 따라 적절히 에러 처리
            } else {
                panic(r)
            }
        }
    }()
    
    if request.URI != "/" {
        panic(fmt.Errorf("not found %s", request.URI))
    }
    
    return handleIndex(request)
}

func handleIndex(request *Request) *Response {
    var err error

    // 비지니스 로직
    if err != nil {    
        panic(fmt.Errorf("internal server error %s", err))
    }
}

channels, sync.WaitGroup, errgroup

defer, panic, recover 방식의 한계는 goroutine을 사용할 수가 없다는 것.

goroutine을 쓰는이 경우엔 다른 방법을 사용해야 함. channel 또는 errgroup을 쓰는 방법이 있음.

func useChannel() {
	goRoutineNum := 2
	
	errChannel := make(chan error, goRoutineNum)
	
	var wg sync.WaitGgroup
	wg.Add(goRoutineNum)

	go func() {
		defer wg.Done()

		if err := someFunc(); err != nil {
			errChannel <- err
			return
		}
	}()

	go func() {
		defer wg.Done()

		if err := someFunc(); err != nil {
			errChannel <- err
			return
		}		
	}()


	wg.Wait()
	close(errChannel)

	for err := range errChannel {
		// handle err
	}
}

func useErrGroup() {
	var g errgroup.Group

	g.Go(func() error {
		if err := someFunc(); err != nil {
			return err
		}
		return nil
	})

	g.Go(func() error {
		if err := someFunc(); err != nil {
			return err
		}
		return nil
	})

	if err := g.Wait(); err != nil {
		// handle err
	}
}

channel을 쓰면 각각의 에러에 대해서 적절한 처리를 하는 것이 가능. 하지만 대부분의 경우 거기까진 필요없음. 그냥 모든 goroutine에 대해서 에러가 하나 이상 발생했는지 체크하는 경우가 더 많음. 이 경우에는 errgroup을 쓰는게 좀 더 빠르고 간편함.

https://evilmartians.com/chronicles/errors-in-go-from-denial-to-acceptance?fbclid=IwAR2QLLZ4KrPPX7wQk5faERPOqMQNm4znUH7rvTLTw0GeZiYPnrR80TFWzbE