10

The following code was a proof of concept for a message batching routine. Do I avoid goto like the plague and rewrite this code? Or do you think the goto is an expressive way to get this done?

If you'd rewrite please post some code...

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
}
Josh K
  • 28,364
  • 20
  • 86
  • 132
jsw
  • 1,752
  • 1
  • 14
  • 20
  • 11
    [ **What do you think** ](http://xkcd.com/292/)? – Josh K Jul 07 '10 at 18:38
  • 5
    In this question, the word 'expressive' translates to: "sucky and embarrassing, but I might be able to rationalize it with a wordy label." Rewrite it. And reright it while you are at it. – Adam Crossland Jul 07 '10 at 18:41
  • lol. i knew this question would get some good responses. nice pun @Adam. – jsw Jul 07 '10 at 18:52
  • 1
    At least you used a named label; I inherited some 'VB.NET' code that had numerous Gotos with _numeric_ labels. It gave me flashbacks of GWBasic, with line numbers incrementing by 10 just in case you might need to insert some new code later. Please, have pity on the poor maintenance engineer who will later own your code and avoid the temptation. – Dan Bryant Jul 07 '10 at 18:53
  • 1
    @Dan: I remember the horror of having to add more then ten lines in any given section. – Josh K Jul 07 '10 at 18:56
  • ouch line numbers? vb.net? our build server would never let this code through. just wanted to have some fun... – jsw Jul 07 '10 at 19:00
  • 1
    does anyone but me thing it's a little suspicious how many goto-related questions have be asked in the last couple days? – rmeador Jul 07 '10 at 19:37
  • You might be able to find valid reasons for using a forward goto, but a backward goto like this is better avoided. – Amarghosh Jul 08 '10 at 12:31
  • http://weblogs.asp.net/stevewellens/archive/2009/06/01/why-goto-still-exists-in-c.aspx – jsw Jul 08 '10 at 18:06
  • @Dan: +1 for the GW-BASIC reference. The second language I ever learned (first was LOGO). – Tim Goodman Jul 09 '10 at 00:17

10 Answers10

45

Goto will get you into some sticky situations

Pretty much sums up my thoughts on "goto."

Goto is bad programming practice for many reasons. Chief among them is that there is almost never a reason for it. Someone posted a do..while loop, use that. Use a boolean to check if you should continue. Use a while loop. Goto's are for interpreted languages and a call back to assembler days (JMP anyone?). You're using a high level language for a reason. So that you and everyone else doesn't look at your code and get lost.


To keep this answer somewhat current I'd like to point out that a combination of goto and bracing errors caused a major SSL bug in iOS and OS X.

Jon Schneider
  • 25,758
  • 23
  • 142
  • 170
Josh K
  • 28,364
  • 20
  • 86
  • 132
  • 2
    that happened to me earlier today! i'm thinking about writing a little vs2010 extension that sets the computer beeping when you type goto. – jsw Jul 07 '10 at 19:06
  • 2
    Actually, GOTO statements do have their advantages, provided they are used only when (1) the alternative solution(s) would be less readable or be less intuitive, and (2) it's easy to follow the flow of the program. –  Jul 08 '10 at 03:30
  • 1
    For example, compare: -- while expression_1 while expression_2 ... if amazing_exception then goto get_out end if end while end while get_out: ... -- with the alternative (which is arguably *less* readable and intuitive): -- 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 -- So many people hate GOTO for no good reason, but it does have its uses---they're just rare. ;-) –  Jul 08 '10 at 03:32
  • 1
    @Thomas: I disagree: http://gist.github.com/468178. Tell me that is not readable. How it is more readable to have the program flow jumping in and out of scope? – Josh K Jul 08 '10 at 15:45
  • 1
    Certainly, in the case of that example, it's more readable to use an exception flag. But compare http://gist.github.com/468890 with http://gist.github.com/468893, or even http://gist.github.com/468894, for example. Now, personally, I'd usually take the latter option---http://gist.github.com/468894---without the GOTO statement---but I do think that GOTO *can* have its time and place. Don't get me wrong--I think that in most situations, using GOTO is the more ineffective or clumsy solution. But structured programming is not just programming minus the GOTO statement, as some people seem to think. –  Jul 09 '10 at 01:26
  • 1
    @Thomas: Even in the first example why not simply say `error("{TEXT}"); return;` in that if clause? You're other examples didn't seem like anything you would write if you know at all what you're doing. – Josh K Jul 09 '10 at 02:56
18

Replace the goto with a do-while, or simply a while loop if you don't want the "always run once" functionality you have right now.

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
}
corsiKa
  • 81,495
  • 25
  • 153
  • 204
18

It's so amazingly easy to rid yourself of GOTO in this situation it makes me cry:

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
}
Randolpho
  • 55,384
  • 17
  • 145
  • 179
  • 2
    +1 for demonstrating that nearly all GOTOs are just a minute of thought away from death. – Adam Crossland Jul 07 '10 at 18:49
  • Heh... Apparently not *that* easy, since I messed up the body of the while loop. Ironically, the original works as intended thanks to the single if statement in the while block. – Randolpho Jul 07 '10 at 18:52
5

I guess the goto is SLIGHTLY more readable intuitively... But if you WANTED to avoid it I think all you'd have to do is throw the code in a while(true) loop, and then have a break statement at the end of the loop for a normal iteration. And the goto could be replaced with a continue statement.

Eventually you just learn to read and write loops and other control flow structures instead of using goto statements, at least in my experience.

Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
4

Kind of related to Josh K post but I'm writing it here since comments doesn't allow code.

I can think of a good reason: While traversing some n-dimensional construct to find something. Example for 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...

I know you can use "n" whiles and booleans to see if you should continue.. or you can create a function that maps that n-dimensional array to just one dimension and just use one while but i believe that the nested for its far more readable.

By the way I'm not saying we should all use gotos but in this specific situation i would do it the way i just mentioned.

Carlos Muñoz
  • 17,397
  • 7
  • 55
  • 80
3

You could refactor is to something like this.

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);
    }
}
ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
0

Umm, I'm not really sure you want to goto out of a try block. I'm pretty sure that is not a safe thing to do, though I'm not 100% sure on that. That just doesn't look very safe...

Michael Dorgan
  • 12,453
  • 3
  • 31
  • 61
  • Would that apply to `continue` inside a `while` loop as well? (See my answer for what I mean) – Platinum Azure Jul 07 '10 at 18:40
  • We know that works. I was just worried about breaking out of a critical type section with goto. Seems an area that could easily be buggy. – Michael Dorgan Jul 07 '10 at 18:43
  • 7
    Jumping out of scope is safe. It's jumping into scope that is unsafe. For this reason, C# does not allow you to jump into scope. Of course, if your critical section is being handled explicitly with function calls in the monitor namespace or whatever, you'll need to put those in a finally block. – Brian Jul 07 '10 at 18:44
  • More than fair. Makes sense as well. Thanks. – Michael Dorgan Jul 07 '10 at 18:45
0

Wrap the "HaveAnotherMessage" into a method that takes in the buffer and may call itself recursively. That would seem to be the easiest way to fix this.

JB King
  • 11,860
  • 4
  • 38
  • 49
0

I would avoid goto in this case, and refactor it. The method reads too long in my opinion.

Mikael Svenson
  • 39,181
  • 7
  • 73
  • 79
0

I think your method is too big. It mixes different levels of abstraction, like error processing, message retrieval and message processing.

If you refactor it in different methods, the goto naturally goes away (note: I assume your main method is called 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);
}

...
Jordão
  • 55,340
  • 13
  • 112
  • 144