Apple 检查 return 值而不是错误的模式背后的基本原理是什么?

What is the rationale behind Apple's pattern of checking return value rather than error?

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.
}

并附上声明:

Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.

我一直想知道为什么这个模式如此重要?为什么我们应该始终检查 return 值?如果我们检查错误是否为零,有什么问题?

这个设计不是很不寻常,也比较一下标准 C 中的 errno。

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

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

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

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

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

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

假设您根据其他一些方法实现了一个方法:

-(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. 有一个缓存请求。我们就 return success == YES 一切都很好。
  2. 尝试从缓存中读取时发生不可恢复的错误。 readCachedRequestReturningError: 将设置 err 并设置 success == NO 并且调用者将调用 presentError: 或其他任何
  3. 尝试进行网络请求时发生错误。同#2,设置errsuccess == NO.
  4. 没有缓存,但是我们可以进行网络请求。 readCachedRequestReturningError: 会将 err 设置为有效的 NSError{MYFileDomain, MYFileNotFoundCode},但随后 sendUncachedRequestReturningError: 会成功并设置 success == YES,并且根本不会触及 err,留下之前的错误。如果您现在检查 err 而不是检查 return 值,当一切顺利时您会认为出现错误。

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