Я часто возвращаю логическое значение из метода, который используется в нескольких местах, чтобы хранить всю логику вокруг этого метода в одном месте. Все (внутренний) вызывающий метод должен знать, была ли операция успешной или нет.
Я использую Python, но вопрос не обязательно специфичен для этого языка. Есть только два варианта, которые я могу придумать
- Вызовите исключение, хотя обстоятельства не являются исключительными, и не забудьте перехватить это исключение в каждом месте, где вызывается функция
- Верните логическое значение, как я делаю.
Это действительно простой пример, который демонстрирует то, о чем я говорю.
import os
class DoSomething(object):
def remove_file(self, filename):
try:
os.remove(filename)
except OSError:
return False
return True
def process_file(self, filename):
do_something()
if remove_file(filename):
do_something_else()
Хотя это функционально, мне действительно не нравится этот способ что-то делать, он «пахнет» и может иногда приводить к множеству вложенных ifs. Но я не могу придумать более простой способ.
Я мог бы обратиться к более философии LBYL и использовать os.path.exists(filename)
перед попыткой удаления, но нет никаких гарантий, что файл не будет заблокирован за это время (это маловероятно, но возможно), и мне все еще нужно определить, было ли удаление успешным или нет.
Является ли это «приемлемым» дизайном, и если нет, то каким будет лучший способ его разработки?
Ваша интуиция в этом верна, есть лучший способ сделать это: монады .
Какие монады?
Монады - это (перефразируя Википедию) способ объединения операций вместе, скрывая механизм объединения; в вашем случае механизм сцепления - это вложенный
if
s. Скройте это, и ваш код будет сильно пахнуть лучше.Есть пара монад, которые будут делать именно это («Возможно» и «Либо»), и, к счастью для вас, они являются частью действительно хорошей библиотеки монад Python!
Что они могут сделать для вашего кода
Вот пример использования монады «Either» («Failable» в связанной библиотеке), где функция может возвращать Success или Failure, в зависимости от того, что произошло:
Теперь это может не сильно отличаться от того, что у вас есть сейчас, но подумайте, как все будет, если у вас будет больше операций, которые могут привести к отказу:
На каждом из
yield
s вprocess_file
функции, если вызов функции возвращает Failure,process_file
функция в этот момент завершает работу, возвращая значение Failure из сбойной функции, вместо продолжения через оставшуюся часть и возвратаSuccess("All ok.")
Теперь представьте, что делаете выше с вложенными
if
s! (Как бы вы справились с возвращаемым значением !?)Вывод
Монады хороши :)
Примечания:
Я не программист на Python - я использовал библиотеку монад, на которую я ссылался выше, в скрипте, который я использовал для какой-то автоматизации проекта. Я понимаю, однако, что в целом предпочтительным идиоматическим подходом является использование исключений.
IIRC В скрипте lib на странице есть ссылка на опечатку, хотя я забыл, где находится банкомат. Я буду обновлять, если я помню.Я сравнил свою версию со страницей и нашел:def failable_monad_examle():
->def failable_monad_example():
-p
вexample
пропускал.Чтобы получить результат функции Failable (например,
process_file
), вы должны зафиксировать результат в avariable
и сделать a,variable.value
чтобы получить его.источник
Функция является контрактом, и ее название должно указывать, какой контракт она будет выполнять. ИМХО, если вы назовете его
remove_file
так, чтобы он удалил файл, и если вы не сделаете это, должно произойти исключение. С другой стороны, если вы назовете егоtry_remove_file
, он должен «попытаться» удалить и вернуть логическое значение, чтобы сказать, был ли файл удален или нет.Это привело бы к другому вопросу - это должно быть
remove_file
илиtry_remove_file
? Это зависит от вашего сайта вызова. На самом деле, вы можете использовать оба метода и использовать их в разных сценариях, но я думаю, что удаление файла само по себе имеет большие шансы на успех, поэтому я предпочитаю иметь толькоremove_file
это исключение броска при сбое.источник
В этом конкретном случае может быть полезно подумать о том, почему вы не сможете удалить файл. Допустим, проблема в том, что файл может существовать или не существовать. Тогда у вас должна быть функция,
doesFileExist()
которая возвращает true или false, и функция,removeFile()
которая просто удаляет файл.В вашем коде вы сначала проверите, существует ли файл. Если это так, позвоните
removeFile
. Если нет, то делай другие вещи.В этом случае вы все еще можете вызвать
removeFile
исключение, если файл не может быть удален по какой-либо другой причине, например, с разрешениями.Подводя итог, следует выбросить исключения для вещей, которые, ну, в общем, исключительные. Так что, если совершенно нормально, что файл, который вы пытаетесь удалить, может не существовать, то это не исключение. Напишите логический предикат, чтобы проверить это. С другой стороны, если у вас нет разрешений на запись для файла или если он находится в удаленной файловой системе, которая внезапно становится недоступной, это вполне может быть исключительными условиями.
источник