에러 처리까지 익혔다면, 이제 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과 동시성 기초로 넘어가면 흐름이 자연스럽다.
'backend' 카테고리의 다른 글
| Go 언어 goroutine과 동시성 기초: 병렬 처리의 기본 단위 이해하기 (0) | 2026.01.16 |
|---|---|
| Go 언어 에러 처리 방식 정리: error 인터페이스와 실전 패턴 (0) | 2026.01.15 |
| Go 언어 인터페이스(interface): 느슨한 결합과 다형성 이해하기 (0) | 2026.01.13 |
| Go 언어 구조체와 메서드: 데이터와 동작을 함께 다루는 방법 (0) | 2026.01.12 |
| Go 언어 제어문과 반복문 정리: if, switch, for 제대로 이해하기 (0) | 2026.01.11 |