LBYL против EAFP в Java?

Недавно я обучал себя 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. Это просто предпочтение?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
67
0
21 177
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 вызывает некоторые накладные расходы, даже если исключение не создано.

Этот вид EAFP частично зависит от того, будут ли исключения, на которые вы тестируете, возникать очень часто. Если они маловероятны, тогда EAFP разумен. Если они распространены, то LBYL может быть лучше. Ответ, вероятно, также зависит от доступной парадигмы обработки исключений. В C необходимо LBYL.

Jonathan Leffler 02.01.2009 00:26

«общее исключение» вовсе не исключение, поэтому, конечно, в этом случае предпочтительнее будет LBYL, не так ли?

Fredy Treboux 28.04.2011 22:33

Сказать «блок try-catch вызывает некоторые накладные расходы, даже если исключение не генерируется» - это все равно что сказать «определение метода вызывает некоторые накладные расходы, даже если метод не вызывается». Другими словами, накладные расходы незначительны, пока не будет вызван метод / исключение. Эриксон объяснил это лучше всего, когда я спросил об этом.

Iain Samuel McLean Elder 09.11.2012 01:19

Я полностью и полностью не согласен. Логично, что EAFP является - хороший способ сделать валидацию. Потому что, если проверка, вызывающая исключение, в любом случае уже существует, дополнительная проверка может только выйти из синхронизации и ничего не сохранить. Однако из-за сравнительно медленных исключений в C++, Java и C# LBYL требуется, когда ожидается, что сбой произойдет, но редко.

Jan Hudec 11.01.2013 16:24

Разве блоки try-catch вложенный не побеждают [предположительно] большей детализации LBYL? Просто операции должны быть написаны в большем количестве строк, что, возможно, также будет более читаемым.

n611x007 08.02.2013 00:48

EAFP также может иметь лучшие свойства безопасности (устраняя некоторые состояния гонки) в многопоточных средах.

Mark E. Haase 02.07.2013 21:12

EAFP - хороший пример DRY. Если вы используете логику помимо исключений, вы (а) можете проверять условия, которые на самом деле не являются ошибками, потому что ваш проверочный код не синхронизирован с вызываемой службой или библиотекой; и (b) наоборот, ваш проверочный код может не обрабатывать условия, которые на самом деле являются ошибками - и вам все равно придется использовать исключения. Если базовая служба или библиотека будет использовать исключения, когда она (авторитетно) обнаруживает ошибку, вам следует использовать исключения. Это плюс проблема условий гонки, описанная Джонатаном Леффлером, предполагает, что EAFP - это правильный путь.

Chris Johnson 23.10.2014 22:43

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

Millie Smith 11.05.2015 01:56

вы все еще могли бы сделать B b=null; C c=null; try{b=a.getB();try{c=b.getC();}catch(NullPointerException){/‌​*stuff2*/}}catch(Nul‌​lPointerException){/‌​*stuff*/}

Austin_Anderson 09.08.2017 21:30

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

gzimmers 06.03.2020 21:38

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

mipadi, есть ли у вас какое-нибудь представление о том, как python это делает?

duffymo 01.01.2009 18:13

@duffymo У меня был такой же вопрос, и я нашел его здесь: stackoverflow.com/questions/598157/…

Tim Lewis 23.06.2011 19:01

@duffymo большая часть дискуссий сосредоточена вокруг LBYL и EAFP, но один из ответов, связанных с тем, как на самом деле реализованы исключения в CPython: docs.python.org/c-api/intro.html#exceptions

Tim Lewis 23.06.2011 19:16

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

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

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

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

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

Прекрасный пример, Джонатан. Это, вероятно, имеет большой смысл для разработчиков Java, которые имели дело с параллельным программированием и идиомой с двойной проверкой блокировки.

David Mann 09.05.2014 07:32

Это хороший момент, но я не верю, что это действительно связано с LBYL и EAFP. Каждый раз, когда вы, например, открываете файл, вы в основном делаете одно и то же, одна операция, которая возвращает ошибку, вы не проверяете, можете ли вы ее открыть, прежде чем это сделать. Я утверждаю, что такой код может быть LBYL: несколько вызовов функций, каждый вызов функции может завершиться неудачно, и каждый проверяется на месте. TOCTOU - более общая проблема. Кроме того, ваш ответ касается проблемы правильности, тогда как я считаю, что вопрос касался случаев, когда оба решения были бы правильными.

ctn 11.07.2019 15:09

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

Кроме того, oranlooney.com/lbyl-vs-eafp предоставляет хороший набор плюсов и минусов для каждого подхода.

Tim Lewis 23.06.2011 19:24

Я не согласен с тем, что свободная типизация в Python делает более или менее разумным использование EAFP. Когда вы просите прощения, вы просите прощения в ожидаемых случаях. Например, если метод «cancel» собирается отменить объект, мы перехватим исключения, которые, как мы ожидаем, вернутся. Мы ожидаем, что отмена может завершиться неудачно, потому что у объекта есть активные отношения, которые не позволяют его отменить, и мы хотим сообщить об этом обратно в пользовательский интерфейс. Если метод отмены терпит неудачу из-за непредвиденного деления сценария на ноль, мы хотим, чтобы он завершился нормально, как и любая другая ошибка в программе.

David Baucum 12.06.2015 21:08

ссылка больше не работает, чтобы кто-то мог прочитать статью здесь вместо web.archive.org/web/20161208191318/http://www.oranlooney.com‌ /…

cookiemonster 27.02.2020 17:07

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

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.

Другие вопросы по теме