Go 오류 처리: 귀찮은가, 멋진가?
Source: Dev.to
몇 주 동안 코딩을 완전히 처음 시작했을 때, 나는 “내가 뭘 하러 들어온 거지?” 라는 생각을 계속 했습니다. 아래와 같은 코드를 볼 때마다 무엇을 보고 있는지 겨우 겨우 이해했죠:
file, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
var result MyStruct
err = json.Unmarshal(data, &result)
if err != nil {
log.Fatal(err)
}
오류 검사를 위해 아홉 줄을 쓰는 것이 터무니없게 느껴졌습니다.
Go 오류 처리의 문제점
YouTube 초보 튜토리얼에서는 파이썬과 자바스크립트의 오류 검사를 다루었지만, Go에 대해서는 공감할 수 있는 내용이 없었습니다. 나는 코드를 완전히 이해하지 못한 채 스니펫을 복사해가며 단순히 이렇게 쓰곤 했습니다:
if err != nil {
log.Fatal(err)
}
대부분의 경우 오류를 읽지도 않았고, log.Fatal(err)가 절대 실행되지 않기를 바랐을 뿐이었습니다.
오류를 무시하면 버그가 생긴다
나중에 설정 파일을 읽고, API 호출을 하고, 결과를 디스크에 쓰는 CLI 도구를 만들면서 미묘한 버그를 도입했습니다:
data, _ := json.Marshal(payload)
빈 식별자(_)를 사용해 오류를 무시한 탓에 마샬된 본문이 비어 있어 API 호출이 조용히 실패했습니다. 한 줄의 오류 메시지만 있었더라면 디버깅에 들어간 시간을 크게 절약했을 것입니다.
오류는 값이며, 예외가 아니다
Go에서 오류는 결과와 함께 반환되는 일반적인 값입니다. 함수가 실패할 수 있을 때는 두 가지를 반환합니다: 결과와 error. if err != nil을 쓰는 것은 본질적으로 “여기서 뭔가 잘못될 수 있다. 어떻게 할까?” 라는 질문을 하는 것입니다. 이 사고방식을 받아들이면 오류는 명확한 의사결정 지점이 됩니다.
fmt.Errorf 로 컨텍스트 추가하기
처음엔 오류를 바로 로그에 남겼습니다:
log.Fatal(err) // 모호한 메시지
해결책은 fmt.Errorf 로 컨텍스트를 감싸는 것이었습니다:
data, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
%w 동사는 원본 오류를 보존하면서, 프로그램이 무엇을 하려고 했는지에 대한 유용한 설명을 제공합니다.
보일러플레이트용 에디터 스니펫
마찰을 줄이기 위해 VS Code에 스니펫을 설정했습니다. ife 를 입력하고 Tab 을 누르면 다음과 같이 확장됩니다:
if err != nil {
return err
}
공식 Go 확장에도 이 스니펫이 기본으로 포함되어 있으니, 스니펫 설정을 확인해 보세요. 하루에 수십 번씩 이 패턴을 타이핑하는 것이 이제는 고통이 아닙니다.
결론 – 귀찮은가, 멋진가?
둘 다입니다. 처음에는 장황함이 귀찮게 느껴지지만, 왜 존재하는지 이해하면 멋집니다. Go는 오류 처리를 눈에 보이게, 그리고 지역적으로 만들도록 의도했으며, 이는 조용히 실패해 프로그램이 예기치 않게 크래시 나는 것을 방지합니다. if err != nil 하나하나가 코드에 직접 박힌 의사결정 지점이므로, 언제 오류가 발생했는지 항상 알 수 있고 어떻게 처리할지 결정해야 합니다.
첫 번째 언어로서 Go는 놀라울 정도로 좋은 학습 환경을 제공했습니다. 문제를 무시할 수 없었습니다—Go가 허용하지 않았거든요. 그리고 문제가 생겼을 때, 오류는 거의 항상 Go가 알려준 바로 그 위치에 있었습니다.
지금 Go를 배우고 있다면, 가장 예상치 못했던 언어 특징은 무엇인가요? 댓글에 남겨 주세요.