【问题标题】:Is this goto expressive?这是goto表达吗?
【发布时间】:2010-07-07 18:36:17
【问题描述】:

以下代码是消息批处理例程的概念证明。我是否像瘟疫一样避免goto并重写这段代码?还是您认为goto 是完成此任务的一种表达方式?

如果您要重写,请发布一些代码...

var queue = new Queue<TraceItem>(this.batch);
while (this.connected)
{
    byte[] buffer = null;
    try
    {
        socket.Recv(out buffer);
    }
    catch
    {
        // ignore the exception we get when the socket is shut down from another thread
        // the connected flag will be set to false and we'll break the loop
    }

HaveAnotherMessage:
    if (buffer != null)
    {
        try
        {
            var item = TraceItemSerializer.FromBytes(buffer);
            if (item != null)
            {
                queue.Enqueue(item);

                buffer = null;
                if (queue.Count < this.batch && socket.Recv(out buffer, ZMQ.NOBLOCK))
                {
                    goto HaveAnotherMessage;
                }
            }
        }
        catch (Exception ex)
        {
            this.ReceiverPerformanceCounter.IncrementDiagnosticExceptions();
            this.tracer.TraceException(TraceEventType.Error, 0, ex);
        }
    }

    // queue processing code
}

【问题讨论】:

  • [ 你怎么看 ](xkcd.com/292)?
  • 在这个问题中,“富有表现力”这个词的意思是:“糟糕而令人尴尬,但我也许可以用一个冗长的标签来合理化它。”重写它。并在您使用时重新调整它。
  • 至少你使用了一个命名标签;我继承了一些“VB.NET”代码,其中包含许多带有 numeric 标签的 Goto。它让我想起了 GWBasic,行号增加了 10,以防您以后可能需要插入一些新代码。请可怜可怜的维护工程师,他们以后将拥有您的代码并避免诱惑。
  • @Dan:我记得必须在任何给定部分添加超过 10 行的恐惧。
  • 除了我之外还有其他人怀疑过去几天问了多少与 goto 相关的问题吗?

标签: c# goto


【解决方案1】:

差不多总结了我对“goto”的看法。

Goto 是一种糟糕的编程习惯,原因有很多。其中最主要的是几乎没有理由。有人发布了do..while 循环,使用它。使用boolean 检查您是否应该继续。使用 while 循环。 Goto 用于解释语言和回拨汇编器时代(JMP 任何人?)。您使用高级语言是有原因的。这样您和其他人就不会看到您的代码而迷失方向。


为了使这个答案保持最新,我想指出goto 和支撑错误的组合导致major SSL bug in iOS and OS X

【讨论】:

  • 今天早些时候发生在我身上!我正在考虑编写一个小的 vs2010 扩展程序,当您键入 goto 时设置计算机发出哔哔声。
  • 实际上,GOTO 语句确实有其优点,只要它们仅在 (1) 替代解决方案可读性较差或不太直观,以及 (2) 易于遵循程序的流程。
  • 例如,比较: -- while expression_1 while expression_2 ... if Amazing_exception then goto get_out end if end while end while get_out: ... -- 与替代方案(可以说是 可读性和直观性较差): -- flag = false while expression_1 while expression_2 ... if Amazing_exception then flag = true break end if end while if flag == true then break end if end while -- 很多人讨厌GOTO 无缘无故,但它确实有它的用途——它们很少见。 ;-)
  • @Thomas:我不同意:gist.github.com/468178。告诉我这是不可读的。让程序流跳入和跳出范围如何更具可读性?
  • 当然,在该示例的情况下,使用异常标志更具可读性。但是例如将gist.github.com/468890gist.github.com/468893 甚至gist.github.com/468894 进行比较。现在,就个人而言,我通常会选择后一种选择---gist.github.com/468894---without GOTO 声明---但我确实认为 GOTO 可以有它的时间和地点。不要误会我的意思——我认为在大多数情况下,使用 GOTO 是更无效或更笨拙的解决方案。但结构化编程不仅仅是编程减​​去 GOTO 语句,正如某些人所认为的那样。
【解决方案2】:

将 goto 替换为 do-while,或者如果您不想要现在拥有的“始终运行一次”功能,则只需一个 while 循环。

var queue = new Queue<TraceItem>(this.batch);
while (this.connected)
{
    byte[] buffer = null;
    try
    {
        socket.Recv(out buffer);
    }
    catch
    {
        // ignore the exception we get when the socket is shut down from another thread
        // the connected flag will be set to false and we'll break the loop
    }

    do {
        if (buffer != null)
        {
            try
            {
                var item = TraceItemSerializer.FromBytes(buffer);
                if (item != null)
                {
                    queue.Enqueue(item);
                    buffer = null;
                }
            }
            catch (Exception ex)
            {
                this.ReceiverPerformanceCounter.IncrementDiagnosticExceptions();
                this.tracer.TraceException(TraceEventType.Error, 0, ex);
            }
        }
    } while(queue.Count < this.batch && socket.Recv(out buffer, ZMQ.NOBLOCK))

    // queue processing code
}

【讨论】:

  • Imo,我更喜欢 Josh 的回答。
  • 如果你有一个 if (xxx) goto LABEL; 这应该是一个强烈的迹象表明你可以将它重写为一个循环
【解决方案3】:

在这种情况下摆脱 GOTO 非常容易,让我哭了:

var queue = new Queue<TraceItem>(this.batch);
while (this.connected)
{
    byte[] buffer = null;
    try
    {
        socket.Recv(out buffer);
    }
    catch
    {
        // ignore the exception we get when the socket is shut down from another thread
        // the connected flag will be set to false and we'll break the loop
    }
    bool hasAnotherMessage = true
    while(hasAnotherMessage)
    {
        hasAnotherMessage = false;
        if (buffer != null)
        {
            try
            {
                var item = TraceItemSerializer.FromBytes(buffer);
                if (item != null)
                {
                    queue.Enqueue(item);

                    buffer = null;
                    if (queue.Count < this.batch && socket.Recv(out buffer, ZMQ.NOBLOCK))
                    {
                        hasAnotherMessage = true;
                    }
                }
            }
            catch (Exception ex)
            {
                this.ReceiverPerformanceCounter.IncrementDiagnosticExceptions();
                this.tracer.TraceException(TraceEventType.Error, 0, ex);
            }
        }
    }
    // queue processing code
}

【讨论】:

  • +1 证明几乎所有 GOTO 都离死亡只有一分钟的思考时间。
  • 嘿...显然不是容易,因为我搞砸了while循环的主体。具有讽刺意味的是,由于 while 块中的单个 if 语句,原始作品按预期工作。
【解决方案4】:

我想 goto 在直观上更具可读性...但是如果您想避免它,我认为您所要做的就是将代码放入 while(true) 循环中,然后使用 break 语句在循环结束时进行正常迭代。 goto 可以替换为 continue 语句。

至少根据我的经验,最终你只是学会了读写循环和其他控制流结构,而不是使用goto 语句。

【讨论】:

    【解决方案5】:

    有点与 Josh K 的帖子有关,但我在这里写它是因为 cmets 不允许代码。

    我能想到一个很好的理由:在遍历一些 n 维构造来寻找某些东西时。 n=3 的示例 //...

    for (int i = 0; i < X; i++)
        for (int j = 0; j < Y; j++)
            for (int k = 0; k < Z; k++)
                if ( array[i][j][k] == someValue )
                {
                    //DO STUFF
                    goto ENDFOR; //Already found my value, let's get out
                }
    ENDFOR: ;
    //MORE CODE HERE...
    

    我知道您可以使用“n”while 和布尔值来查看是否应该继续.. 或者您可以创建一个函数将该 n 维数组映射到一个维度并且只使用一个 while 但我相信嵌套因为它更具可读性。

    顺便说一句,我并不是说我们都应该使用 goto,但在这种特定情况下,我会按照我刚才提到的方式来做。

    【讨论】:

    • 你可以设置i = X, j = Y, k = Z 然后说continue;
    【解决方案6】:

    你可以重构是这样的。

    while (queue.Count < this.batch && buffer != null)
    {
        try
        {
            var item = TraceItemSerializer.FromBytes(buffer);
            buffer = null;
            if (item != null)
            {
                queue.Enqueue(item);
                socket.Recv(out buffer, ZMQ.NOBLOCK)
            }
        }
        catch (Exception ex)
        {
            this.ReceiverPerformanceCounter.IncrementDiagnosticExceptions();
            this.tracer.TraceException(TraceEventType.Error, 0, ex);
        }
    }
    

    【讨论】:

      【解决方案7】:

      嗯,我不太确定你想跳出 try 块。我很确定这不是一件安全的事情,尽管我对此不是 100% 确定的。这看起来不太安全......

      【讨论】:

      • 这是否也适用于while 循环内的continue? (请参阅我的答案以了解我的意思)
      • 我们知道这行得通。我只是担心用 goto 打破一个关键类型的部分。似乎是一个容易出问题的区域。
      • 跳出范围是安全的。它正在跳入不安全的范围。出于这个原因,C# 不允许您跳入作用域。当然,如果您的关键部分正在通过监视器命名空间中的函数调用或其他方式显式处理,您需要将它们放在 finally 块中。
      • 不公平。也有道理。谢谢。
      【解决方案8】:

      将“HaveAnotherMessage”包装到一个方法中,该方法接收缓冲区并可以递归调用自身。这似乎是解决此问题的最简单方法。

      【讨论】:

      • 你认为使方法递归会更好吗? :\ 一个简单的循环有什么问题?
      • 使用递归来处理来自套接字的可能开放的数据量似乎不是最好的主意。
      • @Adam 至少没有人建议添加 try..catch(StackOverflowException) 块 :)
      • @Michael,我真希望我有。
      • -1:开放式套接字读取的递归?那简直是自找麻烦!坏主意!
      【解决方案9】:

      在这种情况下我会避免 goto,并对其进行重构。在我看来,这个方法读起来太长了。

      【讨论】:

        【解决方案10】:

        我认为你的方法太大了。它混合了不同级别的抽象,如错误处理、消息检索和消息处理。

        如果你用不同的方法重构它,goto 自然会消失(注意:我假设你的主要方法称为Process):

        ...
        
        private byte[] buffer;
        private Queue<TraceItem> queue;
        
        public void Process() {
          queue = new Queue<TraceItem>(batch);
          while (connected) {
            ReceiveMessage();
            TryProcessMessage();
          }
        }
        
        private void ReceiveMessage() {
          try {
            socket.Recv(out buffer);
          }
          catch {
            // ignore the exception we get when the socket is shut down from another thread
            // the connected flag will be set to false and we'll break the processing
          }
        }
        
        private void TryProcessMessage() {
          try {
            ProcessMessage();
          }
          catch (Exception ex) {
            ProcessError(ex);
          }
        }
        
        private void ProcessMessage() {
          if (buffer == null) return;
          var item = TraceItemSerializer.FromBytes(buffer);
          if (item == null) return;
          queue.Enqueue(item);
          if (HasMoreData()) {
            TryProcessMessage();
          }
        }
        
        private bool HasMoreData() {
          return queue.Count < batch && socket.Recv(out buffer, ZMQ.NOBLOCK);
        }
        
        private void ProcessError(Exception ex) {
          ReceiverPerformanceCounter.IncrementDiagnosticExceptions();
          tracer.TraceException(TraceEventType.Error, 0, ex);
        }
        
        ...
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多