Go 에러, 에러처리
Go에서 에러 처리(Error Handling)는 매우 중요한 개념입니다. Go는 예외(Exception)를 사용하지 않고, 반환 값으로 에러를 전달하여 명시적으로 처리하는 방식을 채택하고 있습니다. 이는 코드의 가독성과 오류 처리의 명확성을 높이는 장점이 있습니다.
에러 인터페이스
Go 표준 라이브러리는 error 인터페이스를 제공하며, 이는 에러를 나타내기 위한 기본 타입입니다. error 인터페이스는 단 하나의 메서드를 가집니다:
type error interface {
Error() string
}
에러 생성
에러는 주로 errors 패키지의 New 함수를 사용하여 생성할 수 있습니다.
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("something went wrong")
fmt.Println(err)
}
에러 반환
함수는 일반적으로 두 개 이상의 값을 반환하여, 그 중 하나는 에러를 나타냅니다. 호출자는 반환된 에러 값을 확인하여 에러가 발생했는지 여부를 판단할 수 있습니다.
예제: 에러 반환
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(4, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result, err = divide(4, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
위 예제에서 divide 함수는 두 개의 값을 반환합니다. 첫 번째 값은 나눗셈 결과이고, 두 번째 값은 에러입니다. 호출자는 반환된 에러를 확인하여 나눗셈이 성공했는지 실패했는지 알 수 있습니다.
사용자 정의 에러
사용자 정의 에러를 만들려면 error 인터페이스를 구현하는 타입을 정의할 수 있습니다.
예제: 사용자 정의 에러
package main
import (
"fmt"
)
type MyError struct {
Message string
Code int
}
func (e MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func doSomething(flag bool) error {
if !flag {
return MyError{
Message: "something went wrong",
Code: 123,
}
}
return nil
}
func main() {
err := doSomething(false)
if err != nil {
fmt.Println(err)
}
}
위 예제에서 MyError 구조체는 error 인터페이스를 구현합니다. doSomething 함수는 MyError 타입의 에러를 반환할 수 있습니다.
에러 래핑과 언래핑
Go 1.13 버전부터는 에러 래핑과 언래핑을 지원합니다. 에러를 래핑하면 추가적인 컨텍스트 정보를 에러에 첨부할 수 있습니다.
예제: 에러 래핑과 언래핑
package main
import (
"errors"
"fmt"
)
func doSomething() error {
return fmt.Errorf("doSomething failed: %w", errors.New("underlying error"))
}
func main() {
err := doSomething()
if err != nil {
fmt.Println(err)
var underlyingError error
if errors.As(err, &underlyingError) {
fmt.Println("Underlying error:", underlyingError)
}
}
}
패키지 수준의 에러 변수
종종 패키지 수준에서 에러 변수를 선언하여 재사용할 수 있습니다.
예제: 패키지 수준의 에러 변수
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) (string, error) {
if id != 1 {
return "", ErrNotFound
}
return "Item 1", nil
}
func main() {
_, err := findItem(2)
if err != nil {
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
} else {
fmt.Println("An error occurred:", err)
}
}
}
위 예제에서 ErrNotFound는 패키지 수준에서 선언된 에러 변수입니다. findItem 함수는 특정 조건에서 이 에러를 반환합니다. 호출자는 반환된 에러가 ErrNotFound와 일치하는지 확인할 수 있습니다.
에러 처리 전략
- 즉시 반환: 함수가 에러를 발생시키면 즉시 반환하여 호출자가 처리하도록 합니다.
- 에러 래핑: 에러를 래핑하여 추가 정보를 제공하고, 호출자가 래핑된 에러를 처리할 수 있게 합니다.
- 로깅: 중요한 에러는 로그에 기록하여 문제를 추적하고 분석할 수 있게 합니다.
- 재시도: 일시적인 에러인 경우, 재시도 로직을 구현할 수 있습니다.
- 패닉: 치명적인 에러의 경우, panic을 사용하여 프로그램을 중단시킬 수 있습니다. 그러나 panic은 신중하게 사용해야 하며, 주로 복구 불가능한 상황에서 사용됩니다.
package main
import (
"fmt"
"log"
"os"
)
func riskyOperation() error {
return fmt.Errorf("something went wrong")
}
func main() {
err := riskyOperation()
if err != nil {
log.Printf("Operation failed: %v", err)
os.Exit(1)
}
fmt.Println("Operation succeeded")
}
위 예제에서는 log 패키지를 사용하여 에러를 로그에 기록하고, 프로그램을 종료합니다.
이와 같이 Go에서는 명시적인 에러 처리를 통해 프로그램의 안정성과 가독성을 높일 수 있습니다. 에러 처리 전략을 잘 설계하면, 프로그램의 유지보수성과 확장성도 함께 향상됩니다.
git : https://github.com/kkaok/study-golang/tree/master/src/example/error
출처 : chatGPT