【问题标题】:LBYL vs EAFP in Java?Java中的LBYL与EAFP?
【发布时间】:2010-09-29 02:54:22
【问题描述】:

我最近在自学 Python,发现了 LBYL/EAFP 习语,用于在代码执行前进行错误检查。在 Python 中,似乎接受的样式是 EAFP,并且似乎与该语言很好地配合。

LBYL(Lok BYou Leap 之前):

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

EAFP(E更容易A询问F宽恕比P授权):

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 方法。只是偏好吗?

【问题讨论】:

    标签: java python error-handling idioms


    【解决方案1】:

    如果您正在访问文件,EAFP 比 LBYL 更可靠,因为 LBYL 中涉及的操作不是原子的,文件系统可能会在您查看和跳跃之间发生变化。实际上,标准名称是TOCTOU——检查时间,使用时间;检查不准确导致的错误是 TOCTOU 错误。

    考虑创建一个必须具有唯一名称的临时文件。找出所选文件名是否存在的最佳方法是尝试创建它 - 确保使用选项来确保如果文件已经存在则操作失败(在 POSIX/Unix 术语中,open() 的 O_EXCL 标志)。如果您尝试测试文件是否已经存在(可能使用access()),那么在显示“否”和您尝试创建文件的时间之间,可能是某人或其他人创建了该文件。

    相反,假设您尝试读取现有文件。您检查文件是否存在 (LBYL) 可能会说“它在那里”,但是当您实际打开它时,您会发现“它不存在”。

    在这两种情况下,您都必须检查最终操作 - LBYL 不会自动提供帮助。

    (如果您在使用 SUID 或 SGID 程序,access() 会提出不同的问题;它可能与 LBYL 相关,但代码仍然必须考虑失败的可能性。)

    【讨论】:

    • 很好的例子,乔纳森。这对于处理并发编程和双重检查锁习语的 Java 开发人员来说可能很有意义。
    • 这是一个很好的观点,但我不认为这真的与 LBYL 与 EAFP 有关。例如,每当您打开一个文件时,您基本上都在做同样的事情,一个返回错误的操作,您并没有在实际执行之前检查是否可以打开它。我认为这样的代码可以是 LBYL:多个函数调用,每个函数调用都可能失败,并且每个函数都被当场检查。 TOCTOU 是一个更普遍的问题。此外,您的回答解决了正确性问题,而我认为问题是关于两种解决方案都正确的情况。
    【解决方案2】:

    除了 Python 和 Java 中异常的相对成本之外,请记住,它们之间的理念/态度存在差异。 Java 试图对类型(以及其他所有内容)非常严格,需要明确、详细地声明类/方法签名。它假定您在任何时候都应该确切地知道您正在使用什么类型的对象以及它能够做什么。相比之下,Python 的“鸭子类型”意味着您不确定(也不应该关心)对象的清单类型是什么,您只需要关心它是否会在您要求它时发出嘎嘎声。在这种放任自流的环境中,唯一理智的态度就是假设事情会奏效,但如果不奏效,就要准备好应对后果。 Java 天生的限制性不适合这种随意的方法。 (这并不是要贬低方法或语言,而是说这些态度是每种语言习语的一部分,在不同语言之间复制习语往往会导致尴尬和沟通不畅……)

    【讨论】:

    • 另外,oranlooney.com/lbyl-vs-eafp 为每种方法提供了一套很好的优缺点。
    • 我不同意 Python 中的松散类型使得使用 EAFP 或多或少是明智的。当您请求宽恕时,您要求在预期的情况下获得宽恕。例如,如果“取消”方法要取消一个对象,我们将捕获我们期望返回的异常。我们预计取消可能会失败,因为对象具有阻止它被取消的活动关系,我们希望将其传达回 UI。如果取消方法由于某些不可预见的除以零场景而失败,我们希望它像程序中的任何其他错误一样正常失败。
    • 链接失效了,所以有人可以在这里阅读文章web.archive.org/web/20161208191318/http://www.oranlooney.com/…
    【解决方案3】:

    在 Python 中处理异常的效率比在 Java 中更高,这至少是部分您在 Python 中看到该结构的原因。在 Java 中,以这种方式使用异常会更加低效(在性能方面)。

    【讨论】:

    【解决方案4】:

    就个人而言,我认为这得到了惯例的支持,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 是必需的。
    • “常见异常”根本不是异常,所以在这种情况下当然首选 LBYL,你不觉得吗?
    • 说“即使没有抛出异常,try-catch 块也会产生一些开销”就像说“即使没有调用方法定义也会产生一些开销”。换句话说,在调用方法/异常之前,开销可以忽略不计。 Erickson 在我问到这个问题时解释得最好。
    • 我完全不同意。从逻辑上讲,EAFP 进行验证的好方法。因为如果抛出异常的检查已经存在,那么额外的检查只能不同步并且什么都不保存。但是,由于 C++、Java 和 C# 中的异常速度相对较慢,因此在预计会发生但很少发生故障时需要使用 LBYL。
    • EAFP 在多线程环境中还可以具有更好的安全属性(消除一些竞争条件)。
    【解决方案5】:

    考虑一下这些代码 sn-ps:

    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,但由于 isdigitisdecimal 之间的细微差别而失败;当使用字符串“①²³?₅”调用时,它会抛出错误而不是正确返回默认值。

    根据定义,后者使用 EAFTP 会导致正确处理。没有行为不匹配的范围,因为需要该要求的代码就是断言该要求的代码。

    使用 LBYL 意味着获取内部逻辑并将它们复制到每个调用站点。与其对您的要求进行一种规范的编码,不如在每次调用该函数时都有机会搞砸。

    值得注意的是,EAFTP不是关于异常的,Java 代码尤其不应该普遍使用异常。这是关于将正确的工作分配给正确的代码块。例如,使用 Optional 返回值是编写 EAFTP 代码的一种完全有效的方式,并且在确保正确性方面比 LBYL 更有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-06
      • 2017-10-21
      • 2011-03-06
      • 2014-01-21
      相关资源
      最近更新 更多