仅关注成功或失败时返回布尔值


15

我经常发现自己从一个方法返回一个布尔值,该方法用于多个位置,以便将一个方法周围的所有逻辑包含在一个地方。所有(内部)调用方法都需要知道该操作是否成功。

我正在使用Python,但问题不一定特定于该语言。我只能想到两种选择

  1. 引发异常(尽管情况并不特殊),并记住在调用函数的每个位置都捕获该异常
  2. 在执行操作时返回布尔值。

这是一个非常简单的示例,演示了我在说什么。

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()

尽管它是功能性的,但我真的不喜欢这种做事的方式,它“闻起来”,有时会导致很多嵌套的if。但是,我想不出一种更简单的方法。

os.path.exists(filename)在尝试删除之前,我可以使用更多的LBYL原理并使用它,但是不能保证文件不会同时被锁定(这不太可能,但是可能),我仍然必须确定删除是否成功。

这是“可接受的”设计吗?如果不是,那么设计它的更好方法是什么?

Answers:


11

boolean当方法/函数在进行逻辑决策时很有用时,您应该返回。

exception当方法/函数不太可能在逻辑决策中使用时,您应该抛出。

您必须决定故障的重要性以及是否应该处理故障。如果您可以将失败分类为警告,请返回boolean。如果对象进入错误状态,使以后对其进行调用变​​得不稳定,则抛出exception

另一种做法是返回objects而不是返回结果。如果调用open,则它应返回一个File对象或null无法打开。这样可以确保程序员拥有处于可用状态的对象实例。

编辑:

请记住,当语言的类型为布尔值或整数时,大多数语言都会丢弃该函数的结果。因此,在没有左手分配结果的情况下可以调用该函数。使用布尔结果时,请始终假定程序员忽略了返回值,并使用该值来决定是否应将其作为异常。


这是对我正在执行的操作的验证,因此我喜欢答案:-)。在对象上,尽管我了解您来自何处,但在大多数情况下我都不会使用它。我想变干,所以我将对象重新调整为一个方法,因为我只想对它做一件事。然后,剩下的就是我现在拥有的相同代码,还有其他方法。(也为例子给我删除文件,这样一个空文件对象不太多说:-)

删除是棘手的,因为无法保证。我从未见过文件删除方法会引发异常,但是如果编程失败,程序员该怎么办?连续循环重试?不,这是操作系统问题。该代码应记录结果并继续。
Reactgular

4

您对此的直觉是正确的,还有一种更好的方法可以做到这一点:monads

什么是单子?

Monads(用维基百科来解释)是将操作链接在一起的一种方式,同时隐藏了链接机制。在您的情况下,链接机制是嵌套if的。隐藏它,您的代码会闻起来更好。

有几个monad可以做到这一点(“也许”和“任何一个”),对您来说很幸运,它们是一个非常漂亮的python monad库的一部分!

他们可以为您的代码做什么

这是一个使用“ Either” monad(链接的库中的“ Failable”)的示例,其中函数可以根据发生的情况返回成功或失败:

import os

class DoSomething(object):

    def remove_file(self, filename):
        try:
            os.remove(filename)
            return Success(None)
        except OSError:
            return Failure("There was an OS Error.")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        do_something_else()
        mreturn(Success("All ok."))

现在,这看起来可能与您现在所拥有的并没有太大不同,但是请考虑一下,如果您进行更多可能导致失败的操作,情况将会如何:

    def action_that_might_fail_and_returns_something(self):
        # get some random value between 0 and 1 here
        if value < 0.5:
            return Success(value)
        else:
            return Failure("Bad value! Bad! Go to your room!")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        yield action_that_might_fail(somearg)
        yield another_action_that_might_fail(someotherarg)
        some_val = yield action_that_might_fail_and_returns_something()
        yield something_that_used_the_return_value(some_val)
        do_something_else()
        mreturn(Success("All ok."))

在函数的每个yields处process_file,如果函数调用返回Failure,则process_file函数将退出,此时从失败的函数返回Failure值,而不是继续执行其余操作并返回Success("All ok.")

现在,想象一下使用嵌套ifs 进行上述操作!(您将如何处理返回值!?)

结论

Monads很好:)


笔记:

我不是Python程序员-我在一个忍者脚本中使用了链接到上面的monad库,以实现某些项目自动化。不过,我认为,通常首选的惯用方法是使用异常。

IIRC在链接的页面上的lib脚本中有一个错字,尽管我忘记了它是ATM。如果我记得的话,我会更新的。我将我的版本与页面的版本进行了比较,发现:def failable_monad_examle():-> def failable_monad_example():- 缺少pin example

为了获得Failable装饰函数(例如process_file)的结果,您必须在中捕获结果,variable然后执行a variable.value来获取结果。


2

函数是一个合同,其名称应表明它将履行的合同。恕我直言,如果您命名它remove_file,则应删除该文件,否则,将导致异常。另一方面,如果命名为try_remove_file,则应“尝试”删除该文件,然后返回布尔值以告知该文件是否已删除。

这将导致另一个问题-应该是remove_file还是try_remove_file?这取决于您的呼叫站点。实际上,您可以同时使用这两种方法,并在不同的情况下使用它们,但是我认为删除文件本身成功的可能性很高,因此我更喜欢remove_file在失败时仅抛出异常。


0

在这种特殊情况下,考虑一下为什么您可能无法删除文件可能很有用。假设问题在于文件可能存在或可能不存在。然后,您应该具有一个doesFileExist()返回true或false的函数,以及一个removeFile()仅删除文件的函数。

在您的代码中,您首先要检查文件是否存在。如果是这样,请致电removeFile。如果不是,则执行其他操作。

在这种情况下,removeFile如果由于某些其他原因(例如权限)而无法删除文件,您可能仍想引发异常。

总而言之,对于异常情况,应该抛出异常。因此,如果完全正常的情况是您要删除的文件可能不存在,那也不例外。编写一个布尔谓词进行检查。另一方面,如果您没有文件的写许可权,或者该文件位于突然无法访问的远程文件系统上,则可能是例外情况。


这是我所举示例的非常具体的示例,我宁愿避免。我尚未编写此文件,它将存档文件并记录数据库中已发生的事实。可以随时重新加载文件(尽管一旦加载的文件被重新加载的可能性大大降低),就有可能在检查和删除尝试之间通过另一个进程锁定文件。失败没有什么例外。这是标准的Python,不要先打扰检查并在引发时捕获异常(如果需要),我这次完全不想使用它。

如果没有什么异常的失败,那么检查是否可以删除文件是程序逻辑的合法部分。单一职责原则要求您应该具有检查功能和removeFile功能。
迪马(Dima)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.