接口中的所有/大多数方法都应该返回Option吗?

时间:2012-05-30 11:59:22

标签: scala functional-programming

使用无效参数返回选项的所有接口(特征)方法是否是一个好的做法?

让我举个例子。如果我要为具有特征的概率分布实现库

trait Similarity {
   def getDensity(): Double
}

由于大多数分布都没有在整个真实空间中定义,因此总会存在一些非法参数,例如:高斯分布的非正方差。如果我理解正确,我应该返回Option[Double]而不是Double并抛出IllegalArgumentException

我认为大多数功能/计算都是如此。在这种情况下,什么是“最佳做法”?我担心这会让图书馆过于笨拙。

由于

4 个答案:

答案 0 :(得分:6)

我不会抛出IllegalArgumentException,因为它不是问题的参数,而是对象的状态。如果它是一个例外,IllegalStateException将匹配。

然而,真正的答案取决于您在遇到问题时希望调用者做什么。

如果他们自己会抛出一个异常,那就是你应该做的,省去他们的麻烦。

如果他们根据不可能的答案做一些不同的事情,选项[双]是一个很好的指标。

值得了解但不太可能有用的可能性是Double.NaN,实际上是一个空对象,但是对于双打。

答案 1 :(得分:3)

答案主要是风格和意图的问题。

如果错误实际上是异常情况,那么抛出异常实际上并没有错误。如果您决定沿着抛出异常的路径前进,我建议抛出一个ArithmeticException,或者您编写的某个子类,因为这更能说明问题所在。

这是一种经常发生的错误,最好是由来电者处理吗?或者这是一种罕见的情况,最好在上层处理?或者甚至可能是某些更基本错误的指标,例如数据或编码问题?

为了进行比较,将整数除以0是正确的,但强制每个人在每次划分时都要处理它会很快变老。当您确定不会使用您提供的数据抛出异常时,尤其如此。想象一下写x / 2 + 5

// normally
x / 2 + 5

// divide returns Option[Int]
(x / 2).map(_ + 5).get

// divide returns Either[ArithmeticException, Int]
(x / 2).right.map(_ + 5).right.get

如果调用者可以并且应该处理此错误,则Option[Double]Either[someErrorClass, Double]会很好。

Option很不错,如果你不在乎它为什么失败/无效,只是它是。调用者也很容易处理。

Either如果有多种原因导致失败,那就很好了,并且调用者知道原因很重要。但是,这比Option处理起来要困难得多。

答案 2 :(得分:2)

通常,对于API,如果总是定义,我将返回Double,如果有时输入值有时未定义,则返回Option [Double]。如果对于无效输入未定义,我将使用[someError,Double],其中将返回Left以进行无效输入。 (或来自scalaz的验证,与之相似)。

认为我不会做的就是返回null或抛出异常。如果你要返回一个左边指示错误的Either,左边的错误可能是Throwable(例如IllegalArgumentException或IllegalStateException),但我会避免抛出它。

答案 3 :(得分:2)

除了已经给出的答案:

Option类型并不总是唯一的选择。并且大多数接口返回Option当然是不可取的。通常情况下,只有在的情况下,调用者才能期望该方法有时会返回“无”

设计一个好的API需要更多的思考。看看你的特质和课程。如果他们不能提供这个或那个财产,他们是完整的吗? - 如果在没有特定属性的情况下它们不完整,则该属性不应为Option值。相反,您可能会说:如果无法提供属性,则该对象将具有不同的类型。

为了举例,想一下代表棋盘游戏地图的网格。每个字段都由Cell数据类型表示。有些细胞可能有颜色。

API的第一个版本可能如下所示:

trait Cell {
  def color:Color
}

现在,您在某些时候注意到某些单元格没有颜色。例如,空单元格。或者只包含文本的单元格,该文本应该以默认的GUI颜色呈现。

所以你最终考虑这个版本:

trait Cell {
  def color:Option[Color] = None
}

现在,如果需要,每个Cell实现都可以自由覆盖color属性。 但这不是唯一可行的解​​决方案。

想想这个替代方案:

trait Cell { }
trait ColoredCell extends Cell {
  def color:Color
}

现在,单元格的类型确定它是否有颜色,并强制着色单元格有颜色。

UI层上的某些代码可能包含这样的片段:

...
val cell:Cell = grid cellAt coordinates
val uiComponent:JComponent = ...
cell match {
  case coloredCell:ColoredCell => uiComponent setColor coloredCell.color
  case _ => // No color assigned
}

这种方法最适用于不可变对象。它们永远不会改变,而是返回一个代表修改版本的新实例。

例如,每个单元格可能都有一个设置颜色的方法:

trait Cell {
  def withColor(color:Color):ColoredCell
}

它返回一个新单元格,它代表单元格的副本,具有不同的颜色。当然,我们已经知道这将是ColoredCell的一个实例,所以我们把它放在方法合同中。

有时,这种方法可以很好地运作,但您应该事先仔细检查它与您的模型的匹配程度。