Dirty Coding Tricks To Make a Deadline 683
Gamasutra is running an article with a collection of anecdotes from game developers who had to employ some quick and dirty fixes to get their products to ship on time. Here's a brief excerpt:
"Back at [company X] — I think it was near the end of [the project] — we had an object in one of the levels that needed to be hidden. We didn't want to re-export the level and we did not use checksum names. So right smack in the middle of the engine code we had something like the following. The game shipped with this in: if( level == 10 && object == 56 ) {HideObject();} Maybe a year later, an artist using our engine came to us very frustrated about why an object in their level was not showing up after exporting to what resolved to level 10. I wonder why?"
Have you ever needed to insert terrible code to make something work at the last minute?
Wow. Talk about old news. (Score:1, Informative)
This is literally a dupe from 2000. Wow. It'll take me a bit to look it up...
Re:One word.. (Score:5, Informative)
The goto statement is very useful. Your dislike of it is irrational. Do you even know why you do not like it? Often, goto is the best solution to given problem.
Re:One word.. (Score:3, Informative)
So, where's your example?
Finite State Machines. They really are quite difficult to implement without goto logic and is exactly what you do when discussing theory.
return, break, continue? (Score:4, Informative)
Have you ever wrote a function / procedure with more then one return statement? Or used break or continue in a loop? Then you can use goto as well. From a structure point of view goto, break, continue and return are all unconditional jumps. They are one of a kind. And looking back in retrospect: Since goto need to be paired with a label it's the least evil of the group.
Note that Pascal the archetype of structured programming had goto but it did not have break, continue or return.
Re:Really? Not Slashdot's fault, if so... (Score:5, Informative)
Re:Study Assignment (Score:2, Informative)
Probably he didn't spend much time implementing his version so wasn't too worried. He probably invests most of his time in actual research instead of pesky undergrads.
Re:One word.. (Score:3, Informative)
Yes it is, goto requires a label, and thus requires someone reading your code to go hunting for it. Return is known to push control flow to the exit of the function. One obfuscates code, the other doesn't.
Re:One word.. (Score:3, Informative)
Error handling in C code is my typical example of that. It mostly avoids the need for lots of if statements to make sure that you clean up all that you need to and nothing more.
There are other ways to go about it, but in general I'm not convinced they are better.
The most common use of "goto" in that circumstance is to enforce "only one return".
Which is every bit the pedantic lunacy that goto-hate is.
Re:Example (Score:5, Informative)
OMG. I didn't even knew you could write that in C (and I have my name in the comp.lang.c FAQ...)
Of course, I checked C99, and, no, you can't write that:
3.6.6 Jump statements
Syntax
jump-statement: ;
goto identifier ;
continue ;
break ;
return expression<opt>
identifier being defined as:
identifier:
nondigit
identifier nondigit
identifier digit
So, goto *f() is a no-no (as is probably &&a)
But, anyway, wow. Gcc actually compiles that to something that somewhat runs...
Re:Example (Score:3, Informative)
Yes, it is non-standard. But quite useful for some things (although not in the way written above). Consider a function that needs to return and then resume where it left off the next time it is called. Could be done by wrapping it in a large switch, but this is more direct and efficient.
#include
void *f(void *p)
{
if(p != 0)
goto *p;
return &
a: printf("A\n");
return &
b: printf("B\n");
return &
c: printf("C\n");
return &
}
int main(int ac, char **av)
{
void *p = f(0);
p = f(p);
p = f(p);
p = f(p);
p = f(f(f(f(p))));
return 0;
}
[and perhaps it's time to get myself an account on here... captcha: "untested"]
char Str[255] (Score:3, Informative)
char Str[255];
sprintf(Str, "%s, %s", Lastname, FirstName);
I really wish snprintf was available on my C implementation.
Re:One word.. (Score:3, Informative)
I have not used a 'goto' statement in about 15 years now. But it's not some irrational fear. It was all due to someone saying 'there's no situation that requires a goto' and 'goto statements make code hard to read'.
And after I used goto a little more, I realized they were correct. In any good, modern language, 'goto' is completely unnecessary and makes your code harder to read.
If you're breaking out of a loop, you should use 'break'. If you're continuing a loop, 'continue'. If you're handling an error, throw an exception. If you're calling another piece of code, make that code a function and call it properly.
Re:One word.. (Score:3, Informative)
Re:One word.. (Score:4, Informative)
How is this so hard to understand?
It's not. But nor is code that uses goto in the idiom that FourthAge posted above.
My objection to that code is that if you do even just three or four allocations, you start getting very large indentations pretty fast, especially if you like 8-character indentations [sourceforge.jp]. (They get pretty long even with 4, which is what I like!) This causes a problem if you restrict yourself to 80-columns [sourceforge.jp].
Arguably this is a problem with restricting yourself to 80 columns, but that's not entirely unreasonable even if I'd rather the limit be ~100. But guess how many your code uses? Your longest line is 109, longer even than my longer preference. Even if you use 4-character indentations, your longest line is still 80 characters.
If you reformat your code to use 8-character indentations and 80-character column limits, your code would become a bit uglier as you'd have to wrap four lines (a couple of which don't really have a good breaking point), and hence a little harder to understand on its face.
By contrast, the original code fits entirely within 80 characters (admittedly only just) without anything remotely like an awkward line break.
Further, in some sense it doesn't entirely follow the flow of the code. Perhaps this is a vice and not a virtue, but I tend to think of places where there's an unusual condition (like a failed allocation) as not on the same "level" as normal control flow. In this sense, I would think of that procedure as basically linear "with some provisions for exceptional conditions". In that sense, your proposal of the cascaded ifs doesn't really match my mental model of how the code behaves -- the indentation of the normal code changes depending on how many exceptional conditions might arise, and there's not really any reason why it should by that model. By contrast, the original code matches it.
(Incidentally, "linear with some provisions for exceptional conditions" is basically how I'd write it either in a language that supports exceptions or a language that has RAII. In the former case, there's a decent chance do just one try block for the body of the function, and in the associated catch test to see if each item is allocated in turn. (This doesn't match the structure of the real code exactly, but it's closer to it than it is to your nested-if code.) In the latter case, you'd have basically the real kernel code except that the deallocation would be in the RAII objects' destructors. Again, no nested ifs.)
setjmp, longjmp (Score:1, Informative)
Actually, C has functions that are supposed to do this and work; setjmp and longjmp are supposed to let you jump between functions etc and unwind the stack; if I recall correctly, I even used them for interrupt handling code in MS DOS.
Re:One word.. (Score:3, Informative)
From the linked article: [joelonsoftware.com]
I have done code that way, before exceptions came into vogue. To do it right, you've got to have many many many error-checking if() statements after every call to any function that could fail. That could maybe be done as neatly as with exception-handling, if one was ever careful to maintain one's error-codes in a single numeric heirarchy, so as to allow 'families' of errors. The error-checking statements can then select for specific errors or for families of errors, like this: if (errorCode & ERROR_FAMILY_FILEIO).
But that is a lot of work, and the code won't be any cleaner than with exception-handling. In fact it will be uglier because you can no longer just call a bunch of functions in a row without checking returns, allowing a single exception-handler at the bottom to catch any errors it thinks it can handle.
Nor is the author correct in contending that 'throw' is somehow less visible or less handy or less maintainable than 'return'.
And he won't get the very very nice exception-handling support that some languages like C# offer.
Re:One word.. (Score:1, Informative)
This'll add the loop code for something that will never loop, even on an optimizing compiler (How will it know that you'll definitely break out at some point?)
Besides, cleanup1() will almost certainly have to wait until after action2() in most cases. I'd say increase cleanup_stage for every action performed, then use a switch() {} with no breaks
but that's just goto with a fancy wrapper.
Re:One word.. (Score:1, Informative)
1. you quickly will run out of indentation space, unless you use a shallow indentation, which is a readability problem of its own. The goto-variant allows the "meat" of the function to be just a couple indentation levels in.
2. If you later add another initialization step, you have to re-indent everything. Even if your editor makes this easy, you'll end up with a huge .patch file which will be painful to merge with any other changes to that function
3. I actually prefer your style for simple cases (2-3 steps) but sometimes when doing kernel work you end up having to do lots of init/cleanup steps and the goto-style starts to become MUCH more readable than deep-indentation. Basically the goto-style scales to any number of init/cleanup steps while the nested variant would be unworkable with >8 steps
Re:Prolog Assignment (Score:2, Informative)
You know, SQL is declarative, and definitely one of the most powerful and useful programming languages available today. I suggest refining your opinion to hate languages that try to make entire problems declarative rather than limiting themselves to appropriate pieces, such as data querying.
Re:Prolog Assignment (Score:4, Informative)
General-purpose declarative languages are bunk, I agree.
However, domain-specific declarative languages often work pretty well. Consider build systems (Make, ant, etc.), or SQL, both of which are declarative.
Re:One word.. (Score:4, Informative)
"How is this so hard to understand?"
For me, it's hard to understand because your most highly indented lines wrap off the edge of the window, back to the left margin, wrecking the indentation cues. In an IDE, it sets a limit on how many conbditions you can check before having to scroll around to see the whole function.
I used to work at a company where this was the standard and I was never fond of it.
Re:Prolog Assignment (Score:3, Informative)
If you couldn't already tell, I have a low opinion of Prolog and declarative languages.
Better not tell your database [wikipedia.org], it might get offended...
Re:Getting around encapsulation (Score:1, Informative)