이번 편의 목표: ValueError를 좁게 처리하고 검증 실패를 구분합니다.

가격에 글자를 입력하면 프로그램이 끝나는 문제를 4편에서 남겨 두었습니다. 이제 반복문과 함수를 배웠으니 이 문제를 해결할 수 있습니다. 오류를 없던 일로 만드는 것이 아니라, 예상한 오류에 다음 행동을 정하는 과정입니다.

변환 오류가 날 부분만 감싼다

Python
def read_positive_amount():
    while True:
        text = input("금액(양의 정수): ").strip()
        try:
            amount = int(text)
        except ValueError:
            print("4500처럼 정수를 입력해 주세요.")
            continue
        if amount <= 0:
            print("금액은 0보다 커야 합니다.")
            continue
        return amount

def main():
    amount = read_positive_amount()
    print(f"기록할 금액: {amount:,}원")

if __name__ == "__main__":
    main()

`사천`, `-10`, `4500`을 순서대로 넣으면 첫 번째는 변환 오류 안내, 두 번째는 범위 안내가 나오고, 마지막에 `4,500원`이 출력됩니다. 문자 형식을 검사하는 문제와 숫자의 범위를 검사하는 문제를 나누었습니다.

`try` 안에서 지정한 예외가 발생하면 `except`로 이동합니다. 처리한 뒤에는 코드 흐름이 이어집니다. 여기서는 `continue`로 다음 입력을 받습니다. 올바른 값이면 `return`이 함수 전체를 끝내므로 반복문도 함께 끝납니다.

모든 예외를 덮지 않는다

`except:`만 쓰면 종료 요청 같은 신호까지 잡아 버릴 수 있습니다. `except Exception:`도 너무 넓은 구간에 사용하면 오타나 계산 오류를 사용자 입력 오류처럼 숨길 수 있습니다. 이번 코드에서는 `int(text)`에서 예상하는 `ValueError`만 처리합니다.

오류 메시지의 마지막 줄은 예외 종류와 설명을 알려 주고, 그 위의 traceback은 어떤 경로로 그 줄까지 왔는지 보여 줍니다. 개발 중에는 traceback을 읽어 원인을 찾는 것이 우선입니다. 사용자가 잘못 입력할 수 있는 부분만 친절한 메시지로 바꾸세요.

함수가 조건을 요구할 때

함수 내부에서 받은 값이 규칙에 맞지 않으면 `raise ValueError("설명")`으로 오류를 직접 알릴 수 있습니다. 예를 들어 저장 함수가 양의 정수 금액만 받기로 했다면 잘못된 값을 조용히 0으로 바꾸기보다 호출자에게 실패를 알리는 편이 분명합니다.

`finally`는 예외 발생 여부와 관계없이 마무리 작업을 할 때 사용합니다. 파일 닫기에는 보통 다음 편의 `with`를 사용하면 더 간단합니다. 예외 문법을 모두 한 번에 쓰기보다, 어떤 실패를 예상하고 어디에서 처리할지부터 결정하세요.

직접 해 보기

목록의 합계를 인원수로 나누는 함수를 만드세요. 인원수가 0 이하이면 `ValueError`를 발생시키세요. 정상 결과와 오류 메시지를 모두 확인하세요.

해설

Python
def split_cost(total, people):
    if people <= 0:
        raise ValueError("인원수는 0보다 커야 합니다.")
    return total / people

print(split_cost(12000, 3))
try:
    print(split_cost(12000, 0))
except ValueError as error:
    print(error)

결과는 `4000.0`과 직접 작성한 오류 메시지입니다. 호출자가 수정할 수 있는 실패를 구체적으로 알려 주는 것이 예외 설계의 출발점입니다.