使用无效参数返回选项的所有接口(特征)方法是否是一个好的做法?
让我举个例子。如果我要为具有特征的概率分布实现库
trait Similarity {
def getDensity(): Double
}
由于大多数分布都没有在整个真实空间中定义,因此总会存在一些非法参数,例如:高斯分布的非正方差。如果我理解正确,我应该返回Option[Double]
而不是Double
并抛出IllegalArgumentException
。
我认为大多数功能/计算都是如此。在这种情况下,什么是“最佳做法”?我担心这会让图书馆过于笨拙。
由于
答案 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
的一个实例,所以我们把它放在方法合同中。
有时,这种方法可以很好地运作,但您应该事先仔细检查它与您的模型的匹配程度。