2009-11-19 13 views
7

只是想知道,如果这被认为是在C#中明确使用goto:这是goto的一个明确使用吗?

IDatabase database = null; 

LoadDatabase: 
try 
{ 
    database = databaseLoader.LoadDatabase(); 
} 
catch(DatabaseLoaderException e) 
{ 
    var connector = _userInteractor.GetDatabaseConnector(); 
    if(connector == null) 
     throw new ConfigException("Could not load the database specified in your config file."); 
    databaseLoader = DatabaseLoaderFacade.GetDatabaseLoader(connector); 
    goto LoadDatabase; 
} 

我觉得这是确定的,因为段小,应该是有意义的。当你想在处理异常之后重试操作时,人们通常可以通过另一种方式从错误中恢复吗?

编辑:这很快。回答一些问题并澄清一些事情 - 这是一个过程的一部分,这个过程本质上是从一个不同类型的项目转变而来。 _userInteractor.GetDatabaseConnector()调用是确定用户是否想要重试的部分(可能使用不同于他们从中加载的配置中的数据库)。如果它返回null,那么没有指定新的数据库连接,操作应该完全失败。

我不知道为什么我没有想到使用while循环。它一定是太接近下午5点。

编辑2:我看了一下LoadDatabase()方法,如果失败,它会抛出DatabaseLoaderException。我更新了上面的代码来捕获异常,而不是异常。

编辑3:普遍的共识似乎是,

  • 使用转到这里是没有必要的 - while循环会做得很好。
  • 使用像这样的异常不是一个好主意 - 我不知道用什么来代替它。
+7

没有必要否决一个合理的问题,因为它有它的话跳转。 – 2009-11-19 03:33:05

+1

到OP:转到使用不需要“清除”。它需要“正确”。你的使用很清楚,但是非常不正确。你在这里有很好的答案,听取他们的意见.-) – 2009-11-19 03:44:27

+2

但是,这不男人,这个问题应该downvoted。 – 2009-11-19 03:52:54

回答

15

有另一种方式,人们通常 这样从错误中恢复,当你想 异常处理后重试操作?

是的,在调用代码中。让这个方法的调用者决定他们是否需要重试逻辑。

UPDATE:

为了澄清,你应该只捕获异常,如果你能真正处理它们。您的代码基本上说:

“我不知道发生了什么,但无论我造成的一切 吹起来......让我们再次做到这一点。”

捕获您可以从中恢复的特定错误,并让其余的泡沫直到下一层进行处理。任何异常情况都会导致上面的错误代表真正的错误。

更新2:

好了,而不是继续通过我将与半伪代码示例阐述意见相当长时间的讨论。

总体思路是,您只需重构代码即可执行测试,并更好地处理用户体验。

//The main thread might look something like this 

try{ 
    var database = LoadDatabaseFromUserInput(); 

    //Do other stuff with database 
} 
catch(Exception ex){ 
    //Since this is probably the highest layer, 
    // then we have no clue what just happened 
    Logger.Critical(ex); 
    DisplayTheIHaveNoIdeaWhatJustHappenedAndAmGoingToCrashNowMessageToTheUser(ex); 
} 

//And here is the implementation 

public IDatabase LoadDatabaseFromUserInput(){ 

    IDatabase database = null; 
    userHasGivenUpAndQuit = false; 

    //Do looping close to the control (in this case the user) 
    do{ 
     try{ 
      //Wait for user input 
      GetUserInput(); 

      //Check user input for validity 
      CheckConfigFile(); 
      CheckDatabaseConnection(); 

      //This line shouldn't fail, but if it does we are 
      // going to let it bubble up to the next layer because 
      // we don't know what just happened 
      database = LoadDatabaseFromSettings(); 
     } 
     catch(ConfigFileException ex){ 
      Logger.Warning(ex); 
      DisplayUserFriendlyMessage(ex); 
     } 
     catch(CouldNotConnectToDatabaseException ex){ 
      Logger.Warning(ex); 
      DisplayUserFriendlyMessage(ex); 
     } 
     finally{ 
      //Clean up any resources here 
     } 
    }while(database != null); 
} 

现在显然我不知道你的应用程序试图做什么,这当然不是一个生产的例子。希望你得到一般想法。重构程序,以避免应用程序流程中出现不必要的中断。

干杯, 乔希

+0

这将是我的常规方法,但这是一个批处理过程的一部分,所以我不能只是抛出并期望重试。它从一个配置文件中检索到的数据库连接器,错误处理使用户有机会从该配置中的错误中恢复。我用'catch(DatabaseLoaderException e)'替换了'catch(Exception e)',以使它更具体。 – 2009-11-19 03:57:01

+0

@Jamie:你也不能抓住,重试,并立即期待成功的结果 - 如果发生异常,循环可能是无止境的。记录错误,甚至通过电子邮件发送。如果你只是抛出错误并放弃,下次批处理运行时它仍然会重试,但是稍后 - 稍后有更多机会(有时间解决运行之间的错误)而不是连续的循环出错。 – 2009-11-19 04:04:20

+0

看看我上面的编辑,我提到了这个。一旦用户停止输入新的数据库连接信息,循环将结束 - >他们可以在任何阶段取消它。 _userInteractor是UI的接口,如果用户取消,则返回null。所以这不会是一个连续的错误。 – 2009-11-19 04:09:28

4

就我个人而言,我会在一个单独的方法中返回一个成功或失败的状态代码。然后,在调用这个方法的代码中,我可以有一些奇妙的次数,我会继续尝试,直到状态代码是“Success”。我只是不喜欢使用try/catch来控制流量。

+0

神奇的次数是至关重要的:如果错误依然存在,那么OP的代码将会进行无限循环(如果发生错误,这可能很可能)。 – 2009-11-19 03:32:47

+0

+1非常“干净”的解决方案。 – PRR 2009-11-19 07:11:48

+0

您可以将支票包裹在if语句周围的幻数上,如果您的幻数是0或-1,那么在您获得成功之前,不要打扰停止循环。 – 2009-11-19 13:15:57

1

不,这是不行的:http://xkcd.com/292/

+1

关于gotos的一揽子声明与不加区分地使用它们一样糟糕。 Goto是一种高级编程结构,仅供高级程序员使用,以简化方法结构。 OP的使用是不恰当的,这是肯定的(已经回答了为什么已经),但是然后说gotos总是不好也是不好的。 – 2009-11-19 03:43:10

+2

嗯...... Argumentum ad XKCD是一个可以接受的优秀谬论! – MPelletier 2009-11-19 03:46:07

+0

很有趣,但实际上并没有帮助。 -1 – Oorang 2009-11-19 04:39:21

7

也许我失去了一些东西,但你为什么不能只使用一个while循环?如果你有代码给出的异常(这是不好的代码)的功能,这将永远给你相同的循环。

IDatabase database = null; 

while(database == null){ 
    try 
    { 
     database = databaseLoader.LoadDatabase(); 
    } 
    catch(Exception e) 
    { 
     var connector = _userInteractor.GetDatabaseConnector(); 
     if(connector == null) 
       throw new ConfigException("Could not load the database specified in your config file."); 
     databaseLoader = DatabaseLoaderFacade.GetDatabaseLoader(connector); 
     //just in case?? 
     database = null; 
    } 
} 

如果您必须在普通代码中使用goto,那么缺少逻辑流程。你可以使用标准结构,如果,同时,等等。

+0

我不应该有upvoted,这可以永远循环。 – MPelletier 2009-11-19 04:15:48

+0

它不会,因为它不会永久加载相同的数据库。 – 2009-11-19 04:25:07

+0

如果LoadDatabase()返回null并且不失败,它会不会? – MPelletier 2009-11-19 04:32:06

2

是否清楚?不是真的。我想,你实际上想要做的是首先尝试加载数据库,然后,如果没有工作,尝试以不同的方式加载它。是对的吗?让我们以这种方式编写代码。

IDatabase loadedDatabase = null; 

// first try 
try 
{ 
    loadedDatabase = databaseLoader.LoadDatabase(); 
} 
catch(Exception e) { } // THIS IS BAD DON'T DO THIS 

// second try 
if(loadedDatabase == null) 
{ 
    var connector = _userInteractor.GetDatabaseConnector(); 
    if(connector == null) 
     throw new ConfigException("Could not load the database specified in your config file."); 
    databaseLoader = DatabaseLoaderFacade.GetDatabaseLoader(connector); 
    loadedDatabase = databaseLoader.LoadDatabase() 
} 

这更清楚地说明你实际上在做什么。作为额外的好处,其他程序员不会刨出你的眼睛。 :)

注意:你几乎肯定不想捕捉异常。可能会有一个更具体的例外情况,您宁可捕捉。这也将捕获TheComputerIsOnFireException,之后它不值得重试。

+0

啊,我现在看到你想每次失败都要求用户提供不同的数据库连接,所以这是行不通的。但它应该更好地回答你的问题:不,你的构建与goto不清楚。 :) 正如其他人所指出的那样,一个while循环更合适。 – 2009-11-19 03:46:57

1

在旁注中,我认为如果您总是发生异常,则可能会出现无限循环。

从技术上说,你的goto结构没有问题,但对我来说,我会选择使用while循环来代替。喜欢的东西:

IDatabase database = null; 

bool bSuccess = false; 
int iTries = 0 
while (!bSuccess) // or while (database == null) 
{ 
    try 
    { 
     iTries++; 
     database = databaseLoader.LoadDatabase(); 
     bSuccess = true; 
    } 
    catch(DatabaseLoaderException e) 
    { 
     //Avoid an endless loop 
     if (iTries > 10) 
      throw e; 

     var connector = _userInteractor.GetDatabaseConnector(); 
     if(connector == null) 
      throw new ConfigException("Could not load the database specified in your config file."); 
     databaseLoader = DatabaseLoaderFacade.GetDatabaseLoader(connector); 
    } 
}