Загальний консенсус "не використовуйте винятків!" здебільшого походить з інших мов і навіть там іноді застаріла.
У програмі C ++ викидання виключень дуже дороге через "розмотування стека". Кожна заява локальної змінної є як with
вираз у Python, і об'єкт у цій змінній може запускати деструктори. Ці деструктори виконуються, коли викидається виняток, але також при поверненні з функції. Ця "ідіома RAII" є невід'ємною мовною особливістю і дуже важливо писати надійний, правильний код - тому RAII проти дешевих винятків був компромісом, який C ++ вирішив у напрямку RAII.
На початку C ++ багато коду не було написано безпечним для винятків способом: якщо ви фактично не використовуєте RAII, легко просочити пам'ять та інші ресурси. Отже, якщо викидати винятки, цей код буде неправильним. Це вже не розумно, оскільки навіть стандартна бібліотека C ++ використовує винятки: ви не можете робити вигляд, що винятки не існують. Однак винятки все ще є проблемою при поєднанні коду C із C ++.
У Java кожен виняток має пов'язаний слід стека. Трасування стека є дуже цінним при налагодженні помилок, але витрачається на витрачені зусилля, коли виняток ніколи не друкується, наприклад, тому що він використовувався лише для управління потоком.
Тож у цих мовах винятки є "занадто дорогими", щоб використовуватись як контрольний потік. У Python це менше питання, а винятки - значно дешевші. Крім того, мова Python вже страждає від деяких накладних витрат, що робить вартість винятків непомітною порівняно з іншими конструкціями контрольного потоку: наприклад, перевірка наявності запису dict із явним тестом на членство, if key in the_dict: ...
як правило, настільки ж швидка, як просто доступ до запису the_dict[key]; ...
та перевірка, якщо ви отримати KeyError. Деякі цілісні мовні функції (наприклад, генератори) розроблені з точки зору винятків.
Тож поки немає жодних технічних причин, щоб спеціально уникати винятків у Python, все ще виникає питання, чи слід використовувати їх замість повернених значень. Проблеми на рівні дизайну з винятками:
вони зовсім не очевидні. Ви не можете легко переглянути функцію і побачити, які винятки вона може кинути, тож ви не завжди знаєте, що зловити. Повертається величина, як правило, більш чітко визначена.
Винятки становлять не локальний потік управління, що ускладнює ваш код. Коли ви кидаєте виняток, ви не знаєте, куди відновиться потік управління. Для помилок, з якими не вдається негайно впоратися, це, мабуть, гарна ідея, коли повідомляти абоненту про стан, це зовсім не потрібно.
Культура Python, як правило, похила на користь винятків, але легко переходити за борт. Уявіть list_contains(the_list, item)
функцію, яка перевіряє, чи містить у списку елемент, рівний цьому елементу. Якщо результат повідомляється за винятками, що абсолютно дратує, тому що ми повинні називати це так:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Повернення булінгу буде набагато зрозумілішим:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Якщо функція вже повинна повертати значення, то повернення спеціального значення для спеціальних умов є досить схильним до помилок, тому що люди забудуть перевірити це значення (це, мабуть, причина 1/3 проблем у С). Виняток, як правило, більш правильний.
Хорошим прикладом є pos = find_string(haystack, needle)
функція, яка шукає перше виникнення needle
рядка в рядку `копиця сіна і повертає початкове положення. Але що робити, якщо в копиці сіна не міститься голка-рядок?
Рішення C і імітоване Python - повернути особливе значення. У C це нульовий покажчик, у Python це -1
. Це призведе до дивовижних результатів, коли позиція використовується в якості рядкового індексу без перевірки, тим більше, що -1
це дійсний індекс в Python. У C ваш покажчик NULL принаймні надасть вам сегмент за замовчуванням.
У PHP повертається особливе значення іншого типу: булеве значення, FALSE
а не ціле число. Як виявляється, це насправді не є кращим завдяки неявним правилам перетворення мови (але зауважте, що і в Python булели можуть використовуватися як ints!). Функції, які не повертають послідовний тип, як правило, вважаються дуже заплутаними.
Більш надійним варіантом було б викинути виняток, коли рядок неможливо знайти, що гарантує, що під час нормального потоку управління неможливо випадково використовувати спеціальне значення замість звичайного значення:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Крім того, завжди можна повернути тип, який не може бути використаний безпосередньо, але його слід спочатку розкрутити, наприклад, кортеж з результатом bool, де булевий сигнал вказує на те, чи стався виняток, чи результат корисний. Потім:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Це змушує вас негайно впоратися з проблемами, але це дуже дратує. Це також заважає легко зав'язувати функцію. Кожен виклик функції потребує трьох рядків коду. Голанг - мова, яка вважає, що ця неприємність варта безпеки.
Отже, підсумовуючи, винятки не є цілком безпроблемними і їх можна остаточно використовувати, особливо коли вони замінюють "нормальне" повернене значення. Але коли вони використовуються для сигналізації спеціальних умов (не обов'язково лише помилок), то винятки можуть допомогти вам розробити чисті, інтуїтивні, прості у використанні API, які важко використовувати.