【问题标题】:Why are null checks bad / why would I want an optional to succeed if it's null?为什么空检查不好/如果它为空,为什么我希望可选项成功?
【发布时间】:2018-10-30 03:22:43
【问题描述】:

我已经阅读了很多类似问题的答案以及一些教程,但没有一个能解决我的主要困惑。我是本地 Java 编码员,但我也使用 Swift 编程。

我为什么要使用可选项而不是空值?

我读到这样可以减少空值检查和错误,但这些都是必要的,或者通过干净的编程很容易避免。

我也读过它,所以所有引用都成功(https://softwareengineering.stackexchange.com/a/309137/227611val length = text?.length)。但我认为这是一件坏事或用词不当。如果我调用长度函数,我希望它包含一个长度。如果没有,代码应该就在那里处理它,而不是继续。

我错过了什么?

【问题讨论】:

  • 我不知道它如何应用于 Java/kotlin,但是在 Swift 中,如果您将一个值声明为“可选”,那么开发人员会更加重视如何决定处理这些值——比如打开它们或检查它们,以及编译器级别的检查。 Java 和 ObjC 中的许多问题归结为“假设”值的状态,这会导致运行时崩溃。因此,可选是使开发人员更加了解潜在风险的语言方式。 Optionals 还向外界提供了一个契约,让人们知道一个值可能没有被初始化
  • @MadProgrammer 有效的推理,而不是微不足道的。但是这不能通过明确的文档来解决吗? IE:如果允许变量为空,则在文档中指定为空。如果它再次为空,或者在它应该发生的时候你没有处理空,那就是一个错误。
  • “文档明确” ???? ...对不起,但我看到了太多与现实相矛盾的文档。通过使用可选项,您正在提供“自我记录代码”。一个 API 为什么要故意在这个主题上含糊其辞,因为开发人员还没有想到要提及(是的,你一直都看到这一点),或者 API 足够抽象以至于不知道一个值是否可以为空。
  • "explicit with documentation" 你总是希望编译器为你做尽可能多的工作。人类并不完美。我们依靠这些工具来捕捉我们的错误。在编译期间捕获它们比在运行时(或者当我们最终开始阅读文档时......)要好得多。如果您不必阅读文档即可了解 API 的合同,那么 API 不是更有用吗?我完全支持可靠而详细的文档,但我更支持没有隐藏陷阱的简单 API。
  • 这个问题不适用于 Kotlin。 Kotlin 通常使用的是 explicitly 可空类型、explicitly 非空类型和空安全运算符,而不是可选类型。尽管 Swift 可选类型和 Kotlin 可空类型的语法非常相似,但它们本质上是不同的。

标签: swift kotlin null optional


【解决方案1】:

选项提供清晰的类型。 Int 存储实际值 - 始终,而 Optional Int(即 Int?)存储 Int 或 nil 的值。可以说,这种显式的“双重”类型允许您制作一个简单的函数,该函数可以清楚地声明它将接受和返回什么。如果您的功能是简单地接受一个实际的 Int 并返回一个实际的 Int,那就太好了。

func foo(x: Int) -> Int

但是如果你的函数想要允许返回值是 nil,参数是 nil,它必须通过明确地使它们成为可选的来做到这一点:

func foo(x: Int?) -> Int?

在其他语言(例如 Objective-C)中,对象总是可以为 nil。 C++ 中的指针也可以为 nil。因此,您在 Obj-C 中收到的任何对象或您在 C++ 中收到的任何指针都应该检查为零,以防万一它不是您的代码所期望的(真正的对象或指针)。

在 Swift 中,关键是您可以声明非可选的对象类型,因此您将这些对象交给任何代码都不需要进行任何检查。他们可以安全地使用这些对象并知道它们是非空的。这是 Swift 选项强大功能的一部分。如果你收到一个可选的,当你需要访问它的值时,你必须显式地将它解包到它的值。那些使用 Swift 编码的人总是尽可能地让他们的函数和属性成为非可选的,除非他们真的有理由让它们成为可选的。

Swift 可选项的另一个美妙之处在于所有用于处理可选项的内置语言结构,使代码编写得更快、阅读更清晰、更紧凑……省去了很多检查的麻烦并解压缩一个可选的,相当于你在其他语言中必须做的。

nil-coalescing 运算符 (??) 就是一个很好的例子,if-let 和 guard 以及许多其他运算符也是如此。

总而言之,可选项鼓励并强制在代码中进行更明确的类型检查 - 由编译器而不是在运行时完成的类型检查。当然,你可以用任何语言编写“干净”的代码,但在 Swift 中这样做更简单、更自动化,这在很大程度上要归功于它的可选项(以及它的非可选项!)。

【讨论】:

  • 在Java中,所有的对象引用都已经是双重类型了。此外,如果您给它无效的输入,我仍然不明白您希望函数在什么情况下执行。 foo(null)foo({unassigned optional}) 应该抛出错误。
  • 他们鼓励代码内文档是一个合乎逻辑的原因。
  • 对。在 Swift 中,我可以传入一个 not 可选的对象,这就是重点。接收代码不需要检查 nil 来验证对象。至于 foo(nil),应该不难想象一个方法可以选择接受一个参数……如果调用者选择传入 nil 作为参数,则方法在没有它的情况下继续。
  • 宾果游戏! “那些使用 Swift 编码的人总是尽可能地让他们的函数和属性成为非可选的,除非他们真的有理由让它们成为可选的。”可选项的真正好处是能够声明非可选项!使用非可选代码编写代码更加容易和安全。有无数种情况不需要选项(可以为空的变量)。很好的答案。
  • @MikeTaverne 这确实清楚了很多,谢谢。我最终从 Smartcat 的回答中得到了答案,但花了几分钟。
【解决方案2】:
  1. 避免编译时出错。这样您就不会无意中传递空值。

  2. 在 Java 中,任何变量都可以为空。因此,在使用它之前检查 null 成为一种习惯。在 swift 中,只有 optional 可以为 null。所以你必须只检查可选的可能的空值。

  3. 您不必总是检查可选项。您可以在不展开选项的情况下同样出色地处理选项。将方法发送到具有 null 值的可选项不会破坏代码。

可能还有更多,但那些帮助很大。

【讨论】:

  • 1) 如果您无意中传递了一个空值,您应该得到一个错误。这是一个错误。
  • 2) 你应该假设一个变量不为空,除非它可能是一个有效的场景。在这种情况下,您应该检查空值。 3) Swift 给了我很多关于假设函数中的可选项不是 null 等问题。也许这只是 Swift 的事情。
  • @Prime624 1) 是的,这是 swift 中的一个错误,但在其他语言中没有。这就是选项有用的原因。 2)好吧,如果我传递一个变量,我不应该费心检查它是否为空,但如果我要使用它,我需要检查它是否具有有效值。因此,情况似乎总是如此。 3) 一开始很难理解可选项的概念,但我开始明智地使用可选项的次数越多,我的应用程序就会停止崩溃,原因不明,包括来自服务器的空响应、错误的 JSON 格式等。
【解决方案3】:

TL/DR:您所说的通过干净的编程可以避免的空检查也可以由编译器以更严格的方式避免。您所说的必要的空值检查可以由编译器以更严格的方式强制执行。可选项是使这成为可能的类型构造。


var length = text?.length

这实际上是选项有用的一种方式的一个很好的例子。如果文本没有值,那么它也不能有长度。在 Objective-C 中,如果 text 为 nil,那么您发送给它的任何消息都不会执行任何操作并返回 0。这一事实有时很有用,它可以跳过大量的 nil 检查,但也可能导致细微的错误.

另一方面,许多其他语言指出了这样一个事实,即您通过在代码执行时立即崩溃来帮助您向 nil 指针发送消息。这使得在开发过程中查明问题变得更容易一些,但是当它们发生在用户身上时,运行时错误并不是那么严重。

Swift 采用了不同的方法:如果text 没有指向有长度的东西,那么没有长度。可选项不是指针,它是一种有值或没有值的类型。你可能会认为变量length 是一个Int,但它实际上是一个Int?,这是一个完全不同的类型。

如果我调用长度函数,我希望它包含一个长度。如果没有,代码应该就在那里处理它,而不是继续。

如果text 为nil,则没有对象可以向其发送length 消息,因此length 甚至不会被调用,结果为nil。有时这很好 - 如果没有 text,那么也没有 length 是有道理的。您可能不在乎——如果您准备在text 中绘制角色,那么没有length 的事实不会打扰您,因为无论如何都没有什么可绘制的。 textlength 的可选状态迫使您处理这些变量在您需要值时没有值的事实。

让我们看一个稍微具体一点的版本:

var text : String? = "foo"
var length : Int? = text?.count

这里,text 有一个值,所以length 也有一个值,但length 仍然是可选的,所以在将来的某个时候,你必须在使用之前检查一个值是否存在它。

var text : String? = nil
var length : Int? = text?.count

在上面的例子中,text 为 nil,所以length 也为 nil。同样,在尝试使用这些值之前,您必须处理 textlength 可能没有值的事实。

var text : String? = "foo"
var length : Int = text.count

猜猜这里发生了什么?编译器会说 哦,不! 因为text 是可选的,这意味着您从中获得的任何值也必须是可选的。但是代码将length 指定为非可选的Int。让编译器在编译时指出这个错误比让用户稍后指出要好得多

var text : String? = "foo"
var length : Int = text!.count

在这里,! 告诉编译器您认为自己知道自己在做什么。毕竟,您只是text 分配了一个实际值,因此假设text 不为零是相当安全的。您可能会编写这样的代码,因为您希望考虑到text 稍后可能会变为 nil 的事实。如果您不确定,请不要强制解包选项,因为...

var text : String? = nil
var length : Int = text!.count

...如果text nil,那么你已经背叛了编译器的信任,你应该得到你(和你的用户)得到的运行时错误:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

现在,如果text 不是可选的,那么生活就很简单了:

var text : String = "foo"
var length : Int = text.count

在这种情况下,您知道 textlength 无需任何检查即可安全使用,因为它们不可能为 nil。你不必小心“干净”——你实际上不能将任何不是有效的String 分配给text,每个String 都有一个count,所以length 将得到一个值。

我为什么要使用可选项而不是空值?

在过去的 Objective-C 时代,我们曾经手动管理内存。有一个small number of simple rules,如果您严格遵守规则,那么Objective-C 的保留计数系统运行良好。但即使是我们当中最优秀的人,有时也会出现失误,有时会出现复杂的情况,很难确切知道该怎么做。 StackOverflow 和其他论坛上与内存管理规则相关的大部分 Objective-C 问题。然后,Apple 引入了 ARC(自动保留计数),其中编译器接管了保留和释放对象的责任,并且内存管理变得更加简单。我敢打赌,现在关于 SO 的 Objective-C 和 Swift 问题中只有不到 1% 与内存管理有关。

Optional 就是这样:它们将跟踪变量是否具有、不具有或不可能没有值的责任从程序员转移到编译器。 p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-18
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    相关资源
    最近更新 更多