[译文]如何才能不崩溃 #7: Dealing with Nothing

原文地址:

http://inessential.com/2015/05/29/how_not_to_crash_7_dealing_with_nothin

考虑一下这行代码:

[thing doStuff];

如果thing为空(nil),没有问题。不会崩溃。什么都不会发生。

但是你不能以此推断nil在任何情况下都OK:

[self doStuff:thing];

如果thing是nil,那么会发生什么?如果依赖于doStuff:的实现 - 那么,它可能会崩溃。考虑下面这行代码:

menuItem.title = thing;

如果menuItem是一个NSMenuItem,那么当thing为nil的时候,它会崩溃。NSMenuItem的头文件上并没有说这些,文档也只提示了一下(“如果你不写title,请使用空字符串(@“”),不是nil。”)

这意味着你需要去确认thing不能为nil。你可能非常确信,thing不是nil。thing是一个字体的名字,但是考虑到一旦发生这种事情,我并没有理由去期盼系统API去获取一个字体名称,而不返回nil - 除此以外,有时候(很少,当然,从没在我的机器上发生过,无论我做什么)。

必读:

Nil接受事物是OK的 - 只要你的代码OK,那就不会发生任何事情。

Nil参数可能或者可能不是OK的。当调用系统API的时候,头文件或者文档并不会告诉你会发生什么。(这个在最大程度使用 为空性标注( nullability annotations) 之后,可能会改变。)

不要相信任何人。

断言

断言是一种记录假设和需求的很好的方式,并确保这些假设是正确的。断言不应该运用于发行版本(查看Xcode的 ENABLE_NS_ASSERTIONS 设置)。

我最喜欢的一个是 NSParameterAssert,我几乎完全使用它作为参数不能为nil的检查。

这非常容易使用:

- (void)someMethod:(id)someParameter {
  NSParameterAssert(someParameter);
  …do whatever…
}

在未来,我可能会开始使用为空性标注和NSParameterAssert两者。(我未来会写一些Swift代码,当它为空的时候,这就是另外一个事情了。但是现在并不讨论它,我没有足够的Swift经验去给予一些好的建议。)

我也经常使用NSAssert。NSAssert使用一个表达式和一个注释,但是我很懒,我从来不写注释。(在这种情况下也行。)

NSAssert(something == somethingElse, nil);

(一个关于懒惰的注意事项:懒惰的程序员不写崩溃的bug,因为他们不想以后还要去修复它。)

我最喜欢的崩溃错误

很多年以前,我的app NetNewsWire 有一个崩溃日志捕获者,它会在程序启动的时候从磁盘抓取最新的崩溃日志,然后发送给我。

当一些新的OS X系统发布(10.5,我想是),苹果改变了磁盘上的崩溃日志的格式。我以为是每个app一个文件,但是苹果修改为每次崩溃一个文件。我不得不编写新的代码去处理新的格式。

我做了一些修改。它传递到一些经验丰富的测试手上。几周之后,测试通过。所有都很好。

之后,有一天,我发行了这个版本,我收到大量的反馈,“它在启动的时候崩溃了!但在再次启动之后就工作正常了。”

这就是问题:当没有崩溃日志的时候,新的代码就崩溃了。之后,在下一次启动的时候 - 现在,它有一个崩溃日志了 - 它不崩溃了。(是的,一个会自我修复的崩溃错误。在崩溃日志捕获者中。就是这样。)

当然,这意味着所有新用户会立即崩溃,不仅仅对于那些很幸运的从来没崩溃过的人。

这对我来说是一个很大的提醒:总是需要考虑没有的情况。什么都不会发生,什么都很正常。但它也可能需要特殊处理,它应该总是被考虑到。

一个不太酷的崩溃错误

我不认为这可以装船 - 我想这只在测试阶段。

Vesper 去同步服务器。服务器返回JSON数据。Cocoa的JSON解析器将JSON的null转化为NSNull对象。

Vesper 期望获得一个NSString,而不是NSNull。Vesper 尝试在NSNull上调用一个字符串的方法,然后崩溃了。

表面上看,这似乎是一个困难的例子,因为你不能确定JSON文本给定的对象是你所期望的。你寻找一个字符串,但是你获得的是一个NSNull。

好吧,NSNull是你想尽可能独立的事情。它看上去像一个可以运行的代码(虽然我并不知道JSON nulls的情况)。(你永远不应该在JSON之外使用NSNull。几乎不要。超级大谎言。每隔几年,你要这么做一次,如果你真的真的需要。也许没有那么。)

这就是一部分,我前面提到的,我喜欢将JSON转换为中间对象。这是集中处理NSNull对象的一大部分 - 我不希望它们泄露到app的其他部分,它们碰到任何东西都会使它们发臭。

但是,另外一点,谁写服务端,谁就是你的死敌。他或她非常讨厌你。

现在,在Vesper的例子中,那就是我。但我仍然不得不去编写代码,哪怕服务端作者在我个人和专业的破坏下,依然不从(即使我知道他很酷,喜欢小猫。)这并不意味着我只检查NSNull - 这在JSON中很正常 - 但是要非常小心每一个数据。

任何事情都可以在任何时间成为任何东西。

(这并不总是乌龟。你想要乌龟 - 但这太简单了。它可能是任何东西。)

所有其他事情

初始化你的变量。就这么做。如果我有每一个这种崩溃的错误,我都会通过初始化变量为nil来修复它 - 好吧,我有一些硬币,但是你只要0个。

不初始化变量就像在玩汽油,然后边玩边说这很不错,因为火柴就在你口袋里。

Article by 付军