2

我有需要在数据库中创建一堆记录并在出现任何问题时回滚所有记录的过程。我想做的是:

Public Structure Result
    Public Success as Boolean
    Public Message as String
End Structure

Private _Repository as IEntityRepository

Public Function SaveOrganization( _
    ByVal organization As rv_o_Organization) As Result
    Dim result = Result.Empty

    _Repository.Connection.Open()
    _Repository.Transaction = _Repository.Connection.BeginTransaction()

    ''//Performs validation then saves it to the database
    ''// using the current transaction
    result = SaveMasterOrganization(organization.MasterOrganization)
    If (Not result.Success) Then
        GoTo somethingBadHappenedButNotAnException
    End If

    ''//Performs validation then saves it to the database
    ''//using the current transaction
    result = SaveOrganziation(dbOrg, organization)
    If (Not result.Success) Then GoTo somethingBadHappenedButNotAnException

somethingBadHappenedButNotAnException:
    _Repository.Transaction.Commit()
    _Repository.Connection.Close()
    Return result
End Sub

这是对 GoTo 语句的正确使用,还是只是非常糟糕的设计?有没有更优雅的解决方案?希望这个样本能够理解这一点

4

16 回答 16

13

如果你不得不问,不要这样做。

对于您的特定代码,您可以这样做:

Public Function SaveOrganization(ByVal organization As rv_o_Organization) As Result
    Dim result As Result = Result.Empty

    _Repository.Connection.Open()
    _Repository.Transaction = _Repository.Connection.BeginTransaction()

    'Performs validation then saves it to the database 
    'using the current transaction
    result = SaveMasterOrganization(organization.MasterOrganization)

    'Performs validation then saves it to the database 
    'using the current transaction
    If result.Success Then result = SaveOrganziation(dbOrg, organization)

    _Repository.Transaction.Commit()
    _Repository.Connection.Close()
    Return result
End Sub
于 2009-02-19T21:58:16.617 回答
6

Goto 的名声如此糟糕,以至于它会导致其他开发人员立即对您的代码产生不好的看法。即使您可以证明使用 goto 是最好的设计选择 - 您也必须一遍又一遍地向任何看到您的代码的人解释它。

为了自己的名声,千万别做。

于 2009-02-19T22:10:09.320 回答
5

真是糟糕的设计。是的。

于 2009-02-19T22:00:03.150 回答
2

可能有一些极端的情况适用,但几乎毫不含糊地,不,不要使用它。

在这种特定情况下,您应该使用 Using 语句以更好的方式处理此问题。通常,您将创建一个实现 IDisposable 的类(或使用已经实现的类),然后在 Dispose 方法中处理清理。在这种情况下,您将关闭与数据库的连接(显然,它从您的设计中再次重新打开)。

另外,我建议在这里也使用 TransactionScope 类,您可以使用它来确定事务的范围,然后提交它,以及在遇到异常时自动中止。

于 2009-02-19T22:02:04.317 回答
2

唯一应该使用 goto 的时候是在没有其他选择的情况下。

找出是否没有其他选择的唯一方法是全部尝试。

在您的特定示例中,您应该使用 try...finally 代替,就像这样(对不起,我只知道 C#)

void DoStuff()
{
    Connection connection = new Connection();
    try
    {
        connection.Open()
        if( SomethingBadHappened )
            return;
    }
    finally
    {
        connection.Close();
    }    
}
于 2009-02-19T22:06:00.080 回答
1

我会非常谨慎地说。每当我不得不考虑使用 GOTO 语句时,我都会尝试重构代码。我能想到的唯一例外是在 vb 中带有 On Error Goto 语句。

于 2009-02-19T21:59:25.103 回答
1

goto 本身并没有什么问题,但这并不是一个非常理想的用法。我认为您对异常的定义提出了太高的要求。

只需抛出一个自定义异常并将您的回滚代码放在那里。如果发生真正的异常,我假设您也想回滚,因此您也可以通过这种方式获得双重职责。

于 2009-02-19T22:02:23.003 回答
1

Gotos 只是一个实现细节。一个 try/catch 很像一个 goto(一个堆栈间 goto!)如果你愿意,可以用 goto 编写一个 while 循环(或任何构造)。Break 和 early return 语句是所有这些语句中伪装最薄的 goto - 它们是公然的(有些人因为相似而不喜欢它们)

所以从技术上讲,它们并没有什么真正的错误,但它们确实使代码变得更加困难。当您使用循环结构时,您将被绑定到大括号的区域。没有人想知道你实际上要去哪里,搜索或纵横交错。

最重要的是,他们有一个非常糟糕的代表。如果你决定使用一个,即使在最好的情况下,你也必须为你的决定辩护,反对所有读过你的代码的人——而你要保护的那些人中的许多人都不具备这种能力让自己做出判断,所以你在到处鼓励糟糕的代码。

针对您的情况的一种解决方案可能是使用提前返回与 goto 相同的事实(ps。有史以来最糟糕的伪代码):

dbMethod() {
    start transaction
    if(doWriteWorks())
        end Transaction success
    else
        rollback transaction
}
doWriteWorks() {
    validate crap
    try Write crap
    if Fail
        return false
    validate other crap
    try Write other crap
    if Fail
        return false
    return true
}

我认为这种模式可以在 VB 中使用,但自 VB 3 以来(大约在 MS 购买它的时候)我就没有使用过它,所以如果事务以某种方式绑定到执行方法上下文或其他东西,那么我不知道。我知道 MS 倾向于将数据库与代码结构非常紧密地绑定,否则我什至不会考虑这不起作用的可能性......

于 2009-02-19T22:24:18.863 回答
1

我一直在特定的地方使用 goto,例如在 Try Catch 上方,以防您提示用户“重试?,取消”,如果重试则转到 StartMyTask: 并根据最大重试次数增加当前尝试设想。

它们在每个循环中也很方便。

For each Items in MyList

 If VaidationCheck1(Item) = false then goto SkipLine
 If ValidationCheck(Item) = false then goto skipline

 'Do some logic here, that can be avoided by skipping it to get better performance.
 'I use then like short circuit operands, why evaluate more than you actually have to?

 SkipLine:
Next

我不会用它们代替函数并制作非常大的长代码块,只是在它们可以真正提供帮助的小地方,主要是为了跳过一些事情。

于 2010-12-11T01:10:17.823 回答
0

每次我看到使用 goto 时,简单的重构都可以处理它。我建议永远不要使用它,除非你“知道”你必须使用它

于 2009-02-19T22:02:27.730 回答
0

我很想说永远不会,但我想总有一种情况可能是最好的解决方案。但是,我在过去 20 年左右的时间里没有使用 Goto 语句进行编程,并且无法预见很快就会需要一个。

于 2009-02-19T22:02:41.523 回答
0

为什么不将每个函数调用包装在一个 try catch 块中,完成后,如果抛出一个异常,您可以捕获它并关闭连接。这样,您就完全避免了 GOTO 语句。

简而言之,GOTO 语句不是一件好事,除非在不寻常的情况下,即使这样,通常也需要进行重构以避免它。不要忘记,它是早期语言的遗留物,在这种情况下是 BASIC。

于 2009-02-19T22:04:03.797 回答
0

我会说非常谨慎地使用,因为它通常与引入意大利面条代码有关。尝试使用方法而不是标签。

我认为使用 GOTO 的一个好例子是创建一个通过选择的流程,该流程在 C# 中可用,但在 VB 中不可用。

于 2009-02-19T22:07:05.787 回答
0

go to 语句倾向于使程序流难以理解。我不记得在过去十年中使用过它,除了在 Visual Basic 6 中结合“错误”。

就我而言,您对 go to 的使用是可以接受的,因为程序流程非常清晰。我不认为使用 try ... catch 会改善很多,因为您需要在 go tos 现在所在的位置抛出异常。

但是,格式不是很吸引人:-)

我会将 go to label 的名称更改为其他名称,因为当一切成功时也会到达此位置。clean_up:会很好。

于 2009-02-19T22:14:21.040 回答
0

嗯,它在 VBscript/ASP 中有一个不错的用途,用于处理错误。我们使用它来将错误处理返回给 ASP,一旦我们完成使用 on error resume next。

在.net?天哪,不!

于 2009-02-19T22:20:26.097 回答
0

每个人都说避免它,但为什么。GOTO 语法是汇编中的跳转语句 - 非常有效。

避免它的主要原因是代码可读性。您需要在代码中找到难以观察的 GOTO 标签。

有些人认为这可能会导致内存泄漏,但我看到专家说这在 .NET 中并非如此。

于 2016-06-02T17:39:49.770 回答