9.3.1 예외 처리란 무엇인가

예외(exception)란 프로그램이 정상적으로 실행될 수 없는 상황을 뜻한다. 항상 동일하게 발생하는 오류는 프로그램을 고쳐 해결하면 된다. 하지만 프로그램 외부의 요인(사용자가 입력한 데이터·프로그램이 실행되는 환경 등)으로 발생하는 실행 오류는 어떻게 해결해야 할까? 입력 데이터와 실행 환경은 프로그램을 사용하는 사람에게 달려있다. “F1 키를 절대 누르지 마시오”, “프로그램 사용중 인터넷 연결을 끊지 마시오”라고 경고해본들, 문제는 여전히 일어날 수 있다. 외부 요인을 통제할 수 없다면, 프로그램 안에 예외 상황에 대한 대응책을 마련해두어야 한다. 이를 예외 처리(exception handling)라고 한다.

“냄비에 육수 1리터를 담고 센 불로 끓인다.”

이 조리법은 조리도구와 식재료가 갖추어져 있을 것을 전제로 한다. 만약 냄비가 없다면 “냄비가 없다”라는 예외 상황이 발생하여 조리를 수행할 수 없을 것이다. 조리법을 수정하여 이 예외를 처리해보자.

“냄비에 육수 1리터를 담고 센 불로 끓인다. 만약 냄비가 없을 경우 주전자를 대신 사용한다.”

수정한 조리법은 “냄비가 없다”라는 예외를 처리할 수 있다. “만약 냄비가 없을 경우 …” 라는 표현에서 알 수 있듯이, 예외 처리는 어떤 ‘조건’에 따라 실행할 지시를 ‘선택’하는 것이다. if 문으로 예외 처리를 해 보고, 더 나은 방법도 알아볼 것이다.

예외 처리는 어디까지?

위의 조리법에서 일어날 수 있는 예외가 “냄비가 없다”뿐일까? 냄비가 있더라도 용량이 1리터에 못미칠 수도 있고, 냄비가 없을 뿐 아니라 주전자도 없을 경우도 있다. 조리법을 다시 수정하면 이런 예외도 처리할 수 있겠으나, 언제나 미처 생각하지 못한 새로운 예외가 발생할 수 있다. 즉, 모든 예외를 예견하는 것은 불가능하다. 예외 처리를 어디까지 할 것인지는 프로그램의 안정성이 어느 정도까지 요구되느냐에 달렸다.

개념 정리

  • 예외: 프로그램이 정상적으로 실행될 수 없는 상황
  • 예외 처리: 에외 상황에서 프로그램이 해야 할 일을 작성해두는 것

9.3.2 if 문으로 예외 처리하기

사용자로부터 수를 입력받아 나누는 프로그램을 예로 들어 보자.

코드 9-17 예외 처리가 필요한 프로그램

# 데이터 입력
print('0이 아닌 정수를 입력해 주세요:', end=' ')
user_number = int(input())

# 결과 출력
print(1 / user_number)

이 프로그램은 사용자가 0을 입력하는 경우 ZeroDivisionError 예외가 발생한다. 이 예외를 if 문으로 처리해 보자.

코드 9-18 if 문으로 예외 처리하기

# 데이터 입력
print('0이 아닌 정수를 입력해 주세요:', end=' ')
user_number = int(input())

# ❶ 예외 처리: 입력값이 0인 경우 프로그램 종료
if user_number == 0:
    print('0으로 나눌 수 없습니다.')
    exit()   # ❷ 프로그램 종료

# 결과 출력
print(1 / user_number)

수정한 프로그램은 ❶ if 문으로 사용자가 입력한 값을 검사하여 잘못된 값이 계산에 사용되는 것을 방지한다. if 문의 본문에는 예외를 감지한 경우의 대처법이 포함된다. 사용자가 다시 (올바른) 값을 입력하도록 요구할 수도 있고, 문제점을 알려준뒤 프로그램을 종료할 수도 있다. ❷ exit() 함수를 호출하면 프로그램이 종료된다. 따라서 사용자가 0을 입력하지 않았을 때만 나눗셈 연산이 실행된다. 동일한 요령으로 사용자가 정수를 입력했는지도 검사할 수 있다.

코드 9-19 두 가지 예외 처리하기

# 데이터 입력
print('0이 아닌 정수를 입력해 주세요:', end=' ')
user_string = input()

# 예외 처리: 입력값이 정수가 아닌 경우 프로그램 종료
if not user_string.isnumeric():
    print(user_string, '은 정수가 아닙니다.')
    exit()   # 프로그램 종료

# 입력값(문자열)을 정수로 변환
user_number = int(user_string)

# 예외 처리: 입력값이 0인 경우 프로그램 종료
if user_number == 0:
    print('0으로 나눌 수 없습니다.')
    exit()   # 프로그램 종료

# 결과 출력
print(1 / user_number)

사용자가 잘못된 값을 입력했을 때 입력을 다시 요청하는 것도 좋은 생각이다. 올바른 데이터를 입력할 때까지 계속 입력을 요청하도록 해야 한다면 while 문을 함께 활용하면 좋다.

코드 9-20 올바른 값이 입력될 때까지 반복 입력하기

while(True):  # ❶ 무한 반복
    # 데이터 입력
    print('0이 아닌 정수를 입력해 주세요:', end=' ')
    user_string = input()
    
    # 예외 처리: 입력값이 정수가 아닌 경우 다시 입력
    if not user_string.isnumeric():
        print(user_string, '은 정수가 아닙니다.')
        continue  # ❷ while 문 본문의 시작 지점에서 다시 반복
    
    # 입력값(문자열)을 정수로 변환
    user_number = int(user_string)
    
    # 예외 처리: 입력값이 0인 경우 다시 입력
    if user_number == 0:
        print('0으로 나눌 수 없습니다.')
        continue  # ❷ while 문 본문의 시작 지점에서 다시 반복
    
    break  # ❸ 반복 중지

# 결과 출력
print(1 / user_number)

코드 9-20은 ❶ while 문을 이용한 무한 반복 블록 안에서 입력과 예외 처리를 계속 반복한다. 잘못된 값이 입력됐을 때는 ❷ continue 문으로 반복 블록을 처음부터 다시 수행하도록 하여 값을 다시 입력받는다. 올바른 값이 입력됐을 때는 ❸ break 문으로 반복을 중지한다.

실행 결과:

0이 아닌 정수를 입력해 주세요: 이백
이백 은 정수가 아닙니다.
0이 아닌 정수를 입력해 주세요: 0
0으로 나눌 수 없습니다.
0이 아닌 정수를 입력해 주세요: 200
0.005

입력값 검증

사용자가 입력하는 모든 데이터는 의심스러운 데이터다. 사용자는 실수로 잘못된 데이터를 입력해 오류를 유발하기도 하고, 고의로 잘못된 데이터를 입력해 해킹을 시도하기도 한다. 상업용 프로그램에서는 사용자가 입력하는 모든 데이터를 검증해야 한다.

연습문제

연습문제 9-3 파일 읽기 프로그램 1

연오는 텍스트 파일의 내용을 읽어 화면에 출력하는 프로그램을 다음과 작성했다.

print('파일 이름을 입력하시오: ', end='')
filename = input()

# filename 파일 열기
with open(filename) as f:
    # 파일의 내용을 읽어 화면에 출력
    print(f.read())

처음에는 이 프로그램이 잘 실행된다고 생각했다.

파일 이름을 입력하시오: sample.txt
파이썬 프로그래밍
샘플 텍스트 파일입니다

그런데 프로그램을 사용해 본 친구들이, 프로그램이 제대로 동작하지 않는다고 하며 다음과 같은 오류 메시지를 보내주었다.

Traceback (most recent call last):
  File "exercise_9_3.py", line 5, in <module>
    with open(filename) as f:
FileNotFoundError: [Errno 2] No such file or directory: 'some-file.txt'

이 오류가 발생하는 이유는 무엇인지 설명해 보아라. 그리고 이 오류가 일어나지 않도록 하려면 어떤 처리가 필요한지, 그리고 그 처리를 수행할 수 있는지 또는 없는지를 설명해 보아라.

9.3.3 if 문을 이용한 예외 처리의 한계

if 문을 이용해 예외 처리를 하면 몇 가지 문제점이 있다.

  1. 예외를 나타내는 값(오류 코드)과 정상 값을 구별하기가 어렵다.
  2. 함수를 연달아 호출할 때, 예외를 함수 밖으로 전달하기가 불편하다.
  3. 예외 상황인지 항상 미리 검사해야 한다.

오류 코드와 정상 값을 구별하는 문제

함수 안에서 예외 처리를 할 때는 생각해 보자. 함수는 return 문을 이용해 결과를 반환한다. 그런데 함수 호출 도중에 예외가 발생한다면, 예외가 발생한 사실을 함수 밖으로 어떻게 알릴 수 있을까?

코드 9-21은 네트워크에 연결된 컴퓨터와 신호를 주고받는 데 걸리는 시간을 측정하여 반환하는 함수다. 정상적인 경우에는 응답에 걸린 시간을 수로 반환할 것이다. 하지만 서버에 접속할 수 없는 경우에는 어떤 값을 반환해야 할까?

코드 9-21 함수 밖으로 예외 전달하기

# 주의: 다음은 예를 위한 가짜 코드이며 실행되지 않는다.
def ping(address):
    """대상 주소(address)의 컴퓨터와 신호를 주고받는 데 걸리는 시간(초)을 측정하여 반환한다."""
    
    if 주소가_잘못된_경우:
        return -1  # ❶ 오류 코드를 반환한다
    
    if 인터넷_연결이_안_된_경우:
        return -2  # ❶
    
    if 서버에_접속할_수_없는_경우:
        return -3  # ❶
    
    seconds = ...   # 정상적인 경우
    return seconds  # ❷ 응답 시간을 반환한다

위 코드의 ping() 함수는 예외를 함수 밖으로 알리기 위해 ❶ -1, -2, -3을 반환한다. 응답 시간은 음수가 될 수 없으므로, 함수를 호출한 쪽에서는 ❷ 양수를 정상적인 결과로, ❶ 음수를 예외로 판단할 수 있다. 함수를 호출한 쪽에서 어떤 예외가 일어났는지도 알 수 있도록 각 예외 상황마다 반환하는 값을 서로 다르게 정해 두었다. 이처럼 예외를 나타내는 값을 정해 둔 것을 오류 코드(error code)라고 한다.

오류 코드는 함수마다 제각각 정의하게 될 가능성이 높다. 오류 코드는 ‘비정상적인 반환값’이어야 하는데, 그런 값은 함수마다 다를 수밖에 없기 떄문이다. 예컨대 양수를 반환하는 함수는 음수를 오류 코드로 약속할 수 있겠지만, 음수를 반환하는 함수는 그렇게 할 수 없다. 모든 함수에서 사용할 수 있는 통일된 오류 코드를 만들기 어렵다.

함수를 연달아 호출할 때, 예외 처리를 중복으로 해야 하는 문제

함수를 연달아 호출할 때, 함수 밖으로 오류 코드를 전달하려면 호출되는 함수들 속에서 예외 처리를 중복으로 해야 한다. 앞의 ping() 함수를 호출하여 두 서버의 응답시간을 비교하는 함수를 정의한다고 해 보자.

코드 9-22 함수 밖으로 전달된 예외를 더 바깥으로 전달하기

def compare_two_servers(a, b):
    """두 서버 주소 a, b를 전달받아, 응답시간이 더 짧은 서버 주소를 반환한다."""
    # 서버 a와 서버 b의 응답시간 측정
    response_a = ping(a)
    response_b = ping(b)
    
    # ❶ 서버 a 또는 서버 b에서 예외가 발생한 경우 예외를 밖으로 전달
    if response_a < 0:
        return response_a
    if response_b < 0:
        return response_b
    
    # 예외가 발생하지 않은 경우, 응답시간이 더 짧은 서버 주소 반환
    return a if response_a < response_b else b

compare_two_servers() 함수는 오류 코드를 반환하는 ping() 함수를 내부에서 호출하는데, ❶ 그 오류 코드를 자신을 호출한 곳으로 다시 전달해야 한다. 이미 ping() 함수에서 예외 검사를 마쳤지만 ping() 함수를 호출하는 곳에서는 모두 ping() 함수의 반환값을 검사해 밖으로 전달해야 하는 셈이다. 예외 처리를 중복으로 해야 하는 데다, 여러 함수에서 오류 코드를 관리하기가 더욱 힘들어진다.

에외를 미리 검사해야 하는 문제

다음 두 지시를 비교해 보자.

  1. “냄비가 있는지 확인한다. 냄비가 있으면 냄비에 육수 1리터를 담고 센 불로 끓인다. 냄비가 없으면 주전자를 대신 사용한다.”
  2. “냄비에 육수 1리터를 담고 센 불로 끓인다. 냄비가 없을 경우 주전자를 대신 사용한다.”

둘다 냄비가 없는 상황을 상정한 조리법이지만 접근법이 다르다. 1은 먼저 허락을 구한 뒤에 일을 하는 방법이다. 2는 일을 먼저 하고, 문제가 생기면 다른 용서를 구하는 방법이다. 1은 확인을 한 직후에 순식간에 상황이 바뀌어 버리면(확인할 때는 냄비가 있었는데, 조리할 때는 사라진 경우) 예외 처리를 할 수 없다는 단점이 있다.

9.3.4 try 문으로 예외 처리하기

파이썬에서 예외 처리를 올바르게 하는 방법은 try 문을 사용하는 것이다. try 문을 사용하면 다음과 같은 장점이 있다.

  1. 정상 값과 구별하기 어려운 오류 코드 대신, 예외 객체라는 특별한 정보로 예외를 전달할 수 있다.
  2. 함수 호출 속에서 예외가 발생하면 예외 객체가 함수 바깥으로 전달된다.
  3. 예외가 발생할 것을 미리 확인하는 대신, 예외가 발생했을 때 처리하는 방법을 따른다.

이 세 장점은 모두 앞에서 살펴본 if 문의 한계를 해결한 것이다.

try 문과 except 절

try 문은 except 절과 함께 사용한다. ‘try’는 ‘시도하라’라는 뜻이다. try 문의 본문 블록에 예외 발생 가능성이 있는 코드를 기술하여, 코드를 일단 ‘시도해’ 보도록 한다. ‘except’는 ‘~를 제외하고’라는 뜻이다. except 절에는 처리할 예외의 종류와 처리 방법을 기술한다. 예외의 종류에 따라 그 대처법도 다를 것이므로, 하나의 try 문은 여러 개의 except 절을 포함할 수 있다. 하지만 예외를 하나도 처리하지 않는다면 try 문 자체가 필요 없을 것이므로, except 절이 최소한 하나는 있어야 한다.

try:
    예외가 발생할 수 있는 코드 블록
    ...
except 예외종류:
    예외종류에 해당하는 예외가 발생했을 때 실행할 코드 블록
    ...
(필요에 따라 except 절을 추가로 작성)

‘예외종류’란 예외를 나타내는 클래스다. 파이썬에는 다양한 예외 상황이 클래스로 정의되어 있다. 발생한 예외가 어떤 예외인지 알기 위해서는 오류 메시지를 확인하면 된다. 예를 들어, 0으로 나누는 오류의 메시지를 대화식 셸로 확인해 보면 다음과 같은 오류 메시지를 얻을 수 있다.

코드 9-23 0으로 나누는 오류의 오류 메시지

>>> 1 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

오류 메시지의 마지막 행이 오류의 종류와 내용을 알려주는 행이다. 여기서 콜론(:) 왼쪽의 이름이 오류의 종류를 나타내는 클래스다. 즉, 0으로 나누는 오류는 파이썬에서 ZeroDivisionError로 정의되어 있다. 예외의 종류는 잠시후 더 소개한다.

try 문을 이용해 0으로 나누는 오류와 사용자가 입력한 값을 정수로 변환할 수 없는 오류를 처리해 보자.

코드 9-24 0으로 나누는 오류의 오류 메시지

# ❶ try 블록에 예외가 일어날 수 있는 코드를 기술한다.
try:
    print('0이 아닌 정수를 입력해 주세요:', end=' ')
    user_number = int(input())
    print(1 / user_number)
# ❷ 처리해야 할 예외의 이름과 처리방법을 except 블록에 기술한다.
except ZeroDivisionError:  # 0으로 나누는 오류 처리
    print('0으로 나눌 수 없습니다.')
except ValueError:         # int 유형이 될 수 없는 문자열의 오류 처리
    print('입력한 값은 정수가 아닙니다.')

if 문을 이용한 코드 9-18에서는 계산을 실행하기에 앞서 값이 정수로 변환할 수 있는 문자열인지, 0은 아닌지 등을 미리 검사했다. 하지만 try 문을 이용한 코드 9-24에서는 그런 검사를 수행하지 않았다. ❶ try 문에서 바로 코드를 실행하고, ❷ 예외가 일어나면 except 절의 코드를 실행하도록 했다.

try 문을 사용할 때도, 사용자가 올바른 값을 입력할 때까지 계속 반복 입력받도록 할 수 있다. try 문 전체를 while 문으로 감싸고 정상 실행되었을 때 break 문으로 반복 블록을 빠져나오면 된다.

코드 9-25 올바른 입력을 반복하여 요구하기

while True:
    try:
        print('0이 아닌 정수를 입력해 주세요:', end=' ')
        user_number = int(input())
        print(1 / user_number)
        break  # 예외가 발생하지 않은 경우, 반복을 빠져나간다
    except ZeroDivisionError:
        print('0으로 나눌 수 없습니다.')
    except ValueError:
        print('입력한 값은 정수가 아닙니다.')

try 문의 본문을 실행하는 도중 예외가 발생한 경우에는 본문의 나머지 내용을 실행하지 않고, 그 예외를 처리할 수 있는 except 절을 실행한다. 따라서 코드 9-25에서 ZeroDivisionError, ValueError 예외가 발생한 경우에는 try 문 마지막의 break 문은 실행되지 않고, 계속해서 반복 입력받는다.

0이 아닌 정수를 입력해 주세요: 백
입력한 값은 정수가 아닙니다.
0이 아닌 정수를 입력해 주세요: 0
0으로 나눌 수 없습니다.
0이 아닌 정수를 입력해 주세요: 20
0.05

try 문의 else 절

try 문에도 else 절을 작성할 수 있다. else 절에 작성한 코드는 try 문의 본문을 다 실행할 때까지 예외가 발생하지 않은 경우에 실행된다. try 절의 작성 양식에 else 절을 추가해 두자.

try:
    예외가 발생할 수 있는 코드 블록
    ...
except 예외종류:
    예외종류에 해당하는 예외가 발생했을 때 실행할 코드 블록
    ...
(필요에 따라 except 절을 추가로 작성)
else:
    예외가 발생하지 않은 경우 실행할 코드 블록

else 절을 이용하면 코드 9-25를 다음과 같이 수정할 수 있다.

코드 9-26 try 문에서 else 절 사용하기

while True:
    try:
        print('0이 아닌 정수를 입력해 주세요:', end=' ')
        user_number = int(input())
        result = 1 / user_number
    except ZeroDivisionError:
        print('0으로 나눌 수 없습니다.')
    except ValueError:
        print('입력한 값은 정수가 아닙니다.')
    
    # 예외가 발생하지 않은 경우에만 실행
    else:
        print(result)  # 결과를 출력하고
        break          # 반복을 빠져나간다

try 절의 본문에 작성하지 않고 else 절에 코드를 작성하는 이유는 의도하지 않은 예외 처리를 방지하기 위해서다. 예외 처리의 대상이 되는 코드와 이어서 수행될 코드를 명시적으로 구별하는 것이다.

처리되지 않은 예외의 전파

try 문은 except 절에 처리할 예외를 명시한다. except 절에서 처리하지 않은 예외가 발생한다면 어떤 일이 일어날까? 직접 확인해보자. 코드 9-26를 실행하고, 수를 입력하는 대신 Ctrl + C 키를 입력해 보자.

실행 결과 (Ctrl + C 입력):

0이 아닌 정수를 입력해 주세요: ^C
Traceback (most recent call last):
  File "example_9_26.py", line 4, in <module>
    user_number = int(input())
KeyboardInterrupt

파이썬 프로그램 실행중에 Ctrl + C 키를 입력하면 KeyboardInterrupt라는 예외가 발생한다. 코드 9-26의 try 문에는 이 예외를 처리하는 except 절이 없기 때문에, 예외는 처리되지 않은 채로 try 문을 빠져나와 버린다. 예외가 try 문을 빠져나왔을 때의 결과는 try 문을 사용하지 않았을 때와 같다. 즉, 처리되지 않은 예외로 인해 실행시간 오류가 발생하고, 오류 메시지가 출력된 후 프로그램 실행이 중단된다.

try 문을 빠져나온 예외는 그 바깥쪽의 try 문으로도 처리할 수 있다.

코드 9-27 try 문을 빠져나온 예외 처리하기

# 블록 1: 바깥쪽 try 문
try:
    
    # 블록 2: while 문
    while True:
        
        # 블록 3: 안쪽 try 문
        try:
            print('0이 아닌 정수를 입력해 주세요:', end=' ')
            user_number = int(input())
            result = 1 / user_number
        except ZeroDivisionError:
            print('0으로 나눌 수 없습니다.')
        except ValueError:
            print('입력한 값은 정수가 아닙니다.')
        else:
            print(result)  # 결과를 출력하고
            break          # 반복을 빠져나간다

# 바깥쪽의 try 문에서 KeyboardInterrupt 예외를 처리한다
except KeyboardInterrupt:
    print('Ctrl + C를 누르셨군요.')

코드 9-27을 실행하고 Ctrl + C 키를 입력해보면 세번째 블록인 안쪽 try 문에서 예외가 발생했지만 try 문 블록과 while 문 블록을 빠져나가 바깥쪽 try 문에서 처리되는 것을 확인할 수 있다.

실행 결과 (Ctrl + C 입력):

0이 아닌 정수를 입력해 주세요: ^C
Ctrl + C를 누르셨군요.

예외가 코드 블록 안에서 발생하는 경우 예외 객체는 가장 가까이 있는 try 문이 처리할 때까지 코드 블록을 차례대로 빠져나간다. 이 특징을 이용하면 함수 호출 안에서 일어난 예외를 함수 호출 밖에서 처리할 수 있다.

코드 9-28 함수 연쇄 호출 속의 오류

def a(x):
    return 8 / x      # x가 0인 경우 오류 발생

def b(y):
    return a(y - 1)   # y가 1인 경우 오류 발생

def c(z):
    return b(z - 2)   # z가 3인 경우 오류 발생

def d():
    print(c(int(input())))

d()

위 코드는 사용자가 무엇을 입력하느냐에 따라 정상 실행될 수도, 오류를 일으킬 수도 있다. 그런데 예외 처리를 어디에서 해야 할까? 8 / 0이 실행될 수 있는 a() 함수? a()에 잘못된 값을 전달할 가능성이 있는 b(), c() 두 함수? 사용자로부터 값을 입력받는 d() 함수? 모든 코드가 예외를 일으킬 가능성이 있다. 따라서 다음과 같이 예외 처리할 수 있다.

코드 9-29 함수 연쇄 호출 속의 오류 처리하기 (불필요한 방법)

def a(x):
    try:
        return 8 / x     # x가 0인 경우 오류 발생
    except ZeroDivisionError:
        print('0으로는 나눌 수 없습니다.')

def b(y):
    try:
        return a(y - 1)  # y가 1인 경우 오류 발생
    except ZeroDivisionError:
        print('0으로는 나눌 수 없습니다.')

def c(z):
    try:
        return b(z - 2)  # z가 3인 경우 오류 발생
    except ZeroDivisionError:
        print('0으로는 나눌 수 없습니다.')

def d():
    try:
        print(c(int(input())))
    except ZeroDivisionError:
        print('0으로는 나눌 수 없습니다.')

try:
    d()
except ZeroDivisionError:
    print('0으로는 나눌 수 없습니다.')

위 코드와 같이 모든 함수에서 예외를 처리할 필요는 없다. try 문을 사용하면 안쪽에서 발생한 오류를 바깥에서 처리할 수 있다. 예외가 실행 흐름을 따라 밖으로 빠져나오는 경우, 그 사이의 한 지점에서 한 번만 예외를 처리해주면 된다.

코드 9-30 함수 연쇄 호출 속의 오류 처리하기

def a(x):
    return 8 / x     # x가 0인 경우 오류 발생

def b(y):
    return a(y - 1)  # y가 1인 경우 오류 발생

def c(z):
    return b(z - 2)  # z가 3인 경우 오류 발생

def d():
    try:
        print(c(int(input())))
    except ZeroDivisionError:
        print('0으로는 나눌 수 없습니다.')

d()

위 코드는 d() 함수에서만 0으로 나누는 오류를 처리했다. 하지만 a()에서 오류가 발생하더라도 그 예외가 실행 흐름을 따라 빠져나와 결국 d() 함수에서 처리되므로 문제 없다. 단, a(8)과 같이 a() 함수를 직접 호출한다면 예외 처리가 되지 않을 것이다.

try 문을 사용하는 방식을 if 문을 사용할 때와 비교해 보자. if 문을 사용할 때는 코드를 실행하기 전에 예외 상황을 미리 검사해야 했다. 그리고 예외 정보를 함수 밖으로 전달하기 위해 return 문과 오류 코드를 써야 했다. try 문을 사용할 때는 예외 상황을 미리 검사하지 않는다. 예외는 함수 호출과 코드 블록을 저절로 빠져나오며, 원하는 위치에서 처리할 수 있다.

try 문을 이용하면 if 문보다 편리하고 정확하게 예외 처리를 할 수 있다. 실행시간 오류는 try 문으로 예외 처리하는 것을 기억하자.

개념 정리

  • 예외 처리를 할 때는 if 문과 오류 코드를 이용하기보다는, try 문을 이용하는 것이 좋다.
  • 미리 허락을 구하는 것보다 저지른 후에 용서를 구하는 것이 쉽다.

연습문제

연습문제 9-4 파일 읽기 프로그램 2

연습문제 9-3에서 살펴본 파일 읽기 프로그램을 try 문을 이용해 수정해라. 사용자가 입력한 파일 이름과 일치하는 파일이 없을 때, 오류를 일으키지 말고 다음과 같이 적절한 안내를 하도록 한다.

파일 이름을 입력하시오: some-file.txt
파일이 존재하지 않아 읽을 수 없습니다.