GOTO Hell - Part 1
Look, I'm not a bad programmer . I'm not. In fact, I'm a doggone good programmer. I've got a genetic predisposition for it, in the first place. Secondly, I've been doing it for a very long time. It's because I'm so good at it, in fact — combining creative thinking with vast experience — that I got myself into this boondoggle in the first place.
What boondoggle? Well, like any of us, I code as efficiently and as cleverly as I can. I know the capabilities of my language, mvBASIC very well. I've experimented, looked over lots of other people's code, cherry-picked the best ideas and have generally done anything I can get away with that will solve the problem at hand. Pushing the envelope of what is possible. And just because I have only programmed in this "Pick-ish" world, I've still had to remain cognizant of lowest-common-denominator practicalities between our various sibling platforms. Having said that (rather defensively) it is true that MultiValue and its high-octane BASIC language is the only thing I've ever used to solve real-world problems. That just makes me a native speaker! I can do tricks with my pony that riders of other breeds would never even consider. Should I adhere to practices that were born from limits of more restrictive environments? No!
Okay, maybe.
It mattered more when I decided to push my application through a process dubbed a 'transpiler' that converted my BASIC code into C# code. Cool, right? It is! But it turns out that while it can transform my syntax, it can't fix my structure.
Yeah, structure.
Remember those debates about structured programming? All that shouting about 'GOTO' statements? I scoffed at people that got hard-nosed about it, maintaining that you can take it too far and make your code less readable and less efficient. (I still do believe this, not going to lie.) Thus, I created a forgiving philosophy on structure and GOTO statements. There are times and places, I argued, when a GOTO is your very best option.
"Oh, yeah?" I hear you demand.
Yeah. I mean, yeah, I thought so. For example, I don't like to have RETURNs out of a program scattered around. I want a single exit point. So, in my programs, label 999 RETURN is always at the bottom, and there are GOTO 999s scattered throughout. I know what RETURN that is - it is always the main exit from the subroutine. That's how I do it. That's how my father did it … and it's been working pretty well so far. (~Tony Stark)
But I had a few other GOTO-forgiveness policies that I regret. I used a GOTO as a sort of jump-over. Why? Don't laugh. I hate it when my program gets deeply indented. I prefer
IF NOT(THIS) THEN GOTO 50
Do something.
50 * CARRY ON
Over
IF THIS THEN
Blah blah blah blah
END
CARRY ON
Admittedly, it's not so bad when there are one or two levels, but if you keep going, you can get so far indented that you need a scooter to get far enough over to the right to follow the logic. My policy of judicious use of GOTO has applied here, too.
Don't think I can't hear you judging me.
This worked out well enough in simple scenarios. But I got creative, and I would jump over to places inside the same loop, or another loop, or another IF-THEN indent. And it was still fine — I would go so far as to say beautifully efficient — in our platform. For example, say I've got one of these . I need to do this first, but after that, it's the same as one of those .
VITAMIN.C=0
POTASSIUM=0
BEGIN CASE
CASE FRUIT = ORANGE
COLOR=ORANGE
GOTO ALL.CITRIS
CASE FRUIT = LEMON
COLOR=YELLOW
GOTO ALL.CITRIS
CASE FRUIT = LIME
COLOR=GREEN
GOTO ALL.CITRIS
CASE FRUIT = BANANA
POTASSIUM = 1
CASE 1 ;* ALL CITRIS
ALL.CITRIS *
<-- LABEL
VITAMIN.C=1
Do a bunch of other stuff to citrus
END CASE
Same deal with an IF clause. I do the specific thing and then jump into the THEN or the ELSE of another common clause to do other stuff in common. In truth, it is pure laziness dressed up in a frilly gown of efficiency.
It gets worse, so if you are on your high horse, just stay up there.
I did this also in a FOR NEXT loop. I coded for 30+ years in mvBASIC and never knew (or even ran into) the CONTINUE statement. Seriously, hush. So, I would do this.
FOR THISX= 1 TO 100
IF THIS THEN
DO THIS
GOTO 50
END
Do stuff
50 NEXT THISX
And it let me.
In case you never ran into it either, this functions identically:
FOR THISX= 1 TO 100
IF THIS THEN
CONTINUE
END
Do stuff
NEXT THISX
We glory in the epic flexibility, creativity, and code-speed that programming in mvBASIC has given us. But now, when trying to be more compatible with the outside world, this extreme forgiveness in the language has a price. These constructs will not even compile.
We have choices in our approach to fixing the problem. One thing we could do is read all the mainstream books and truly write our software according to structure regulations to which much of the not-privileged-to-be-PICK world adheres. Burn out our eyeballs, burn up our free time and burn out our advantage. I propose a more holistic treatment plan. Yes, haters, I'm going to stick with some of my GOTO use. But I had to restructure around things that would not compile. In doing so, I tried some things that weren't smart and eventually arrived at some solutions that were … smarter. So, I thought I'd share. I bet you're feeling bad about judging me now that you realize that I'm going to help make your life easier, don't you?
Let's dispense with the formalities:
Structured programming is a logical programming method that is considered a precursor to object-oriented programming (OOP). Structured programming facilitates understanding and modification and has a top-down design approach, dividing a system into compositional subsystems.
C is called a structured programming language because to solve a large problem, C programming language divides the problem into smaller modules called functions or procedures each of which handles a particular responsibility. The program which solves the entire problem is a collection of such functions.
Truly there are advantages; I'm sure we all agree.
- Reduced complexity
- Complexity is what you make of it. You can write more or less complex code, either way.
- Modularity to tackle problems in a logical bite-sized (no pun intended) progression.
There is a simple beauty in this. - Maintenance is easier.
- Like the story about the guy who never gets a new suit. He gets a new jacket one year, new shirt another. If the software is truly modular, there is no wholesale refactoring project on the horizon.
Convinced? Let's do it!
Here's where we use INCLUDES, right? No.
Our collective instinct to create massive semi-modularized INCLUDEs is just smashing bits together to create an old-school monolithic program. It's quinoa-coding. Sure there are lots of little separate bits, but a spoonful is still basically mush. And yes, I have a number of these giant programs broken into INCLUDEs and recognize that I am pointing out the color of the kettle.
Then we should switch out any GOTO in our code with a GOSUB, right? No.
The common argument is that without structure we end up with spaghetti code. I would contend that you haven't seen a pasta nightmare until you've written code with a bunch of GOSUBs and RETURNs all over the place.
Some people like to create a program that goes like this:
Header
Header
Header
GOSUB 100
GOSUB 200
GOSUB 300
RETURN
100 *
Do stuff
RETURN
200 *
Do stuff
RETURN
300 *
Do stuff
RETURN
If you like this structure, well, that's fine. Lots of people do. Lots of people also leave the walls in their house painted white. It's a perfectly valid choice.
Still, it isn't really modularized. You can't re-use those century-labeled chunks outside of this program. Not without jumping through hoops, anyway.
After some tinkering and cussing, it became apparent that the best choice was to go straight to external subroutines — even if they have (gasp) arguments. I would argue (get it?) that arguments can be controlled in a way that INCLUDES, internal GOSUBs and COMMONs cannot. With an external subroutine, you decide exactly what gets passed and what comes back. No accidental variable switching. It is the closest thing to a METHOD in some environments, a FUNCTION in others. The functionality, the purpose of the routine is fully nugget-ized.
Now that you have external nuggets that you hope to reuse, the issue of naming and keeping track of them gains urgency. Meet me here next issue for this discussion.
I know many people will have thoughts on this subject matter. I've heard some of the arguments and I'm open to hearing others. You know where to find me.