Scala的case类和类字段中使用Optional会产生代码异

Scala的case类和类字段中使用Optional会产生代码异

本文介绍了在Scala的case类和类字段中使用Optional会产生代码异味吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Stackoverflow上已经有很多关于使用Java中 Optional 的正确方法的讨论(如,)

There were already quite a few discussions on Stackoverflow about proper ways of using Optional in Java (Discussions like this one, or this)

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

As of now, using Optional for class members in Java is widely recognized as a code smell and even discouraged by fact that it deliberately does not implement Serializable interface. Also, we should avoid it in DTOs, constructors and methods' input parameters. From OOP point of view everything I have read so far about Optional appeals to my reason.

我的问题是,Scala的FP方面是否会以我们应该使用 Optional 的方式进行某些更改?特别是因为在Scala中实现 Optional 似乎更加丰富.我发现很多文章描述了如何在Scala中使用它,但是没有一个单独的主题详尽列出了何时我应该使用它和何时我应该不会.

My question is, does FP side of Scala change something in a way we should use Optional ? Especially since implementation of Optional in Scala seems to be way richer. I have found plenty of articles describing how to use it in Scala, but not a single one that exhaust topic when should I use it and when should I not.

推荐答案

简短答案

Option字段具有用例;他们本质上不是坏的.但是,即使几个完善的库(例如 ScalaTest )定义具有Option字段的类,后者(IMO)往往是一种代码味道,因为他们常常为了自己的利益而做太多事情.

Short answer

Option fields have use cases; they're not intrinsically bad. However, even though several well established libraries (e.g. ScalaTest) define classes with Option fields, the latter, IMO, tend to be a code smell, as they often try to do too much for their own good.

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

In many cases, a type containing optional fields can easily and advantageously be replaced by an algebraic data type.

考虑一个处理帐户的业务领域.某个帐户的一天以打开帐户开始,但最终可能会关闭.除其他数据外,帐户还包含帐户的开立和关闭日期(如果适用).

Consider a business domain dealing with accounts. An account starts its life one day as an open account, but may eventually be closed. Accounts, among other data, contains the dates on which they were open and closed, where applicable.

这是使用Option字段实现的帐户:

Here is an implementation of an account, using an Option field:

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

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

We also have an account service, which defines, among other things, a close method:

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

由于多种原因,这种方法是有问题的.一个问题是Account的性能不是特别出色:由于closedOn是盒装"类型,因此可以说,您的间接寻址级别太多了.此外,Account的内存占用也不理想:已关闭的帐户"包含一个非常不有趣的值(None),这是浪费空间.

This approach is problematic, for a number of reasons. One problem is that Account isn't particularly performant: because closedOn is a "boxed" type, you have one level of indirection too many, so to speak. Moreover, Account's memory footprint is less than ideal: a "closed account" contains a pretty uninteresting value (None), which is a waste of space.

另一个更严重的问题是close方法不能在类型级别上强制参数为打开帐户",而结果为关闭帐户".您将必须编写测试来检查您的实施是否实施了此业务规则.

Another, more serious, problem is that the close method cannot enforce, at the type level, that the parameter be an "open account" and the result be a "closed account". You would have to write tests to check that this business rule is enforced by your implementation.

请考虑以下替代设计:

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更具表现力,并且 难于滥用 :

This small ADT remedies the performance problem, but there is more... You can now encode the business rule at the type level! This is an example of making illegal states unrepresentable (a phrase attributed to Yaron Minsky). As a result, your service's API becomes more expressive and harder to misuse:

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

此示例可能足以使您确信第二种方法是可取的,并且最好避免使用Option字段(或至少谨慎使用).

This example may be sufficient to convince you that the second approach is preferable, and that Option fields are best avoided (or, at least, used sparingly).

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

For more more about eliminating optional fields towards making illegal states unrepresentable, see

  • Yaron Minsky's blogpost
  • Scott Wlaschin's blogpost
  • Richard Feldman's elm-conf 2016 talk (skip to the 21'25'' mark, then rewind and watch the whole talk for great good!)

这篇关于在Scala的case类和类字段中使用Optional会产生代码异味吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-22 13:04