【问题标题】:Is it possible to "return twice" from nested function?是否可以从嵌套函数“返回两次”?
【发布时间】:2015-03-06 08:10:54
【问题描述】:

我有一个函数(表单中的事件处理程序),其结构如下:

Dim errMsg as String = ""

CheckIfValidUser(..., errMsg)
If errMsg.Length > 0 Then
    ShowError(errMsg)
    LogError(errMsg)
    Return
End If

CheckIfBookAvailable(..., errMsg)
If errMsg.Length > 0 Then
   ShowError(errMsg)
   LogError(errMsg)
   Return
End If

ReserveBook(..., errMsg)
If errMsg.Length > 0 Then
   ShowError(errMsg)
   LogError(errMsg)
   Return
End If

BookReserved = True

我注意到大部分代码都是类似的结构,所以我尝试重构如下:

Dim errMsg as String = ""
Dim HandleError = Sub()
                      If errMsg.Length > 0 Then
                          ShowError(errMsg)
                          LogError(errMsg)
                          Return
                      End If
                  End Sub

CheckIfValidUser(..., errMsg)
HandleError() 

CheckIfBookAvailable(..., errMsg)
HandleError()

ReserveBook(..., errMsg)
HandleError()

BookReserved = True

但它不起作用,因为我需要“返回两次”而不是仅仅从嵌套函数返回!使用 goto 也不起作用,因为现有标签超出了嵌套函数的范围。

在 .net 中有没有办法做到这一点?我知道可以从 HandleError 返回一个布尔值并在其上分支,但随后它又回到相同的重复结构。

【问题讨论】:

  • 这是一种非常古老的编码风格。更现代的风格是让每个执行验证的函数在出现问题时抛出 exception,然后(通常尽可能高)有一个捕获这些异常的异常处理程序,显示消息并记录它们。
  • @Damien_The_Unbeliever 哦,我没注意到这是一种旧风格;我只是发现以这种方式编写时流程很明显。抛出异常对我来说有点模棱两可:您无法通过仅查看调用来判断哪个函数将/不会抛出异常(以及哪些异常)!
  • 以同样的方式,您无法仅通过查看此代码来判断哪些情况会导致errMsg 在任何这些方法中具有非零长度。一般来说,好的文档对于治愈这些弊病有很长的路要走。而且,至少在例外情况下,您不能忘记检查errMsg

标签: .net vb.net winforms goto


【解决方案1】:

如果您修改方法以引发异常而不是返回 ByRef 错误消息,您的代码可以重写如下:

Try
    CheckIfValidUser(...)
    CheckIfBookAvailable(...)
    ReserveBook(...)
    BookReserved = True

Catch ex As Exception
   ShowError(ex.Message)
   LogError(ex.Message)
End Try

通常,捕获所有异常(“Pokemon 异常处理”)被认为是不好的做法,除非在最外层(即用户界面)执行此操作。由于您显示一条错误消息(并且由于您提到这是表单中的事件处理程序),因此这里似乎就是这种情况。


如果您无法更改方法的结构,另一种选择是:

Dim errMsg as String = ""

CheckIfValidUser(..., errMsg)
If errMsg = "" Then CheckIfBookAvailable(..., errMsg)
If errMsg = "" Then ReserveBook(..., errMsg)

If errMsg <> "" Then
   ShowError(errMsg)
   LogError(errMsg)
   Return
End If

BookReserved = True

就我个人而言,我更喜欢第一个选项,因为这比当今 .NET 中的操作方式更为惯用。不必将您的业务逻辑与技术错误处理细节聚集在一起正是使异常如此强大的原因。

【讨论】:

  • 转念一想,它实际上看起来很整洁。但是如果某些方法需要在失败时释放资源怎么办? (例如,如果 CheckIfBookAvailable 失败,则退出图书检查系统)嵌套 try/catch?
  • @jack3694078:这就是Try...Finally 的用途(因为您通常希望在成功和失败后始终退出图书检查系统)。
  • 其实并非如此。预订图书后,用户需要输入更多信息(地址等),然后才能进行 OrderReservedBook() 和注销。这是另一个函数(实际上是另一种形式)。
  • @jack3694078:我明白了,这是有道理的。在这种情况下,我仍然会使用内部 Try...Finally 块,但在 finally 块中执行类似 If Not BookReserved Then Logout 的操作。
【解决方案2】:

处理异常的一个选项可以是将您的Check subs 重写为返回布尔值的函数(true 成功,false 如果errMsg 包含错误)并且每个调用都有级联的IF 表达式下一个功能。并且最后有一段错误处理代码

If CheckIfValidUser(..., errMsg) Then
    if CheckIfBookAvailable(..., errMsg) then
        ReserveBook(..., errMsg)
    End If
End If

If errMsg.Length > 0 Then
   ShowError(errMsg)
   LogError(errMsg)
   Return
End If

BookReserved = True

但我同意这是过去的 C 风格......

【讨论】:

    猜你喜欢
    • 2016-07-27
    • 1970-01-01
    • 2018-02-25
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 2012-12-23
    • 1970-01-01
    • 2020-11-30
    相关资源
    最近更新 更多