Перевірити обробку перших проти виключень?


88

Я працюю над книгою "Head First Python" (це мою мову вивчити в цьому році), і я потрапив до розділу, де вони сперечаються про дві методи коду:
Перевірка обробки перших проти винятків.

Ось зразок коду Python:

# Checking First
for eachLine in open("../../data/sketch.txt"):
    if eachLine.find(":") != -1:
        (role, lineSpoken) = eachLine.split(":",1)
        print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())

# Exception handling        
for eachLine in open("../../data/sketch.txt"):
    try:
        (role, lineSpoken) = eachLine.split(":",1)
        print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
    except:
        pass

Перший приклад стосується безпосередньо проблеми у .splitфункції. Другий лише дозволяє оброблювачу винятків впоратися з цим (і ігнорує проблему).

У книзі вони стверджують, що використовувати обробку виключень, а не перевіряти спочатку. Аргумент полягає в тому, що код винятку вловлює всі помилки, коли перевірка спочатку зафіксує лише те, про що ви думаєте (і ви пропустите кутові випадки). Мене вчили перевіряти спочатку, тому мій інстинкт мав це робити, але їх ідея цікава. Я ніколи не думав використовувати обробку винятків для розгляду справ.

Який із двох в цілому вважається кращою практикою?


12
Цей розділ у книзі не розумний. Якщо ви знаходитесь у циклі, і ви кидаєте винятки за цією чисельністю, це дуже дорого. Я спробував окреслити кілька хороших моментів, коли це зробити.
Джейсон Себрінг

9
Просто не потрапляйте в пастку "файл існує перевірка". Файл існує! = Має доступ до файлу, або він буде існувати протягом 10 мс, необхідних для отримання відкритого дзвінка до мого файлу тощо. Blogs.msdn.com/b/jaredpar/archive/2009/04/27/…
Біллі ONeal

11
Винятки в Python вважаються інакше, ніж в інших мовах. Наприклад, спосіб ітерації через колекцію - викликати на неї .next (), поки не викине виняток.
WuHoUnited

4
@ emeraldcode.com Це не зовсім вірно щодо Python. Я не знаю специфіки, але мова побудована навколо цієї парадигми, тому кидання виключень не настільки дороге, як в інших мовах.
Ізката

Це сказало, що для цього прикладу я б використав оператор-захист:, if -1 == eachLine.find(":"): continueтоді й решта циклу також не буде відступати.
Ізката

Відповіді:


68

У .NET звичайна практика уникати надмірного використання Винятків. Одним з аргументів є продуктивність: в .NET, викидання виключення обчислювально дорого.

Ще одна причина, щоб уникнути їх надмірного використання, полягає в тому, що читати код, який занадто багато покладається на них, може бути дуже важким. Запис Джоела Спольського добре описує проблему.

В основі аргументу лежить наступна цитата:

Аргументація полягає в тому, що я вважаю, що винятки є не кращими за "goto's", які вважаються шкідливими з 60-х років, оскільки вони створюють різкий перехід від однієї точки коду до іншої. Насправді вони значно гірші, ніж гото:

1. Вони невидимі у вихідному коді . Дивлячись на блок коду, включаючи функції, які можуть або не можуть викидати винятки, немає ніякого способу побачити, які винятки можуть бути кинуті та звідки. Це означає, що навіть ретельна перевірка коду не виявляє потенційних помилок.

2. Вони створюють занадто багато можливих точок виходу для функції. Щоб написати правильний код, вам дійсно доведеться продумати кожен можливий шлях коду через вашу функцію. Кожен раз, коли ви викликаєте функцію, яка може викликати виняток, і не вловлює її на місці, ви створюєте можливості для сюрпризних помилок, викликаних функціями, які різко припиняються, залишаючи дані в непослідовному стані або інших кодових шляхів, які ви не зробили думати про.

Особисто я викидаю винятки, коли мій код не може робити те, про що він укладений. Я схильний використовувати try / catch, коли я збираюся мати справу з чимось поза межами моєї межі процесу, наприклад, виклик SOAP, виклик бази даних, введення файлу в систему або системний виклик. В іншому випадку я намагаюся кодувати захисно. Це не важке і швидке правило, але це загальна практика.

Скотт Гензельман також пише про винятки в .NET тут . У цій статті він описує кілька головних правил щодо винятків. Мій улюблений?

Не слід кидати винятки для речей, які постійно відбуваються. Тоді вони були б "ординарками".


5
ось ще один момент: якщо ведення журналу винятків увімкнено в усьому додатку, краще використовувати виняток лише для виняткових умов, а не для розпорядників. В іншому випадку журнал стане захаращеним, а реальні причини помилок затьмаряться.
rwong

2
Гарна відповідь. Зауважте, хоча винятки мають високу ефективність на більшості платформ. Однак, як ви зауважили з моїми коментарями щодо інших відповідей, продуктивність не є врахуванням у випадку вирішення правила ковдри щодо того, як щось зашифрувати.
mattnz

1
Цитата Скотта Хензельмана краще описує ставлення .Net до винятків, ніж "надмірне використання". Продуктивність часто згадується, але справжній аргумент - це зворотне, чому ВИ ПОТРІБНО використовувати винятки - це робить код важче зрозуміти та вирішити, коли звичайний стан призводить до виключення. Що стосується Джоеля, то точка 1 насправді є позитивною (невидимий означає, що код показує, що він робить, а не те, що не робить), а пункт 2 не має значення (ви вже в невідповідному стані, і не повинно бути винятку) . Проте +1 для "не можу зробити те, про що просили".
jmoreno

5
Хоча ця відповідь чудово підходить для .Net, вона не дуже пітонічна , тому, враховуючи, що це питання пітона, я не розумію, чому відповідь Ivc більше не проголосували.
Марк Бут

2
@IanGoldby: ні. Обробка винятків насправді краще описується як відновлення виключень. Якщо ви не можете відновити виняток, ви, ймовірно, не повинні мати жодного коду обробки винятків. Якщо метод A викликає метод B, який викликає C, і C кидає, то, швидше за все, ВСІ АБО Б А повинні відновитись, а не обидва. Рішення "якщо я не можу зробити X я зроблю Y" слід уникати, якщо Y вимагає, щоб хтось інший виконав завдання. Якщо ви не можете закінчити завдання, залишається лише очищення та ведення журналу. Очищення в .net має бути автоматичним, журнал повинен бути централізованим.
jmoreno

78

Зокрема, в Python зазвичай вважається кращою практикою ловити виняток. Це, як правило, називається простіше просити прощення, ніж дозвіл (EAFP), порівняно з Look For You Leap (LBYL). Бувають випадки, коли LBYL в деяких випадках видасть тонкі помилки .

Однак будьте обережні як із голими except:висловлюваннями, так і з зашифрованими, за винятком тверджень, оскільки вони можуть також маскувати помилки - щось подібне було б краще:

for eachLine in open("../../data/sketch.txt"):
    try:
        role, lineSpoken = eachLine.split(":",1)
    except ValueError:
        pass
    else:
        print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())

8
Як програмувач .NET, я пригнічую це. Але знову ж таки, ви люди робите все дивно. :)
Філ

Це винятково розчаровує (каламбур не призначений), коли API не узгоджуються з приводу того, які винятки кидаються за яких обставин, або коли декілька різних видів відмов підпадають під один і той же тип винятків.
Джек

Отже, ви використовуєте той самий механізм для несподіваних помилок і очікуваних видових значень повернення. Це приблизно так само добре, як використання 0 як числа, помилкового bool та недійсного вказівника, який закриє ваш процес із кодом виходу 128 + SIGSEGV, адже як зручніше, вам зараз не потрібні різні речі. Як спорк! Або взуття на носках ...
yeoman

2
@yeoman, коли кинути виняток - це інше питання, це стосується використання try/ exceptа не встановлення умовного для "це наступне ймовірність кинути виняток", і практика Python, безумовно, віддає перевагу першому. Це не завадить, що такий підхід тут (мабуть) більш ефективний, оскільки, у випадку, коли розкол вдався, ви ходите по рядку лише один раз. Щодо того, чи splitварто тут кидати виняток, я б сказав, що це безумовно слід - одне поширене правило - ви повинні кидати, коли не можете робити те, що говорить ваше ім'я, і ​​ви не можете розділитись на відсутньому роздільнику.
lvc

Я не вважаю це поганим, повільним чи жахливим, тим більше, що трапляється лише конкретний виняток. Відповідь мені насправді подобається Python. Це просто смішно, як іноді це зовсім не відчуває смаку, як сказано, що C використовує число нуль, спорти та улюблені взуття усього часу Рандалла Манро з пальцями ніг :) Плюс до того, коли я перебуваю в Python та API, він каже, що це як це зробити, я піду :) Я заздалегідь перевіряю умови, звичайно, ніколи не є доброю ідеєю через одночасність, супроводи чи одну з тих, що додаються далі вниз ...
yeoman

27

Прагматичний підхід

Ви повинні бути захисними, але до певної точки. Ви повинні написати обробку виключень, але до пункту. Я буду використовувати веб-програмування як приклад, тому що тут я живу.

  1. Припустимо, що весь вхід користувача поганий і пишіть захисно лише до точки перевірки типу даних, перевірки шаблону та зловмисного введення. Оборонне програмування повинно бути речами, які потенційно можуть траплятися дуже часто, які ви не можете контролювати.
  2. Напишіть обробку винятків для мережевих служб, які часом можуть виходити з ладу та витончено обробляти відгуки користувачів. Програмування винятків слід використовувати для мережевих речей, які час від часу можуть виходити з ладу, але, як правило, є надійними І вам потрібно підтримувати роботу програми.
  3. Не заважайте писати оборонно в рамках програми після підтвердження вхідних даних. Це марно витрачати час і роздуває ваш додаток. Нехай вона підірветься, бо це щось дуже рідкісне, з яким не варто звертатися, або це означає, що вам потрібно уважніше подивитися на кроки 1 і 2.
  4. Ніколи не пишіть обробку винятків у вашому основному коді, який не залежить від мережевого пристрою. Це погано програмувати і дорого коштувати. Наприклад, написання пробного лову в разі масиву поза межами циклу означає, що ви не правильно програмували цикл в першу чергу.
  5. Нехай все обробляється за допомогою централізованого реєстрації помилок, що фіксує винятки в одному місці після виконання вищевказаних процедур. Ви не можете зафіксувати кожен крайовий випадок, як це може бути нескінченним, вам потрібно лише написати код, який обробляє очікувану операцію. Ось чому ви використовуєте центральну обробку помилок як крайній спосіб.
  6. TDD приємний тим, що певним чином намагається привернути вас без набряку, тобто дає певну впевненість у нормальній роботі.
  7. Бонусними балами є використання інструменту покриття коду, наприклад, Стамбул є хорошим для вузла, оскільки це показує вам, де ви не тестуєтесь.
  8. Застереження до всього цього - винятки, сприятливі для розробників . Наприклад, мова кине, якщо ви неправильно використали синтаксис і поясните чому. Тож чи повинні ваші бібліотеки утиліт залежать від основної частини вашого коду.

Це з досвіду роботи у великих командних сценаріях.

Аналогія

Уявіть, якби ви весь час носили космічний костюм всередині МКС. Важко було б взагалі йти до ванної чи їсти. Було б супер об'ємно всередині космічного модуля рухатися. Це було б смоктати. Написання купки спроб улов у ваш код подібне. У вас повинен бути якийсь момент, коли ви говорите, ей, я забезпечив МКС, і мої космонавти всередині все добре, тому просто не практично носити космічний костюм для кожного сценарію, який, можливо, може статися.


4
Проблема в пункті 3 полягає в тому, що вона передбачає, що програма, і програмісти, які працюють над нею, ідеальні. Вони не є, тому найкраще програму захищати з урахуванням цього. Приблизні суми на ключовому етапі можуть зробити програмне забезпечення набагато надійнішим, ніж менталітет "Якщо входи перевіряються все ідеально".
mattnz

ось для чого тестування.
Джейсон Себрінг

3
Тестування - це далеко не все. Я ще не бачив тестового набору, який має 100% код та "екологічне" покриття.
Мар'ян Венема

1
@emeraldcode: Ви хочете працювати зі мною, я б хотів, щоб у команді був хтось, хто завжди, за винятком, перевіряє кожну перестановку кожного крайового випадку, яке програмне забезпечення коли-небудь виконується. Повинно бути приємно, знаючи, що абосолуїт впевнений, що ваш код ідеально перевірений.
mattnz

1
Погодьтеся. Існують сценарії, що і захисне програмування, і обробка винятків працюють добре і погано, і ми, будучи програмістами, повинні навчитися їх розпізнавати та вибирати техніку, яка найкраще підходить. Точка 3 мені подобається, тому що я вважаю, що нам потрібно на певному рівні коду припускати, що слід виконувати деякі контекстуальні умови. Ці умови задовольняються захисним кодуванням у зовнішньому шарі коду, і я думаю, що обробка винятків є придатною, коли ці припущення порушені у внутрішньому шарі.
яобін

15

Основний аргумент книги полягає в тому, що версія для винятків коду є кращою, оскільки вона захопить все, що ви могли б не помітити, якби спробували написати власну перевірку помилок.

Я думаю, що це твердження справедливо лише в дуже конкретних обставинах - там, де вам не байдуже, чи правильний результат.

Немає сумнівів, що підвищення винятків є надійною та безпечною практикою. Ви повинні робити це кожного разу, коли відчуєте, що в поточному стані програми є щось, з чим ви (як розробник) не можете чи не хочете мати справу.

Ваш приклад, однак, про ловлі виключення. Якщо ви спіймаєте виняток, ви не захищаєте себе від сценаріїв, які, можливо, ви не помітили. Ви робите саме навпаки: ви припускаєте, що ви не пропустили жодного сценарію, який міг би спричинити виняток такого типу, і тому ви впевнені, що добре його зловити (і тим самим запобігти тому, щоб програма вийшла з програми, як би будь-який неприхований виняток).

Використовуючи підхід виключення, якщо ви бачите ValueErrorвиняток, ви пропускаєте рядок. Використовуючи традиційний підхід, який не є винятком, ви підраховуєте кількість повернених значень split, і якщо це менше 2, ви пропускаєте рядок. Якщо ви відчуваєте себе більш захищеними від підходу до винятку, оскільки, можливо, ви забули деякі інші "помилкові" ситуації у вашій традиційній перевірці помилок, і ви except ValueErrorзахопили б їх для вас?

Це залежить від характеру вашої програми.

Якщо ви пишете, наприклад, веб-браузер або відеоплеєр, проблема з входами не повинна спричиняти збій, за винятком невловимого винятку. Набагато краще вивести щось чутливо віддалене (навіть якщо, строго кажучи, неправильне), ніж кинути.

Якщо ви пишете заявку, де важлива коректність (наприклад, бізнес або інженерне програмне забезпечення), це був би жахливий підхід. Якщо ви забули про якийсь сценарій, який виникає ValueError, найгірше, що ви можете зробити - це мовчки ігнорувати цей невідомий сценарій і просто пропустити лінію. Ось як дуже тонкі та дорогі помилки виявляються в програмному забезпеченні.

Ви можете подумати, що єдиний спосіб, який ви бачите ValueErrorв цьому коді, - це splitповернути лише одне значення (замість двох). Але що робити, якщо printзгодом у вашій заяві почнеться вираз, який виникає ValueErrorза певних умов? Це призведе до того, що ви пропустите деякі рядки не тому, що вони пропускають :, а тому, що printна них не вдається. Це приклад тонкої помилки, про яку я згадував раніше - ви нічого не помітили, просто втратите кілька рядків.

Моя рекомендація полягає у тому, щоб уникнути (але не підвищувати!) Винятків у коді, коли отримання неправильного результату гірше, ніж вихід. Єдиний раз, коли я вловлю виняток у такому коді - це, коли у мене справді тривіальне вираження, тому я можу легко пояснити, що може спричинити кожен із можливих типів виключень.

Що стосується впливу на ефективність використання винятків, то це тривіально (у Python), якщо винятки не зустрічаються часто.

Якщо ви використовуєте винятки для обробки умов, що виникають у звичайному режимі, ви можете в деяких випадках заплатити величезні витрати на продуктивність. Наприклад, припустимо, ви віддалено виконуєте якусь команду. Ви можете перевірити, чи текст вашої команди проходить принаймні мінімальне підтвердження (наприклад, синтаксис). Або ви можете зачекати, коли буде винято виняток (що відбувається лише після того, як віддалений сервер проаналізує вашу команду та знайде з цим проблему). Очевидно, колишній набирає порядку на швидкість. Ще один простий приклад: ви можете перевірити, чи число дорівнює нулю ~ 10 разів швидше, ніж намагатися виконати ділення, а потім перехопити виняток ZeroDivisionError.

Ці міркування мають значення лише в тому випадку, якщо ви часто надсилаєте неправильно сформовані рядки команд на віддалені сервери або отримуєте аргументи з нульовим значенням, які використовуєте для поділу.

Примітка: я припускаю, що ви б використовували except ValueErrorзамість справедливого except; як зазначали інші, і як говориться в самій книзі на кількох сторінках, ви ніколи не повинні використовувати голі except.

Ще одне зауваження: правильний підхід, що не стосується виключення, - це підрахунок кількості повернених значень split, а не пошук :. Останнє є надто повільним, оскільки повторює виконану роботу splitі може майже подвоїти час виконання.


6

Як правило, якщо ви знаєте, що твердження може призвести до недійсного результату, протестуйте його та вирішіть його. Використовуйте винятки для речей, яких ви не очікуєте; речі, які є "винятковими". Це робить код зрозумілішим у контрактному розумінні ("не повинен бути нульовим" як приклад).


2

Використовуйте те, що коли-небудь добре працює в ..

  • обрана мова програмування з точки зору читабельності та ефективності коду
  • ваша команда та набір узгоджених конвенцій про коди

Як обробка винятків, так і захисне програмування - це різні способи вираження однакових намірів.


0

TBH, не має значення, використовуєте ви try/exceptмеханіку чи ifперевірку тверджень. Ви часто бачите і EAFP, і LBYL у більшості базових ліній Python, при цьому EAFP є дещо частішим. Іноді ЕСПЦ є набагато зручнішим для читання / ідіоматичне, але в даному випадку я думаю , що це добре в будь-якому випадку.

Однак ...

Буду обережним, використовуючи вашу поточну інформацію. Кілька очевидних проблем зі своїм кодом:

  1. Дескриптор файлу просочився. Сучасні версії CPython ( специфічний інтерпретатор Python) фактично закриють його, оскільки це анонімний об'єкт, який є лише в області застосування під час циклу (gc запустить його після циклу). Однак інші перекладачі не мають такої гарантії. Вони можуть витікати дескриптор прямо. Ви майже завжди хочете використовувати withідіому під час читання файлів на Python: винятків дуже мало. Це не одна з них.
  2. Поводження з винятками Pokemon нахмуриться, оскільки воно маскує помилки (тобто голі exceptтвердження, які не вловлюють конкретний виняток)
  3. Ніт: для розпакування кортежу вам не потрібні паролі. Можна просто зробитиrole, lineSpoken = eachLine.split(":",1)

Ivc має гарну відповідь щодо цього та EAFP, але також просочує дескриптор.

Версія LBYL не обов'язково є настільки ефективною, як версія EAFP, тому твердження, що кидання винятків "дороге з точки зору продуктивності", категорично неправдиво. Це дійсно залежить від типу обробних рядків:

In [33]: def lbyl(lines):
    ...:     for line in lines:
    ...:         if line.find(":") != -1:
    ...:             # Nuke the parens, do tuple unpacking like an idiomatic Python dev.
    ...:             role, lineSpoken = line.split(":",1)
    ...:             # no print, since output is obnoxiously long with %timeit
    ...:

In [34]: def eafp(lines):
    ...:     for line in lines:
    ...:         try:
    ...:             # Nuke the parens, do tuple unpacking like an idiomatic Python dev.
    ...:             role, lineSpoken = eachLine.split(":",1)
    ...:             # no print, since output is obnoxiously long with %timeit
    ...:         except:
    ...:             pass
    ...:

In [35]: lines = ["abc:def", "onetwothree", "xyz:hij"]

In [36]: %timeit lbyl(lines)
100000 loops, best of 3: 1.96 µs per loop

In [37]: %timeit eafp(lines)
100000 loops, best of 3: 4.02 µs per loop

In [38]: lines = ["a"*100000 + ":" + "b", "onetwothree", "abconetwothree"*100]

In [39]: %timeit lbyl(lines)
10000 loops, best of 3: 119 µs per loop

In [40]: %timeit eafp(lines)
100000 loops, best of 3: 4.2 µs per loop

-4

По суті, обробка винятків повинна бути більш підходящою для мов OOP.

Другий момент - це продуктивність, тому що не потрібно виконувати eachLine.findдля кожного рядка.


7
-1: Продуктивність є надзвичайно поганою причиною правил ковдри.
mattnz

3
Ні, винятки абсолютно не пов'язані з ООП.
Паббі

-6

Я думаю, що захисне програмування шкодить продуктивності. Ви також повинні вибирати лише винятки, з якими збираєтесь працювати, нехай час виконання буде мати справу з тим винятком, з яким ви не знаєте, як впоратися.


7
І все-таки anotehr -1 за занепокоєння щодо продуктивності щодо читабельності, основного розуму. Продуктивність не є причиною.
mattnz

Чи можу я знати, чому ти розбігаєшся поширювати -1, не пояснюючи це? Оборонне програмування означає більше рядків коду, тобто нижча продуктивність. Хтось хотів би пояснити, перш ніж збити бал?
Маной

3
@Manoj: Якщо ви не помірялися з профілером і не знайшли блок коду неприйнятно повільним, код для читабельності та ремонтопридатності задовго до продуктивності.
Даеніт

Що сказав @Manoj з додаванням, що менше коду універсально означає менше працювати над налагодженням та підтримкою. Вражання часу розробника на що-небудь менше, ніж ідеальний код надзвичайно високий. Я припускаю (як я), що ви не пишете ідеального коду, вибачте мене, якщо я помиляюся.
mattnz

2
Дякую за посилання - Цікаво прочитав, що я маю з цим погодитися, до певної міри ... Працюючи над життєво важливими системами, як і я "Система надрукувала слід стека, тому ми точно знаємо, чому ці 300 людей загинули без потреби. .... "Насправді не вийде занадто добре зійти в позицію свідків. Я думаю, це одна з тих речей, де кожна ситуація має різний відповідний відгук.
mattnz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.