【问题标题】:What is the rationale behind Apple's pattern of checking return value rather than error?Apple 检查返回值而不是错误的模式背后的基本原理是什么?
【发布时间】:2019-01-18 10:37:58
【问题描述】:

Apple 的Using and Creating Error Objects 指南提供了以下代码示例:

NSError *theError;
BOOL success = [myDoc writeToURL:[self docURL] ofType:@"html" error:&theError];

if (success == NO) {
    // Maybe try to determine cause of error and recover first.
    NSAlert *theAlert = [NSAlert alertWithError:theError];
   [theAlert runModal]; // Ignore return value.
}

并附上声明:

重要提示:成功或失败由方法的返回值指示。虽然间接返回 Cocoa 错误域中的错误对象的 Cocoa 方法如果通过直接返回 nil 或 NO 指示失败,则保证返回此类对象,但在尝试对NSError 对象。

我一直想知道为什么这种模式如此重要?为什么我们总是要检查返回值?如果我们检查 error 是否为 nil 有什么问题?

【问题讨论】:

    标签: objective-c cocoa nserror


    【解决方案1】:

    这种设计并不是很不寻常,也可以比较标准 C 中的 errno。

    该设计具有许多潜在优势:

    • 函数不必通过指针写入成功。这不仅使此类函数的实现更容易且不易出错,而且还可以带来很小的性能优势(例如,如果函数成功,这可以防止 CPU 缓存失效)。

    • 如果我们总是在访问错误之前检查函数是否失败,我们可以将相同的错误指针用于多个函数。否则,我们可能会遇到之前的失败,而不是最近的函数失败。

    • 这使得验证代码更容易编写。例如。默认情况下,函数可以设置错误。如果所有验证都通过,该函数可以简单地返回成功,而不必重置错误变量。

    • 一个函数在调用其他函数时可以使用相同的错误指针,但是这些帮助函数的失败并不一定意味着顶层函数的失败。

    在您的具体情况下,变量NSError *theError; 尚未初始化。访问该变量而不首先分配它会调用未定义的行为。文档仅保证在发生错误时将设置变量。

    【讨论】:

    • 还有;计算误差可能很昂贵。如果调用者只需要知道成功/失败,那么它可以为错误参数传递 NULL 并且被调用者可以知道不必费心计算错误。
    • 关于未初始化变量的最终评论仅适用于 ARC 之前的代码(并且引用的 Apple 指南最初是在 ARC 之前的时代编写的)。在ARC代码中,默认多年,变量会自动初始化为nil
    【解决方案2】:

    想象一下你用其他几个方法来实现一个方法:

    -(BOOL)sendCachedRequestReturningError: (NSError**)err {
        BOOL success = [self readCachedRequestReturningError:err];
        if (!success && (*err).domain == MYFileDomain && (*err).errorCode == MYFileNotFoundCode) {
            success = [self sendUncachedRequestReturningError:err];
        }
    
        return success;
    }
    

    现在这里有4条代码路径:

    1. 有一个缓存请求。我们将返回success == YES,一切都很好。
    2. 尝试从缓存中读取时发生不可恢复的错误。 readCachedRequestReturningError: 将设置err 并设置success == NO,调用者将调用presentError: 或其他任何内容
    3. 尝试执行网络请求时出错。同#2,设置errsuccess == NO
    4. 没有缓存,但我们可以发出网络请求。 readCachedRequestReturningError: 会将err 设置为有效的NSError{MYFileDomain, MYFileNotFoundCode},但随后sendUncachedRequestReturningError: 将成功并设置success == YES,并且根本不接触err,将先前的错误留在其中。如果你现在检查err而不是检查返回值,当一切顺利时你会认为有错误。

    注意:上面的代码被大大简化了,因为我们只关心错误。当然,在实际程序中,这些方法可能会有另一个返回参数用于请求的实际回复,或者会返回回复或nil,而不是successBOOL。它还可能检查err 是否为NULL

    【讨论】:

    • err.domain -> (*err).domain,别忘了先检查NULL
    • 好收获。我将留下NULL-check out,因为这是一个简单的示例,但我同意在实际应用中您可能会将NSError** 标记为_Nullable 并检查。
    猜你喜欢
    • 2017-06-01
    • 1970-01-01
    • 2014-05-14
    • 2017-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    • 2012-05-18
    相关资源
    最近更新 更多