Go 언어에서 에러를 처리하는 기본적인 방식은 함수가 에러를 반환하고, 호출자가 그 에러를 적절히 처리하는 것이다.
런타임 에러가 발생했을 때 프로그램이 강제로 종료되는 것보다는 적절한 에러 메시지를 출력하는 것이 사용자 경험을 향상시킨다.
Go 언어에서는 이러한 패턴을 장려하며, 함수는 보통 결과값과 함께 에러도 반환한다.
-
fmt 패키지의 Errof() 함수를 이용해서 원하는 에러 메시지를 만들 수 있다.
-
errors 패키지의 New() 함수를 이용해서 error를 생성할 수 있다.
package main
import (
"errors"
"fmt"
)
func main() {
// 에러 발생 시나리오 1: 파일을 찾을 수 없는 경우
err1 := fileNotFoundError("myFile.txt")
if err1 != nil {
fmt.Println("에러 발생:", err1)
}
// 에러 발생 시나리오 2: 유효하지 않은 사용자 입력
err2 := invalidInputError(42)
if err2 != nil {
fmt.Println("에러 발생:", err2)
}
}
// fmt.Errorf()를 사용하여 포맷된 에러 메시지를 생성.
func fileNotFoundError(fileName string) error {
return fmt.Errorf("파일을 찾을 수 없습니다: %s", fileName)
}
// errors.New()를 사용하여 간단한 에러 메시지를 생성.
func invalidInputError(input int) error {
return errors.New(fmt.Sprintf("유효하지 않은 입력: %d", input))
}
error는 인터페이스로 문자열을 반환하는 Error() 메서드로 구성되어 있다.
type error interface{
Error() string
}
어떤 타입이든 문자열을 반환하는 Error() 메서드를 포함하고 있다면 에러로 사용할 수 있다.
이를 이용하면 에러에 더 많은 정보를 포함시킬 수 있다.
package main
import "fmt"
type PasswordError struct {
Len int
RequireLen int
}
func (err PasswordError) Error() string {
return "암호 길이가 짧습니다."
}
func RegisterAccount(name, password string) error {
if len(password) < 8 {
return PasswordError{len(password), 8}
}
return nil
}
func main() {
err := RegisterAccount("myID", "myPw")
if err != nil {
if errInfo, ok := err.(PasswordError); ok {
fmt.Printf("%v Len: %d RequireLen: %d\n", errInfo, errInfo.Len, errInfo.RequireLen)
}
} else {
fmt.Println("회원 가입 됐습니다.")
}
}
위 코드에서 사용된 ok는 Go 언어의 타입 단언(type assertion)
을 사용할 때 얻을 수 있는 불리언 값이다.
타입 단언은 특정 인터페이스가 특정 타입을 가지고 있는지를 검사하고, 그 타입으로 변환된 값을 얻기 위해 사용된다.
-
만약 err이
PasswordError 타입
이면, ok는 true가 되고, errInfo는 err의 PasswordError 타입으로 변환된 값을 가진다. -
만약 err이
PasswordError 타입
이 아니라면, ok는 false가 되고, errInfo는 PasswordError 타입의 제로 값 (이 경우는 PasswordError{Len: 0, RequireLen: 0}
)이 된다.
Go 언어에서는 에러 랩핑(wrapping)
을 통해 하위 에러에 대한 추가 컨텍스트를 제공하거나, 에러를 한 단계 더 추상화하여 상위 레벨의 에러를 생성할 수 있다.
%w 형식 지정자
를 사용하여 fmt.Errorf
를 통해 에러를 랩핑하고, errors.As
함수를 사용하여 랩핑된 에러를 확인할 수 있다.
package main
import (
"errors"
"fmt"
)
// 사용자 정의 에러 타입.
type CustomError struct {
msg string
}
func (e *CustomError) Error() string {
return e.msg
}
func main() {
// 하위 레벨에서 에러 생성
err1 := &CustomError{"원본 에러"}
// 에러 랩핑
err2 := fmt.Errorf("랩핑된 에러: %w", err1)
// 랩핑된 에러 출력
fmt.Println(err2)
// 랩핑된 에러에서 원본 에러 추출
var errUnwrapped *CustomError
if errors.As(err2, &errUnwrapped) {
fmt.Println("추출된 원본 에러:", errUnwrapped)
}
}
Go 언어에서 panic 함수는 프로그램이 회복할 수 없는 상태에 직면했을 때 사용된다.
panic을 호출하면 즉시 현재 함수의 실행을 멈추고, 콜 스택을 거슬러 올라가면서 각 함수에 대한 defer 스테이트먼트를 실행한다.
이러한 과정이 루트까지 계속되면, 프로그램은 에러 메시지와 함께 종료된다.
panic은 주로 예상치 못한 에러가 발생했을 때, 또는 정상적인 에러 처리로는 회복할 수 없는 상황에서 사용된다.
panic은 어떤 타입의 인자도 받을 수 있지만, 주로 문자열 또는 에러 타입을 전달하는 것이 일반적이다.
panic(42)
panic("unreachable")
panic(fmt.Errorf("This is error num:%d , num)
Go 언어에서 panic
은 현재 함수의 실행을 즉시 중단하고, 함수의 호출 스택을 거슬러 올라가며 각 함수의 defer 구문
을 실행한다.
이때 recover 함수
를 사용하면 panic
으로 인해 중단된 함수 호출 스택에서 프로그램의 제어를 회복할 수 있다.
recover
는 panic
에 의해 전달된 객체를 반환하며 이 객체는 interface{} 타입
이므로,
실제 사용하기 위해서는 타입 단언(type assertion)
이나 타입 스위치(type switch)
를 사용하여 적절한 타입으로 변환해야 한다.
아래 예제 코드는 panic
과 recover
를 사용하여 예외 상황을 처리하고, 발생한 패닉의 타입에 따라 다른 처리를 하는 방법을 보여준다.
package main
import (
"fmt"
"net"
"os"
)
func main() {
fmt.Println("Calling a function...")
callFunction()
fmt.Println("Returned normally from the function.")
}
func callFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in callFunction:", r)
// 타입 검사를 통해 특정 타입의 패닉에 대한 처리를 수행
if _, ok := r.(net.Error); ok {
fmt.Println("Recovered a net.Error in callFunction")
} else if msg, ok := r.(string); ok {
fmt.Println("Recovered a string error:", msg)
} else {
fmt.Println("Recovered unknown error")
}
}
}()
}
defer func() { ... }()
이 부분은 익명 함수를 정의하고, 바로 실행한다.
defer 키워드
는 이 함수가 callFunction 함수
의 나머지 부분이 실행된 후, callFunction 함수
가 리턴하기 직전에 실행되도록 한다.