에러 처리
에러 처리에 대해 알아보자.
에러 처리의 기본
실습 문제의 목표는 각 챕터에서 배운 용법을 어느 문법으로 직접 활용
해보는 것이었기 때문에 예외 처리하는 부분에서 허점
이 많이 있었습니다. 에러 처리를 하는 이유는 컴파일러가 알아차리지 못하는 프로그램상의 오류를 예방
하기 위해서입니다. 따라서 반환값이 있는 함수는 에러 처리(논리상 예외가 있을만한 부분을 에러 처리)
를 통해 결괏값과 에러 값을 함께 반환
해야합니다.
Go언어 패키지에서 제공하는 표준 함수는 어떻게 에러 처리를 하는지 알아보겠습니다. 우리가 항상 사용했던 코드가 에러 처리가 되어있는 코드입니다.
package main
import "fmt"
func main() {
var input string
_, err := fmt.Scanln(&input);
if err != nil {
panic(err)
}
fmt.Println(input);
}
우리는 지금까지 fmt.Scanln() 함수를 사용자로부터 값을 입력받는 용도로만 사용해왔습니다. 그래서 이 함수가 반환값이 없는 것으로 알고 있었습니다. 하지만 Scanln() 함수를 포함한 “fmt” 패키지의 표준 입/출력 함수는 첫 번째 입력 개수, 두 번째 반환값은 에러 값
과 같이 모두 반환 값을 가집니다.
아래 예시는 다양한 표준 함수들이 반환 값이 존재한다는 것정도로 확인하기 바랍니다.
"fmt" 패키지에서 제공하는 함수입니다.(전부는 아님)
func Scan(a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Print(a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
아래는 "os" 패키지에서 제공하는 파일 처리 함수입니다.(전부는 아님)
func Create(name string) (file *File, err error)
func Open(name string) (file *File, err error)
func (f *File) Close() error
모든 함수의 상세 정보를 알고싶다면 [Go언어 공식 사이트](https://go.dev) 에서 패키지를 확인하세요.
사용자에게 두 실수를 입력받고 나눗셈을 하는 프로그램을 만든다고 생각해봅시다. 이때, 나누는 수를 0으로 하면 무한대가 되기 때문에 에러 처리를 하도록 하고싶습니다. 그럼 두 가지를 설정
해야합니다.
1. 어떻게 에러 값을 설정할 것인가?
2. 어떻게 에러 상황을 출력하고 처리할 것인가?
에러 값 설정
에러 값을 설정하기에 앞서 error 타입이라는 것이 무엇인지 알아야합니다. error는 우리가 일반적으로 생각하는 자료형이 아닌 인터페이스형
입니다. error 인터페이스는 Error() 라는 string형을 반환값으로 갖는 메소드
를 한개만 가지고 있습니다.
type error interface {
Error() string
}
func (e *errorString) Error() string {
return e.s
}
type errorString struct {
s string
}
그렇다면 어떻게 구조체 errorString 포인터를 초기화할 수 있는지 알아보겠습니다. "errors" 패키지의 New() 함수를 이용
하면 됩니다. errors.New("에러값") 형태
로 입력해 함수를 호출하면 아래 형식과 같이 “에러값”이 errorString 구조체 형으로 변환되고 포인터를 반환합니다.
func New(text string) error {
return &errorString(text)
}
package main
import (
"fmt"
"errors"
)
func divide(a float32, b float32) (result float32, err error) {
if b == 0 {
return 0, errors.New("0으로 나누지마")
}
result = a / b
return
}
func main() {
var num1, num2 float32
fmt.Scanln(&num1, &num2)
result, err := divide(num1, num2)
if err != nil {
panic(err)
}
fmt.Println(result)
}
에러 출력 및 처리
그동안 Panic을 이용해서 panic(err) 형식
으로 에러 값을 출력하고 panic을 발생시켜 프로그램을 종료하는 방식으로 에러를 처리했습니다. 물론 이 방법도 틀린 방법은 아닙니다. 그렇지만 주로 에러를 처리하는 다른 방법이 있기 때문에 다른 형식을 확인해보겠습니다.
아래는 "log" 패키지에서 제공하는 에러 출력 함수
입니다. 각 함수는 에러 값을 출력하고 처리하는 방식이 다릅니다. 당연히 이 함수들을 사용하기 위해서 “log”를 import 해야합니다.
func Fatal(v ...interface{}) : 에러 로그 출력 및 프로그램 종료
func Panic(v ...interface{}) : 시간, 에러 메시지 출력 및 패닉 발생, defer 구문이 없을 시 런타임 패닉을 발생시키고 콜스택 출력
func Print(v ...interface{}) : 시간, 에러 메시지 출력 하지만 프로그램 종료하지 않음
package main
import (
"fmt"
"log"
)
func divide(a float32, b float32) (result float32, err error) {
if b == 0 {
return 0, fmt.Errorf("%.2f으로 나누지마", b)
}
result = a / b
return
}
func main() {
var num1, num2 float32
fmt.Scanln(&num1, &num2)
result, err := divide(num1, num2)
if err != nil {
log.Print(err)
}
fmt.Println(result)
}
fmt.Errorf("%.2f으로 나누지마", b) 형식
을 이용해 에러 값의 포멧을 지정해 저장하고, log.Print(err) 형식
을 이용해 시간, 에러 메시지를 출력 하지만 프로그램은 종료하지 않습니다. 그래서 fmt.Println(result)이 실행됩니다.
package main
import (
"fmt"
"log"
)
func errorChek(n int) (string, error) {
if n == 1 {
s := "Goorm"
return s, nil // 정상 동작이므로 에러 값은 nil
}
return "", fmt.Errorf("%d는 1이 아닙니다.", n) // 1이 아닐 때는 에러 리턴
}
func main() {
s, err := errorChek(1) // 매개변수에 1을 넣었으므로 정상 동작
if err != nil {
log.Fatal(err)
}
fmt.Println(s) // Hello 1
s, err = errorChek(2) // 매개변수에 2를 넣었으므로 에러 발생
if err != nil {
log.Print(err)
}
fmt.Println(s)
defer func() {
s, err = errorChek(4) // 매개변수에 4를 넣었으므로 에러 발생
if err != nil {
log.Fatal(err)
}
fmt.Println(s)
}()
s, err = errorChek(3) // 매개변수에 3을 넣었으므로 에러 발생
if err != nil {
log.Panic(err) // defer 함수로 이동
}
fmt.Println(s)
// 에러가 발생하여 프로그램이 종료되었으므로 이 아래부터는 실행되지 않음
fmt.Println(s)
fmt.Println("Hello, world!")
}
log.Print(err)
는 에러 메시지만 출력합니다. 따라서 중요하지 않은 문제에만 적용하는 것이 좋습니다. 그다음 log.Panic(err)
이 실행되고 panic이 발생하기 전에 defer 구문을 실행합니다. 마지막으로 defer 구문의 익명 함수 안에 있는 log.Fatal(err)이 실행
되고 프로그램이 완전히 종료됩니다.
그리고 log.Panic(err)과 log.Fatal(err)
의 차이로는 Panic은 런타임 에러를 발생
시키고 프로그램을 종료하고 Fatal은 프로그램을 정상적으로 완전히 종료
합니다. 그리고 log.Panic()과 Panic() 함수는 같은 역할을 합니다.
package main
import (
"fmt"
"log"
)
func errorChek(score int) (int, error) {
if score >= 0 {
return score, nil
}
return 0, fmt.Errorf("시험 점수를 양의 정수로 입력하세요.")
}
func main() {
var score int
fmt.Scanln(&score)
s, err := errorChek(score)
if err != nil {
log.Panic(err)
}
fmt.Printf("시험 점수는 %d점입니다.",s)
}