Hacker News new | ask | show | jobs
by loup-vaillant 5351 days ago
Could you give your estimate of the properly used/badly used ration of copy/paste, preprocessor macros, and goto? Mine would be "too low for me to worry about".

Okay, there are some (corner) cases where they really are a good idea. But "never ever use this Chtulu Abomination" still is a damn good heuristic.

1 comments

Good copy/paste: Anything boilerplate such as manually initialized structures/arrays of stuff, language-specific boilerplate (Java comes to mind here) that is essentially the same repeated shit but you gotta do it anyway, unrolling loops, setting up switch statements. Anything unavoidable (or too expensive to refactor) that's repetitive.

Preprocessor macros: Code generators, compile-time switchable code (such as logging) without filling your source files with #ifdefs

Goto: Managing complex resource allocation/deallocation within a function:

    bool success = false;
    int fd = -1;
    char* memory = NULL;

    fd = open(filename, "rb");
    if(fd == -1)
    {
        LOG_ERROR("Could not open %s: %s", filename, strerror(errno));
        goto done;
    }

    memory = malloc(BUFF_SIZE);
    if(memory == NULL)
    {
        LOG_ERROR("Out of memory");
        goto done;
    }

    for(blah blah blah)
    {
        if(failed to read or whatever)
        {
            LOG_ERROR("It's borked");
            goto done;
        }
    }

    ...
    success = true;

    done:
    if(memory != NULL)
    {
        free(memory);
    }
    if(fd != -1)
    {
        close(fd);
    }
    return success;
I disagree that dismissing a tool out of hand as an "abomination" is a commendable approach. If you take that attitude (or instill it in others), you'll probably never learn the proper use of such a specialized tool, leaving you ill-equipped to deal with the situations those tools handle well.
Boilerplate: your language is bad. Switch to a better one, or modify the front-end of your compiler (they probably won't let you, though, and that sucks). I mean it, the cost of boilerplate is really high.

Unrolling loops: I never saw an instance where that was necessary. Plus, the compiler can often do it for you. I know we often use high performance applications (video decoders, 3D games…), but very, very few of us write ones.

Switch statement: the syntax of the construct is heavy, we should lighten it. The rest is hardly boilerplate any more:

  light_switch (expr) {
    1  : i11; i12;  // "break;" is implicit
    42 : i21;
       : i_default; // "default" is implicit
  }
Preprocessor macros: I agree (I mean, I back-pedal), they are more useful than the rest. However, I still avoid them by default, as they make really good foot-guns.

Goto: your example shows exceptions (try…catch finally here). Goto makes much less sense when you have them.

Now my point isn't to never do those things at all. Only to think of them as last resorts. The "Chtulu Abomination" metaphor helps me do that.

"your language is bad. Switch to a better one"

All languages have boilerplate somewhere. It's unavoidable. Switching languages just because there are pain points is not a solution, because you'll simply be exchanging one problem for another.

"or modify the front-end of your compiler"

This is most definitely pie-in-the-sky. In the real world of real business, you can't do this.

"I mean it, the cost of boilerplate is really high."

Oh, I agree wholeheartedly. But you need to work in the languages that programmers understand today. I'm not going to have nearly as much success hiring smalltalk programmers as I would have hiring Python programmers, for example.

"Unrolling loops: I never saw an instance where that was necessary."

I have, but then again I've been at this for 20 years.

"Plus, the compiler can often do it for you."

You can't be sure until you look at the disassembly. Often, it does it wrong.

"Switch statement: the syntax of the construct is heavy, we should lighten it."

That's not going to happen within the next decade. Meanwhile cut & paste is my tool of choice to cut through the boilerplate.

"Goto: your example shows exceptions (try…catch finally here). Goto makes much less sense when you have them."

But it makes LOTS of sense when you don't have them. And it's still useful in C++ and Objective-C, where you still end up interfacing with C libraries. If you don't know about the goto "poor man's exception", you'll either end up with repeated and buggy deallocation code, or a monstrosity of nested scopes, or a bunch of check-return-and-throw constructs, which in the case of Objective-C will slow your program way down because it implements try/catch using longjmp.

Goto is also useful for breaking out of inner loops when the language doesn't support that (some languages support "break [label], to make it seem less like a goto).

I don't consider these to be last resorts; I consider them specialized tools. Much like design patterns, they're for mitigating some deficiencies in a programming language, but they're only effective if you know how to use them.

It seems we mostly agree.

However, I'd like to challenge the belief that modifying the front-end of a compiler is too hard, or unreasonable. Even in the so-called "real world" where screwing up means you're fired.

First, the point is to tweak the language, not the compiler. For instance, we may want to lighten the switch() syntax before GCC does, but we do not want to modify GCC itself if there's a simpler way.

More often than not, there is as simpler way: just write a parser and a printer for your language, so you can do source-to-source compilation by chaining them. Printers are easy. Parsers are almost as easy, except for C++.

Then tweak your parser (lighten some syntax, add some keywords…), do some pre-processing between the parser and the printer (yeah, true macros), whatever.

Now there are some caveats: such a pre-processor may confuse IDEs, and may screw up error reporting (where errors don't track back to the actual source code). I personally don't care much about the former, but to solve the latter, the base compiler need to provide a way to be told where a given line of "source" code actually comes from (very useful for tools such as Lex/Yacc). Unfortunately, taking advantage of this will greatly complicate your pre-processor.