问题 Scala中空值检查的最佳实践


我明白那个 null 在Scala中不受欢迎,并且应始终将可选值包装在一个 Option (或“null type”,如果有的话)。

优点很明显,永远不需要检查 null 值和结果代码更安全,更舒适。

然而,在实践中,没有什么可以阻止价值 null  - 规则不是由编译器强制执行的,而是更多的绅士协议。例如,在与Java API交互时,这很容易破坏。

这有最好的做法吗?比方说,我需要写一个 Url case类,并且可以从a创建此类的实例 java.net.URI

object Url {
  def apply(uri: URI): Url = Url(uri.getScheme, uri.getHost, uri.getRawPath)
}

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Url = copy(protocol = value)
  def host(value: String): Url = copy(host = value)
  def path(value: String): Url = copy(path = value)
}

一个 URI 实例可以返回 null 对于 getSchemegetHost 和 getRawPath。它被认为足以防止这些 apply(URI) 方法(用 require 陈述,例如)?从技术上讲,为了完全安全,最好是保护 protocolhost 和 path 辅助方法,以及构造函数,但这听起来像是一个非常多的工作和样板。

相反,它是否被认为是安全的,以防止已知接受/返回的API null 价值观(URI 在我们的示例中)并假设外部调用者将不会通过 null 价值观或只是责备他们呢?


4530
2018-06-02 10:07


起源



答案:


在处理纯函数时,总是更好地坚持总体实现而不是使用 nullrequire 或抛出异常,以编码数据中的所有故障。类似的类型 Option 和 Either 真的是那样的。它们都是monad,所以你可以使用“for”-syntax来实现它们。

对代码的以下修改显示了一个总函数 apply,当输入Java时,它只产生有意义的值 URI 提供没有 null秒。我们通过包装结果来编码“有意义”的概念 Option

object Url {
  def apply(uri: java.net.URI): Option[Url] =
    for {
      protocol <- Option(uri.getScheme)
      host <- Option(uri.getHost)
      path <- Option(uri.getRawPath)
    }
    yield Url(protocol, host, path)
}

case class Url(protocol: String, host: String, path: String)

功能 Option 是一个标准的空检查功能,它映射 null 至 None 并包装值 Some


关于“设置”功能,您可以实现 null - 使用相似的方式检查它们 Option。例如。:

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Option[Url] = 
    Option(value).map(v => copy(protocol = v))
  // so on...
}

不过我会建议不要这样做。在Scala中,从不使用常规 nulls,所以只有实施它才是常规的 null当从Java库中获取值时,处理“桥”API。这就是为什么 null转换时,检查确实很有意义 java.net.URI,但在那之后你就在斯卡拉世界,那里应该没有 nulls,因此 null - 检查只是变得多余。


9
2018-06-02 10:51



值得一提的是 nullCheck 只是 Option  目的 在斯卡拉。 - Ionuț G. Stan
谢谢,你的观点很好,我明白了。我觉得它没有解决保护帮助者的问题 protocol, host 和 path 但是方法,还是我错过了什么?我应该只考虑来电者永远不会通过 null? - Nicolas Rinaudo
你是什​​么意思不保护他们?他们都不会成为 null。 - goral
@IonuţG.Stan感谢您的纠正。我完全忘记了这一点。 - Nikita Volkov
@NicolasRinaudo请查看更新。 - Nikita Volkov


答案:


在处理纯函数时,总是更好地坚持总体实现而不是使用 nullrequire 或抛出异常,以编码数据中的所有故障。类似的类型 Option 和 Either 真的是那样的。它们都是monad,所以你可以使用“for”-syntax来实现它们。

对代码的以下修改显示了一个总函数 apply,当输入Java时,它只产生有意义的值 URI 提供没有 null秒。我们通过包装结果来编码“有意义”的概念 Option

object Url {
  def apply(uri: java.net.URI): Option[Url] =
    for {
      protocol <- Option(uri.getScheme)
      host <- Option(uri.getHost)
      path <- Option(uri.getRawPath)
    }
    yield Url(protocol, host, path)
}

case class Url(protocol: String, host: String, path: String)

功能 Option 是一个标准的空检查功能,它映射 null 至 None 并包装值 Some


关于“设置”功能,您可以实现 null - 使用相似的方式检查它们 Option。例如。:

case class Url(protocol: String, host: String, path: String) {
  def protocol(value: String): Option[Url] = 
    Option(value).map(v => copy(protocol = v))
  // so on...
}

不过我会建议不要这样做。在Scala中,从不使用常规 nulls,所以只有实施它才是常规的 null当从Java库中获取值时,处理“桥”API。这就是为什么 null转换时,检查确实很有意义 java.net.URI,但在那之后你就在斯卡拉世界,那里应该没有 nulls,因此 null - 检查只是变得多余。


9
2018-06-02 10:51



值得一提的是 nullCheck 只是 Option  目的 在斯卡拉。 - Ionuț G. Stan
谢谢,你的观点很好,我明白了。我觉得它没有解决保护帮助者的问题 protocol, host 和 path 但是方法,还是我错过了什么?我应该只考虑来电者永远不会通过 null? - Nicolas Rinaudo
你是什​​么意思不保护他们?他们都不会成为 null。 - goral
@IonuţG.Stan感谢您的纠正。我完全忘记了这一点。 - Nikita Volkov
@NicolasRinaudo请查看更新。 - Nikita Volkov