Go에는 에러를 처리하기 위한 다양한 예약어들과 내장 함수들이 존재한다.

그 중 defer, panic, recovery에 대해서 공부해 봤다.

확실히 쓸만하고 자주 쓸 것 같은 예약어들이었다.




1. defer


defer는 특정 list에 LIFO 형식으로 명령어를 쌓고, defer를 실행한 함수가 끝날 경우 순서대로 이를 실행해주는 예약어이다.


함수를 실행했을 때, 의도치 않게 에러가 나거나 해서 다음 실행해야 하는 명령어가 실행되지 않을 때가 있다.

대표적인 에러의 예는 다음과 같은 코드에서 발생할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
 
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
 
    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}
cs


파일을 복사하여 다른 파일에 다시 붙여넣는 간단한 함수이다.

여기서 7번 줄의 os.Create에서 익셉션이 발생한다고 가정해 보자.

2번 줄에서 src에는 원본 파일이 열려서 핸들이 반환되어 있는 상태고, 익셉션의 발생으로 따로 핸들을 닫지 않고 함수가 종료된다.

이때 defer를 이용하여 명시해 주면 간단히 Close 처리를 할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
 
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()
 
    return io.Copy(dst, src)
}
cs


위와 같이 코드를 수정해 주면, os.Create에서 에러가 나더라도 함수가 종료되면서 src.Close가 수행되어 핸들은 정상적으로 닫힌다.


defer에는 몇 가지 알아둬야 할 특징들이 있다.

첫째로, defer 의 list에 추가되는 명령어와 그 인자들은 추가되는 시점의 값을 그대로 가지며, defer의 list는 스택과 같이 LIFO로 작동하기 때문에 마지막에 넣은 명령어에서 처음 넣은 명령어까지 역순으로 명령어가 실행된다는 것이다.

다음 코드를 보자.


1
2
3
4
5
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
cs


이 함수는 실행되면서 defer에 총 4개의 명령어를 쌓고, 이때 i는 각각 0, 1, 2, 3의 순서대로 쌓인다.

b 함수가 끝나면서 출력되는 결과는 그 역순으로 각각 3, 2, 1, 0이 출력된다.


두번째로, defer문은 함수에 정의된 named results를 사용하는 것이 가능하다.

예를 들어,


1
2
3
4
5
6
7
8
9
10
11
12
package main
 
import "fmt"
 
func c() (i int) {
    defer func() { i++ }()
    return 1
}
 
func main() {
    fmt.Printf("return : %d", c())
}
cs


이 코드에서 c 함수가 최종적으로 리턴하기 전에 defer이 실행되면서 return되는 i가 1 증가하게 되어 리턴 값은 2가 된다.

에러 처리 등에 쓸만 한 예약어인 듯 하다.




2. panic


panic은 golang의 내장 함수로, 함수 콜 스택을 거슬러 올라가면서 프로그램이 종료될 때까지 계속 진행중인 작업을 중단하는 함수이다.

콜 스택의 끝까지 도달할 경우 익셉션을 내면서 종료된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
package main
 
import "fmt"
 
func test_panic() {
    fmt.Println("Hello")
    panic("Unknown Error :D")
    fmt.Println("World!")
}
 
func main() {
    test_panic()
}
cs


위와 같은 예시 코드를 실행하면 다음과 같은 결과가 나온다.





3. recover


recover라는 말만 봐도 대충 예상이 되는데, 이 함수는 panic() 으로 인해서 함수가 종료되기 시작했을 경우 이를 중지하고 정상 작동하도록 만드는 함수이다.

함수 특성상 defer 내에서 사용하지 않으면 의미가 없다.


위에서 짰던 코드를 살짝 바꾸어서 이렇게 만들어 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
 
import "fmt"
 
func test_panic() {
    defer func() {
        r := recover()
        if r != nil {
            fmt.Println("Recovered in test_panic : ", r)
        }
    }()
 
    fmt.Println("Hello")
    panic("Unknown Error :D")
    fmt.Println("World!")
}
 
func main() {
    test_panic()
}
cs


이걸 실행시키면 panic에 의해 test_panic이 종료되고, 이후 defer이 실행되면서 내부의 recover이 한번 더 실행되어 panic은 멈추고 정상 종료되는것을 볼 수 있다.



블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요