🔥 Errors 인터페이스

524자
7분

Go 프로그램에서는 error 값을 사용하여 에러 상태를 표현합니다. error 타입은 fmt.Stringer와 유사한 내장 인터페이스입니다.

type error interface {
    Error() string
}
 
go

(fmt.Stringer와 마찬가지로 fmt 패키지는 값을 출력할 때 error 인터페이스를 찾습니다.)

함수는 종종 error 값을 반환하고, 호출하는 코드는 에러가 nil과 같은지 테스트하여 에러를 처리해야 합니다.

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("숫자로 변환할 수 없습니다: %v\n", err)
    return
}
fmt.Println("변환된 정수:", i)
 
go

위의 코드에서는 strconv.Atoi 함수를 사용하여 문자열 "42"를 정수로 변환하려고 시도합니다.

  • i 변수에는 변환된 정수 값이 저장됩니다.
  • err 변수에는 변환 과정에서 발생한 에러가 저장됩니다.
  • if 문을 사용하여 errnil인지 확인합니다.
    • 에러가 발생하면 (err != nil), fmt.Printf를 사용하여 에러 메시지를 출력하고 함수를 종료합니다.
    • 에러가 발생하지 않으면 (err == nil), fmt.Println을 사용하여 변환된 정수 값을 출력합니다.

nil error는 성공을 나타내고, nil이 아닌 error는 실패를 나타냅니다.

사용자 정의 에러 타입을 만들어 보겠습니다.

package main
 
import (
	"fmt"
	"time"
)
 
type MyError struct {
	When time.Time
	What string
}
 
func (e *MyError) Error() string {
	return fmt.Sprintf("%v 시점에 %s 에러가 발생했습니다.",
		e.When, e.What)
}
 
func run() error {
	return &MyError{
		time.Now(),
		"작업이 실패했습니다.",
	}
}
 
func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
 
go
  • MyError 구조체를 정의하여 사용자 정의 에러 타입을 만듭니다.
    • When 필드는 에러가 발생한 시간을 저장합니다.
    • What 필드는 에러에 대한 설명을 저장합니다.
  • Error() string 메서드를 구현하여 MyError 타입이 error 인터페이스를 만족하도록 합니다.
    • 이 메서드는 에러 메시지를 포맷팅하여 반환합니다.
  • run 함수에서는 MyError 값을 생성하여 에러로 반환합니다.
    • time.Now()를 사용하여 현재 시간을 When 필드에 저장합니다.
    • What 필드에는 에러 설명을 저장합니다.
  • main 함수에서는 run 함수를 호출하고 반환된 에러를 확인합니다.
    • 에러가 nil이 아니면 (err != nil), 에러 메시지를 출력합니다.

이렇게 Go에서는 error 인터페이스를 사용하여 에러 처리를 간편하게 할 수 있습니다. 사용자 정의 에러 타입을 만들어 에러에 대한 추가 정보를 제공할 수도 있죠.

참고로 run 함수에서 &MyError 포인터를 반환하는 이유는 error 인터페이스를 만족시키기 위해서입니다.

Go에서 인터페이스는 값으로도 전달될 수 있고, 포인터로도 전달될 수 있습니다. error 인터페이스는 Error() string 메서드를 가지고 있는데, 이 메서드는 포인터 리시버((e *MyError))로 정의되어 있습니다.

func (e *MyError) Error() string {
	return fmt.Sprintf("%v 시점에 %s 에러가 발생했습니다.",
		e.When, e.What)
}
 
go

따라서 MyError 값을 error 인터페이스로 사용하려면, 포인터를 사용해야 합니다. 만약 run 함수에서 MyError 값을 직접 반환한다면, error 인터페이스로 사용할 수 없게 됩니다.

func run() error {
	return MyError{
		time.Now(),
		"작업이 실패했습니다.",
	}
}
 
go

위와 같이 MyError 값을 직접 반환하면, MyError 타입은 error 인터페이스를 구현하지 않은 것으로 간주됩니다. 컴파일 에러가 발생하겠죠.

하지만 &MyError를 반환함으로써, *MyError 타입의 포인터가 error 인터페이스를 구현하는 것으로 인식됩니다. 그래서 run 함수는 error 인터페이스를 반환할 수 있게 되는 거예요.

func run() error {
	return &MyError{
		time.Now(),
		"작업이 실패했습니다.",
	}
}
 
go

이렇게 &MyError를 반환하면, run 함수의 반환 타입인 error 인터페이스와 호환되고, MyError 타입의 에러를 error 인터페이스로 사용할 수 있게 됩니다.

포인터를 사용하는 것은 Go에서 인터페이스를 다룰 때 자주 사용되는 패턴이에요. 값 타입과 포인터 타입 모두 인터페이스를 구현할 수 있지만, 메서드의 리시버 타입에 따라 적절한 방식을 선택해야 합니다.

지금까지 에러 처리를 알아 보았습니다. 에러 처리는 프로그램의 안정성과 사용자 경험을 향상시키는 데 중요한 역할을 합니다. 적절한 에러 처리를 통해 프로그램의 문제를 파악하고 대응할 수 있으므로 에러 처리에 익숙해 지는 것은 매우 중요합니다.