Swift 中的故障安全断言

Fail safe assertions in Swift

我通常在 Objective-C 中使用断言来声明一个值。在调试版本中,我断言以停止程序的执行并检查我的假设是否不正确。但是,在生产构建中,我找到了一种安全失败的方法,可以最大限度地减少对用户的影响。我通过创建一个宏来实现这一点,该宏将 NSAssert 封装在一个 if 语句中,该语句还执行我希望 运行 作为生产故障保护的代码。例如:

我会使用的断言宏:

#define AssertTrueOrExecute(condition, action) \
  if (!condition) { \
    NSAssert(testCondition, @"Condition failed"); \
    action; \
  }

在我的应用程序中的某个地方,我会有这样的东西:

- (void)someMethod
{
  BOOL testCondition = ...
  // Ensure the testCondition is true before proceeding any further
  AssertTrueOrExecute(testCondition, return);
  // Potentially unsafe code that never gets executed if testCondition is false
}

- (void)someReturningMethod
{
  BOOL testCondition = ...
  // Ensure the testCondition is true before proceeding any further
  AssertTrueOrExecute(testCondition, return @"safe string");
  // Potentially unsafe code that never gets executed if testCondition is false
}

因为我不能像 Swift 中提到的那样定义宏,有没有办法实现相同的行为?这就是我如何为我的 AssertTrueOrExecute 宏设置一个 Swift 等价物?

更新:

为了进一步解释这个问题,如果我使用 Swift 我目前会写这样的东西:

func someMethod () {
    let testCondition : Bool = ...

    // Ensure the testCondition is true before proceeding any further
    if (!testCondition) {
      assert(testCondition);
      return;
    }
    // Potentially unsafe code that never gets executed if testCondition is false
}

所以问题更多的是如何以类似的方式包装带有断言的 if 语句我有 Objective-C 宏,以便我可以尽早断言或 return例如?

更新二:

另一个例子是 return 的函数,例如:

func someReturningMethod () -> String {
    let testCondition : Bool = ...

    // Ensure the testCondition is true before proceeding any further
    if (!testCondition) {
      assert(testCondition);
      return "safe string";
    }    
    // Potentially unsafe code that never gets executed if testCondition is false
    return "some other string"
}

Swift 中没有宏,但在 Swift 中可能有其他方法可以实现与 Objective-C 中相同的功能。

然而,这里真正的问题是,您试图以您不应该采用的方式解决问题:

不要混合程序员错误运行时错误

相反,明确区分什么是程序员错误和什么是运行时错误。用断言处理程序员错误,用NSError处理运行时错误分别在Swift和try&catchthrow.

请注意,程序员错误的 "scope" 并不局限于程序因断言失败而失败:很可能此类错误具有不良副作用,使程序处于无效状态,并且通常此断言会检测到可能在断言失败之前很长时间内发生的错误。因此,当断言失败时,您的程序很可能已经处于无效状态。

根据经验,断言失败不应发生在生产代码中(阅读绝不能)。好吧,这些是 程序员错误 并且应该被修复,不是吗?您在单元测试中使用断言来验证您的假设。如果您仍然担心,您的假设可能会在生产中中断,并且还确定这不是运行时错误(应该始终妥善处理),那么它应该停止程序——无论如何,所有的赌注都没有了。在 Swift 中,您可以为此使用 fatalError

有时,违反特定假设是程序员错误还是运行时错误的区别并不总是那么明显,可能取决于上下文。不过,作为一名程序员,您始终可以定义它是什么。以字符串参数为例:如果您直接从想要创建帐户的用户输入的文本字段中获取它并被要求提供他的姓名,您应该验证该字符串,如果没有则 return/throw 一个错误'不符合您的期望 - 例如,如果它是空的、太短等。也就是说,在 Swift 中,您可能 throw 一个错误并在调用站点上优雅地处理它,可能在视图控制器中。另一方面,您 定义 初始化 User 对象是没有意义的,其 name 将为空。也就是说,在您的 init 例程中,您 定义 有效用户名不能为空的先决条件,并使用 assertfatalError 进行检查。在这种情况下,当没有代码路径初始化名称为空的用户时,您的程序是正确的。