Проверьте сначала против обработки исключений?

88

Я работаю над книгой «Head First Python» (это мой язык для изучения в этом году), и я попал в раздел, где они спорят о двух методах кода:
Проверка First против Exception обработка.

Вот пример кода 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функции. Второй просто позволяет обработчику исключений справиться с этим (и игнорирует проблему).

В книге утверждается, что вместо обработки сначала используется обработка исключений. Аргумент заключается в том, что код исключения будет перехватывать все ошибки, тогда как проверка в первую очередь будет отлавливать только то, о чем вы думаете (и вы пропустите угловые случаи). Меня научили проверять в первую очередь, поэтому мой первоначальный инстинкт должен был это сделать, но их идея интересна. Я никогда не думал об использовании обработки исключений для рассмотрения дел.

Что из двух обычно считается лучшей практикой?

jmq
источник
12
Этот раздел в книге не умный. Если вы в цикле, и вы бросаете исключения снова и снова, это очень дорого. Я попытался наметить некоторые хорошие моменты, когда это сделать.
Джейсон Себринг
9
Только не попадайтесь в ловушку "проверка файла существует". Файл существует! = Имеет доступ к файлу или что он будет существовать в течение 10 мс, необходимых для получения доступа к моему открытию файла и т. Д. Blogs.msdn.com/b/jaredpar/archive/2009/04/27/…
Billy ONeal
11
Об исключениях в Python думают иначе, чем в других языках. Например, способ перебора коллекции состоит в том, чтобы вызывать .next () для нее, пока она не сгенерирует исключение.
WuHoUnited
4
@ emeraldcode.com Это не совсем верно в отношении Python. Я не знаю специфики, но язык был построен вокруг этой парадигмы, поэтому создание исключений не так дорого, как в других языках.
Иската
Тем не менее, для этого примера я бы использовал выражение защиты: if -1 == eachLine.find(":"): continueтогда остаток цикла также не будет отступать.
Иската

Ответы:

68

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

Другая причина избежать их чрезмерного использования заключается в том, что может быть очень трудно читать код, который слишком сильно зависит от них. Запись в блоге Джоэла Спольски хорошо описывает проблему.

В основе аргумента лежит следующая цитата:

Причина в том, что я считаю исключения не лучше, чем «goto», считающиеся вредными с 1960-х годов, в том смысле, что они создают резкий переход от одной точки кода к другой. На самом деле они значительно хуже, чем у Гото:

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 выбрасывает, то, скорее всего, EITH A OR B должно восстановиться, а не оба. Решение «если я не могу сделать X, я сделаю Y» следует избегать, если Y требует, чтобы кто-то еще закончил задачу. Если вы не можете выполнить задачу, все, что осталось, это очистить и записать в журнал. Очистка в .net должна быть автоматической, регистрация должна быть централизованной.
Jmoreno
78

В частности, в Python обычно лучше ловить исключение. Как правило, его называют « Проще просить прощения, чем разрешения» (EAFP) по сравнению с «Look Before 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())
LVC
источник
8
Как программист .NET, я съеживаюсь от этого. Но опять же, вы, люди, делаете все странно. :)
Фил
Это крайне неприятно (каламбур не предназначен), когда API не согласованы относительно того, какие исключения генерируются при каких обстоятельствах, или когда несколько разных видов сбоев генерируются под одним и тем же типом исключения.
Джек,
Таким образом, вы в конечном итоге используете тот же механизм для непредвиденных ошибок и ожидаемого вида возвращаемых значений. Это почти так же хорошо, как использование 0 в качестве числа, ложное логическое выражение и недопустимый указатель, который завершит ваш процесс с кодом выхода 128 + SIGSEGV, потому что, насколько это удобно, вам сейчас не нужны разные вещи. Понравился spork! Или туфли на пальцах ног ...
Йомен
2
@yeoman, когда выбрасывать исключение - это другой вопрос, этот вопрос касается использования try/, exceptа не установки условного выражения для «есть вероятность, что следующее вызовет исключение», и практика Python определенно предпочтительнее первого. Не повредит, что этот подход (возможно) более эффективен, поскольку в случае успешного разбиения вы можете пройти строку только один раз. Что касается того, splitдолжно ли здесь генерироваться исключение, я бы сказал, что оно определенно должно - одно общее правило: вы должны бросать, когда вы не можете делать то, что говорит ваше имя, и вы не можете разделить пропущенный разделитель.
lvc
Я не нахожу это плохим, медленным или ужасным, тем более, что ловится только определенное исключение. На самом деле мне нравится Python. Просто забавно, что иногда он вообще не имеет никакого вкуса, как сказал С, используя нулевое число, Spork и неизменно любимые ботинки Рэнделла Манро с пальцами ног :) :) Плюс, когда я нахожусь в Python, и API говорит, что это способ сделать это, я пойду на это :) Проверка предварительных условий, конечно, никогда не будет хорошей идеей из-за параллелизма, сопрограмм или одного из тех, которые добавляются в будущем ...
Йомен
27

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

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

  1. Предположим, что все вводимые пользователем данные неверны и защищенно пишут только до точки проверки типа данных, проверки шаблонов и вредоносного внедрения. Защитное программирование должно быть вещами, которые потенциально могут происходить очень часто, которые вы не можете контролировать.
  2. Напишите обработку исключений для сетевых служб, которые могут иногда давать сбой и корректно обрабатывать отзывы пользователей. Программирование исключений следует использовать для сетевых вещей, которые могут время от времени выходить из строя, но, как правило, надежны, и вам нужно, чтобы ваша программа работала.
  3. Не утруждайте себя необходимостью писать в приложении после проверки входных данных. Это пустая трата времени и раздувает ваше приложение. Позвольте ему взорваться, потому что это либо что-то очень редкое, с которым не стоит обращаться, либо это означает, что вам нужно внимательно изучить шаги 1 и 2.
  4. Никогда не пишите обработку исключений в вашем основном коде, который не зависит от сетевого устройства. Это плохое программирование и дорогостоящее для производительности. Например, написание try-catch для массива out of bounds в цикле означает, что вы изначально не запрограммировали цикл правильно.
  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. Nit: Вам не нужны парены для распаковки кортежей. Можно просто сделать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

В основном обработка исключений должна быть более подходящей для языков ООП.

Второй момент - производительность, потому что вам не нужно выполнять eachLine.findдля каждой строки.

Elalfer
источник
7
-1: производительность - крайне плохая причина для общих правил.
Mattnz
3
Нет, исключения полностью не связаны с ООП.
Pubby
-6

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

Манодж
источник
7
Тем не менее, анорер-1 за беспокойство по поводу производительности по сравнению с читабельностью, бла-бла. Производительность не является причиной.
Mattnz
Могу ли я знать, почему вы бегаете раздачи -1 без объяснения? Защитное программирование означает больше строк кода, что означает более низкую производительность. Кто-нибудь хочет объяснить, прежде чем сбить счет?
Манодж
3
@Manoj: Если вы не провели измерения с помощью профилировщика и не обнаружили, что блок кода неприемлемо медленен, код для удобочитаемости и удобства обслуживания задолго до производительности.
Daenyth
То, что @Manoj сказал с добавлением, что меньшее количество кода повсеместно означает меньше работы при отладке и обслуживании. Время, затраченное на разработку менее совершенного кода, чрезвычайно велико. Я предполагаю (как и я), что вы не пишете идеальный код, простите меня, если я ошибаюсь.
Mattnz
2
Спасибо за ссылку - интересно читать, что я должен с этим согласиться, в какой-то момент ... Работая над жизненно важными системами, как и я "Система напечатала трассировку стека, так что мы точно знаем, почему эти 300 человек погибли без необходимости. .... "на самом деле не собирается слишком хорошо падать в качестве свидетеля. Я полагаю, это одна из тех вещей, где каждая ситуация имеет свой соответствующий ответ.
Mattnz