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

问题描述:

我正在编写一个Web应用程序,其中使用异常来处理错误情况。通常情况下,我发现自己写的助手这样的:作为函数参数设计模式的Scala异常

def someHelper(...) : Boolean {...} 

,然后用它是这样的:

if (!someHelper(...)){ 
    throw new SomeException() 
} 

这些例外代表相同的参数无效的事情,处理时,他们发出一个有用的错误消息的用户,例如

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

这是一个合理的方法吗?我怎么能将这个异常传入辅助函数并将它抛出?我在构建这种功能的类型时遇到了困难。

我大多数时间使用Either,没有例外。我通常会使用异常,就像你已经做过的或者类似的方式一样,当控制流程必须走回头路,回到某个遥远的点时,否则就没有什么明智的做法。然而,当异常还算可以在本地处理,我会代替

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 
}) 
Left(err)

或比赛VS Right(form),或各种其他的东西。

如果我不想处理错误在那里,而是要处理的返回值,我

myMethod(blah).right.map{ form => 
    foo(form) 
    bar(form) 
} 

,我会得到一个Either与错误信息不变为Left,如果是错误消息,或者{ foo(form); bar(form) }的结果为Right(如果没有问题)。您还可以使用flatMap链接错误处理,例如如果你想执行就这么远,正确的价值观额外的检查,并拒绝其中的一些,你可以

myMethod(blah).right.flatMap{ form => 
    if (!checkSomething(form)) Left("Something didn't check out.") 
    else Right(form) 
} 

它的这种处理,这使得使用Either更方便(通常是更好的表现,如果异常是常见的)比例外,这就是我使用它们的原因。

(事实上,在很多情况下,我不关心为什么出事了,只出了问题,在这种情况下,我只使用一个Option

没有什么特别之处传递一个异常实例的一些方法:

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

但我不得不说,我得到一个非常不同的感觉,你的整个想法只是气味。如果您想验证用户输入,只需编写验证器,例如UserValidator这将有一些像isValid方法来测试用户输入并返回一个布尔值,你也可以在那里实现一些消息。例外是真正用于不同的目的。

+0

咦?例外是准确的这种情况!以便与用户通信的应用程序部分可与系统的其他部分分离。如果'someHelper'是用于转换源自用户的数据的本地有用函数,那么肯定不应该知道如何与用户通信以显示错误消息。为了验证它,通信模块可能不应该知道对'someHelper'数据的要求。 – Ben 2011-12-22 04:02:45

+0

被警告。创建异常会填充堆栈跟踪,这可能是一项昂贵的操作。如果你走这条路线,按名称传递异常,所以它只在你使用它时创建:'def someMethod(e:=> Exception)'。 – leedm777 2011-12-22 04:34:58

+0

@Ben我的意思是,这些异常不是用来保存应用程序的状态信息,而是通过异常实例传递(而不是抛出)并用逻辑围绕它们看起来非常像这样 – 2011-12-22 05:49:58

处理你想要做的事情的两种最常见的方法是让助手自己创建并抛出一个异常,或者你正在做的是:让调用代码检查结果,然后抛出一个有意义的例外,如果需要。

我从来没有见过一个库,你传递你希望助手抛出的异常。正如我所说的on another answer,简单地实例化一个异常有一个令人惊讶的实际成本,如果你在整个代码中遵循这个模式,你可能会看到一个整体性能问题。这可以通过使用by-name parameters来缓解,但是如果您忘记将=>置于几个关键功能中,则会出现难以追踪的性能问题。

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

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

def helperB(..., onError: => Exception) { if (stuff) throw onError } 

我会每次选A。

现在,如果我不得不在A和你现在拥有的东西之间进行选择,那就是折腾了。这真的取决于上下文,你试图与帮手完成什么,他们可能会如何使用,等等。

最后一点,在这种情况下命名是非常重要的。如果你使用返回码辅助线路,你的助手应该有问题名称,例如isValid。如果你有异常投掷助手,他们应该有动作名称,比如validate。甚至可能会强调它,比如validate_!

另一种可选择的方法你可以检查出scalaz验证器,它为这种情况提供了很大的灵活性(例如,我应该在错误时崩溃,在最后累积错误并报告或完全忽略它们?)。 A few examples可能会帮助您决定这是否适合您。

如果您发现很难找到图书馆的方式,this answer给出了一些介绍性材料的一些指示;或退房。