自测是否仍然必要和有意义?

3
在Objective-C实例中通常的初始化顺序(例如在指定的初始化器中)如下所示:
- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self != nil) {
    // Do your stuff.
  }
  return self;
}

这是一个众所周知的模式,但真的有必要测试self是否被分配吗?我的意思是,如果超级方法中出现错误,它不会引发异常而只是返回nil吗?这真的是一种安全的模式吗?如果调用super时出现问题但仍然返回(随机)指针怎么办?
当然,这是防御性编程的问题,但对于你确信永远不会出现nil结果的情况下,进行如此多的测试看起来有些夸张。如果您知道可能会发生(必须记录),那么检查self当然是有意义的。

1
如果你返回 nil,很有可能仍然会遇到异常,只是不在 init 方法中。 - nhgrif
是的。遵循已经建立的约定。 - uchuugaka
如果调用 super 函数出现问题,然后返回的不是 nil 或有效指针而是垃圾值,那么这段代码存在严重的 bug,开发者应该受到惩罚。 - rmaddy
1
我刚刚注意到这是一个旧的讨论:https://dev59.com/aXM_5IYBdhLWcg3wp06l - Nikolai Ruhe
3个回答

3

当程序出现问题时,在Objective-C程序中引发异常是不合理的行为。抛出异常应仅用于关键、无法恢复、程序员错误的情况。

从init方法中发出错误信号的正确方法是返回nil,并可选地填充传递的NSError指针以提供更多信息。测试超级初始化器是否返回nil可以防止您进行无用的初始化,并有助于避免可能的崩溃,例如:

@implementation { 
  int _foo; // instance variable
}

- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self != nil) {
    _foo = 2;
  }
  return self;
}

如果父类初始化程序返回nil,那么这将导致崩溃,因为_foo = 2self->_foo = 2的简写方式,它将解除一个空指针。(只有当自身为nil时,消息发送才会返回nil;直接访问实例变量并不能保证这一点。)


一个可以很强烈地支持返回 nil 而不是引发错误的事实是,Objective-C 有这种范式,你通常不需要检查 nil。也许初始化器中的检查就是为此付出的代价? - Mike Lischke
我认为这个问题不是关于异常与返回nil的比较。这两种情况都有合理的原因,并且在苹果的框架中有许多例子。关键是:这两种情况都必须有文档记录并解释原因。如果存在从超类返回nil的可能性,那么你显然不能省略条件判断。 - Nikolai Ruhe

2

有些类,甚至包括苹果自己的NS类,在初始化器中可能会返回nil,例如NSArray和NSDictionary的initWithContentsOfFile:initWithContentsOfURL:方法。这些情况应该抛出异常吗?不需要!它们很容易被检测到并且很容易恢复。抛出异常只是给那些懒得进行正确错误检查的程序员提供了一个支持,对于那些做过检查的人来说则是一个麻烦。


在这些情况下,返回 nil 当然是有意义的,但这并不是重点。关键是如果不能有 nil 的情况下检查 nil(我期望这种情况比可以返回 nil 的情况要多得多)。 - Mike Lischke

0
问题归结为:有没有(很多)已知的类从初始化程序返回nil
我认为很少有这样的情况。当然,已知的情况(其中构造函数通常通过引用接受NSError)必须返回nil以指出错误。
但是,大多数其他初始化程序要么返回self参数,要么返回类的另一个实例。如果不是这种情况,则应在文档中明确说明,因为代码通常依赖于对象创建不会失败。
因此,我认为在检查超类行为时省略对nil的测试是完全安全的。
另一方面,这种模式如此普遍,以至于共同开发人员可能会对省略感到困惑,并可能指出它是疏忽。因此,如果您计划切换到不带条件的样式,则应该以某种方式将其传达给您的同事开发人员。

这正是我所思考的方向。有太多为永远不会返回nil的情况分配检查的操作。有更好的方法来消耗CPU周期。 - Mike Lischke
2
我不确定我同意你的说法,即只有“少数”类返回nil。例如,UIView的指定初始化程序(initWithFrame:,如OP的问题中所述)说明它可能返回nil,因此任何UIView都可能从其init方法返回nil。我认为这是很常见的,我认为假设没有这样的警告是初始化不能失败的保证是不安全的。 - Jesse Rusak
1
@JesseRusak 尽管如此,大多数在 init 之外的代码并不知道 initWithFrame: 可能会返回 nil,并且最终会遇到 addSubview: 抛出异常的情况,其中它抱怨 nil 参数。 在没有立即的空值检查的情况下,在应用程序通常仍然无法按预期工作。 因此,在 init 中崩溃实际上可能是一件好事,并且可以使调试更容易。 - Nikolai Ruhe
@JesseRusak,我刚刚提交了一个文档错误报告(radar 15671536),关于initWithFrame:返回nil的问题。 - Nikolai Ruhe
@NikolaiRuhe 好的。我认为他们不会改变这个,因为任何init方法都可以返回nil,所以对于initWithFrame:也是如此。 - Jesse Rusak
@JesseRusak 正是这个普遍规则在这里受到了质疑。 - Nikolai Ruhe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接