프로그래머는 보통 완벽한 소프트웨어를 작성하려고 노력하지만, 대개 실패해요.
예상치 못하게 문제가 생기기도 하는데, 우리는 그런 상황에 대처할 수 있어야 해요.
어떤 언어 설계자들은 오류를 가능한 한 빨리 감지한 다음, 디버깅에 도움이 되는 정보를 담은 메시지와 함께 실행을 종료하는 것이 우선이라고 믿어요.
데이터 과학 언어들은 좀 더 미묘한 접근을 취하는 편이에요. 어떤 오류는 즉시 종료해야 할 만큼 심각하지만, 대개는 문제를 나중에 처리할 것으로 표시해 두고 실행을 계속하는 편이 더 나아요.
Nothingness 개념에서 살펴봤듯이, Julia는 문제가 되는 값을 위한 여러 가지 자리 표시자를 제공해요: missing, NaN, Inf.
특정 상황에서 이런 방식이 프로그램 종료보다 더 나은지 여부는 프로그래머의 판단에 달려 있어요.
세부 내용으로 들어가기 전에 용어에 대해 한 가지 짚고 넘어갈게요: Julia 문서에서는 "error"와 "exception"이라는 단어를 거의 같은 의미로 사용해요. 아래 내용도 마찬가지로 일관성이 없을 수 있어요.
커리큘럼의 이 시점쯤이면, Julia에서 나오는 오류 메시지를 많이 봤을 거예요. 예를 들어:
julia> Int(3.14)
ERROR: InexactError: Int64(3.14)
실수를 정수로 변환하려고 하면 정밀도가 손실되기 때문에 InexactError가 발생해요.
InexactError는 Julia에 기본으로 내장된 여러 유형 (현재 25개) 중 하나예요.
이들은 모두 Exception의 하위 유형이에요:
julia> supertype(InexactError)
Exception
throw()여러분의 코드에서 직접 생성하면 유용한 표준 오류 유형도 있어요.
모든 구체적 유형과 마찬가지로, 오류에도 생성자가 있어요. 다양한 인자를 받기 때문에, 사용하려는 오류의 문서를 확인해 보세요.
julia> DomainError(42, "out of range")
DomainError(42, "out of range")
오류를 사용하려면 생성자를 throw() 함수로 감싸요:
julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range
error()빠르고 간단하게 처리하고 싶을 때는 error() 함수가 편리해요.
이 함수는 문자열(또는 문자열의 구성 요소)을 인자로 받아요:
julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong
새로운 오류 유형을 만드는 것은 원칙적으로 아주 쉬워요.
Exception의 하위 유형을 하나 더 추가하면 돼요:
julia> struct MyError <: Exception end
julia> throw(MyError)
ERROR: MyError
단언의 기본 아이디어는 "이 명제는 참이어야 하니, 거짓이면 크게 문제를 제기하라"는 거예요. 이것의 가치는 주로 디버깅 중에 있는데, 프로덕션 코드는 단언에 실패해서는 안 되기 때문이에요.
Types 개념에서 살펴봤듯이, 예를 들어 함수의 반환 유형을 확인하기 위해 유형 단언을 추가할 수 있어요.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
더 일반적으로, @assert 매크로를 사용하면 불리언으로 평가되는 모든 표현식을 검사할 수 있어요:
julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd
try...catch
어떤 오류는 반드시 치명적이지만, 대개는 프로그램이 원활하게 복구되기를 기대해요.
기본적으로 오류가 발생하면 현재 함수가 즉시 종료되고, 그 오류(정보 메시지 포함)는 호출한 함수로 전달돼요.
이 과정은 호출 스택을 따라 계속 올라가서, 최상위 코드가 오류 메시지와 함께 종료될 때까지 이어져요.
어느 단계에서든, 오류를 처리하려고 시도하는 try...catch 블록으로 그 오류를 가로챌 수 있어요.
julia> n = -1;
julia> try
log_n = log(n)
catch problem
if problem isa DomainError # number out of range
# See next section for more on @warn and @info
@warn "you may have supplied a negative real number: $n"
@info "trying with complex argument"
log_n = log(Complex(n)) # fallback calculation
elseif problem isa MethodError # no idea what n is
@error "please supply a valid argument"
else
rethrow() # the error could be anything else
end
end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im # success
위 예시에서 log(n)은 n이 양의 실수이거나 임의의 복소수여야 해요.
try ... catch는 음의 실수 값에서 생기는 문제를 잡아내고, 수학 표기법으로 올바른 복소수 답인 iπ를 반환해요.
예를 들어 문자열 인자를 넘기면, 사용자에게 고쳐 달라고 요청하는 것 외에는 복구할 방법이 없어요.
마지막으로 모든 경우를 처리하기 위해, DomainError도 MethodError도 아닌 모든 것에 대해 rethrow()를 추가했어요.
참고: 때로는 try...catch가 꼭 필요하지만, 남용하지 않도록 해요.
대신 if...else 블록을 사용할 수 있다면, 예외를 잡는 것보다 훨씬 성능이 좋아요.
위에서 다룬 error() 함수를 @error 매크로와 혼동하지 않도록 해요.
이 함수는 예외를 생성하고, 잡히지 않으면 호출 스택 위로 전달돼요.
@error 매크로는 @debug, @info, @warn과 함께 Logging 모듈의 일부이며, 프로그램 흐름을 바꾸지 않으면서 정보 메시지를 생성하기 위한 것이에요.
출력은 기본적으로 터미널로 가요(심각도에 따라 색으로 구분돼요). 하지만 실제 애플리케이션에서는 다른 가능성도 많아요.
julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1
julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1
try...catch 아래의 앞선 예시도 참고해 봐요.
엘레나는 신문 공장의 새 품질 관리자예요. 회사에 막 합류한 터라, 공장의 여러 공정을 살펴보며 무엇을 개선할 수 있을지 알아보기로 했어요. 그런데 기술자들이 품질 검사를 대부분 손으로 하고 있다는 걸 알게 됐어요. 자동화할 좋은 기회가 보여서, 프리랜서 개발자인 여러분에게 일부 기계를 모니터링할 소프트웨어를 만들어 달라고 부탁해요.
첫 번째 임무는 생산실의 습도 수준을 모니터링하는 소프트웨어를 작성하는 거예요. 이미 회사 소프트웨어에 연결된 센서가 있어서 방의 습도 백분율을 주기적으로 알려줘요.
습도 백분율이 너무 높으면 오류를 던지는 함수를 소프트웨어에 구현해야 해요.
습도가 허용 가능한 수준이면 Info 로그가 추가돼요.
함수 이름은 humiditycheck로 하고, 습도 백분율을 인자로 받아야 해요.
백분율이 70%를 넘으면 ErrorException과 함께 중단해야 해요(정확한 메시지는 중요하지 않지만, 측정된 습도 수준은 포함해야 해요).
그렇지 않으면 h가 습도 백분율인 메시지 "humidity level check passed: h%"와 함께 Info 로그를 추가해요.
julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%
엘레나는 첫 번째 작업에 매우 만족해서, 기계의 온도를 모니터링하는 일을 맡아 달라고 부탁해요. 기술자 그렉과 이야기하다 보면, 기계의 온도가 500°C를 넘으면 기술자들이 과열을 걱정하기 시작한다는 걸 알게 돼요.
기계에는 내부 온도를 측정하는 센서가 달려 있어요. 그런데 이 센서는 매우 민감해서 자주 고장 나요. 이 경우 기술자들이 센서를 교체해야 해요.
여러분이 할 일은 온도를 인자로 받는 temperaturecheck 함수를 구현하는 거예요. 이 함수는 모든 게 정상이면 로그를 추가하고, 센서가 고장 났거나 기계가 과열되기 시작하면 오류를 던져요.
나중에 오류에 따라 다르게 대응해야 한다는 것을 알고 있으니, 두 종류의 오류를 구분할 방법이 필요해요.
nothing이 돼요.
이 경우 ArgumentError와 함께 중단해야 해요(메시지는 중요하지 않아요).DomainError를 던져야 해요.t가 온도인 메시지 "temperature check passed: t °C"와 함께 Info 로그를 추가해요.julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken
julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"
julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C
다음 작업을 위해서는 더 일반적인, 모든 상황을 포괄하는 오류를 정의해야 해요.
오류라는 점과 이름이 MachineError라는 점 외에는 구현 세부 사항은 중요하지 않아요.
도움이 된다고 생각하는 필드와 메시지를 자유롭게 포함해도 돼요.
이제 기계가 오류를 감지할 수 있고 사용자 지정 기계 오류도 있으니, 모든 것이 어떻게 동작하는지 보고하는 래퍼 함수를 추가해요. 이 래퍼는 이전 함수들의 로그를 반환하는 것 외에도, 발생하는 실패 유형에 따라 로그를 추가해야 해요.
ErrorException이 발생하면, h가 습도 백분율인 메시지 "humidity level check failed: h%"와 함께 Error 로그를 추가해야 해요.ArgumentError가 발생하면, 메시지 "sensor is broken"와 함께 Warn 로그를 추가해야 해요.DomainError가 발생하면, t가 온도인 메시지 "overheating detected: t °C"와 함께 Error 로그를 추가해야 해요.MachineError 하나를 던져야 해요.humiditycheck와 temperaturecheck의 로그만 추가돼요.습도와 온도를 인자로 받는 machinemonitor() 함수를 구현해요.
julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C
julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated
Error: MachineError
julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated
Error: MachineError
julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated
Error: MachineError
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.