Panic, Defer, Recover
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 57번째.
Go는 에러를 반환하는 것을 강조하지만, 어떤 상황들은 정말로 복구 불가능합니다. 이러한 경우를 위해 Go는 예외적인 상황을 처리하기 위한 메커니즘인 panic, defer, 그리고 recover를 제공합니다.
panic은 즉시 정상적인 실행을 중단합니다. 이는 인덱스 범위를 벗어난 접근과 같은 프로그래밍 오류를 위해 예약된 것이며, 유효하지 않은 사용자 입력과 같이 예상 가능한 오류를 위한 것이 아닙니다:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer는 주변 함수가 정상적으로 반환되든 패닉으로 인해 반환되든 상관없이, 해당 함수가 반환될 때 실행될 함수를 예약합니다. 지연된 호출은 역순(후입선출, LIFO)으로 실행됩니다:
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// 출력: Opening file, Processing..., Closing file
}recover는 패닉(panic)을 복구하고 정상적인 실행 상태로 돌아갑니다. 이는 지연된(deferred) 함수 내부에서만 동작합니다:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}이 패턴은 panic을 일반적인 error로 변환하여 프로그램이 계속 실행될 수 있도록 합니다. 이 패턴은 드물게 사용하십시오. 예상되는 실패 사례에 대해서는 error를 반환하는 것을 선호하고, panic/recover는 상태 손상이나 복구 불가능한 조건과 같은 진정으로 예외적인 상황을 위해 남겨두십시오.
챌린지
쉬움예외적인 상황을 우아하게 처리하기 위해 panic, defer, recover를 사용하는 방법을 보여주는 안전한 나눗셈 계산기를 만들어 보겠습니다. 여러분의 계산기는 위험한 연산을 시도하고, 패닉(panic)을 정상적으로 처리할 수 있는 일반적인 에러로 변환할 것입니다.
코드는 두 개의 파일로 구성됩니다:
calculator.go: 패닉 복구 기능이 포함된 나눗셈 로직을 작성합니다.두 개의 정수를 인자로 받고, 나누는 수(divisor)가 0이면
"division by zero"라는 메시지와 함께 패닉을 발생시키는Divide함수를 구현하세요. 그렇지 않으면 나눗셈의 정수 결과를 반환합니다.두 개의 정수를 인자로 받고 정수 결과와 에러를 모두 반환하는
SafeDivide함수를 구현하세요. 이 함수는 다음을 수행해야 합니다:- 익명 함수와 함께
defer를 사용하여 모든 패닉으로부터 복구(recover)합니다. - 패닉이 복구되면, 이를
"calculation error: [panic message]"형식의 에러로 변환합니다. - 내부적으로
Divide함수를 호출합니다. - 성공하면 결과와
nil을 반환하고, 패닉이 복구되면 0과 에러를 반환합니다.
- 익명 함수와 함께
main.go: 입력으로부터 두 개의 정수를 읽고SafeDivide를 사용하여 계산을 안전하게 수행합니다. 또한 올바른 순서로 정리(cleanup) 메시지를 출력하여defer를 시연합니다.main 함수는 다음을 수행해야 합니다:
- 시작할 때
Starting calculation을 출력합니다. defer를 사용하여Cleanup complete출력을 예약합니다.- 또 다른
defer를 사용하여Releasing resources출력을 예약합니다. - 입력값으로
SafeDivide를 호출합니다. - 결과에 따라
Result: [value]또는Error: [error message]를 출력합니다.
- 시작할 때
다음 입력이 제공됩니다:
- 줄 1: 첫 번째 정수 (피제수)
- 줄 2: 두 번째 정수 (제수)
지연된 호출(deferred calls)은 역순(후입선출, LIFO)으로 실행되므로, 정리 메시지는 예약된 순서와 반대로 나타나야 함을 기억하세요.
예를 들어, 20과 4가 주어지면 출력은 다음과 같아야 합니다:
Starting calculation
Result: 5
Releasing resources
Cleanup complete그리고 10과 0이 주어지면 출력은 다음과 같아야 합니다:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete치트 시트
Go는 예외적인 상황을 처리하기 위해 panic, defer, 그리고 recover를 제공합니다.
panic은 즉시 정상적인 실행을 중단하며, 복구할 수 없는 프로그래밍 오류를 위해 예약되어 있습니다:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer는 주변 함수가 반환될 때 실행될 함수를 예약합니다. 지연된 호출(Deferred calls)은 역순(후입선출, last-in, first-out)으로 실행됩니다:
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Output: Opening file, Processing..., Closing file
}recover는 패닉을 포착하고 정상적인 실행으로 되돌립니다. 이는 오직 지연된 함수(deferred function) 내부에서만 작동합니다:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}이 패턴은 패닉을 일반적인 에러로 변환합니다. 예상되는 실패에 대해서는 에러를 반환하는 것을 권장하며, panic/recover는 정말로 예외적인 상황을 위해 남겨두어야 합니다.
직접 해보기
package main
import (
"fmt"
)
func main() {
// 입력 읽기
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// 시작 메시지 출력
fmt.Println("Starting calculation")
// TODO: defer를 사용하여 "Cleanup complete" 메시지 예약
// TODO: defer를 사용하여 "Releasing resources" 메시지 예약
// 기억하세요: 지연된 호출(deferred calls)은 LIFO 순서(후입선출)로 실행됩니다.
// TODO: 입력 값으로 SafeDivide 호출
// TODO: 에러 반환 여부에 따라
// "Result: [value]" 또는 "Error: [error message]" 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서