2024.04.01
error
타입의 값을 반환하여 오류를 처리한다. 이것은 전적으로 관례에 의한 것이지만, 절대 위반해서 안되는 강력한 관례이다.error
파라미터로 nil
이 반환된다. 만약 문제가 있다면, 오류 값이 반환된다.nil
이 아닌 오류를 반환할 때, 다른 반환 값은 제로 값으로 설정해야 한다.func calcRemainderAndModd(numerator, denominator int) (int, int, error) {
if denominator == 0 {
return 0, 0, erros.New("denominator is 0")
}
return numerator / denominator, numerator % denominator, nil
}
if
문을 사용하여 오류 변수가 nil
이 아닌지 확인하도록 하자.nil
을 반환하는 이유는 nil
은 모든 인터페이스 타입에 대한 제로 값이기 때문이다._
) 을 사용해서 명시적으로 무시하도록 하는 것errors.New
: 문자열을 받아 error
를 반환환다.fmt.Errorf
: errors.New
와 같이, 반환된 오류 인스턴스의 Error
메서드가 호출될 때 문자열이 반환된다.// errors.New 함수 사용
func doubleEvent(i int) (int, error) {
if i % 2 != 0 {
return 0, errors.New("only even numbers are processed")
}
return i * 2, nil
}
// fmt.Errorf 함수 사용
func doubleEvent(i int) (int, error) {
if i % 2 != 0 {
return 0, fmt.Errorf("only even numbers are processed")
}
return i * 2, nil
}
Err
로 시작한다.// 표준 ZIP 파일 패키지인 archive/zip 중,
// 전달된 데이터가 ZIP 파일 포맷이 아닌 경우에 반환하는 ErrFormat 오류
func main(){
data := []byte("This is not a zip file")
notAZipFile := bytes.NewReader(data)
_, err := zip.NewReadder(notAZipFile, int64(len(data)))
if err == zip.ErrFormat {
fmt.Println("Told you so")
}
}
오류는 인터페이스이기 때문에, 로깅이나 오류 처리를 위한 추가적 정보를 포함하여 자신만의 오류를 정의할 수 있다.
( 예를 들어, 종류를 나타내기 위해 오류의 일부로 상태 코드를 포함할 수 있다. )
type Status int
const (
InvalidLogin Status = iota + 1
NotFound
)
type StatusErr struct {
Status Status
Message string
}
func (se StatusErr) Error() string {
return se.Message
}
func LoginAndGetData(uid, pwd, file string) ([]byte, error) {
err := login(uid, pwd)
if err != nil {
return nil, StatusErr{
Status: InvalidLogin,
Message: fmt.Sprintf("invalid credentials for user %s", uid),
}
}
data, err := getData(file)
if err != nil {
return nil, StatusErr{
Status: NotFound,
Message: fmt.Sprintf("file %s not found", file),
}
}
return data, nil
}
fmt.Errorf
함수는 특수한 형식 동사 %w
를 가지고 있다. 다른 오류의 형식 지정된 문자열과 원본 오류를 포함하는 형식 지정된 문자열의 오류를 생성하는데 사용할 수 있다.errors
패키지에 Unwrap
함수를 제공한다. 오류를 전달하면 래핑된 오류가 있는 경우 이를 반환하고, 없다면 nil
을 반환한다.func fileChecker(name string) error {
f, err := os.Open(name)
if err != nil {
// fmt.Errorf로 오류 래핑
return fmt.Errorf("in fileChecker: %w", err)
}
f.Close()
return nil
}
func main() {
err := fileChecker("not_here.txt")
if err != nil {
fmt.Println(err)
// errors.Unwrap으로 오류 언래핑
if wrappedErr := errors.Unwrap(err); wrappedErr != nil {
fmt.Println(wrappedErr)
}
}
}
Unwrap
메서드를 구현할 필요가 있다.type StatusErr struct {
Status Status
Message string
err error
}
func (se StatusErr) Error() string {
return se.Message
}
func (se StatusErr) Unwrap() string {
return se.err
}
==
를 사용하여 확인할 수 없으며 래핑된 사용자 지정 오류와 일치시키기 위해 타입 단언이나 타입 스위치를 사용할 수도 없다.errors
패키지에 Is
와 As
를 제공한다.errors.Is
를 사용한다.func fileChecker(name string) error {
f, err := os.Open(name)
if err != nil {
// fmt.Errorf로 오류 래핑
return fmt.Errorf("in fileChecker: %w", err)
}
f.Close()
return nil
}
func main() {
err := fileChecker("not_here.txt")
if err != nil {
if errors.Is(err, os.ErrNotExists) {
fmt.Println("That fille doesn't exist")
}
}
}
errors.Is
는 지정된 오류와 래핑된 오류를 비교하기 위해 ==
를 사용한다. 당신이 정의한 오류 타입이 해당 동작을 하지 않을 경우 당신의 오류에 Is
메서드를 구현하도록 하자.errors.As
함수는 반환된 오류가 특정 타입과 일치하는지 확인할 수 있다.true
를 반환하면, 오류는 두 번째 파라미터로 할당된다.err := AFunctionThatReturnsAnError()
var myErr MyErr
if errors.As(err, &myErr) {
fmt.Println(myError.Code)
}
errors.As
의 두 번째 파라미터로 오류의 포인터나 인터페이스 포인터가 아닌 다른 것을 전달하면, 메서드는 패닉을 발생시킨다.errors.Is
비교를 Is
메서드로 재정의 할 수 있듯이, 기본 errors.As
비교도 해당 오류 타입 내의 As
메서드로 재정의 할 수 있다.errors.Is
를 사용하자. 특정 타입을 찾을 때는 errors.As
를 사용하자.func DoSomeThings(val1 int, val2 string) (string, error) {
val3, err := doThing1(val1)
if err != nil {
return "", fmt.Errorf("in DoSomeThings: %w", err)
}
val4, err := doThing2(val2)
if err != nil {
return "", fmt.Errorf("in DoSomeThings: %w", err)
}
result, err := doThing3(val3, val4)
if err != nil {
return "", fmt.Errorf("in DoSomeThings: %w", err)
}
return result, nil
}
defer
로 이용하여 더 간단히 작성해 볼 수 있다.func DoSomeThings(val1 int, val2 string) (string, error) {
defer func() {
if err != nil {
err = fmt.Errorf("in DoSomeThings: %w", err)
}
}()
val3, err := doThing1(val1)
if err != nil {
return "", err
}
val4, err := doThing2(val2)
if err != nil {
return "", err
}
return doThing3(val3, val4)
}
defer
함수가 실행을 시작한다.defer
가 완료되면, 호출 함수에 연결된 defer
가 main 함수에 도달할 때까지 계속 실행된다.recover
는 패닉을 확인하기 위한 defer
내부에서 호출될 수 있다. recover
가 일어나면 실행은 정상적으로 진행된다.func div60(i int) {
defer func() {
int v := recover(); v != nil {
fmt.Println(v)
}
}()
fmt.Println(60 / i)
}
func main() {
for _, val := range []int{1, 2, 0, 6} {
div60(val)
}
}
defer
로 등록된 함수만 실행할 수 있기 때문에, recover
는 반드시 defer
내에서 호출해야 한다.panic
과 recover
는 다른 언어에서 예외 처리와 닮아 있지만, 해당 함수들은 예외 처리처럼 사용하도록 의도된 것은 아니다. 치명적 상황에 대한 panic
을 예약하고 안정적으로 이런 상황을 처리할 수 있는 방법으로 recover
를 사용하자.recover
를 사용하여 소프트웨어 모니터링을 위해 상황을 로깅하고 os.Exit(1)
으로 종료하여 안전하게 처리한다.panic
과 recover
에 의존하지 않는 이유는 recover
가 무엇이 실패할 수 있는지 명확하지 않기 때문이다. 관용적인 Go는 아무것도 언급하지 않고 모든 것을 짧은 코드로 처리하는 것보다 가능한 실패 조건을 명시적으로 설명하는 코드를 선호한다.panic
과 recover
에 대한 유혹을 받는 이유 중 하나는 문제가 발생했을 때, 스택 트레이스를 얻을 수 있기 때문이다. 기본적으로 Go는 그것을 제공하지 않는다.