在Scala的case类和类字段中使用Optional是否有代码味道?

时间:2017-02-04 15:46:27

标签: java scala oop functional-programming

在Stackoverflow上已经有很多关于在Java中使用 Optional 的正确方法的讨论(像this oneor this这样的讨论)

截至目前,在Java中使用 Optional 作为类成员被广泛认为是一种代码气味,甚至因故意不实现 Serializable 接口而气馁。此外,我们应该在DTO,构造函数和方法的输入参数中避免它。从OOP的角度来看,到目前为止我所读到的关于 Optional 的所有内容都吸引了我的理由。

我的问题是,Scala的FP端是否会以某种方式改变某些内容 Optional ?特别是因为在Scala中实现 Optional 似乎更加丰富。我发现很多文章描述如何在Scala中使用它,但没有一篇文章耗尽主题应该使用它时 我不应该。

2 个答案:

答案 0 :(得分:9)

简短回答

Option个字段有用例;他们本质上并不坏。然而,即使几个成熟的库(例如this answer)定义了具有Option字段的类,后者,IMO,往往是代码气味,因为它们经常试图为自己的利益做太多

在许多情况下,包含可选字段的类型可以轻松有利地用代数数据类型替换。

一个例子

域名

考虑处理帐户的业务域。帐户有一天会以开放帐户开始,但最终可能会关闭。除其他数据外,帐户包含开放和关闭的日期(如适用)。

使用Option字段

以下是帐户的实施,使用Option字段:

final case class Account(openedOn: LocalDate, closedOn: Option[LocalDate], ...)

我们还有一个帐户服务,除其他外,还定义了close方法:

trait AccountService {
  // ...
  def close(account: Account): Account
}

出于多种原因,这种方法存在问题。一个问题是Account并不是特别有效:因为closedOn是一个"盒装"类型,你有一个层次的间接太多,可以这么说。此外,Account的内存占用量还不尽如人意:a"已关闭的帐户"包含一个非常无趣的值(None),这是浪费空间。

另一个更严重的问题是,close方法无法在类型级别强制执行参数为"开放帐户"结果是一个"已关闭的账户"。您必须编写测试以检查您的实现是否强制执行此业务规则。

使用小型ADT(并避开Option字段)

考虑以下替代设计:

sealed trait Account { ... }

final case class OpenAccount(openedOn: LocalDate, ...) extends Account

final case class ClosedAccount(openedOn: LocalDate, closedOn: LocalDate, ...) extends Account

这个小型ADT可以解决性能问题,但还有更多......您现在可以在类型级别对业务规则进行编码!这是使非法国家无法代表的一个例子(Yaron Minsky的一句话)。因此,您服务的API变得更具表现力,ScalaTest

trait AccountService {
  // ...
  def close(account: OpenAccount): ClosedAccount
}

这个例子可能足以让你相信第二种方法更可取,并且最好避免使用Option字段(或至少谨慎使用)。

资源

有关消除使非法国家无法代表的可选字段的更多信息,请参阅

答案 1 :(得分:8)

Option scala实现Serializable

强烈建议在可变属性中使用scala中的OptionOption[T]被认为优于T,因为前者比后者更安全。

  

截至目前,在Java中使用Optional作为类成员的做法很普遍   被认为是代码气味

相反,在scala中使用null替换可选属性被视为代码气味。

尽管Scala是一种功能语言,但它也是一种促进类型安全的语言。在理想世界中,真正完全类型安全的语言不会有NullpointerException等运行时异常,而Option在Scala中扮演重要角色以避免它。

Option [T]表明属性可以处于null状态(即None)并强制属性的客户端处理null场景。因此,Option会向类型系统添加更多信息,并使代码更加类型安全。

使用模式匹配和Monad / Monoid等语言功能,在Scala中使用可选数据类型的经济性在Scala中非常便宜且用户友好,与Java相比。

模式匹配

 optionalVariable match {
   case Some(x) => /* handle when variable has a value*/
   case None => /* handle when the variable doesn't have a value*/
 }

选项为Monad

optionalVariable foreach { x => /* run code when the variable is defined*/ }
optionalVariable map { x => /* map optional type to another type */}

修改

Jubobs非常适合使用Option替换为自定义类型的情况。但我认为还有更多的情况,其中可选属性更有意义。例如:如果Account对象具有可选属性,例如emailIdphoneNo,则Option [T]将是更好的解决方案,因为为每个组合创建自定义类型将是不切实际的并且会导致类爆炸。