LBYL против EAFP в Java?
Недавно я преподавал Python и обнаружил идиомы LBYL/EAFP в отношении проверки ошибок перед выполнением кода. В Python кажется, что принятый стиль - это EAFP, и, похоже, он хорошо работает с языком.
LBYL ( L ook B выше Y ou 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) A sk F, чем P ermission):
def safe_divide_2(x, y):
try:
return x/y
except ZeroDivisionError:
print "Divide-by-0 attempt detected"
return None
Мой вопрос таков: я даже не слышал об использовании EAFP в качестве основной конструкции проверки данных, исходящей из фона Java и С++. Является ли EAFP чем-то разумным для использования на Java? Или слишком много накладных расходов из-за исключений? Я знаю, что на самом деле есть только накладные расходы, когда исключение действительно бросается, поэтому я не уверен, почему более простой метод EAFP не используется. Это просто предпочтение?
Ответы
Ответ 1
Лично, и я думаю, что это подкреплено конвенцией, 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
Это может выглядеть намного менее изящным (и да, это грубый пример - нести со мной), но он дает вам гораздо большую детализацию в обработке ошибки, в отличие от того, чтобы обернуть все это в попытке поймать NullPointerException
, а затем попытайтесь выяснить, где и почему вы его получили.
Как я вижу, EAFP никогда не должен использоваться, за исключением редких ситуаций. Кроме того, поскольку вы подняли вопрос: да, блок try-catch накладывает некоторые накладные расходы, даже если исключение не выбрано.
Ответ 2
Если вы обращаетесь к файлам, EAFP более надежен, чем LBYL, потому что операции, связанные с LBYL, не являются атомарными, и файловая система может измениться между временем, которое вы смотрите, и временем, когда вы прыгаете. На самом деле стандартное название TOCTOU - время проверки, время использования; ошибки, вызванные неточной проверкой, являются ошибками TOCTOU.
Рассмотрим создание временного файла, который должен иметь уникальное имя. Лучший способ узнать, существует ли выбранное имя файла, - попытаться создать его - убедитесь, что вы используете параметры, чтобы убедиться, что ваша операция завершается с ошибкой, если файл уже существует (в терминах POSIX/Unix, флаг O_EXCL до open()
). Если вы попытаетесь проверить, существует ли файл (возможно, с помощью access()
), то между тем, когда указано "Нет" и время, когда вы пытаетесь создать файл, кто-то или что-то еще может создать файл.
И наоборот, предположим, что вы пытаетесь прочитать существующий файл. Ваша проверка, что файл существует (LBYL), может сказать "он есть", но когда вы его открываете, вы обнаружите, что "его нет".
В обоих случаях вы должны проверить окончательную операцию - и LBYL не помог автоматически.
(Если вы возитесь с программами SUID или SGID, access()
задает другой вопрос, это может иметь отношение к LBYL, но код все равно должен учитывать возможность сбоя.)
Ответ 3
В дополнение к относительной стоимости исключений в Python и Java, имейте в виду, что существует разница в философии/отношении между ними. Java пытается быть очень строгим в отношении типов (и всего остального), требующих явных подробных объявлений сигнатур класса/метода. Предполагается, что в любой момент вы должны знать, какой тип объекта вы используете и что он способен делать. Напротив, Python "утиная печать" означает, что вы не знаете точно (и не должны заботиться) о том, что такое тип манифеста, вам нужно только заботиться о том, чтобы он обманывал вас, когда вы его просите. В такой разрешающей среде единственное разумное отношение - предполагать, что все будет работать, но будьте готовы справиться с последствиями, если они этого не сделают. Естественная ограниченность Java не подходит для такого случайного подхода. (Это не предназначено для унижения ни подхода, ни языка, а скорее сказать, что эти отношения являются частью каждой языковой идиомы, а копирование идиом между разными языками может часто приводить к неловкости и плохой коммуникации...)
Ответ 4
Исключения обрабатываются более эффективно в Python, чем в Java, что по крайней мере частично объясняет, почему вы видите эту конструкцию в Python. В Java более неэффективно (с точки зрения производительности) использовать исключения таким образом.
Ответ 5
Рассмотрим эти фрагменты кода:
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.