Go/Golang - Error-Handling
에러 핸들링
기본적인 에러 반환 처리 (go bufio)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func ReadFile(filename string) (string, error) {
file, err := os.Open(filename) // 파일 열고
if err != nil {
return "", err // 에러시 반환
}
defer file.Close() // 닫는거 잊지말고 실행
rd := bufio.NewReader(file) // bufio.Reader 객페로 file 읽기
line, _ := rd.ReadString('\n') // 한줄 읽기, \n로 끝나지 않는 경우에만 error 반환
return line, nil
}
func WriteFile(filename string, line string) error {
file, err := os.Create(filename) // file handle과 error를 반환
if err != nil {
return err
}
defer file.Close()
_, err = fmt.Fprintln(file, line) // 쓴 길이, 에러를 반환
return err
}
const filename string = "data1.tx"
func main() {
line, err := ReadFile(filename)
if err != nil {
err = WriteFile(filename, "file writing ongoing")
if err != nil{
fmt.Println("파일 생성에 실패했습니다.", err)
return
}
line, err = ReadFile(filename)
if err != nil {
fmt.Println("파일 읽기에 실패했습니다.", err)
return
}
}
fmt.Println("파일내용:", line)
}
사용자 정의 에러 반환 처리 (fmt.Errorf(), errors.New())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import (
"fmt"
"math"
"errors"
)
// 1. fmt.Errorf()를 사용한 사용자 정의 에러처리
func Sqrt1(f float64) (float64, error) {
if f < 0 {
return 0, fmt.Errorf(
"(사용자정의_Errorf())제곱근은 양수여야 합니다. f:%g", f)
}
return math.Sqrt(f), nil
}
// 2. errors.New()를 사용한 사용자 정의 에러처리
func Sqrt2(f float64) (float64, error) {
if f < 0 {
errTxt := "(사용자정의_New())제곱근은 양수여야 합니다. "
errTxt = errTxt + fmt.Sprintf("f:%g", f)
return 0, errors.New(errTxt)
}
return math.Sqrt(f), nil
}
func main() {
sqrt, err := Sqrt2(-2)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sqrt(-2) = %v\n", sqrt)
}
error 타입
error
타입은 실제로는 interface
이며 문자열을 반환하는 Error()
메소드로 이루어져 있다. 결국 어떤 타입이던 Error()
메소드를 포함하고 있다면 에러로 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 에러 구조체 선언
type PasswordError struct {
Len int
RequireLen int
}
// Error() 메서드 선언
// PasswordError 구조체 메서드로 Error()함수를 선언했으므로 해당 구조체는 error인터페이스로 쓰일수 있다.
func (err PasswordError) Error() string {
return "LenghError 암호 길이가 짧습니다."
}
func RegisterAccount(name, password string) error {
if len(password) < 8 {
return PasswordError{ len(password), 8 } // error 반환
}
return nil
}
func main() {
err := RegisterAccount("myID", "myPw")
if err != nil {
if errInfo, ok := err.(PasswordError); ok { // 인터페이스 변환
fmt.Printf("%v Len:%d RequireLen:%d\n", errInfo, errInfo.Len, errInfo.RequireLen)
}
} else {
fmt.Println("회원 가입됐습니다.")
}
}
error rapping
error를 감싸 새로운 error를 만들 수도 있다. 도출하는 error의 깊이를 다르게
하면 에러의 내용을 감싸고 그 밖에 에러의 위치정보
를 나타낼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import (
"fmt"
"errors"
"bufio"
"strings"
"strconv"
)
func MultipleFromString(str string) (int, error) {
// NewScanner는 io.Reader를 인수로 받으므로, string타입을 to.Reader로 만들기 위해
// strings.NewReader()를 사용한다.
scanner := bufio.NewScanner(strings.NewReader(str))
scanner.Split(bufio.ScanWords) // bufio.ScanLines를 쓰면 한 단어씩 끊어읽어온다.
pos := 0
a, n, err := readNextInt(scanner)
if err != nil {
return 0, fmt.Errorf("failed to readNextInt(), pos:%d err:%w", pos, err)
}
pos += n + 1
b, n, err := readNextInt(scanner)
if err != nil {
return 0, fmt.Errorf("failed to readNextInt(), pos:%d err:%w", pos, err)
}
return a * b, nil
}
func readNextInt(scanner *bufio.Scanner) (int, int, error) {
if !scanner.Scan() {
return 0, 0, fmt.Errorf("failed to scan")
}
word := scanner.Text()
number, err := strconv.Atoi(word) // 문자를 int로 변환
if err != nil {
return 0, 0, fmt.Errorf("Failed to convert word to int, word:%s err:%w", word, err)
}
return number, len(word), nil
}
func readEq(eq string) {
rst, err := MultipleFromString(eq)
if err == nil {
fmt.Println(rst)
} else {
fmt.Println(err)
var numError *strconv.NumError
// error 안에 감싸진 에러 중 두번째 인수의 타입인 *strconv.NumError로 변환될 수 있는 에러가 있다
if errors.As(err, &numError) {
fmt.Println("NumberError:", numError)
}
}
}
func main() {
readEq("123 3")
readEq("123 abc")
}
panic
패닉(Panic)
은 프로그램을 정상 진행시키기 어려운 상황을 만났을 경우, 프로그램의 흐름을 중지시키는 기능이다. panic()
내장 함수를 호출하고 인수로 에러 메세지를 입력하면 프로그램을 즉시 종료하고 에러 메세지를 출력하고 함수 호출순서를 나타내는 콜 스택(call stack)
을 표시한다. 콜 스택(call stack)
이란 panic이 발생한 마지막 함수 위치부터 역순(?)으로 호출 순서를 표시한다. 아래 예시에서 출력 내용을 보면 panic이 발생한 시점부터 되돌아나오면서 출력되는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func divide(a, b int) {
if b == 0 {
panic("b는 0일 수 없습니다") // b가 0인 경우 panic() 함수를 호출해 프로그램을 강제 종료한다.
}
fmt.Printf("%d / %d = %d\n", a, b, a/b )
}
func main() {
divide(9, 3)
divide(9, 0)
}
// 출력 내용
// 9 / 3 = 3
// panic: b는 0일 수 없습니다
// goroutine 1 [running]:
// main.divide(0x9?, 0x3?)
// /Users/lsy/Desktop/Go-Study/error/error5/error5.go:7 +0x105
// main.main()
// /Users/lsy/Desktop/Go-Study/error/error5/error5.go:14 +0x31
panic interface
panic()은 내장 함수의 인수로 interface{} 타입을 받았으므로 모든 타입을 사용할 수 있다.
1
func panic(interface{})
panic 전파, 복구(recover)
panic은 호출 순서를 역순으로 전파된다. main() -> a() -> b() -> c() 순으로 가는 과정에서 c()에 panic이 발생하면, 거꾸로 c() -> b() -> a() -> main() 순으로 전달되는데 마지막까지 panic이 복구되지 않으면 프로그램은 강제종료된다. 이 panic()
이 진행중인 단계 중 recover()
가 호출되는 경우, panic 객체를 반환하고 그렇지 않으면 nil
을 반환한다. panic()
이 복구되면 프로그램은 정상 종료된다.
recover() 결과 처리
recover()
모양은 다음과 같다. 따라서 recover()
로 변환한 타입을 실제 사용하려면 다음같은 타입검사를 해야한다.
1
func recover() interface{}
1
2
3
if r, ok := recover().(net.Error); ok {
fmt.Println("r is net.Error Type)
}
Questions
Q1. 사용자 예러처리 방법 2가지를 말하라.
1
2
1) fmt.Errorf()
2) errors.New()
위의 두 방법을 토대로 아래 코드를 채워넣어라.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import (
"fmt"
"math"
"{ blank }"
)
// 1. { blank }를 사용한 사용자 정의 에러처리
func Sqrt1(f float64) (float64, error) {
if f < 0 {
return 0, { blank }(
"(사용자정의_Errorf())제곱근은 양수여야 합니다. f:%g", f)
}
return math.Sqrt(f), nil
}
// 2. { blank }를 사용한 사용자 정의 에러처리
func Sqrt2(f float64) (float64, error) {
if f < 0 {
errTxt := "(사용자정의_New())제곱근은 양수여야 합니다. "
errTxt = errTxt + fmt.Sprintf("f:%g", f)
return 0, { blank }
}
return math.Sqrt(f), nil
}
func main() {
sqrt, err := Sqrt2(-2)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Sqrt(-2) = %v\n", sqrt)
}
1
2
3
1) "errors"
2) fmt.Errorf()
3) errors.New()
Q2. Panic(패닉)이 무엇인지 설명하라.
또 이를 복구시키는 함수는 무엇이며 반환값은 무엇인가?
1
2
1) 프로그램을 정상 진행시키기 어려운 경우, 프로그램 흐름을 중지시키는 기능
2) recover() , panic 객체 or nil