이 글은 Effective Go를 일부 번역한 글입니다.


함수

Defer

Go의 defer는 defer를 실행한 함수 반환 직전에 (미뤄진) 함수 호출이 이뤄지도록 예약한다. 함수 반환 경로와 상관 없이 자원을 반드시 해제해야 하는 등 여러 상황을 처리하는 데 낯설지만 효과적인 방법이다. 뮤텍스를 풀거나 파일을 닫는 게 전형적인 예다.

// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed if we return here.
        }
    }
    return string(result), nil // f will be closed if we return here.
}

Close 같은 함수 호출을 미루는 건 두 가지 장점이 있다. 첫째, 파일 닫기를 절대 잊지 않으리라 보장한다. 나중에 함수를 수정하면서 새로운 반환 경로를 추가한다면 이런 실수를 하기 쉽다. 둘째, 닫기를 열기 근처에 두게 된다. 이러면 함수 끝에 두는 것보다 훨씬 명확해진다.

미뤄진 함수에 들어갈 인수는 (함수가 메서드라면 그 수신자도 포함한다) 함수를 호출할 때가 아니라 defer를 실행할 때 계산한다. 함수를 실행하면서 변수 값이 바뀔 우려를 피하는 동시에, 미룬 지점 하나에 여러 함수 실행을 연기할 수 있다. 여기 간단한 예가 있다.

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

미뤄진 함수는 후입선출(LIFO) 순서로 실행된다. 따라서 이 코드는 함수 반환 때 4 3 2 1 0을 출력할 것이다. 더 그럴듯한 예제는 프로그램에서 함수 실행을 추적하는 간단한 방법이다. 간단하게 추적 루틴 두 개를 만들어 보았다.

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

// Use them like this:
func a() {
    trace("a")
    defer untrace("a")
    // do something....
}

defer를 실행할 때 미뤄진 함수에 넣을 인수를 계산한다는 걸 활용해 개선해 보았다. 추적 해제 루틴 인수를 추적 루틴이 만들어 줄 수 있다.

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

이 예는 이렇게 출력한다.

entering: b
in b
entering: a
in a
leaving: a
leaving: b

다른 언어에서 구역 단위(Block-level) 자원 관리에 익숙해진 프로그래머들은 defer가 이상해 보일 수도 있다. 하지만 defer가 구역 기반이 아닌 함수 기반이란 사실을 알아야 defer를 가장 흥미롭고 강력하게 사용할 수 있다. panic과 recover를 다룰 때 defer의 가능성에 대한 또 다른 예를 보여주겠다.