Meandering Trajectory

Go 코드에서의 에러 처리 본문

컴퓨터/GoLang

Go 코드에서의 에러 처리

latentis 2017. 11. 18. 22:23

에러타입(error)의 정체

Go는 예외처리를 함수의 반환값에 기반하여 처리한다. 하지만 C와 달리 에러의 타입을 표준화 했다. 에러타입은 error라는 이름을 가지고 있고 다음과 같이 정의되어 있다.

type error interface {
    Error() string
}

Error() 메서드 한개를 가진 인터페이스다. 이 인터페이스를 구현한 어떤 타입이든 에러가 될 수 있다. 그냥 정수형도 사용자 정의 타입으로 만들면 에러로 사용 가능하고 멤버가 없는 구조체도 Error() 메서드만 구현하면 에러로 사용할 수 있다.

// 정수를 사용자 정의 타입으로 정의
type intAsError int

// Error() 메서드 구현
func (i intAsError) Error() string {
    return fmt.Sprintf("Error Code(%d)", i)
}

// 빈 구조체
type EmptyError struct {
}

// Error() 메서드 구현
func (ee EmptyError) Error() string {
    return "속빈 에러"
}

errors.New

errors 패키지의 New 메서드는 파라미터로 문자열을 받아 대응되는 에러 오브젝트를 만들어 반환한다. 해당 에러 오브젝트에 대해 Error() 메서드를 호출하면 생성할 때 넘긴 문자열이 반환된다.

간단한 사용예는 다음과 같다.

package main

import (
    "errors"
    "fmt"
)

func div(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("0으로 나누면 되겠니?")
    }
    return a / b, nil
}

func main() {
    var (
        a = 10
        b = 0
    )

    v, e := div(a, b)
    if e != nil {
        fmt.Println(e.Error())
    } else {
        fmt.Printf("%v / %v = %v\n", a, b, v)
    }
}

위 코드의 div는 첫번째 파라미터를 두번째 파라미터로 나눈 결과를 반환하는 함수인데 중간에 보면 0으로 나누려고 하는 경우에 에러를 반환하는 코드가 있다. 이 프로그램을 실행하면(go run main.go 실행)

0으로 나누면 되겠니?

b가 0이므로 이렇게 에러메시지가 출력된다. 이렇게 에러 오브젝트를 바로 만들어 반환하고 싶을 때 errors.New() 메서드를 쓸 수 있다.

errors.New의 실제 구현은 Go 언어를 약간만 알고 있는 사람이면 누구나 짤 수 있는 수준의 코드다. 아래 코드는 실제 Go 표준 라이브러리에 있는 것을 그대로 옮겨온 것이다.

func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

errors.New 메서드는 string 타입 멤버를 하나 가진 errorString 타입 오브젝트를 반환하고, Error() 메서드는 이 구조체의 string 타입 멤버인 s를 반환하게 되어 있다.

만약 다양한 에러가 있고 그 에러에 따라 다른 형태의 예외처리가 필요한 상황이라면 단순히 errors.New()로 에러를 반환하는 것으로는 충분치 않다. 그때그때 오브젝트가 생성되므로 같은 내용의 에러라도 에러 오브젝트의 포인터는 다른 값을 갖게 되어 if 문이나 switch 문으로 처리하기 적절치 못하기 때문이다. 그럴 경우 다음과 같은 방법을 사용하면 된다.

var (
    ErrA = errors.New("Error A")
    ErrB = errors.New("Error B")
    ...
    ErrZ = errors.New("Error B")
)

이렇게 선언하면 다음과 같이 조건문에서 비교가 되므로 에러 종류에 따른 처리가 수월해진다.

err := fn(x)
if err == ErrA {
    // ErrA 처리
} else if err == ErrB {
    // ErrB 처리
}

복잡한 에러의 경우

자신이 만든 메서드나 함수에서만 반환하는 특별한 에러가 있고 해당 에러 오브젝트에 더 복잡한 정보를 넣고 싶다면 어떻게 해야 할까? 그럴 경우는 앞서 소개한 방법으로는 부족하다. 원하는 기능을 가진 구조체를 따로 만들어야 한다.

특별한 형식으로 된 텍스트 파일을 파싱하는 어떤 함수가 있다고 하자. 해당 함수가 파싱을 하다 에러가 발생한 경우 반환하는 에러에 처리하던 파일명과 라인 및 컬럼 번호 그리고 에러 내용을 포함한 에러 오브젝트를 반환하고 싶다면 다음과 같은 구조체를 에러로 사용할 수 있을 것이다.

type ParseError struct {
    file    string
    line    int64
    col     int64
    message string
}

func (e *ParseError) Error() string {
    return fmt.Sprintf("%s: %d, %d --> %s", e.file, e.line, e.col, e.message)
}

func Parse(file string) error {
    // 원래 파싱 하는 코드가 여기에 마악 있다고 가정한다.
    ... 중략 ..

    // 파싱 중에 에러가 발생에서 에러 반환
    return &ParseError{
        file:    file,
        line:    100,
        col:     4,
        message: "문법이 이게 말이 된다고 생각하냐?",
    }
}

위 Parse() 함수를 쓰다가 에러가 발생한 경우 다음과 같이 type assertion을 사용해 파싱 에러인지 여부를 검사할 수 있다.

    err := Parse("abc.txt")
    // type assertion을 사용해 파싱 에러인지 검사한 뒤 return
    if err, ok := err.(*ParseError); ok {
        fmt.Println(err.Error())
        return
    }


'컴퓨터 > GoLang' 카테고리의 다른 글

인터페이스 타입과 nil  (0) 2018.02.10
Go가 내 에러를 먹었어염! ㅠ.ㅠ  (0) 2017.12.02
new와 make  (0) 2017.11.12
select 문: 여러 채널에서 데이터 읽어 처리하기  (0) 2017.11.10
Go 언어: 빈 슬라이스와 nil  (0) 2017.10.20
Comments