Недавно я обучал себя Python и обнаружил идиомы LBYL / EAFP в отношении проверки ошибок перед выполнением кода. В Python, похоже, принятый стиль - EAFP, и он, кажется, хорошо работает с языком.
LBYL (RR_2_Rook Before You Leap):
def safe_divide_1(x, y):
if y == 0:
print "Divide-by-0 attempt detected"
return None
else:
return x/y
EAFP (это Easier to Аsk Forgiveness, чем пermission):
def safe_divide_2(x, y):
try:
return x/y
except ZeroDivisionError:
print "Divide-by-0 attempt detected"
return None
У меня такой вопрос: я даже не слышал об использовании EAFP в качестве основной конструкции проверки данных, исходящей из фона Java и C++. Можно ли использовать EAFP в Java? Или слишком много накладных расходов из-за исключений? Я знаю, что накладные расходы возникают только тогда, когда на самом деле создается исключение, поэтому я не уверен, почему не используется более простой метод EAFP. Это просто предпочтение?




Лично я считаю, что это подтверждено условностями, EAFP никогда не подходит. Вы можете рассматривать это как эквивалент следующего:
if (o != null)
o.doSomething();
else
// handle
в отличие от:
try {
o.doSomething()
}
catch (NullPointerException npe) {
// handle
}
Кроме того, учтите следующее:
if (a != null)
if (b != null)
if (c != null)
a.getB().getC().doSomething();
else
// handle c null
else
// handle b null
else
// handle a null
Это может выглядеть намного менее элегантно (и да, это грубый пример - несите меня), но он дает вам гораздо большую детализацию при обработке ошибки, в отличие от того, чтобы обернуть все это в try-catch, чтобы получить этот NullPointerException, и затем попытайтесь выяснить, где и почему вы его взяли.
На мой взгляд, EAFP никогда не следует использовать, за исключением редких ситуаций. Кроме того, поскольку вы подняли вопрос: да, блок try-catch вызывает некоторые накладные расходы, даже если исключение не создано.
«общее исключение» вовсе не исключение, поэтому, конечно, в этом случае предпочтительнее будет LBYL, не так ли?
Сказать «блок try-catch вызывает некоторые накладные расходы, даже если исключение не генерируется» - это все равно что сказать «определение метода вызывает некоторые накладные расходы, даже если метод не вызывается». Другими словами, накладные расходы незначительны, пока не будет вызван метод / исключение. Эриксон объяснил это лучше всего, когда я спросил об этом.
Я полностью и полностью не согласен. Логично, что EAFP является - хороший способ сделать валидацию. Потому что, если проверка, вызывающая исключение, в любом случае уже существует, дополнительная проверка может только выйти из синхронизации и ничего не сохранить. Однако из-за сравнительно медленных исключений в C++, Java и C# LBYL требуется, когда ожидается, что сбой произойдет, но редко.
Разве блоки try-catch вложенный не побеждают [предположительно] большей детализации LBYL? Просто операции должны быть написаны в большем количестве строк, что, возможно, также будет более читаемым.
EAFP также может иметь лучшие свойства безопасности (устраняя некоторые состояния гонки) в многопоточных средах.
EAFP - хороший пример DRY. Если вы используете логику помимо исключений, вы (а) можете проверять условия, которые на самом деле не являются ошибками, потому что ваш проверочный код не синхронизирован с вызываемой службой или библиотекой; и (b) наоборот, ваш проверочный код может не обрабатывать условия, которые на самом деле являются ошибками - и вам все равно придется использовать исключения. Если базовая служба или библиотека будет использовать исключения, когда она (авторитетно) обнаруживает ошибку, вам следует использовать исключения. Это плюс проблема условий гонки, описанная Джонатаном Леффлером, предполагает, что EAFP - это правильный путь.
EAFP, аналогично тому, что говорит mehaase, полезен в ситуациях, когда вы не можете контролировать выдаваемую ошибку - например, при парсинге веб-сайта.
вы все еще могли бы сделать B b=null; C c=null; try{b=a.getB();try{c=b.getC();}catch(NullPointerException){/*stuff2*/}}catch(NullPointerException){/*stuff*/}
Я знаю, что это очень старое. Но я думаю, что это хорошо, когда к этому вопросу приходят новые люди. EAFP более практичен и полезен с точки зрения параллелизма и многопоточности. Допускается повышенная атомарность (при этом не достигнутая полностью) и уменьшение условий гонки. EAFP следует использовать в распределенных вычислениях по мере необходимости и применять соответствующим образом. Я лично считаю, что оба должны использоваться всегда, и ни один из них не является неправильным сам по себе и предназначен для использования в конкретной ситуации.
Исключения обрабатываются более эффективно в Python, чем в Java, что, по крайней мере, составляет частично, почему вы видите эту конструкцию в Python. В Java более неэффективно (с точки зрения производительности) использовать исключения таким образом.
mipadi, есть ли у вас какое-нибудь представление о том, как python это делает?
@duffymo У меня был такой же вопрос, и я нашел его здесь: stackoverflow.com/questions/598157/…
@duffymo большая часть дискуссий сосредоточена вокруг LBYL и EAFP, но один из ответов, связанных с тем, как на самом деле реализованы исключения в CPython: docs.python.org/c-api/intro.html#exceptions
Если вы обращаетесь к файлам, EAFP более надежен, чем LBYL, потому что операции, задействованные в LBYL, не являются атомарными, и файловая система может измениться между временем, когда вы смотрите, и временем, когда вы прыгаете. Собственно, стандартное название - TOCTOU - Time of Check, Time of Use; ошибки, вызванные неточной проверкой, являются ошибками TOCTOU.
Рассмотрите возможность создания временного файла, который должен иметь уникальное имя. Лучший способ узнать, существует ли выбранное имя файла еще, - это попытаться создать его - убедитесь, что вы используете параметры, гарантирующие, что ваша операция завершится неудачно, если файл уже существует (в терминах POSIX / Unix, флаг O_EXCL для open()). Если вы попытаетесь проверить, существует ли уже файл (возможно, с помощью access()), то между моментом, когда будет сказано «Нет», и временем, когда вы попытаетесь создать файл, кто-то или что-то еще могло создать файл.
И наоборот, предположим, что вы пытаетесь прочитать существующий файл. Ваша проверка того, что файл существует (LBYL), может сказать «он есть», но когда вы действительно открываете его, вы обнаруживаете, что «его там нет».
В обоих этих случаях вы должны проверить последнюю операцию - и LBYL автоматически не помог.
(Если вы возитесь с программами SUID или SGID, access() задает другой вопрос; он может иметь отношение к LBYL, но код все равно должен учитывать возможность сбоя.)
Прекрасный пример, Джонатан. Это, вероятно, имеет большой смысл для разработчиков Java, которые имели дело с параллельным программированием и идиомой с двойной проверкой блокировки.
Это хороший момент, но я не верю, что это действительно связано с LBYL и EAFP. Каждый раз, когда вы, например, открываете файл, вы в основном делаете одно и то же, одна операция, которая возвращает ошибку, вы не проверяете, можете ли вы ее открыть, прежде чем это сделать. Я утверждаю, что такой код может быть LBYL: несколько вызовов функций, каждый вызов функции может завершиться неудачно, и каждый проверяется на месте. TOCTOU - более общая проблема. Кроме того, ваш ответ касается проблемы правильности, тогда как я считаю, что вопрос касался случаев, когда оба решения были бы правильными.
Помимо относительной стоимости исключений в Python и Java, имейте в виду, что между ними существует разница в философии / подходе. Java пытается быть очень строгим в отношении типов (и всего остального), требуя явных, подробных объявлений сигнатур классов / методов. Предполагается, что в любой момент вы должны точно знать, какой тип объекта вы используете и на что он способен. Напротив, «утиная типизация» в Python означает, что вы не знаете наверняка (и не должны заботиться), какой тип манифеста является объектом, вам нужно только позаботиться о том, чтобы он крякал, когда вы его об этом просите. В такой благоприятной среде единственное разумное отношение - предполагать, что что-то сработает, но быть готовым иметь дело с последствиями, если они этого не сделают. Естественная ограниченность Java не подходит для такого случайного подхода. (Это не призвано принижать ни подход, ни язык, а скорее сказать, что такое отношение является частью идиомы каждого языка, и копирование идиом между разными языками часто может приводить к неловкости и плохому общению ...)
Кроме того, oranlooney.com/lbyl-vs-eafp предоставляет хороший набор плюсов и минусов для каждого подхода.
Я не согласен с тем, что свободная типизация в Python делает более или менее разумным использование EAFP. Когда вы просите прощения, вы просите прощения в ожидаемых случаях. Например, если метод «cancel» собирается отменить объект, мы перехватим исключения, которые, как мы ожидаем, вернутся. Мы ожидаем, что отмена может завершиться неудачно, потому что у объекта есть активные отношения, которые не позволяют его отменить, и мы хотим сообщить об этом обратно в пользовательский интерфейс. Если метод отмены терпит неудачу из-за непредвиденного деления сценария на ноль, мы хотим, чтобы он завершился нормально, как и любая другая ошибка в программе.
ссылка больше не работает, чтобы кто-то мог прочитать статью здесь вместо web.archive.org/web/20161208191318/http://www.oranlooney.com /…
Рассмотрим эти фрагменты кода:
def int_or_default(x, default=0):
if x.isdigit():
return int(x)
else:
return default
def int_or_default(x, default=0):
try:
return int(x)
except ValueError:
return default
Они оба выглядят правильно, правда? Но одного из них нет.
Первый, использующий LBYL, терпит неудачу из-за тонкого различия между isdigit и isdecimal; при вызове со строкой «²³?₅» он выдаст ошибку, а не вернет значение по умолчанию.
Позднее, использование EAFTP, по определению приводит к правильной обработке. Нет возможности для поведенческого несоответствия, потому что код, которому требуется требование, является код, который утверждает это требование.
Использование LBYL означает использование внутренней логики и ее копирование в сайт вызова каждый. Вместо того, чтобы иметь одну каноническую кодировку ваших требований, у вас есть бесплатный шанс испортить каждый раз, когда вы вызываете функцию.
Стоит отметить, что EAFTP не об исключениях, и особенно код Java, не должен использовать исключения повсеместно. Речь идет о том, чтобы дать правильную работу правильному блоку кода. Например, использование возвращаемых значений Optional - это совершенно правильный способ написания кода EAFTP, который намного эффективнее для обеспечения правильности, чем LBYL.
Этот вид EAFP частично зависит от того, будут ли исключения, на которые вы тестируете, возникать очень часто. Если они маловероятны, тогда EAFP разумен. Если они распространены, то LBYL может быть лучше. Ответ, вероятно, также зависит от доступной парадигмы обработки исключений. В C необходимо LBYL.