Error handling in Go

Go ์–ธ์–ด ์—๋Ÿฌ ํ•ธ๋“ค๋ง

  • Go์—์„œ๋Š” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์œ„ํ•œ try/catch ๋ฉ”์†Œ๋“œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋Œ€ ์—๋Ÿฌ๋Š” ์ •์ƒ์ ์€ return value๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.

  • ์šฐ๋ฆฌ๋Š” ์—๋Ÿฌ๋ฅผ ํ”„๋กœ๊ทธ๋žจ์— ์น˜๋ช…์ ์ธ ๋ฌด์–ธ๊ฐ€๋กœ ๋ฐฐ์› ์ง€๋งŒ Go์—์„œ๋Š” ๋‹ค๋ฅธ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค.

  • Go์—์„œ์˜ ์—๋Ÿฌ๋Š” ํ•จ์ˆ˜๊ฐ€ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ผ ๋ฟ์ด๋‹ค.

val, err := myFunction( args... );

if err != nil {
    // handle error
} else {
    // success
}
  • error๋Š” Go์˜ built-in ํƒ€์ž…์ด๊ณ  zero value๋Š” nil์ด๋‹ค. ๊ด€์šฉ์ ์œผ๋กœ ์—๋Ÿฌ๋ฅผ ํ•ธ๋“ค๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—๋Ÿฌ๋ฅผ ํ•จ์ˆ˜์˜ ๋งˆ์ง€๋ง‰ return value๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๊ทธ๊ฒƒ์ด nil๊ฐ’์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋จผ์ € ์—๋Ÿฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€ ํ™•์ธํ•˜์ž.

type error interface {
    Error() string
}

์—๋Ÿฌ๋Š” ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์ด๊ณ  Error()๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์—๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์šฐ๋ฆฌ๋งŒ์˜ ์—๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž

package main

import "fmt"

// ๊ตฌ์กฐ์ฒด ๋งŒ๋“ค๊ธฐ 
type MyError struct{}

// Error๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์ฃผ
func (myErr *MyError) Error() string {
    return "Something unexpected happend!"
}

func main() {
    
    myErr := &MyError{}
    
    fmt.Println(myErr)
    //์ฐธ๊ณ ๋กœ Println()์€ ๊ฐ’์ด ์—๋Ÿฌ๋ผ๋ฉด ์ž๋™์œผ๋กœ Error()๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
}

์กฐ๊ธˆ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ๋ณด์ž

package main

import "fmt"
import "errors"

func main() {

    // create error
    myErr := errors.New("Sonething unexpected happend!")
    
    fmt.Println(myErr)
}

์œ„ ์˜ˆ์ œ์—์„œ New()๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ •ํ•ด์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—๋Ÿฌ error ๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿผ myErr๋Š” ๋ฌด์Šจ ํƒ€์ž…์ผ๊นŒ

fmt.Printf("Type of myErr is %T \n", myErr)
fmt.Printf("Value of myErr is %v \n", myErr)
-------------------------------------------------------
Type of myErr is *errors.errorString
Value of myErr is &errors.errorString{s:"Something unexpected happend!"}

์—ฌ๊ธฐ์„œ myErr๋Š” s๋ผ๋Š” ๋ฌธ์ž์—ด ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ๊ตฌ์กฐ์ฒด errors.errorString์˜ ํฌ์ธํ„ฐ์ด๋‹ค.

์šฐ๋ฆฌ๋Š” Newํ•จ์ˆ˜๊ฐ€ s๋ฅผ ํฌํ•จํ•œ errorString struct๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๊ฒƒ์œผ๋กœ ์ถ”์ธกํ•  ์ˆ˜ ์žˆ๋‹ค. error ํŒจํ‚ค์ง€๋ฅผ ํ™•์ธํ•ด๋ณด์ž

type errorString struct {
    s string
}

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

func New(text string) error {
    return &errorString{text}
}
Newํ•จ์ˆ˜๋Š” s๋ฌธ์ž์—ด์„ ํฌํ•จํ•˜๊ณ  Error๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ๊ตฌํ˜„ํ•ด๋ณผ ์ฐจ๋ก€๋‹ค

package main

import "fmt"
import "errors"

// divide two number
func Divide(a, int, b int) (int, error) {

    // can not divide by '0'
    if b == 0 {
        return 0, errors.New("Can not device by Zero!")
    } else {
        return (a / b), nil
    }
}

func main() {
    //divide 4 by 0
    if result, err := Divide(4, 0); err != nil {
        fmt.Println("Error occured: ", err)
    } else {
        fmt.Println("4/0 is", result)
    }
}
---------------------------------------------------------------------------------
Error occured: Can not devuce by Zero!
  • ์œ„ ์˜ˆ์ œ๋Š” 2๊ฐœ์˜ int๋ฅผ ๋ฐ›์•„์„œ ๋‚˜๋ˆ„๊ธฐ๋ฅผ ํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  0์œผ๋กœ ๋‚˜๋ˆ„๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  0์ด๋ผ๋ฉด non-nil error๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ปค์Šคํ…€ ์—๋Ÿฌ

์ง€๊ธˆ๊นŒ์ง€๋Š” ๋‹จ์ˆœํ•œ ํƒ€์ž…์˜ ์—๋Ÿฌ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ์ƒํ™ฉ์—์„œ๋Š” ์—๋Ÿฌ์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ •๋ณด๋“ค์ด ํ•„์š”๋‹ค.

HTTP์š”์ฒญ์„ ํ•˜๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด ์–ด๋–ค ๋ฉ”์†Œ๋“œ๋กœ ์š”์ฒญ์„ ํ–ˆ๊ณ , ์ƒํƒœ์ฝ”๋“œ๊ฐ€ ๊ถ๊ธˆํ•  ๊ฒƒ์ด๋‹ค. ๊ฐ€์ƒ์˜ 403์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž.

package main

import "fmt"

type HttpError struct {
    status int
    method string
}

func (httpError *HttpError) Error() string {
    return fmt.Sprintf("Something went wrong with the %v request. 
            Server returned %v status.", httpError.method, httpError.status)
}
// mock ํ•จ
func GetUserEmail(userid int) (string, error) {
    //return failed, return an error
    return "", &HttpError{403, "GET"}
}

func main() {
    //get user email address
    if email, err := GetUserEmail(1); err != nil {
        fmt.Println(err) //print error
        
        if errVal := err.(*HttpError); errVal.status == 403 {
            fmt.Println("Performing session cleaning...")
        }
    } else {
        // do something with the 'email'
        fmt.Println("User email is", email)
    }
}
---------------------------------------------------------------------------
Something went wrone with the GET request. Server returned 403 status.
Performing session cleaning...
  • ์šฐ์„  method, status ๋ฅผ ํฌํ•จํ•˜๋Š” HttpError ๊ตฌ์กฐ์ฒด๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

  • ์—ฌ๊ธฐ์„œ Error ๋ฉ”์†Œ๋“œ๋Š” ์ƒํƒœ์ฝ”๋“œ์™€ ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ™์ด ๋ฐ˜ํ™˜ํ•ด ์ค€๋‹ค.

์œ„ ์˜ˆ์ œ์—์„œ๋Š” ๋‹จ์ˆœํžˆ err.(*HttpError)๋ฅผ ์‚ฌ์šฉ HttpError ํ•œ ์ข…๋ฅ˜์˜ ์—๋Ÿฌ์— ๋Œ€ํ•ด์„œ๋งŒ ๋‹ค๋ฃจ์–ด๋ณด์•˜๋‹ค.

๋งŒ์•ฝ ์—ฌ๋Ÿฌ๊ธฐ์ง€์˜ ์—๋Ÿฌ ์ข…๋ฅ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋‹ค๋ฃจ์–ด์•ผ ํ• ๊นŒ. - type switch์— ๋Œ€ํ•ด ์•Œ์•„๋ณด

package main

import "fmt"

//network error
type NetworkError struct {}

func (e *NetworkError) Error() string{
    return "A network connection was aborted"
}

// file save fail error
type FileSaveFailedError struct {}

func (e *FileSaveFailedError) Error() string{
    return "The requested file could not be saved."
}

// a function that can return either of the above errors
func saveFileToRemote() error {
    result := 2 //mock result of save operation
    
    if result == 1 {
        return &NewNetworkError()
    } else if resutl == 2 {
        return &FileSaveFailedError{}
    } else {
        return nul
    }
}

func main() {
    // check type
    switch err := saveFileToRemote(); err.type {
        case nil:
            fmt.Println("File successfully saved.")
        case *NetworkError:
            fmt.Println("Network Error:", err)
        case *FileSavedFailedError:
            fmt.Println("File save Error:", err)
    }
} 
--------------------------------------------------------------------------
File save Error: The request file could not be saved.

์œ„ ์˜ˆ์ œ์—์„œ๋Š” NetworkError, FileSavedFailedError 2๊ฐ€์ง€ ํƒ€์ž…์˜ ์—๋Ÿฌ๊ฐ€ ์žˆ๋‹ค.

saveFileToRemote ํ•จ์ˆ˜๋Š” nil๋˜๋Š” 2๊ฐ€์ง€ ์—๋Ÿฌ์ค‘ ์–ด๋–ค๊ฒƒ์ด๋“  ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

mainํ•จ์ˆ˜์—์„œ๋Š” type switch๋ฅผ ํ†ตํ•ด ์šฐ๋ฆฌ๊ฐ€ ์–ด๋–ค ์ข…๋ฅ˜์˜ ์—๋Ÿฌ๊ฐ€ ์ผ์–ด๋‚ฌ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Saving original error as a context

๋‹จ์ˆœํžˆ ์›๋ž˜ ์žˆ๋Š” ์—๋Ÿฌ๋ฅผ ํฌํ•จํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ•ด๋‹น ์—๋Ÿฌ๋ฅผ ํฌํ•จํ•˜๋Š” ์ƒˆ๋กœ์šด ๊ตฌ์กฐ์ฒด๋ฅผ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.

package main

import "fmt"

//simple user unauthorized error
type UnauthorizedError struct {
    UserId int
    OriginalError error
}

// add some context to the original error message
func (httpErr *UnauthorizedError) Error() string {
    return fmt.Sprintf("User unauthorized Error: %v", httpErr.OriginalError)
}

// mock function call to validate user, returns error
func validateUser{ userId int } error {
    
    //mock general error from a function call: getSession(userId)
    err := fmt.Errorf("Session invalid for user id %d", userId)
    
    //return UnauthorizedError with original error
    return &UnauthorizedError{userId, err}
}

func main() {
    //validate user with id '1'
    err := validateUser(1)
    
     if err != nil {
         fmt.Println(err)
     } else {
         fmt.Println("User is allowed to perform this action!")
     }
 }
 --------------------------------------------------------------------
 User unauthorized Error: Session invalid for user id 1
  

์œ„ ์˜ˆ์ œ์—์„œ๋Š” OriginalError๋ฅผ ํฌํ•จํ•˜๋Š” UnauthorizedError struct๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

Error ๋ฉ”์†Œ๋“œ ์•ˆ์—์„œ๋Š” %v ๋ฅผ ํ†ตํ•ด httpErr.OriginalError์˜ Error๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

Last updated