【问题标题】:Scala exception as a function argument design patternScala 异常作为函数参数设计模式
【发布时间】:2011-12-22 22:07:59
【问题描述】:

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

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

然后像这样使用它:

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

这些异常代表无效参数之类的东西,当处理它们时,它们会向用户发送有用的错误消息,例如

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

这是一个合理的方法吗?我怎样才能将异常传递给辅助函数并将其抛出?我在为这样的函数构造类型时遇到了麻烦。

【问题讨论】:

    标签: scala exception types


    【解决方案1】:

    我大部分时间都使用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 比使用异常更方便(并且通常性能更好,如果异常很常见),这就是我使用它们的原因。

    (事实上,在很多情况下,我并不关心为什么出了什么问题,只是那个出了问题,在这种情况下,我只使用@987654334 @.)

    【讨论】:

      【解决方案2】:

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

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

      但我不得不说,我有一种非常明显的感觉,就是你的整个想法闻起来很香。如果您想验证用户输入,只需编写验证器,例如UserValidator 将有一些类似isValid 的方法来测试用户输入并返回一个布尔值,你也可以在那里实现一些消息传递。异常实际上是为了不同的目的。

      【讨论】:

      • 嗯?例外完全适用于这样的情况!这样应用程序中与用户通信的部分就可以与系统的其他部分解耦。如果someHelper 是一些用于转换源自用户的数据的本地有用函数,那么它当然不应该知道如何与用户通信以显示错误消息。并且通信模块可能不应该知道someHelper的数据的要求才能验证它。
      • 请注意。创建异常会填充其堆栈跟踪,这可能是一项昂贵的操作。如果您走这条路线,请按名称传递异常,因此只有在您使用它时才会创建它:def someMethod(e: => Exception)
      • @Ben 我的意思是异常不打算用作保存应用程序状态信息,并且传递异常实例(而不是抛出)并用逻辑包围它们看起来非常像这样
      • @dave 谢谢,戴夫!一个不错的建议。虽然我的意思是展示构造的琐碎性。
      • 我同意,这是对 JVM 异常能力的滥用。异常是一种处理异常事件的方法,否则会导致应用程序崩溃,绕过正常的控制流。它们绝对不应该用于无效数据,这是正常处理的一部分。
      【解决方案3】:

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

      我从未见过一个库,您可以在其中传递您希望助手抛出的异常。正如我所说的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_!

      【讨论】:

        【解决方案4】:

        对于另一种方法,您可以查看scalaz Validators,它为这种情况提供了很大的灵活性(例如,我应该在错误时崩溃、累积错误并在最后报告还是完全忽略它们?)。一个few examples 可能会帮助您确定这是否适合您。

        如果您觉得很难找到进入图书馆的途径,this answer 会提供一些介绍性材料的提示;或查看。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-07-21
          • 2018-09-21
          • 1970-01-01
          • 2017-02-14
          • 2013-03-12
          • 2022-10-25
          • 2023-03-21
          相关资源
          最近更新 更多