Go - Error Handling: A Beginner's Guide

안녕하세요, 미래의 Go 프로그래머 여러분! 오늘 우리는 Go에서의 에러 처리 세계로 뛰어들어 보겠습니다. 프로그래밍 초보자라고 걱정하지 마세요 - 저는 수년간 수많은 학생들을 가르쳐왔으니 단계별로 안내해드리겠습니다. 이 흥미로운 여정을 함께 시작해 보세요!

Go - Error Handling

Go에서의 에러 이해

에러 처리에 뛰어들기 전에, 먼저 프로그래밍의 맥락에서 에러가 무엇인지 이해해 보겠습니다. 케이크를 만드는 것을 상상해 보세요(케이크 빵 비유를 좋아합니다!). 가끔 계획대로 되지 않을 때가 있습니다 - 설탕이 떨어질 수도 있고, 오븐이 제대로 예열되지 않을 수도 있습니다. 프로그래밍에서도 비슷한 예상치 못한 상황이 발생할 수 있으며, 우리는 이를 "에러"라고 부릅니다.

Go에서는 에러가 값을 가집니다. 이 간단한 개념은 Go가 에러를 처리하는 방식의 기본이 되며, 다른 많은 프로그래밍 언어와 다릅니다. 기본적인 예제를 보겠습니다:

package main

import (
"fmt"
"errors"
)

func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("에러:", err)
} else {
fmt.Println("결과:", result)
}
}

func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("0으로 나눌 수 없습니다")
}
return a / b, nil
}

이 예제에서 우리는 10을 0으로 나누려고 시도하고 있습니다. 이는 수학적으로 불가능합니다. 이를 분해해 보겠습니다:

  1. 우리는 결과와 에러를 반환하는 divide 함수를 정의합니다.
  2. 피제수(ب)가 0이라면 errors.New()를 사용하여 에러를 반환합니다.
  3. main 함수에서 에러가 nil이 아님을 확인합니다.
  4. 에러가 있다면 인쇄하고, 그렇지 않으면 결과를 인쇄합니다.

이 프로그램을 실행하면 다음과 같이 보입니다: "에러: 0으로 나눌 수 없습니다"

에러 인터페이스

Go에서 error 형은 실제로는 인터페이스입니다. 인터페이스에 익숙하지 않으신 경우 걱정 마세요 - contract를 생각하면 됩니다. error 인터페이스는 다음과 같습니다:

type error interface {
Error() string
}

Error() 메서드를 반환하는 모든 형은 이 인터페이스를 구현합니다. 이는 당신이 자신의 에러 형을 만들 수 있음을 의미합니다! 예제를 보겠습니다:

package main

import "fmt"

type MyError struct {
message string
}

func (e *MyError) Error() string {
return e.message
}

func sayHello(name string) error {
if name == "" {
return &MyError{"이름이 비어 있습니다"}
}
fmt.Println("안녕하세요,", name)
return nil
}

func main() {
err := sayHello("")
if err != nil {
fmt.Println("에러:", err)
}

err = sayHello("알리스")
if err != nil {
fmt.Println("에러:", err)
}
}

이 예제에서 우리는 커스텀 MyError 형을 만듭니다. sayHello 함수는 이름이 비어 있을 때 이 에러를 반환합니다. 이 프로그램을 실행하면 다음과 같이 보입니다:

에러: 이름이 비어 있습니다
안녕하세요, 알리스

다중 에러 처리

종종 여러 가지 잠재적인 에러를 처리해야 합니다. Go의 다중 값 반환은 이를 간단하게 만듭니다:

package main

import (
"fmt"
"os"
)

func main() {
file, err := os.Open("없는 파일.txt")
if err != nil {
fmt.Println("파일 열기 에러:", err)
return
}
defer file.Close()

// 파일에서 읽기...
}

이 예제에서 우리는 존재하지 않는 파일을 열려고 시도합니다. os.Open 함수는 파일 핸들과 에러를 반환합니다. 에러가 nil이 아니라면, 우리는 그것을 인쇄하고 함수를 종료합니다.

defer 키워드

이전 예제에서 defer file.Close() 줄을 보셨나요? defer 키워드는 나중에 프로그램의 실행에서 함수 호출을 수행하는 Go의 방법입니다. 정리 목적으로 자주 사용됩니다. 미래의 자신에게 "이 작업을 잊지 마!"라고 말하는 것과 같습니다.

에러 래핑

때로는 원래 에러 정보를 잃지 않고 에러에 추가적인 맥락을 추가하고 싶습니다. Go 1.13에서는 에러 래핑을 도입했습니다:

package main

import (
"fmt"
"os"
)

func readFile(filename string) error {
_, err := os.Open(filename)
if err != nil {
return fmt.Errorf("'%s' 파일 열기 실패: %w", filename, err)
}
// 파일 내용 읽기...
return nil
}

func main() {
err := readFile("없는 파일.txt")
if err != nil {
fmt.Println(err)
if os.IsNotExist(err) {
fmt.Println("파일이 존재하지 않습니다")
}
}
}

이 예제에서 우리는 원래 에러에 추가적인 맥락을 추가하기 위해 fmt.Errorf%w 문법을 사용합니다. 이는 추가 정보를 포함하면서도 원래 에러 유형을 확인할 수 있는 능력을 유지합니다.

일반 에러 처리 방법

다음 표는 Go에서 일반적으로 사용되는 에러 처리 방법을 요약합니다:

방법 설명 예제
간단한 if 검사 에러가 nil이 아님을 확인 if err != nil { ... }
형 추론 특정 에러 형을 확인 if e, ok := err.(*os.PathError); ok { ... }
에러 래핑 에러에 추가적인 맥락을 추가 fmt.Errorf(" 처리 실패: %w", err)
커스텀 에러 형 자신의 에러 형을 만들기 type MyError struct { ... }
panic과 recover 불가回复한 에러에 사용 panic("아주 나쁜 일이 발생했습니다")

Go에서는 에러를 명시적으로 처리하는 것이 관례입니다. 무시하지 마세요 - 미래의 자신(그리고 팀원들)이 감사할 것입니다!

결론

Go에서의 에러 처리는 처음에는 길게 보일 수 있지만, 잠재적인 에러를 사전에 고려하고 처리하도록 장려합니다. 이는 더욱 견고하고 신뢰할 수 있는 코드를 만들어줍니다. Go 여정을 계속하면서 명확한 에러 처리가 디버깅과 코드 유지보수를 훨씬 쉽게 만들어준다는 것을 깨달을 것입니다.

계속 연습하고, 에러를 두려워하지 마세요 - 그들은 당신이 더 나은 코드를 작성하는 데 도움을 주는 가면을 쓴 친구들입니다! 행복하게 코딩하세요, 그리고 기억하세요: 프로그래밍에서도, 인생에서도, 실수를 하되 그것을 우아하게 처리하세요!

Credits: Image by storyset