[Python] 파이썬에서의 예외처리(Exception,assert,Raise)

·2023년 10월 16일

예외처리란?

프로그램을 작성하면서, 각양각색의 이유로 오류가 발생할 수 있다. 하지만 다들 알다시피, 파이썬 프로그램 실행 중 오류가 생기면 즉시 프로그램이 종료된다!

이는 실제 서비스하는 프로그램에서 큰 문제가 될 수 있다.
(만약에 은행 서버에서 sql 한 번 잘못 날렸다가 빈 값 받아오게 되면 은행 서버가 다 다운되는 대참사를 목격할 수 있게 된다)

즉, 오류가 발생하면 프로그램을 종료하지 않고 그 이후 어떤 행위를 취할 것인지를 설정해야하는데, 이를 예외처리라 한다.
또, 보안 상 예외 메세지를 사용자에게 노출하지 않기 위해서도 사용한다.
예외처리는 주로 try,catch,finally를 이용한다.
(finally는 오류가 나도, 정상적으로 실행해도 마지막으로 꼭 실행할 구문이다.)

오류의 3종류

컴파일 에러

컴파일 시 발생하는 에러임. 웬만하면 IDE에서 잡아주고, 고치지 않는 이상 실행이 안된다. 개발자가 인식하기 편하다.

런타임 에러

실행하고 나서 발생하는 에러임. 컴파일러는 실행 전에 이러한 오류를 알 수 없음. 자바에서는 이런 런타임 에러를 예외,에러 라는 두 종류로 구분함

  • 예외: 우리가 수습할 가능성이 있는 미약한 오류
  • 에러: 수습 불가능한 오류 (메모리 부족, 스택오버플로우 등)

  • 언체크 예외: 런타임 예외와 그의 자식들. 자동으로 예외를 던져준다
  • 체크 예외: 컴파일러가 체크하고, 예외를 던지던지 처리해야한다. 따라서 코드에 예외 처리 문구가 꼭 있어야하고, 이 때문에 예외가 누수되거나, 의존관계가 생겨버리는 문제가 생긴다. (SQLException의 경우, SQL을 사용하는 경우에만 쓸 수 있음. JDBC로 바꾸는 경우 따로 JDBCException으로 고쳐야 함)

try, except

try 내의 구문을 실행하다가 오류가 나면 except 구문으로 이동한다. 이 때, try내에서 발생한 오류에 대해 except구문으로 가져오고 싶다면, 앞에 except를 넣는다. (주로 except Exception as e: 로 붙여 쓴다.)

try:
	실행 시도 할 구문
except (에러 타입, 디폴트값은 Except):
	명시된 타입의 에러가 발생했을 때 실행한 구문
else:
	예외가 발생하지 않았을 때 처리하는 수행 코드
finally:
	예외 여부 발생 상관 없이 항상 실행하는 수행 코드

assert

assert는 주로 함수 내부의 값이 잘못되면 실행한다.

def add(x,y):
#x,y가 정수로만 받아야 한다.
	assert str(type(x))!="int" or str(type(y))!="int" "x or y is not integer"
	return x+y

언제 쓰이나?

  • 실사용에서는 발생하기 어렵거나 불가능하나, 앞으로도 발생해서는 안될 예외에 대해 사용.
  • 발생해서는 안되는 예외 상황을 개발자에게 명시
  • production 환경이 아닌 개발환경에서만 잡아내고 싶은 예외가 있을 때.
  • 예외 처리할 필요도 없이 발생하면 무조건 프로그램 동작을 멈춰야 하는 예외를 잡아낼 때

raise

assert가 조건을 만족했을 경우 오류를 발생시키는 것이라면,
raise는 아무런 조건 없이 오류를 발생하는 경우다.

raise 에러객체

try 내에서 raise를 만나면, 뒤에 명시한 에러가 일어난다.


try:
    x = int(input('7의 배수를 입력하세요'))
    if (x%7!=0):
        raise Exception ('7의 배수가 아닙니다.')
    print(x)
except Exception as e:
    print(e)

except 안 raise의 의미

참고로 raise가 except 구문 내에서 있다면, 현재 상위에서 발생했던 예외를 다시 발생시킨다.
-> 따라서 상위에서 따로 예외처리가 또다시 필요하다.

def multiple_seven():
    try:
        x = int(input('7의 배수 입력 : '))
        if (x % 7) != 0:
            raise Exception('7의 배수가 아님.')
        print(x)
    except Exception as e:
        print(f'in 예외 발생 : {e}')
        raise # 현재 상위에서 발생한 예외를 한번 더 일으킨다
        
try:
    multiple_seven()
except Exception as e:
    print(f'out 예외 발생 : {e}')
    # 이처럼 상위 단계에서 한 번 더 예외 처리가 필요하다!!!

언제 쓰이나?

  • 발생해서는 안되는 예외 상황을 명시
  • 해당 예외가 발생시킬 수 있는 side effect 예방

throw

예외가 생긴 그 부분에서 예외가 발생한 후 상황을 처리하는 것이 아니라,
부모 클래스에 해당 예외를 떠넘긴다.

else

만약에 try가 성공적으로 실행되었을 때, 실행 후 else 뒤에 오는 구문을 실행하도록 한다.

finally

try 안 구문이 오류가 나던, 나지 않던 마지막에 실행되어져야 하는 구문이다.

Spring에서의 예외처리

예외처리를하면서, SQL쿼리를 잘못 입력한 경우, SQLException이발생하도록 따로 에러 타입을 명시해줬다.
이는 체크 예외이기 때문에, 에러 처리할 수 있는 컴포넌트(예를 들어 서블릿 오류 페이지)가 받을 수 있을 때 까지 상위 컴포넌트에게 같은 타입의 예외를 계속 던져야한다.


하지만 언체크예외는 (RuntimeSQLException)모다? 필수로 안 던져도 된다!
처음에 에러가 발생한 컴포넌트애서 예외를 발생시키고, 예외 처리 컴포넌트에서만 잡아내면 된다.

아직 해결되지 않은 의존관계

하지만 이는 SQL을 이용할 때만의 이야기다. 만약에 Sql이 아닌 JDBC로 DB를 바꾸고자 하는 경우, 또 일일이 오류 타입을바꿔줘야한다. 이는 김영한 선생님께서 그토록 싫어하시는 의존관계가 형성된다는 이야기다.
우리는 이를 인터페이스로 해결할 것이다. 늘 그랬듯이,

  1. 데이터베이스를 인터페이스와 분리시켜 DB기술 자체의 의존 관계를 제거한다.
  2. 커스텀 예외를 만든다.
public class MyDbException  extends  RuntimeException{
    public MyDbException() {
    }

    public MyDbException(String message) {
        super(message);
    }

    public MyDbException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyDbException(Throwable cause) {
        super(cause);
    }
    
}

그 다음 RuntimeSQLException이 들어가는 자리에 그냥 MyDBException을 넣어주면 되는 것이다

결론

** 대부분의 경우 언체크 예외를 사용하고, 정말 어쩔 수 없이 '이 타입의 오류가 발생해!!!'라는걸 알려야 한다면, 체크 예외를 사용할 것

profile
풀스택 호소인

0개의 댓글