본 글의 저작권과 원문은 https://blog.naver.com/sweetie_rex 에 있습니다.
현재의 글은 업데이트가 되지 않음을 유의해서 읽어주세요.
최신 업데이트 된 글을 읽으시려면 아래의 링크를 확인해주세요.

예외처리 (Exception Handling)

인생에는 항상 생각지도 못한 예외 케이스가 발생하지. 프로그래밍도 우리의 삶처럼 항상 생각한대로만 작동하진 않아. 그게 어려운거지.
유능한 프로그래머와 보통의 프로그래머의 차이는 대부분 '예외처리 능력이 얼마나 뛰어난가?' 에 달렸다고 해도 과언이 아니야. 예외처리 능력은 곧 문제해결 능력 이고, 유능한 프로그래머란 문제해결 능력이 뛰어난 프로그래머 라고 할 수 있어.
우리는 예상치 못한 경우가 발생할 때를 대비해서 항상 Plan B 를 준비하지? 프로그래밍에서도 그런 Plan B 를 설정할 수 있어.
프로그램의 완성도는 결국 이러한 Plan B 가 얼마나 사전에 대비가 잘 되어있느냐에 달려있다고도 볼 수 있어.
예를들면 아래와 같아.

...
일반적인 로직들
...

try:
    ...실패할 가능성이 있는 로직들 (Plan A)...
except Exception as e:  # 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
    print(e)
    print("앗, 에러가 발생했어! Plan B 를 실행하자!")
    ...Plan B 로직들...
finally:
    print("에러가 발생하든 발생하지 않든, 최종 마무리!")

...
일반적인 로직들
...

위에서 실패할 가능성이 있는 로직이란, 프로그래밍 문법적으로는 오류가 없지만 정상적으로 실행할 수는 없는 코드를 말해.
예를들면 몇가지가 있는데

  • 숫자를 0으로 나누는 경우
  • 데이터의 형식이 잘못된 경우
  • A 라는 자료형(숫자 등)의 데이터를 기대했지만 B 라는 자료형(문자열 등) 데이터가 들어온 경우
  • 존재하지 않는(또는 삭제된) 데이터에 접근한 경우
  • 권한이 없는 유저가 중요한 데이터에 접근한 경우
  • 아직 구현되지 않은 기능에 접근한 경우
  • 데이터가 충돌된 경우
  • 데이터를 추가하려는데 서버의 용량이 부족한 경우

등등 이 외에도 셀 수 없이 많아.
예제로 Human class 를 새로 만들고, 선언되지 않은 get_relative_age 메소드를 만든 뒤, 다양한 자료형을 넣고 실행 결과를 보자.

from datetime import datetime

class Human(object):
    name = None
    birthyear = None

    def __init__(self, name, birthyear):
        self.name = name
        self.birthyear = birthyear

    def hello(self):
        print(f"저는 {self.name}에요. 나이는 {datetime.now().year - self.birthyear + 1}살이에요. 반가워요.")

    def get_relative_age(self, year):
        print(f"저는 {year}년 기준으로 {year - self.birthyear + 1} 살이에요.")

human = Human("영희", 2002)
human.hello()  # 여기까지는 아무 문제 없이 정상작동

human.get_relative_age(2015)  # 정상 실행하는 코드
human.get_relative_age("2015")  # 오류가 발생하는 코드

print("이 코드가 실행될까?")  # 위에서 에러가 발생하여 실행할 수 없음!

위 코드에서 Human class 의 get_relative_age 메소드는 숫자형 데이터만 파라미터로 받는 것으로 구현되어 있어.
그러나 get_relative_age 메소드에 문자형 데이터를 인자로 넣고 메소드를 실행하는 순간, TypeError: unsupported operand type(s) for -: 'str' and 'int' 라는 에러가 발생하고 프로그램이 즉시 종료될거야.
그리고 print("이 코드가 실행될까?") 라는 코드는 결코 실행되지 않아.

만약 인터넷 또는 게임의 서버에서 별도의 예외처리 없이 이런 일이 발생한다면 흔히 말하는 "서버가 다운됐다", "서버가 죽어버렸다", "프로그램이 뻗어버렸다" 라는 결과가 발생하는거지. 이는 이 프로그램을 사용하는 유저 입장에서 매우매우 안좋은 경험을 심어주기 때문에 가능하면 이런 일이 발생해서는 안되겠지?

그래서 프로그래밍에서는 Plan B 를 실행할 수 있는 방법을 제공해주고 있어.
위의 코드에 예외처리 코드를 추가하여 적용해보자.

Python 의 try / except / finally 구문

...

human.get_relative_age(2015)  # 정상 실행하는 코드
try:
    human.get_relative_age("2015")  # 오류가 발생하는 코드
except Exception as e:  # 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
    print(e)  # 오류내용: unsupported operand type(s) for -: 'str' and 'int'
    print("앗, 에러가 발생했어! Plan B 를 실행하자!")
finally:
    print("여기는 오류가 발생하든 발생하지 않든 항상 실행되는 코드영역")

print("이 코드가 실행될까?")  # 위에서 예외처리를 했기 때문에 실행 됨!

위 코드를 실행시켜보면 try/except 예외처리 구문으로 프로그래밍상 오류가 처리(Exception) 된 것을 볼 수 있어.
그리고 print("이 코드가 실행될까?") 코드까지 정상적으로 실행되지!

다시 try/except 구문을 제거하고 실행해보면 확실히 차이를 느끼게 될 거야.

위의 finally 구문은 예외가 발생하든, 발생하지 않든 항상 실행되는 코드 영역이야. 어떤 경우에도 최종적으로 무조건 실행해야하는 코드를 이 곳에 넣으면 돼. 필요 없다면 finally 구문은 아예 사용하지 않아도 돼.

의도적으로 예외를 발생시키기

개발자가 직접 예외를 발생시킬 수도 있어. 예를 들면, 아직 구현하지 않은 기능에 접근했을 경우, "이 기능은 지금 못 써, 곧 구현 예정이야." 라는 의미로 의도적으로 오류를 발생시키기도 해.

Python 에서는 raise 라는 키워드로 의도적으로 예외를 발생시킬 수 있어.


class Human(object):
    ...
    def get_my_age(self):
        raise NotImplementedError("아직 구현되지 않은 기능")

...

human.get_my_age()  # raise 키워드로 의도적으로 예외가 발생함!

위 코드는 get_my_age 함수를 아직 구현하지 않았다는 의미로 NotImplementedError 라는 에러를 의도적으로 발생시킨 코드야.

예외처리(Exception Handling)에 대하여...

우리는 위 코드처럼 try/except 구문으로 프로그램내의 거의 대부분의 예외를 처리(handling)할 수 있어. 이처럼 예외처리는 다양한 상황에서 필수적으로 사용되고, 우리들이 완성도 높은 프로그램을 만들 수 있도록 도와주지!

인생의 모든 상황에 대한 Plan B 를 대비할 수 있을까? 그건 불가능해.
프로그래밍에서도 마찬가지로 모든 상황에 대해서 100% 완벽한 Plan B 를 구현해놓을 수는 없어. 만약 할 수 있다면 그건 인간이 아닐거야. 그렇지만 로직을 짜면서 예상할 수 있는 모든 예외적 상황에 대해서 Plan B, Plan C 를 구현하는 것은 아주 중요해.

우리가 삶을 살아갈때 다양한 고민과 철학을 가지고 살아가는 것 처럼, 프로그래밍 또한 많은 철학을 가지고 한 줄 한 줄 작성하길 바라! :-)

Python 전체 코드

from datetime import datetime

class Human(object):
    name = None
    birthyear = None

    def __init__(self, name, birthyear):
        self.name = name
        self.birthyear = birthyear

    def hello(self):
        print(f"저는 {self.name}에요. 나이는 {datetime.now().year - self.birthyear + 1}살이에요. 반가워요.")

    def get_relative_age(self, year):
        print(f"저는 {year}년 기준으로 {year - self.birthyear + 1} 살이에요.")

    def get_my_age(self):
        raise NotImplementedError("아직 구현되지 않은 기능")

human = Human("영희", 2002)
human.hello()  # 여기까지는 아무 문제 없이 정상작동

human.get_relative_age(2015)  # 정상 실행하는 코드
try:
    human.get_relative_age("2015")  # 오류가 발생하는 코드
except Exception as e:  # 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
    print(e)  # unsupported operand type(s) for -: 'str' and 'int'
    print("앗, 에러가 발생했어! Plan B 를 실행하자!")
finally:
    print("여기는 오류가 나던 안나던 항상 실행되는 코드영역")

print("이 코드가 실행될까?")  # 위에서 예외처리를 했기 때문에 실행 됨!

try:
    human.get_my_age()  # raise 키워드에 의해 의도적으로 예외가 발생함!
except Exception as e:  # 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
    print(e)  # NotImplementedError: 아직 구현되지 않은 기능
    print("앗, 에러가 발생했어! Plan B 를 실행하자!")

JavaScript 전체 코드

class Human extends Object {
    constructor(name, birthyear) {
        super();
        this.name = name;
        this.birthyear = birthyear;
    }

    hello() {
        console.log(`저는 ${this.name}에요. 나이는 ${new Date().getFullYear() - this.birthyear + 1}살이에요. 반가워요.`);
    }

    getRelativeAge(year) {
        console.log(`저는 ${year}년 기준으로 ${year - this.birthyear + 1} 살이에요.`);
    }

    get_my_age() {
        throw new Error(`아직 구현되지 않은 기능`);
    }
}

const human = new Human("영희", 2002);
human.hello();  // 여기까지는 아무 문제 없이 정상작동

human.getRelativeAge(2015);  // 정상 실행하는 코드
try {
    human.getRelativeAge("2015");  // 오류가 발생하는 코드
} catch (e) {  // 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
    console.log(e);  // unsupported operand type(s) for -: 'str' and 'int'
    console.log("앗, 에러가 발생했어! Plan B 를 실행하자!");
} finally {
    console.log("여기는 오류가 나던 안나던 항상 실행되는 코드영역");
}

console.log("이 코드가 실행될까?");  // 위에서 예외처리를 했기 때문에 실행 됨!

try {
    human.get_my_age();  // throw 키워드에 의해 의도적으로 예외가 발생함!
} catch (e) {  // 모든 예외(Exception)를 e 라는 변수로 받음
    console.log(e);  // Error: 아직 구현되지 않은 기능
    console.log("앗, 에러가 발생했어! Plan B 를 실행하자!");
}

자바스크립트에서는 위의 예제를 그대로 따라 작성해도 get_relative_age 메소드에서 에러가 발생하지 않아. 그 이유는 자바스크립트에서 숫자 연산을 진행할 때, 숫자모양의 string 타입 변수는 그냥 자바스크립트 엔진이 내부적으로 숫자로 바꿔줘버려. 이렇게 자료형을 바꾸는 것을 형변환(Type Conversion) 이라고 하는데, 형변환에는 2가지 방식이 있어.

묵시적 형변환(Implicit Type Conversion)과
명시적 형변환(Explicit Type Conversion) 이야.

묵시적 형변환 은 위의 설명처럼 나의 의도와는 상관없이 자바스크립트 엔진이 내부적으로 바꿔주는 것을 말하고, 명시적 형변환 은 내가 "이런 타입으로 자료형을 변환할거야" 라고 명시적으로 쓰는 것을 말해.
명시적 형변환을 간단하게 코드로 보여주면 아래와 같아.

Number("2015")  // string --> number
String(2015)  // number --> string

JavaScript 와 Java 에서는 throw 라는 키워드를 사용하고 에러를 '던진다' 고 표현해.

Java 전체 코드

import java.util.Calendar;

public class Main {
    public static void main(String[] args) {
        Human human = new Human("영희", 2002);
        human.hello();  // 여기까지는 아무 문제 없이 정상작동

        human.getRelativeAge(2015);  // 정상 실행하는 코드
        try {
            human.getRelativeAge("2015");  // 오류가 발생하는 코드
        } catch (Exception e) {  // 모든 예외(Exception)를 e 라는 변수로 alias(별칭)
            System.out.println(e);  // unsupported operand type(s) for -: 'str' and 'int'
            System.out.println("앗, 에러가 발생했어! Plan B 를 실행하자!");
        } finally {
            System.out.println("여기는 오류가 나던 안나던 항상 실행되는 코드영역");
        }

        System.out.println("이 코드가 실행될까?");  // 위에서 예외처리를 했기 때문에 실행 됨!

        try {
            human.get_my_age();  // throw 키워드에 의해 의도적으로 예외가 발생함!
        } catch (Exception e) {  // 모든 예외(Exception)를 e 라는 변수로 받음
            System.out.println(e);  // Error: 아직 구현되지 않은 기능
            System.out.println("앗, 에러가 발생했어! Plan B 를 실행하자!");
        }

    }
}

class Human extends Object {
    public String name = "";
    public int birthyear = 0;

    public Human(String name, int birthyear) {
        super();
        this.name = name;
        this.birthyear = birthyear;
    }

    public void hello() {
        int year = Calendar.getInstance().get(Calendar.YEAR);
        System.out.println("저는 " + this.name + "에요. 나이는 " + (year - this.birthyear) + "살이에요. 반가워요.");
    }

    public void getRelativeAge(Object year) {
        System.out.println("저는 " + year + "년 기준으로 " + (year - this.birthyear + 1) + "살이에요.");
    }

    public void get_my_age() throws Exception {
        throw new Exception("아직 구현되지 않은 기능");
    }
}
profile
🔥 from Abstraction to Realization

0개의 댓글