4

将所有可能存在未来实现的接口(特征)方法设置为返回选项的无效参数是否是一种好习惯?

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

trait Similarity {
   def getDensity(): Double
}

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

我认为大多数函数/计算也是如此。在这种情况下,什么是“最佳实践”?恐怕这会使图书馆过于笨拙。

谢谢

4

4 回答 4

6

I wouldn't throw an IllegalArgumentException as it is not the arguments that are the problem, but the state of the object. If it were to be an exception, IllegalStateException would match.

However, the real answer depends on what you expect the caller to do in the case of a problem.

If they would themselves throw an exception, that's what you should do, saving them the bother.

If they would do something different based on the answer being impossible, an Option[Double] is a good indicator.

A possibility worth knowing of, but less likely to be useful, is Double.NaN, effectively a Null Object but for Doubles.

于 2012-05-30T12:11:49.937 回答
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

于 2012-05-30T14:48:58.933 回答
2

除了已经给出的答案:

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

设计一个好的 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,所以我们将它放在方法契约中。

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

于 2012-05-30T14:09:14.513 回答
2

一般来说,对于一个 API,如果它总是被定义,我会返回 Double,如果它有时对于有效的输入值是未定义的,我会返回 Option[Double]。如果对于无效输入未定义,我将使用 Either[someError,Double] ,其中将为无效输入返回 Left 。(或来自 scalaz 的验证,类似于两者)。

我认为我永远不会返回 null 或抛出异常。如果要返回 Left 指示错误的 Either,则左侧的错误可以是 Throwable(例如 IllegalArgumentException 或 IllegalStateException),但我会避免抛出它。

于 2012-05-30T12:31:10.950 回答