在case类值上强制实施约束的正确方法是什么?

Nic*_*udo 15 scala

在构建时抛出异常通常被视为不太礼貌的事情,但我不确定在尝试对case类值强制执行约束时如何避免这种情况.

比如说我需要表示一个范围,并且两个边界必须是正的.立即实施将是:

case class Range(from: Int, to: Int)
Run Code Online (Sandbox Code Playgroud)

然而,这并没有确保这两个fromto是积极的,也不是to大于from.

我的第一直觉是按如下方式实施:

case class Range(from: Int, to: Int) {
  require(from >= 0)
  require(to >= 0)
  require(to >= from)
}
Run Code Online (Sandbox Code Playgroud)

然而,这使得Range构造函数不安全.

是否存在一种通用模式来保持案例类的易用性,强制实施值约束并避免抛出异常?

DCK*_*ing 9

这是非常主观的,但我会尝试.

这取决于你的班级是如何构建的.对于在内部代码中随意使用的简单实用程序范围类,任何使用它的程序员都应该知道,输入范围错误的值可能会导致奇怪的结果.你可以假设这些类的合理使用确实没有遇到这个问题,并且在这些罕见的情况下抛出异常可能不是一个不好的做法,表明有人真的搞砸了.如果从背景中可以明显看出应该给课程带来什么价值,那么如果人们不能满足这些理智的期望,我认为抛出异常并不是特别糟糕.

将此与scala中有时仍会发生的NullPointerExceptions或ArithmethicExceptions 的行为进行比较.当面对足够的"精神错乱"时,有时候进行防御性打算是不合理的.

另一方面,如果您的课程由值控制较少的值填充,即它们是用户输入的直接后果,则您必须更好地控制您的构造.使用返回an Option或an 的函数创建一个伴随对象Either:

 object Range {
     def createSafe(from: Int, to: Int): Option[Range] = {
         if(from >= 0 && to >= 0 && to >= from)
            Some(Range(from, to))
         else
            None
     }
 }
Run Code Online (Sandbox Code Playgroud)

然后,您甚至可以通过覆盖apply建议的其他答案来重用案例类的实例化中的验证逻辑.

 object Range {
    def createSafe ...
    def apply(from: Int, to: Int): Range = {
        createSafe(from, to).getOrElse(throw new IllegalArgumentException(...))
    }
 }
Run Code Online (Sandbox Code Playgroud)


whe*_*ies 5

您可以重载apply伴随对象中的运算符以实现您想要做的事情:

object Range{
  def apply(from: Int, to: Int) ={
    val _from = Math.max(0, from)
    val _to = Math.max(0, to)
    if(_from < _to) new Range(_from, _to)
    else new Range(_to, _from)
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,to用作变量名可能会导致一些“有趣”的结果。

  • 只是为了确保我正确理解这一点:将无效输入强制为有效输入比失败更可取?例如,`Range(-1, -20)` 应该返回 `Range(1, 20)`,即使我们绝对确定这不是调用者想要的(不管他想要的是否可行) ? (3认同)