defer와 panic

최승훈·2020년 8월 15일
0

Golang기초

목록 보기
6/7
post-thumbnail

1. 마지막에 꼭 실행하는 defer

defer란?

defer는 함수 앞에 쓰이는 키워드!
특정 함수를 감싸고 있는 함수 내에서 가장 나중에, 끝나기 직전에 실행하게 하는 용법.
Java의 try~finally 구문과 유사한 기능!

try {
	변수의 메모리 할당 및 처리 구문 실행
} catch {
	예외가 발생했을때 처리!(논리적 오류)
} finally {
	마지막으로 실행 된 메소드 및 변수에 할당된 공간 반납.
}

Golang에서의 defer

  1. 블록이 필요하지 않음.
  2. 특정위치나 형식이 필요하지 않음.
  3. 단지 함수앞에 defer를 명시함으로써 사용가능.

defer를 사용하면 처리 흐름 중간에 에러(예외)가 발생해도 마지막에 꼭 defer를 실행하고, 프로그램을 종료하지 않음.

결론적으로, 예상치 못한 에러(panic)이 발생 했을 때 프로그램을 종료하지 않고 defer구문을 실행할때 유용하게 쓰임.

package main

import "fmt"

func main() {
    // main() 함수가 종료되기 직전에 실행됨. 
	defer fmt.Println("End")

	fmt.Println("Start")

	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}

}

defer의 선언위치
에러가 발생하는 코드 전에 선언해야됨.
만약에 에러가 발생하는 코드 이후에 선언하면 defer구문이 호출되지 않음.

package main

import "fmt"

func main() {
	var a , b int = 10 , 0
	defer fmt.Println("Done")

	result := a / b
	fmt.Println(result)
}

defer의 호출 순위

package main

import "fmt"

func printHello() {
	fmt.Println("Hello")
}

func printWorld() {
	fmt.Println("World")
}

func main() {
	defer printWorld()
	printHello()

	for i := 0; i < 3; i++ {
		defer fmt.Println(i)
	}

}

제일 나중에 지연 호출한 함수가 제일먼저 선언됨!(자료구조의 스택(LIFO)와 동일한 원리)

2. 종료하는 panic(), 복구하는 recover()

defer가 선언된 함수는 중간에 프로그램이 에러가 발생하더라고 꼭 마지막에 실행됨.(끝내기 전에 이거는 실행하고 끝내주세요.)

panic

겉으로 보이게 아무런 문제가 없는데 실행해보니 에러가 발생해서 프로그램을 종료시키는 기능.
=> 오류는 프로그램상 허용하지 않는 문법과 같은 비정상적인 상황에 발생.
예외는 프로그램이 실행되면서 논리상으로 부적합한 상황이 발생하는 것.

package main

import "fmt"

func main() {
 var num int = 10.55 // int에 float형을 대입하고있으므로 문법적 '오류'
  
  fmt.Println(num)
  
}
package main

import "fmt"

func main() {
 var num1, num2 int = 10, 0
  fmt.Println(num1/num2) // 나누기 0을하는 예외적상황..
}

만약 panic(예외)이 발생한 함수 안에 defer 구문이 있으면 프로그램을 종료하기 전에 defer 구문을 실행하고 종료.

package main

import "fmt"

func panicTest() {
	var a = [4]int{1,2,3,4}
	defer fmt.Println("Panic Occurred")
	for i := 0; i < 10; i++ {
		fmt.Println(a[i])
	}
}

func main() {
	panicTest()
  	// 실행되지 않음 (panicTest()에서 패닉이 발생해 프로그램이 종료되었기 때문)
	fmt.Println("Done!")
}

panic의 사용자 정의

Java에서 Exception을 상속받아 사용자 정의 예외를 만들 수 있듯이,
Golang의 panic도 panic()함수를 이용해 예외 상황일때(사용자 정의), 직접 panic에러를 발생시킬 수 있음.

panic() 함수 안에 에러 메시지를 사용자 설정으로 출력할 수 있음.

package main

import "fmt"

func main() {
    var opt int
    var num1, num2, result float32

    fmt.Print("1.덧셈 2.뺄셈 3.곱셈 4.나눗셈 선택:")
    fmt.Scan(&opt)
	if opt != 1 && opt != 2 && opt != 3 && opt != 4 {
		panic("1, 2, 3, 4중에 하나만 입력해야합니다!")
	}
    fmt.Print("두 개의 실수 입력:")
    fmt.Scan(&num1, &num2)

    if opt == 1 {
        result = num1 + num2
    } else if opt == 2 {
        result = num1 - num2
    } else if opt == 3 {
        result = num1 * num2
    } else if opt == 4 {
        result = num1 / num2
    }
	
    fmt.Printf("결과: %f\n", result)
}

recover

panic을 막는 recover()함수!

panic상황이 생겼을 때 프로그램을 종료하지 않고 예외를 처리하는 것.
pani 상황에서 프로그램이 종료되지 않고 어떤 구문을 실행시키려면 defer구문을 사용해야됨.

따라서, 예외처리를 하기위해서는 recover() 함수와 defer구문을 항상 같이 사용해야 됨.

package main

import "fmt"

func panicTest() {
	defer func() {
		r := recover() //복구 및 에러 메시지 초기화
		fmt.Println(r) //에러 메시지 출력
	}()

	var a = [4]int{1,2,3,4}

	for i := 0; i < 10; i++ { //panic 발생
		fmt.Println(a[i])
	}
}

func main() {
	panicTest()

	fmt.Println("Hello, world!") // panic이 발생했지만 계속 실행됨
}
package main

import "fmt"

func panicTest() {
	defer func() {
		r := recover() //복구 및 에러 메시지 초기화
		fmt.Println(r) //에러 메시지 출력
	}()

	var num1 , num2 int
	fmt.Scanln(&num1, &num2)
	result := num1 / num2
	fmt.Println(result)
}

func main() {
	for {
		panicTest()
		fmt.Println("Hello, world!") // panic이 발생했지만 계속 실행됨
	}
}

recover()함수의 결론.

  1. panic이 발생해 프로그램이 종료되는 것을 막고 복구함.
  2. 프로그램이 종료되기 전에 실행되어야 함으로 defer가 선언된 함수 안에서 쓰임.
  3. 에러 메세지를 반환함. 따라서 변수에 초기화해서 에러 메세지를 출력 할 수 있음. 변수에 초기화 하지 않으면 따로 에러 메세지를 출력하지 않음.
package main

import "fmt"

func main() {
    // defer함수의 선언
    // main() 함수가 끝나기 직전에 호출 
	defer func() {
      	// recover()함수의 반환값이 nil이 아닌지를 확인.
        // recover()함수의 반환값(메세지)가 있다는 것은 panic이 일어났다는 이야기.
      	// 패닉이 일어 났다면 r 이라는 변수에 에러 메세지를 초기화
		if r := recover(); r != nil{
          	// 에러 내용 출력
			fmt.Println(r)
          	// 메인함수를 다시 호출(재귀호출)
			main()
		}
	}()

	var num1, num2 int
	fmt.Scanln(&num1, &num2)

	result := num1 / num2

	fmt.Println(result)
}
profile
안녕하세요

0개의 댓글