패닉 다루기

패닉 다루기 (Handling panics)

프로그램이 실행되다가 예상치 못한 상황에 부딪혔을 때, Go에서는 **패닉(panic)**이라는 방식으로 그 사실을 알려요. 이번에는 패닉이 어떻게 일어나고, 또 recover로 어떻게 다시 잡아낼 수 있는지 차근차근 살펴볼게요. 두 개의 내장 함수, panicrecover가 바로 이 역할을 도와줍니다.

출처: Go Specification

본문

두 내장 함수가 런타임 패닉(run-time panics)과 프로그램이 직접 정의한 오류 상황을 알리고 다루는 일을 도와줘요.

func panic(interface{})
func recover() interface{}

함수 F를 실행하는 동안 panic을 명시적으로 호출하거나 런타임 패닉이 발생하면 F의 실행이 종료돼요. 그러면 Fdefer한 함수들이 평소처럼 실행되고, 그다음에는 F를 호출한 쪽이 defer한 함수들, 그리고 그다음으로는 실행 중인 고루틴(goroutine)의 최상위 함수가 defer한 함수들까지 차례로 실행돼요. 그 지점에 도달하면 프로그램이 종료되고 오류 상황이 보고되는데, 여기에는 panic에 넘긴 인자의 값도 포함돼요. 이렇게 실행을 종료해 나가는 일련의 과정을 **패닉 상태(panicking)**라고 불러요.

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

recover 함수는 프로그램이 패닉 상태에 빠진 고루틴의 동작을 관리할 수 있게 해줘요. 함수 Grecover를 호출하는 함수 D를 defer하고, G가 실행되고 있는 고루틴 안의 어떤 함수에서 패닉이 발생한다고 가정해 볼게요. defer된 함수들이 실행되다가 D에 도달하면, D가 호출한 recover의 반환 값은 panic 호출에 전달된 값이 돼요. 만약 D가 새로운 panic을 일으키지 않고 정상적으로 반환되면, 패닉 진행이 멈춰요. 이 경우 Gpanic 호출 사이에서 호출된 함수들의 상태는 버려지고, 정상적인 실행이 재개돼요. 그리고 D 이전에 G가 defer했던 함수들이 실행된 뒤, G는 호출자에게 반환하는 방식으로 실행을 종료해요.

recover의 반환 값은 고루틴이 패닉 상태가 아니거나, recover가 defer된 함수에 의해 직접 호출되지 않았을 때는 nil이에요. 반대로 고루틴이 패닉 상태이고 recover가 defer된 함수에 의해 직접 호출되었다면, recover의 반환 값은 nil이 아님이 보장돼요. 이 보장을 지키기 위해, panicnil 인터페이스 값(또는 타입 없는 nil)과 함께 호출하면 런타임 패닉이 발생해요.

아래 예시의 protect 함수는 인자로 받은 함수 g를 호출하고, g가 일으키는 런타임 패닉으로부터 호출자를 보호해줘요.

func protect(g func()) {
	defer func() {
		log.Println("done")  // Println executes normally even if there is a panic
		if x := recover(); x != nil {
			log.Printf("run time panic: %v", x)
		}
	}()
	log.Println("start")
	g()
}

더 알아보기