Go 언어 defer 정리: 자원 관리와 실행 순서 이해하기
에러 처리까지 익혔다면, 이제 Go 코드에서 거의 항상 함께 등장하는
defer 문을 정리할 차례다.
defer는 문법 자체는 단순하지만,
언제 실행되는지, 어떤 순서로 동작하는지를 정확히 이해하지 않으면
의도와 다른 코드가 만들어지기 쉽다.
이 글에서는
- defer의 기본 동작 방식
- 자원 관리에서의 사용 패턴
- 실제로 자주 실수하는 포인트
를 중심으로 정리한다.
defer란 무엇인가
defer는 함수 종료 시점에 실행될 코드를 등록하는 키워드다.
defer fmt.Println("end")
fmt.Println("start")
실행 결과는 다음과 같다.
start
end
- defer로 등록된 코드는 현재 함수가 return되기 직전에 실행된다
- 정상 종료든, 에러 반환이든 동일하게 실행된다
이 특성 때문에, defer는 자원 정리에 매우 잘 어울린다.
[이미지: Go defer 실행 시점 흐름]
자원 관리에서의 defer 패턴
가장 대표적인 사용 예는 파일이나 네트워크 자원 정리다.
file, err := os.Open("test.txt")
if err != nil {
return err
}
defer file.Close()
- 자원 획득 직후 defer로 정리 로직 등록
- 중간에 return이 여러 개 있어도 안전하다
실제로 써보면,
“나중에 닫아야지”라는 생각 자체를 코드에서 제거해준다.
defer는 즉시 평가되고, 나중에 실행된다
defer에서 자주 헷갈리는 부분이다.
x := 10
defer fmt.Println(x)
x = 20
출력 결과는 10이다.
- defer에 전달되는 인자는 defer 선언 시점에 평가
- 실행만 함수 종료 시점에 된다
이 특성을 이해하지 못하면,
로그나 디버깅 코드에서 의도와 다른 결과를 보게 된다.
defer의 실행 순서 (LIFO)
defer는 스택 구조로 실행된다.
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
실행 결과:
3
2
1
- 마지막에 등록한 defer가 가장 먼저 실행된다
- 흔히 LIFO(Last In, First Out)라고 설명한다
[이미지: Go defer 스택 실행 순서]
이 특성 덕분에,
복잡한 자원 정리도 자연스럽게 순서를 맞출 수 있다.
반복문 안에서 defer 사용 시 주의점
for _, f := range files {
defer f.Close()
}
이 코드는 문법적으로는 문제가 없지만,
모든 Close()가 함수 종료 시점에 한꺼번에 실행된다.
⚠️ 주의할 점
- 파일 개수가 많으면 자원 점유 시간이 길어진다
- 루프마다 즉시 닫아야 하는 경우에는 defer가 맞지 않을 수 있다
실무에서는 다음과 같이 분리하는 경우도 많다.
for _, name := range files {
func() {
f, _ := os.Open(name)
defer f.Close()
// 작업 처리
}()
}
defer와 panic / recover의 관계
panic이 발생해도 defer는 실행된다.
defer fmt.Println("cleanup")
panic("error occurred")
- panic 발생
- defer 실행
- 이후 프로그램 종료 (recover가 없다면)
이 특성 때문에,
defer는 최후의 안전장치 역할도 한다.
[이미지: panic 발생 시 defer 실행 흐름]
defer를 남용하면 안 되는 이유
defer는 편리하지만, 비용이 없는 문법은 아니다.
- 함수 호출 비용이 발생한다
- 매우 빈번한 호출 구간에서는 성능에 영향을 줄 수 있다
그래서 실무에서는 보통 이런 기준을 둔다.
- 파일, 락, 커넥션 정리 → defer 사용
- 아주 짧고 빈번한 반복 처리 → 직접 호출 고려
대부분의 일반적인 코드에서는
가독성과 안정성을 위해 defer를 사용하는 쪽이 낫다.
정리
defer는
자원 정리 코드를 실수 없이 작성하게 만드는 도구다.
- 함수 종료 시점에 실행된다
- 인자는 선언 시점에 평가된다
- 여러 개면 역순으로 실행된다
이 특성을 정확히 이해하면,
Go 코드에서 자원 관리와 예외 상황 처리가 훨씬 단순해진다.
이런 경우에 특히 유용하다
- 파일, 네트워크, 락 등 자원 관리
- 에러가 여러 경로로 반환되는 함수
- panic 상황에서도 정리가 필요한 코드
반대로,
아주 성능에 민감한 루프 내부에서는
사용 여부를 한 번 더 고민해보는 게 좋다.
다음 글에서는 defer와 함께 자주 쓰이는 주제인
goroutine과 동시성 기초로 넘어가면 흐름이 자연스럽다.