Golang에서 Defer를 올바르게 사용하는 방법

15365 단어 mailinglistgo


게시물 How to Properly Use Defer in GolangQvault에 처음 등장했습니다.

Go에서 "defer" 키워드는 무엇입니까?



Go 프로그래밍 언어에서 defer은 개발자가 현재 함수가 반환될 때까지 함수 실행을 지연시킬 수 있는 키워드입니다. 일부 사람들을 의아하게 만드는 것은 지연된 함수의 인수가 즉시 평가되지만 함수 자체는 래핑 함수가 종료될 때까지 실행되지 않는다는 것입니다.

간단한 지연 예 - Hello World




func main() {
    defer fmt.Println("world") // deferred until main() exits
    fmt.Println("hello")
 }

// prints:
// hello
// world


언제 연기하고 싶습니까?



Go로 프로그래밍한 후에는 다른 언어로 된 연결이나 파일을 닫는 것을 어떻게 처리했는지 기억하기가 정말 어렵습니다. defer 문은 함수에서 발생해야 하는 모든 종류의 "정리"를 처리하는 매우 깔끔한 방법입니다.

resp, err := http.Get(url)
if err != nil{
    log.Println(err)
}
defer resp.Body.Close()


Go의 표준 http library에서 문서는 클라이언트가 완료되면 HTTP 응답을 닫아야 한다고 지적합니다. The client must close the response body when finished with it .

위의 예에서 "응답을 마치면 응답을 닫겠습니다. 왜 defer해야 합니까?"라고 생각할 수 있습니다. 내 경험상 defer을 사용하는 주된 이유는 Go 개발자가 가드 절을 자유롭게 사용하기 때문입니다. 함수에 종료 지점이 많을 때(조기 return가 될 수 있는 위치) 모든 반환 앞에 응답 클로저를 접두사로 붙이고 싶지 않을 것입니다. 하나라도 놓치면? 예를 들어 보겠습니다.

func getUser() (User, error) {
    resp, err := http.Get("https://example.tld/users")
    if err != nil{
        return User{}, err
    }

    dat, err := io.ReadAll(resp.Body)
    if err != nil {
        resp.Body.Close()
        return err
    }

    user := User{}
    err = json.Unmarshal(dat, &user)
    resp.Body.Close()
    return user, err
}

resp.Body.Close()이 각각의 잠재적 종료 지점인 두 위치에서 어떻게 호출되어야 하는지 확인하십시오. defer 을 사용하면 간단하게 코드를 작성할 수 있습니다.

func getUser() (User, error) {
    resp, err := http.Get("https://example.tld/users")
    if err != nil{
        return User{}, err
    }
    defer resp.Body.Close()

    dat, err := io.ReadAll(resp.Body)
    if err != nil{
        return err
    }

    user := User{}
    err = json.Unmarshal(dat, &user)
    return user, err
}


연기, 패닉, 복구 – 그렇게 하면 안 되는 이유



이것에 너무 많은 시간을 할애하고 싶지는 않지만 일부 사람들은 Go의 기본 제공 recover() 기능을 우연히 발견하고 panic()recover()trycatch과 같이 다른 언어에서 사용하는 것이 좋은 생각이라고 생각했습니다.

Go의 복구() 기능은 무엇입니까?



간단히 말해서 복구는 패닉에 빠진 고루틴의 제어권을 되찾는 내장 함수입니다. 복구는 지연된 함수 내에서만 사용됩니다. 지연된 함수 내에서 recover()을 호출하면 패닉 시퀀스가 ​​중지되고 panic() 함수 호출에 전달된 오류 메시지가 검색됩니다.

func recoverWithMessage() {  
    if r := recover(); r!= nil {
        fmt.Println("recovered from", r)
    }
}

func fullName(firstName *string, lastName *string) string {  
    defer recoverWithMessage()
    if firstName == nil {
        panic("first name cannot be nil")
    }
    if lastName == nil {
        panic("last name cannot be nil")
    }
    return fmt.Sprintf("%s %s\n", *firstName, *lastName)
}

func main() {
    firstName := "Lane"
    lastName := "Wagner"
    fmt.Println(fullName(&firstName, &lastName))
    fmt.Println(fullName(nil, nil))
 }

// prints:
// Lane Wagner
// recovered from first name cannot be nil


위의 예는 error 값을 전달함으로써 더 잘 처리되었을 런타임 문제를 처리하는 복잡하고 관용적이지 않은 방법입니다. panic()recover()을 사용하는 것이 합리적일 수 있는 극단적인 경우가 분명히 있음을 이해합니다. 즉, 저는 약 5년 동안 전문적으로 Go를 작성해 왔으며 특히 애플리케이션 코드에서 충분한 필요성을 느낀 적이 없습니다. 좋은 디자이너가 의도한 대로 error 을 반환할 수 있도록 최선을 다해 프로젝트를 리팩터링하세요.

함수 인수는 언제 평가됩니까?



Go의 다른 고차 함수와 달리 함수를 defer 키워드로 "전달"하면 함수 이름뿐만 아니라 전체 함수 호출을 전달합니다. 이를 통해 함수의 인수를 즉시 평가할 수 있습니다. defer 키워드는 부모 함수가 반환될 때까지 함수 본문이 실행되지 않도록 합니다.

func main() {
    printMath(5, 6, multiply) // the "multiply" function is passed without arguments
}

// printMath does some math and prints the result
func printMath(x, y int, mathFunc func(int, int) int) {
    fmt.Println(mathFunc(x, y))
}

func multiply(x, y int) int {
    return x * y
}


반면에 defer 키워드는 인수를 취합니다.

defer fmt.Println(x + y)

x+y은 즉시 평가되지만 main()이 종료될 때까지 인쇄되지 않습니다.

여러 defer 문은 어떻게 됩니까?



지연된 함수 호출은 스택 데이터 구조로 푸시됩니다. 부모 함수가 반환되면 지연된 모든 호출이 생성된 순서와 반대로 실행됩니다.

defer fmt.Println("third")
defer fmt.Println("second")
defer fmt.Println("first")

// prints:
// first
// second
// third


읽어 주셔서 감사합니다. 이제 과정을 수강하십시오!

기술 분야의 고임금 직업에 관심이 있으십니까? 실습 코딩 과정을 마친 후 인터뷰를 시작하고 멋지게 통과합니다.

Start coding now

질문?



질문이나 의견이 있으면 트위터에서 나를 팔로우하고 연락하십시오. 기사에서 실수를 한 경우 반드시 let me know으로 알려주시면 바로잡을 수 있습니다!

Subscribe 내 뉴스레터에 더 많은 코딩 기사를 보내면 받은 편지함으로 바로 전달됩니다.

좋은 웹페이지 즐겨찾기