【问题标题】:Better way to write retry logic without goto [duplicate]无需 goto 即可编写重试逻辑的更好方法 [重复]
【发布时间】:2010-11-10 20:42:45
【问题描述】:

有没有更好的方法来编写这段代码而不使用goto?这似乎很尴尬,但我想不出更好的方法。我需要能够执行一次重试尝试,但我不想复制任何代码。

public void Write(string body)
{
    bool retry = false;
RetryPoint:
    try
    {
        m_Outputfile.Write(body);
        m_Outputfile.Flush();
    }
    catch (Exception)
    {
        if( retry )
            throw; 
        // try to re-open the file...
        m_Outputfile = new StreamWriter(m_Filepath, true);
        retry = true;
        goto RetryPoint;
    }
}

【问题讨论】:

  • 对不起,无法抗拒! xkcd.com/292
  • 总有更好的方法来编写没有 goto 的逻辑。
  • @McWafflestix:我不同意。在一些非常罕见的情况下,使用goto 实际上会产生更清晰的代码——打破嵌套循环是一个经常被引用的例子(因为C# 没有像Java 那样的标记中断)。请参阅stackoverflow.com/questions/2542289/… 了解更多信息。
  • @Heinzi:好的,你的意思是:对于非常糟糕的代码的某些条件,使用 goto 可以产生更清洁的代码;不过,我会将其归类为一种气味,而且是一种特别糟糕的气味。
  • @McWafflestix:而且很容易修复。它通常表示方法中嵌套深度过大。

标签: c# .net goto


【解决方案1】:

这是我将使用的基本逻辑,而不是 goto 语句:

bool succeeded = false;
int tries = 2;

do
{
    try
    {
        m_Outputfile = new StreamWriter(m_Filepath, true);
        m_Outputfile.Write(body); 
        m_Outputfile.Flush(); 
        succeeded = true;
    }
    catch(Exception)
    {
        tries--;
    }
}
while (!succeeded && tries > 0);

我刚刚添加了尝试次数逻辑,即使原始问题没有。

【讨论】:

  • 这会执行无限尝试,使用 C++ 语法进行捕获,并且不会重新抛出。我不知道为什么它在明显错误的情况下被赞成。
  • 哦,它不会在失败时重新打开。
  • @Steven Sudit:catch 块的主体暗示与原始代码相同。
  • @LBushkin,并非没有一些变化。原始的 catch 块甚至不会在此代码中编译。无论如何,这仍然会永远重试,这不是目标。
  • catch块需要Closestreamwriter!
【解决方案2】:

@Michael's answer(带有正确实现的 out catch 块)在您的情况下可能是最容易使用的,也是最简单的。但为了展示替代方案,这里有一个版本,它将“重试”流控制纳入一个单独的方法:

// define a flow control method that performs an action, with an optional retry
public static void WithRetry( Action action, Action recovery )
{
    try {
        action(); 
    }
    catch (Exception) {
        recovery();
        action();
    }
}

public void Send(string body)
{
    WithRetry(() =>
    // action logic:
    {
       m_Outputfile.Write(body);
       m_Outputfile.Flush();
    },
    // retry logic:
    () =>
    {
       m_Outputfile = new StreamWriter(m_Filepath, true);
    });
}

当然,您可以通过重试计数、更好的错误传播等来改进这一点。

【讨论】:

  • 我真的不知道该怎么做。可以说,这种委托驱动的解决方案是优雅、灵活和可重用的。再说一次,它目前实际上并没有做需要做的事情。最后,我既不会投赞成票,也不会投反对票。
  • @Steven:我错过了什么吗?哪些方面不符合要求?
  • 你提到的方式:没有重试计数,不会抛出最后一次尝试等。毫无疑问,你可以它做需要做的事——我'已经看到了足够多的答案来确定这一点——但你现在还没有。
  • @Steven:好吧,平心而论,OP 从未提到需要执行多次重试,事实上,由于编写了原始代码,它只会执行一次重试。至于在最后一次尝试时抛出,代码应该这样做,因为在 catch 块中重新调用了动作委托。
  • 如果我们只想重试一次,我们可以跳过所有的委托和循环,而不是硬编码。我希望我们能比这种蛮力解决方案做得更好,尤其是考虑到它在大量重试时会失败。
【解决方案3】:

Michael 的方案不太符合要求,就是重试固定次数,抛出最后一次失败。

为此,我会推荐一个简单的 for 循环,倒计时。如果成功,请使用 break 退出(或者,如果方便,请返回)。否则,让 catch 检查索引是否降至 0。如果是,请重新抛出,而不是记录或忽略。

public void Write(string body, bool retryOnError)
{
    for (int tries = MaxRetries; tries >= 0; tries--)
    {
        try
        {
            _outputfile.Write(body);
            _outputfile.Flush();
            break;
        }
        catch (Exception)
        {
            if (tries == 0)
                throw; 

            _outputfile.Close();
            _outputfile = new StreamWriter(_filepath, true);
        }
    }
}

在上面的示例中,返回就可以了,但我想展示一般情况。

【讨论】:

  • @Anthony:谢谢,那是个错误。我会立即修复它。
  • 我又做了一个我认为适用于任何解决方案的更改:在打开一个新的流写入器之前,我做了 Close 旧流写入器。如果不这样做,那么旧的将保持一个句柄打开直到 GC 启动,阻止新的工作!
【解决方案4】:

如果你把它放在一个循环中呢?可能是类似的东西。

while(tryToOpenFile)
{
    try
    {
        //some code
    }
    catch
    {
    }
    finally
    {
        //set tryToOpenFile to false when you need to break
    }
}

【讨论】:

  • 这个有无限次重试,一般不能解决问题。
  • 是的,这就是为什么我说类似的东西。这个想法是循环执行,这似乎是这里每个答案的总体思路。
  • 对,使用循环非常明显(尽管不是通用的)。魔鬼在细节中。
【解决方案5】:
public void Write(string body, bool retryOnError)
{
    try
    {
        m_Outputfile.Write(body);
        m_Outputfile.Flush();
    }
    catch (Exception)
    {
        if(!retryOnError)
            throw; 
        // try to re-open the file...
        m_Outputfile = new StreamWriter(m_Filepath, true);
        Write(body, false);
    }
}

【讨论】:

  • @Steven:当然,为什么不呢?这是一个简单的尾递归并且非常易读:Write(body, false) 非常清楚地记录了作者的意图(再次尝试相同但不要重试)并且它避免了混乱的代码(没有循环,没有 succeeded 变量)。用重试计数替换retryOnError 也是一个简单的练习......
  • 几个原因。由于 GC 的工作方式,递归使对象存活的时间更长,因此当迭代解决方案足够清晰时应避免使用递归。当尾递归优化是可能的时,这不是一个问题。此示例不超过一次递归,但如果您想增加它(并且您可能会),您可以将 bool 更改为重试倒计时。这样的改变会放大效果。
  • 但是除了这个优化问题之外,我主要担心的是这个问题并不特别适合递归解决方案,因此代码将更难理解和维护。迭代版本使其循环显式,而不是期望读者注意到他们看到的第三个“Write”实际上是一个递归调用。无论如何,都不需要 bool 标志,因为重试倒计时可以作为我们何时应该重新抛出的指标。
  • @Steven:没错,这绝对不是递归胜过迭代的例子之一(顺便说一句,对你的迭代解决方案 +1 没有标志)。我仍然认为它是一个有效的(并且希望是有启发性的)替代方案,所以我不会删除答案。我在递归解决方案中看到的一个优点是错误处理代码完全在 catch 子句中(附加参数除外),因此代码代表了自然(非异常)流程程序。另一方面,迭代解决方案从一个大循环开始,该循环仅在异常情况下使用。
  • 我不建议删除您的答案!它很像 LBushkin 的方法,因为它代表了一种在教育上很有价值的独特方法,即使它不一定最适合解决问题。我的批评完全是建设性的。关于将异常代码保存在异常处理程序中的要点。我的 fancy 对此的回答是使用基于委托的重试器,例如 LBushkin 的。这非常干净地隐藏了重试的内容,并且可以过滤掉致命的异常类型(StackOverflow、ThreadAbort 等)。
【解决方案6】:

尝试以下方法:

int tryCount = 0;
bool succeeded = false;

while(!succeeded && tryCount<2){
    tryCount++;
    try{
        //interesting stuff here that may fail.

        succeeded=true;
    } catch {
    }
}

【讨论】:

  • 不需要布尔值。更重要的是,这不会引发最后一次失败。
【解决方案7】:

带有布尔值

public void Write(string body)
{
        bool NotFailedOnce = true;            
        while (true)
        {
            try
            {
                 _outputfile.Write(body);
                 _outputfile.Flush();           
                 return;                    
            }
            catch (Exception)
            {
                NotFailedOnce = !NotFailedOnce;
                if (NotFailedOnce)
                {
                    throw;
                }
                else
                {
                     m_Outputfile = new StreamWriter(m_Filepath, true);
                }
            }
      }        
}

【讨论】:

  • 这不可能。一方面,NotFailedOnce = !NotFailedOnce) 永远是假的,所以它永远不会抛出。相反,它将永远循环。
  • @Steve,再看代码,不是相等检查,而是赋值。
  • 这很聪明,意思很糟糕。编译器会为此生成警告,并且应该这样做,因为谓词中间的赋值很可能是由于拼写错误。同样,通过注意它而不是将其设置为 false 来将 bool 设置为 false 是聪明/不好的。这段代码被不必要地混淆了。
  • @Steven- 同意 if() 中的分配点,我改变了这一点。但是,NOTing 与将其设置为 false 完全不同。注意是确保它只工作一次。
  • 感谢您将赋值移出谓词。如果您查看流程,您会发现您本来可以使用HasFailed。它将设置在实例化流写入器的行的正上方。这样就简单多了。此外,不需要 else(或所有这些大括号),因为 throw 退出当前流程。
猜你喜欢
  • 2010-12-06
  • 2020-10-13
  • 1970-01-01
  • 2020-09-06
  • 1970-01-01
相关资源
最近更新 更多