Home 6. error-handling
Post
Cancel

6. error-handling

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
This post is licensed under CC BY 4.0 by the author.