Scala异常作为函数参数设计模式

Scala异常作为函数参数设计模式,scala,exception,types,Scala,Exception,Types,我正在编写一个web应用程序,其中使用异常来处理错误案例。我经常发现自己在写这样的助手: def someHelper(...) : Boolean {...} if (!someHelper(...)){ throw new SomeException() } 然后像这样使用它: def someHelper(...) : Boolean {...} if (!someHelper(...)){ throw new SomeException() } 这些异常表示无效参

我正在编写一个web应用程序,其中使用异常来处理错误案例。我经常发现自己在写这样的助手:

def someHelper(...) : Boolean {...}
if (!someHelper(...)){
    throw new SomeException()
}
然后像这样使用它:

def someHelper(...) : Boolean {...}
if (!someHelper(...)){
    throw new SomeException()
}
这些异常表示无效参数之类的情况,当处理这些异常时,它们会向用户发送有用的错误消息,例如

try {
     ...
} catch {
    case e: SomeException => "Bad user!"
}

这是否合理的做法?我如何将异常传递到helper函数并将其抛出?我在为这样的函数构造类型时遇到困难。

将异常实例传递给某个方法没有什么特别之处:

def someMethod(e: SomeException) {
  throw e
}
someMethod(new SomeException)

但我必须说我有一种非常明显的感觉,你的整个想法都有味道。如果要验证用户输入,只需编写验证器,例如,
UserValidator
,该验证器将使用类似于
isValid
的方法来测试用户输入并返回布尔值,您也可以在那里实现一些消息传递。例外情况实际上是为了不同的目的

将异常实例传递给某个方法没有什么特别之处:

def someMethod(e: SomeException) {
  throw e
}
someMethod(new SomeException)

但我必须说我有一种非常明显的感觉,你的整个想法都有味道。如果要验证用户输入,只需编写验证器,例如,
UserValidator
,该验证器将使用类似于
isValid
的方法来测试用户输入并返回布尔值,您也可以在那里实现一些消息传递。例外情况实际上是为了不同的目的

两种最常见的方法是让助手自己创建并抛出异常,或者让调用代码检查结果,如果需要,抛出有意义的异常

我从来没有见过一个库,在那里你通过了你期望助手抛出的异常。正如我所说,简单地实例化一个异常会带来惊人的巨大成本,如果您在整个代码中遵循此模式,您可能会看到一个整体性能问题。这可以通过使用来缓解,但是如果您只是忘记在几个关键函数中添加
=>
,那么您就会遇到一个很难跟踪的性能问题

在一天结束时,如果您希望帮助器抛出异常,那么帮助器本身已经知道它想要抛出什么样的异常是有意义的。如果我必须在A和B之间选择:

def helperA(...) { if (stuff) throw new InvalidStuff() }

def helperB(..., onError: => Exception) { if (stuff) throw onError }
每次我都会选择一个

现在,如果我必须在A和你现在拥有的之间做出选择,那是一个折腾。这实际上取决于上下文、您试图用助手完成的任务、它们的其他用途等


最后,在这种情况下,命名非常重要。如果您选择返回代码助手路线,那么助手应该有问题名称,例如
isValid
。如果您有异常抛出帮助程序,它们应该有操作名称,例如
validate
。甚至可能会强调它,比如
validate\ux

处理您尝试执行的操作的两种最常见的方法是,让助手自己创建并引发异常,或者让调用代码检查结果,并在需要时引发有意义的异常

我从来没有见过一个库,在那里你通过了你期望助手抛出的异常。正如我所说,简单地实例化一个异常会带来惊人的巨大成本,如果您在整个代码中遵循此模式,您可能会看到一个整体性能问题。这可以通过使用来缓解,但是如果您只是忘记在几个关键函数中添加
=>
,那么您就会遇到一个很难跟踪的性能问题

在一天结束时,如果您希望帮助器抛出异常,那么帮助器本身已经知道它想要抛出什么样的异常是有意义的。如果我必须在A和B之间选择:

def helperA(...) { if (stuff) throw new InvalidStuff() }

def helperB(..., onError: => Exception) { if (stuff) throw onError }
每次我都会选择一个

现在,如果我必须在A和你现在拥有的之间做出选择,那是一个折腾。这实际上取决于上下文、您试图用助手完成的任务、它们的其他用途等


最后,在这种情况下,命名非常重要。如果您选择返回代码助手路线,那么助手应该有问题名称,例如
isValid
。如果您有异常抛出帮助程序,它们应该有操作名称,例如
validate
。甚至可能会强调它,比如
validate\ux

我大部分时间都使用
或者
,没有例外。我通常使用异常,正如您所做的或类似的方法,当控制流必须返回到某个遥远的点时,否则就没有什么明智的事情可以做。但是,当异常可以在本地公平地处理时,我将改为

def myMethod(...): Either[String,ValidatedInputForm] = {
  ...
  if (!someHelper(...)) Left("Agree button not checked")
  else Right(whateverForm)
}
然后当我调用这个方法时,我可以

myMethod(blah).fold({ err =>
  doSomething(err)
  saneReturnValue
}, { form =>
  foo(form)
  form.usefulField
})
或者在
左侧(err)
右侧(form)
或其他各种情况下进行匹配

如果我不想马上处理错误,而是想处理返回值,那么

myMethod(blah).right.map{ form =>
  foo(form)
  bar(form)
}
我将得到一个
,如果它是一个错误消息,则错误消息保持为
,或者如果它正常,则将
{foo(form);bar(form)}
的结果作为
。您还可以使用
flatMap
链接错误处理,例如,如果您想对迄今为止的正确值执行额外检查并拒绝其中一些值,您可以

myMethod(blah).right.flatMap{ form =>
  if (!checkSomething(form)) Left("Something didn't check out.")
  else Right(form)
}
正是这种处理方式使得使用
比异常更方便(如果异常很常见,通常表现更好),这就是我使用它们的原因


(事实上,在很多情况下,我不在乎为什么会出问题,只在乎它出了问题,在这种情况下,我只使用一个
选项

我大部分时间都使用
或者
,而不是例外。我通常使用异常,正如您所做的或类似的方法,当控制流必须返回到某个distan时