
Go에서 자원을 해제할 때 사용하는 "defer Close()" 는 매우 편리하지만, 반환되는 에러를 무시하는 경우가 많이 있습니다.
그런데 특정 상황에서는 Close() 에러를 무시하는 것이 큰 문제로 발전할 수도 있습니다.
그 이유와 해결 방법을 함께 알아봅시다.
1. Close()
"Close()"는 단순한 자원 반납이 아니라, 메모리에 머물던 데이터를 확정하는 마지막 단계입니다.
예시를 살펴보면 이해가 더 잘 될 것 입니다.
- gzip.Writer : Close() 호출 시 압축 스트림의 마지막 트레일러와 CRC 체크섬 등 마무리 바이트를 기록합니다.
만약 이때 실패하면 파일은 불완전한 상태가 됩니다. - bufio.Writer : 버퍼에 남은 데이터들을 실제 파일이나 네트워크로 밀어내는 Flush 작업이 Close() 시점에 진행됩니다.
이는 Close() 의 실패가 전체 실패로 이어질 수 있다는 것을 알 수 있습니다.
2. 왜 그러면 지금까지 Close 에러를 무시했나
Go의 에러 핸들링 패턴에서는 두 가지 에러가 동시에 발생 했을 때 하나를 포기해야하는 구조적인 한계가 있었습니다.
두 경우은 다음과 같습니다.
- 로직에서 에러가 났는데, defer 내에서 Close에서도 에러가 발생한다면,
마지막 실행된 Close 에러가 원본 에러를 덮어버려 진짜 에러의 원인을 찾지 못함 - 로직에서 발생한 에러를 유지하기 위해서 defer Close()처럼 반환 값을 무시
위 두 경우처럼 덮어쓰기가 되거나, 아니면 무시하는 방식으로 에러를 핸들링 했습니다.
즉 Origin Error 를 볼 것인가, 아니면 Close Error 를 볼 것인가에서 보통은 Close Error를 포기했습니다.
3. errors.join
"Go 1.20+" 부터 도입된 "errors.join"은 이러한 문제들을 해결하게 됩니다.
- 예제
package main
import (
"compress/gzip"
"errors"
"fmt"
"os"
)
func writeGzipFile(path string, data []byte) (err error) {
f, err := os.Create(path)
if err != nil {
return err
}
gz := gzip.NewWriter(f)
defer func() {
err = errors.Join(err, gz.Close(), f.Close())
}()
if _, err := gz.Write(data); err != nil {
return fmt.Errorf("gzip write: %w", err)
}
return nil
}
func main() {
fmt.Println(writeGzipFile("out.txt.gz", []byte("hello\n")))
}
위 예시를 살펴 보고 데이터의 무결성이 중요한 로직의 경우는 errors.join을 활용해서 에러를 핸들링 하면 되겠습니다.
4. 전체 코드 보기
https://github.com/reochoi109/go-handbook/blob/main/error/advanced/multierror/main.go
go-handbook/error/advanced/multierror/main.go at main · reochoi109/go-handbook
A personal handbook of Go patterns and best practices. Lightweight, practical code snippets for real-world backend development. - reochoi109/go-handbook
github.com
'프로그래밍 > golang' 카테고리의 다른 글
| [Golang] 채널(Channel)이란? (0) | 2026.03.18 |
|---|---|
| [Golang] errgroup 이란? (0) | 2026.03.18 |
| [Golang] 에러 분류를 통한 Retry 구현 (0) | 2026.03.18 |
| [Golang] errors.Is와 As를 활용한 에러 핸들링 (0) | 2026.03.18 |
| [Golang] Slog를 활용한 로그 라우팅 설계 (0) | 2026.03.18 |