Програмісти зазвичай намагаються писати бездоганне програмне забезпечення і зазвичай зазнають невдачі.
Щось іде не так, несподівано, і нам потрібно вміти з цим давати раду.
Деякі творці мов уважають, що пріоритет - виявити помилку якнайшвидше, а потім перервати виконання з інформативним повідомленням, щоб допомогти з налагодженням.
Мови для науки про дані зазвичай мають виваженіший підхід. Деякі помилки настільки серйозні, що потрібне негайне завершення, але часто краще позначити проблему як таку, з якою розберемося пізніше, і продовжити виконання.
У концепції Ніщо ми бачили, що Julia надає різні заповнювачі для проблемних значень: missing, NaN та Inf.
Чи кращий такий підхід за завершення програми в конкретній ситуації, вирішує сам програміст.
Кілька слів про термінологію перед тим, як заглибитися в деталі: документація Julia вважає слова «помилка» й «виняток» здебільшого взаємозамінними. Викладений нижче текст може бути таким самим непослідовним.
На цьому місці силабусу ми, мабуть, уже бачили чимало повідомлень про помилки від Julia. Наприклад:
julia> Int(3.14)
ERROR: InexactError: Int64(3.14)
Спроба привести число з плаваючою комою до цілого числа спричиняє втрату точності, тож ми отримуємо InexactError.
InexactError - це тип, один із кількох (наразі 25), вбудованих у Julia як стандартні.
Усі вони - підтипи 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().
Вона приймає як аргумент рядок тексту (англ. string), або складові цього рядка тексту:
julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong
Створювати нові типи помилок у принципі дуже легко.
Достатньо додати ще один підтип Exception:
julia> struct MyError <: Exception end
julia> throw(MyError)
ERROR: MyError
Основна ідея твердження: «це твердження має бути правдою, тож гучно скаржмося, якщо воно неправда». Цінність цього виявляється переважно під час налагодження, адже продакшн-код ніколи не повинен провалювати твердження.
У концепції Типи ми бачили, що можна додавати твердження типу, наприклад, щоб перевірити тип поверненого значення функції.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
Загалом, макрос @assert дає змогу перевірити будь-який вираз, який дає булеве значення (англ. Boolean):
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π у математичному записі.
Якщо ми передамо, наприклад, аргумент-рядок тексту, то відновити роботу не вдасться, хіба що попросити користувача його виправити.
Як останній загальний випадок ми додали rethrow() для всього, що не є ні DomainError, ні MethodError.
Примітка: Іноді 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 і приймати відсоток вологості як аргумент.
Потрібно зупинитися з ErrorException (точне повідомлення не важливе, але воно мусить містити виміряний рівень вологості), якщо відсоток перевищує 70%.
Інакше додайте Info-лог із повідомленням "humidity level check passed: h%", де h - це відсоток вологості.
julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%
Олена дуже задоволена першим завданням і просить нас зайнятися стеженням за температурою машин. Під час розмови з техніком Грегом ми дізнаємося, що якщо температура машини перевищує 500 °C, техніки починають хвилюватися через перегрів.
Машина обладнана датчиком, який вимірює її внутрішню температуру. Варто знати, що датчик дуже чутливий і часто ламається. У такому разі технікам доведеться його замінити.
Потрібно реалізувати функцію temperaturecheck, яка приймає температуру як аргумент і або додає лог, якщо все гаразд, або викидає помилку, якщо датчик зламався чи машина починає перегріватися.
Знаючи, що згодом потрібно буде реагувати по-різному залежно від помилки, нам потрібен механізм, щоб розрізняти ці два види помилок.
nothing.
У такому разі потрібно зупинитися з ArgumentError (повідомлення не важливе).DomainError, який містить виміряну температуру."temperature check passed: t °C", де t - це температура.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, потрібно додати Error-лог із повідомленням "humidity level check failed: h%", де h - це відсоток вологості.ArgumentError, потрібно додати Warn-лог із повідомленням "sensor is broken".DomainError, потрібно додати Error-лог із повідомленням "overheating detected: t °C", де t - це температура.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 вправ та справжнє наставництво від людей, і все це безкоштовно.