10

以下代码是消息批处理例程的概念证明。我是否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
}
4

10 回答 10

45

Goto 会让你陷入一些棘手的情况

几乎总结了我对“goto”的看法。

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


为了使这个答案保持最新状态,我想指出,结合goto和支撑错误导致iOS 和 OS X 中的主要 SSL 错误

于 2010-07-07T18:39:40.050 回答
18

如果您不想要现在拥有的“始终运行一次”功能,则将 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
}
于 2010-07-07T18:41:37.263 回答
18

在这种情况下摆脱 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
}
于 2010-07-07T18:42:50.333 回答
5

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

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

于 2010-07-07T18:38:38.977 回答
4

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

我可以想到一个很好的理由:在遍历一些 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”whiles 和布尔值来查看是否应该继续.. 或者您可以创建一个函数将该 n 维数组映射到一个维度并且只使用一个 while 但我相信嵌套很远更具可读性。

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

于 2010-07-08T03:21:48.250 回答
3

你可以重构是这样的。

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);
    }
}
于 2010-07-07T18:42:43.630 回答
0

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

于 2010-07-07T18:38:42.860 回答
0

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

于 2010-07-07T18:40:30.640 回答
0

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

于 2010-07-07T18:41:08.510 回答
0

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

如果你用不同的方法重构它,goto自然会消失(注意:我假设你的 main 方法被调用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);
}

...
于 2011-04-18T16:53:50.623 回答