golang 스터디 6주차(함수 고급편 / 에러 핸들링)

2023-12-03
7min
Golang

Chapter. 21 함수 고급편

21.1 가변 인수 함수

가변인수 함수의 대표적인 함수로는 fmt 패캐지Println 함수가 있다. Println 의 함수 시그니쳐는 아래와 같다.

go
func fmt.Println(a ...any) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.
go
func fmt.Println(a ...any) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.

여기서 ...a 라는 인자가 가변인수라는걸 알려준다. 함수 내부에서 a 의 타입은 []any 가 된다. 참고로 가변 인수에는 값을 넣지 않아도 된다.

21.2 defer 지연 실행

함수가 종료되기 직전에 실행되어야 하는 코드가 있다. 보통 파일 / 소켓 / http 라이브러리의 context 등이 그렇다. 만약 종료하지 않는다면 내부 자원이 모자라게 되는 문제가 생긴다. C 언어 등에서는 여러 경우의 수를 따지고 해당 자원을 종료해줘야하는 번거로움이 있었는데 golang 에서는 defer 하나만을 이용해 종료해줄 수 있다. 왜냐면 defer 는 함수 종료되기 직전에 반드시 실행되기 때문이다.

http 서버 에서는 아래처럼 사용할 수 있다.

go
func (m UserModel) GetByEmail(email string) (*User, error) { query := ` SELECT id, created_at, name, email, password_hash, activated, version from users where email = $1 ` var user User ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() err := m.DB.QueryRowContext(ctx, query, email).Scan( &user.ID, &user.CreatedAt, &user.Name, &user.Email, &user.Password.hash, &user.Activated, &user.Version, ) if err != nil { switch { case errors.Is(err, sql.ErrNoRows): return nil, ErrRecordNotFound default: return nil, err } } return &user, nil }
go
func (m UserModel) GetByEmail(email string) (*User, error) { query := ` SELECT id, created_at, name, email, password_hash, activated, version from users where email = $1 ` var user User ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() err := m.DB.QueryRowContext(ctx, query, email).Scan( &user.ID, &user.CreatedAt, &user.Name, &user.Email, &user.Password.hash, &user.Activated, &user.Version, ) if err != nil { switch { case errors.Is(err, sql.ErrNoRows): return nil, ErrRecordNotFound default: return nil, err } } return &user, nil }

cancel 은 생ㅋ`성된 컨텍스트를 취소하고 자원을 정리하는데 사용된다.

21.3 함수 타입 변수

golang 에서는 함수 자체도 하나의 변수에 할당이 가능하다.

go
func Sum(a ...int) int { var res int for _, v := range a { res += v } return res } func main() { f := Sum fmt.Println(f(10, 20)) }
go
func Sum(a ...int) int { var res int for _, v := range a { res += v } return res } func main() { f := Sum fmt.Println(f(10, 20)) }

Sum 이라는 함수가 있다. main 내에서 이 함수를 f 변수에 할당하면, 원하는 순간에 f 변수를 이용해 함수를 호출할 수 있다.

f 라는 변수에 담을 수 있다는것에서 알수있다시피 일반 함수의 인자 그리고 return 에도 함수를 사용할 수 있다.

go
func Hello(f func(...int) int) func(...int) int { return f } func main() { f := Sum fmt.Println(f(10, 20)) Hello(f) }
go
func Hello(f func(...int) int) func(...int) int { return f } func main() { f := Sum fmt.Println(f(10, 20)) Hello(f) }

21.4 함수 리터럴

이름 없는 함수로 함수명을 적지 않고 함수 타입 변숫값으로 대입되는 함수를 의미. 함수명이 없기 때문에 직접 함수를 호출할 수 없고 함수 타입 변수로만 호출된다.

go
func f() { i := 0 ff := func() { i++ } i++ ff() } func main() f() }
go
func f() { i := 0 ff := func() { i++ } i++ ff() } func main() f() }

여기서 함수 f 를 보면 내부에 함수를 선언한다. 하지만 여기서 생성되는 함수는 이름없는 함수로, ff 에 바로 변수로 대입이 된다.

여기서 주의 할 점은 생성된 함수에 i 라는 변수가 선언되지 않았는데도 불구하고 i 라는 변수를 사용할 수 있다는 점이다. 가능한 이유는 본인의 컨텍스트에 i 라는 변수가 없으면 본인이 호출된 부모에서 가져오기 때문.

더 주의할점은 i 라는 변수를 이 아닌 참조 를 한다는것.

Chapter 23. 에러 핸들링

에러 핸들링은 프로그램의 에러를 처리하는 방법을 의미.

23.1 에러 반환

가장 기본적인 방식임. golang 에서는 보통 이 방식으로 에러를 핸들링한다. 보통 std 라이브러리 함수를 보면 error 타입이 리턴으로 존재하는걸 알 수 있다. 개발자는 해당 error 를 받아서 핸들링하면 됨.

23.2 에러 타입

error 타입은 구체화된 타입이 아닌 인터페이스다. Error 라는 string 을 return 하는 메서드만 구현하면 그 어떤 타입이라도 error 타입이 될 수 있다 ..!

go
type error interface { Error() string }
go
type error interface { Error() string }

23.2.1 에러 래핑

에러를 감싸서 새로운 에러를 만들어야 하는 경우가 있다. 기본 에러로는 모든 에러 정보를 보여주지 못할 때 사용할 수 있다.

23.3 패닉

패닉은 프로그램을 정상 진행시키기 어려운 상황을 만났을 때 사용한다. 프로그램의 흐름이 중지 된다. panic 를 사용하게 되면 문제 발생 시점에 프로그램을 바로 종료시키게 된다. panic 이 발생되는 예로는 슬라이스 길이를 넘어서는 인덱스에 접근하는 경우가 있다.

go
func panic(v any) The panic built-in function stops normal execution of the current goroutine. When a function F calls panic, normal execution of F stops immediately. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic, terminating G's execution and running any deferred functions. This continues until all functions in the executing goroutine have stopped, in reverse order. At that point, the program is terminated with a non-zero exit code. This termination sequence is called panicking and can be controlled by the built-in function recover. Starting in Go 1.21, calling panic with a nil interface value or an untyped nil causes a run-time error (a different panic). The GODEBUG setting panicnil=1 disables the run-time error.
go
func panic(v any) The panic built-in function stops normal execution of the current goroutine. When a function F calls panic, normal execution of F stops immediately. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic, terminating G's execution and running any deferred functions. This continues until all functions in the executing goroutine have stopped, in reverse order. At that point, the program is terminated with a non-zero exit code. This termination sequence is called panicking and can be controlled by the built-in function recover. Starting in Go 1.21, calling panic with a nil interface value or an untyped nil causes a run-time error (a different panic). The GODEBUG setting panicnil=1 disables the run-time error.

24.3.2 패닉 전파와 복구

패닉은 호출 순서를 거슬러 올라가면서 전파 된다. main -> f -> g -> h 순으로 함수가 호출 되었고, h 에서 패닉이 발생했다면 g -> h -> main 순으로 패닉이 전파된다. 만약 main 에서도 패닉이 복구가 되지 않는다면 프로그램은 종료된다. 패닉을 복구 하기 위해서는 recover 함수를 사용하면 된다.