LBYL против EAFP в Java?

Недавно я обучал себя Python и обнаружил идиомы LBYL / EAFP в отношении проверки ошибок перед выполнением кода. В Python, похоже, принятый стиль - EAFP, и он, кажется, хорошо работает с языком.

LBYL (L ook B до Y или L eap):

def safe_divide_1(x, y):
    if y == 0:
        print "Divide-by-0 attempt detected"
        return None
    else:
        return x/y

EAFP (это E больше, чем разрешение, чем разрешение):

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. Это просто предпочтение?


person ryeguy    schedule 01.01.2009    source источник


Ответы (5)


Лично я считаю, что это подтверждено условностями, 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 влечет за собой некоторые накладные расходы, даже если исключение не генерируется.

person Yuval Adam    schedule 01.01.2009
comment
Такой вид EAFP частично зависит от того, будут ли исключения, на которые вы тестируете, возникать очень часто. Если они маловероятны, тогда EAFP разумен. Если они распространены, то LBYL может быть лучше. Ответ, вероятно, также зависит от доступной парадигмы обработки исключений. В C необходимо LBYL. - person Jonathan Leffler; 02.01.2009
comment
обычное исключение вовсе не исключение, поэтому, конечно, в этом случае будет предпочтительнее LBYL, не так ли? - person Fredy Treboux; 28.04.2011
comment
Сказать, что блок try-catch влечет за собой некоторые накладные расходы, даже если исключение не возникло, - все равно что сказать, что определение метода вызывает некоторые накладные расходы, даже если метод не вызывается. Другими словами, накладные расходы незначительны, пока не будет вызван метод / исключение. Эриксон объяснил это лучше всего, когда я спросил о это. - person Iain Samuel McLean Elder; 09.11.2012
comment
Я полностью и полностью не согласен. Логично, что EAFP - хороший способ проверки. Потому что, если проверка, которая вызывает исключение, все равно уже существует, дополнительная проверка может только выйти из синхронизации и ничего не сохранить. Однако из-за сравнительно медленных исключений в C ++, Java и C # LBYL необходим, когда ожидается, что сбой произойдет, но редко. - person Jan Hudec; 11.01.2013
comment
не вложенные блоки try-catch не побеждают [предположительно] большей детализации LBYL? Просто операции должны быть написаны в большем количестве строк, что, возможно, также будет более читаемым. - person n611x007; 08.02.2013
comment
EAFP также может иметь лучшие свойства безопасности (устранение некоторых условий гонки) в многопоточных средах. - person Mark E. Haase; 02.07.2013
comment
EAFP - хороший пример DRY. Если вы используете логику помимо исключений, вы (а) можете проверять условия, которые на самом деле не являются ошибками, потому что ваш проверочный код не синхронизирован с вызываемой службой или библиотекой; и (b) наоборот, ваш проверочный код может не обрабатывать условия, которые на самом деле являются ошибками - и вам все равно придется использовать исключения. Если базовая служба или библиотека будет использовать исключения, когда она (авторитетно) обнаруживает ошибку, вам следует использовать исключения. Это плюс проблема условий гонки, описанная Джонатаном Леффлером, предполагает, что EAFP - это правильный путь. - person Chris Johnson; 23.10.2014
comment
EAFP, аналогично тому, что говорит mehaase, полезен в ситуациях, когда вы не можете контролировать выдаваемую ошибку - например, при парсинге веб-сайта. - person Millie Smith; 11.05.2015
comment
ты все еще можешь сделать B b=null; C c=null; try{b=a.getB();try{c=b.getC();}catch(NullPointerException){/*stuff2*/}}catch(NullPointerException){/*stuff*/} - person Austin_Anderson; 09.08.2017
comment
Я знаю, что это очень старое. Но я думаю, что это хорошо, когда к этому вопросу приходят новые люди. EAFP более практичен и полезен с точки зрения параллелизма и многопоточности. Допускается повышенная атомарность (при этом не достигнутая полностью) и уменьшение условий гонки. EAFP следует использовать в распределенных вычислениях по мере необходимости и применять соответствующим образом. Я лично считаю, что оба должны использоваться всегда, и ни один из них не является неправильным сам по себе и предназначен для использования в конкретной ситуации. - person gzimmers; 06.03.2020

Если вы обращаетесь к файлам, EAFP более надежен, чем LBYL, потому что операции, задействованные в LBYL, не являются атомарными, и файловая система может измениться между временем, когда вы смотрите, и временем, когда вы прыгаете. Собственно, стандартное название - TOCTOU - Time of Check, Time of Use; ошибки, вызванные неточной проверкой, являются ошибками TOCTOU.

Рассмотрите возможность создания временного файла, который должен иметь уникальное имя. Лучший способ узнать, существует ли выбранное имя файла - это попытаться его создать, убедившись, что вы используете параметры, гарантирующие, что ваша операция завершится ошибкой, если файл уже существует (в терминах POSIX / Unix флаг O_EXCL равен open()). Если вы попытаетесь проверить, существует ли уже файл (вероятно, с помощью access()), то между моментом, когда будет сказано «Нет», и временем, когда вы попытаетесь создать файл, кто-то или что-то еще могло создать файл.

И наоборот, предположим, что вы пытаетесь прочитать существующий файл. Ваша проверка того, что файл существует (LBYL), может сказать «он есть», но когда вы действительно открываете его, вы обнаруживаете, что «его там нет».

В обоих случаях вы должны проверить последнюю операцию - и LBYL автоматически не помог.

(Если вы возитесь с программами SUID или SGID, access() задает другой вопрос; он может иметь отношение к LBYL, но код все равно должен учитывать возможность сбоя.)

person Jonathan Leffler    schedule 01.01.2009
comment
Прекрасный пример, Джонатан. Это, вероятно, имеет большой смысл для java-разработчиков, которые имели дело с параллельным программированием и идиомой дважды проверенной блокировки. - person David Mann; 09.05.2014
comment
Это хороший момент, но я не верю, что это действительно связано с LBYL и EAFP. Каждый раз, когда вы, например, открываете файл, вы в основном делаете одно и то же, одна операция, которая возвращает ошибку, вы не проверяете, можете ли вы ее открыть, прежде чем это сделать. Я утверждаю, что такой код может быть LBYL: несколько вызовов функций, каждый вызов функции может завершиться неудачно, и каждый проверяется на месте. TOCTOU - более общая проблема. Кроме того, ваш ответ касается проблемы правильности, тогда как я считаю, что вопрос касался случаев, когда оба решения были бы правильными. - person ctn; 11.07.2019

Помимо относительной стоимости исключений в Python и Java, имейте в виду, что между ними существует разница в философии / подходе. Java пытается быть очень строгим в отношении типов (и всего остального), требуя явных, подробных объявлений сигнатур классов / методов. Предполагается, что в любой момент вы должны точно знать, какой тип объекта вы используете и на что он способен. Напротив, «утиная типизация» в Python означает, что вы не знаете наверняка (и не должны заботиться) о том, какой тип манифеста является объектом, вам нужно только позаботиться о том, чтобы он крякал, когда вы его об этом просите. В такой снисходительной среде единственное разумное отношение - предполагать, что что-то сработает, но быть готовым иметь дело с последствиями, если они этого не сделают. Естественная ограниченность Java не подходит для такого случайного подхода. (Это не призвано принижать какой-либо подход или язык, а скорее сказать, что такое отношение является частью идиомы каждого языка, и копирование идиом между разными языками часто может привести к неловкости и плохому общению ...)

person Jeff Shannon    schedule 02.01.2009
comment
Кроме того, oranlooney.com/lbyl-vs-eafp предоставляет хороший набор плюсов и минусов для каждого подхода. - person Tim Lewis; 23.06.2011
comment
Я не согласен с тем, что свободная типизация в Python делает более или менее разумным использование EAFP. Когда вы просите прощения, вы просите прощения в ожидаемых случаях. Например, если метод отмены собирается отменить объект, мы перехватим исключения, которые, как мы ожидаем, вернутся. Мы ожидаем, что отмена может завершиться неудачно, потому что у объекта есть активные отношения, которые не позволяют его отменить, и мы хотим сообщить об этом обратно в пользовательский интерфейс. Если метод отмены терпит неудачу из-за некоторого непредвиденного деления на ноль Scenerio, мы хотим, чтобы он отказывался нормально, как и любая другая ошибка в программе. - person David Baucum; 12.06.2015
comment
ссылка больше не работает, чтобы кто-то мог прочитать статью здесь https://web.archive.org/web/20161208191318/http://www.oranlooney.com/lbyl-vs-eafp/ - person cookiemonster; 27.02.2020

Исключения обрабатываются в Python более эффективно, чем в Java, что, по крайней мере, частично, почему вы видите эту конструкцию в Python. В Java более неэффективно (с точки зрения производительности) использовать исключения таким образом.

person mipadi    schedule 01.01.2009
comment
mipadi, есть ли у вас какое-нибудь представление о том, как Python это делает? - person duffymo; 01.01.2009
comment
@duffymo У меня был тот же вопрос, и я нашел его здесь: stackoverflow.com/questions/598157/ - person Tim Lewis; 23.06.2011
comment
@duffymo большая часть дискуссий сосредоточена вокруг LBYL и EAFP, но один из ответов связан с тем, как на самом деле реализованы исключения в CPython: docs.python.org/c-api/intro.html#exceptions - person Tim Lewis; 23.06.2011

Рассмотрим эти фрагменты кода:

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.

person Veedrac    schedule 28.04.2017