Go에서 리소스의 보장된 정리 또는 종료를 위해 지연 호출(defer)을 익명 클로저(closures)와 함께 사용합니다. 이러한 패턴은 정리 로직을 그룹화하고 오류를 적절히 처리하여 읽기 쉽고 안정적인 코드를 보장합니다.
질문의 배경:
Defer는 다른 언어에서 가져온 것이며 Go 개발자의 삶을 크게 단순화합니다. defer와 클로저의 조합은 많은 출구(return, panic)가 있을 수 있는 블록에서 파일, 연결 및 기타 외부 리소스를 정리하는 표준이 되었습니다.
문제:
리소스 정리를 위한 복잡한 로직(예: 파일, 연결, 잠금)을 사용할 때, 오류가 발생하거나 함수에서 나간 경우에도 정리가 이루어짐을 보장해야 합니다. 부주의하게 사용하면 누수, 잘못된 정리 순서 또는 무의미한 오류가 발생할 수 있습니다.
해결책:
익명 함수(closure)와 함께 defer를 사용하여:
코드 예시:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // 파일로 작업하는 로직 fmt.Fprintln(f, "Hello world") return // 여기서 return해도 defer는 수행됩니다 } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("오류:", err) } }
주요 특징:
지연 클로저 안에서 사용되는 변수들은 defer의 선언 시점에 고정되나요, 아니면 실제 호출 시점에 고정되나요?
그들은 defer 선언 시점에 고정되지만 클로저가 변수를 참조하는 경우 defer 실행 시점의 값이 사용됩니다. 이로 인해 때때로 예상치 못한 결과가 발생할 수 있습니다.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // 2, 2, 2를 출력합니다 }
참조에 의한 캡처를 피하기 위해 값을 클로저로 매개변수로 전달할 수 있나요?
네, 익명 함수에 대한 매개변수를 선언하고 현재 값을 전달하면 값이 복사본으로 "동결"됩니다.
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // 2, 1, 0을 출력합니다 }
지연 클로저 안에서 패닉이 발견된 경우, 어떻게 처리하나요?
클로저 내부에서 recover()를 사용하여 패닉이 외부로 나오는 것을 방지하고 부드러운 복구를 구현합니다.
defer func() { if r := recover(); r != nil { log.Println("복구됨:", r) } }()
코드는 여러 파일을 열지만 defer f.Close()를 잊어버립니다. 오류가 발생하면 제어가 반환되고 일부 파일은 정리되지 않으며 리소스 누수가 발생합니다.
장점:
단점:
모든 제품 작업을 위해 지연 클로저를 사용하여 파일 스트림을 안전하게 닫고 파일이 완전히 기록되지 않더라도 오류를 처리합니다.
장점:
단점: