Seven Habits of Highly Ineffective Programmers

Credit: The People Speak!
Credit: The People Speak!

Some programmers are better than others. In fact, there’s a statistical distribution: a few are absolutely brilliant, some are good, most are at least competent, some are barely competent and a few are truly dire. It’s an interesting observation that the Microsofts and Googles of the world will have seriously incompetent people writing parts of their software. And the really bad software can live for years, long after the author has taken his mediocrity with him to another unsuspecting company because one characteristic of bad software is that it is fantastically baroque and no-one else wants to take ownership. So there it stays. It’s almost certainly inefficient and probably insecure but so long as it doesn’t explode, everyone just leaves it the heck alone.

I will point out that bad programmers are not necessarily stupid and are certainly not bad people (or if they are, it’s not related to them also being bad programmers), they’re just individuals who find themselves in a job that they’re unsuited for. That said, the worst programmers share a number of characteristics. There are a lot more than seven, of course, but here’s my list of seven of the worst. Bad programmers…

…Lack basic knowledge

Writing software is an art to be sure, but it’s also a craft and like any craft, there is certain basic knowledge that you need to be able to practise your craft effectively. I don’t expect you to be Donald Knuth but I do expect you to know the basics of data structures and algorithms. I don’t expect you to be able to code a hashtable but I do expect you to know that there is such a thing and why you might use one.

Not understanding the basics of algorithms and data structures will result in serious performance issues. Imagine a list whose members are in sort order that currently contains 1000 items. A method exists to insert new items into the list. The bad programmer’s implementation will add items to the end of the list and then sort it. This is O(nlog2n) or roughly 10000 comparisons. The competent programmer will do a linear search of the list looking for the insertion point. This is O(n/2) or roughly 500 comparisons. The good programmer will use binary search to find the insertion point. This is O(log2n) or roughly 10 comparisons. The worst implementation is three orders of magnitude slower than the best implementation. If there are 1,000,000 items in the list, the worst implementation is heading for disaster.

…Fail to understand idioms

Programming languages are the medium through which we express our intentions. Like human languages, computer languages have their idioms and idiosyncracies. The good programmer will master the idioms while the bad programmer will create anti-patterns.

The anti-patterns are typically irksome rather than disastrous. For example, if you don’t understand the const idiom in C++, you’re likely to write functions like this:

void writeData(char *data) {
    // Do stuff

Leaving aside the fact that char * is effectively a promise to modify the data, the API is a pain to use, requiring ugly const_cast invocations:
std::string data("...");
writeData(const_cast<char *>(data.c_str());

If we don’t own the code, we’re stuck with it. If we do own the code, we’re probably still stuck with it for two reasons:

  • If I fix the signature for writeData so that the argument is declared const, I need to fix all existing calls to writeData as well
  • The rest of the code in the file containing writeData is likely to be terribad. If I touch it, I own it and I don’t want it!

…Use the wrong tools

Imagine you’re looking at a C++ header file and you see something like this:

typedef struct LIST {
  struct LIST *next;
  void* payload;
} list, *plist;

If you don’t recognize that, because you weren’t around in the 1990s, that’s a linked list struct, probably the same one that the bad programmer lifted from Herb Schildt’s ‘Teach Yourself C’ back in 1995 and which he’s been using ever since. Whatever its origin, it’s a sure sign that the programmer is using the wrong tools. std::list would be an obvious replacement but looking at the code more closely might indicate that std::vector or std::deque would be better suited to the task at hand. One thing is certain, however: the right tool for the job is never going to be a hand-rolled linked list.

Windows developers are forced to try and do everything in their graphical apps by the sheer poverty of the operating environment. They can’t reuse tools to do what their application requires because there are none, or none worth using. Unix developers, on the other hand, have more tools to play with than some of them know how to actually use. Have you ever seen something like this?

grep $pattern $file | awk '{ print $2 }'

In case you don’t recognize the syntax, a tool called grep is being used to pick out matching lines from a file and these lines are being piped to another tool called awk to print the second field of each line. As any fule kno, awk can search files for patterns just fine so grep is redundant:

awk "/$pattern/ {print \$2}" $file

…Ignore conventions

Well behaved Unix (and Windows) programs conform to well-established conventions:

  • Input is read (or at least can be read) from a special filehandle called stdin
  • Output is written (or at least can be written) to a special filehandle called stdout
  • Success is indicated by an exit status of 0
  • Failure is indicated by an exit status other than 0
  • Associated error text is written to a special filehandle called stderr
  • Program options are specified by command-line switches that look like -i <FILENAME> or --inputfile <FILENAME>

Following these conventions allows marvelous things to happen. The output of another program, such as grep, can be used as the input to my program, the output of which can be passed to, say, a PDF writer that produces beautifully formatted reports. My program doesn’t need to know how to do pattern matching and it certainly doesn’t need to know how to write PDFs. Instead, it can just get on with doing whatever it is that it does.

A long-departed colleague wrote dozens of Python scripts and compiled C++ programs that had the following characteristics:

  • The command line options were always two XML documents, one for actual options and one for “parameters”
  • The output was always an XML document on stdout, even in the case of failures
  • The exit status was always 0
  • The actual status was somewhere in the output XML, along with any error text
  • Progress text was written to a magic filehandle with a fileno of 3.

Needless to say, he was a terrible programmer.

…Are too clever by half

I said above that bad programmers are not necessarily stupid. Some are really quite clever. Too damned clever if you ask me. Have you ever seen something like this?

void majorfail(char *s) {
    char buf[256], *p = buf;
    while (*p++ = *s++);
    // Do stuff with buf

Bad programmers are insecure programmers as well by which I don’t mean that they’re secretly afraid that they’re not very good but rather that the software they write has security holes large enough to drive trucks through. That while loop may look 1337 hardcore but if I see something like it, I’m not praising the author’s mastery of the pointer but rather asking why he’s reinventing strcpy. And this message goes out to all the bad programmers of the world: 256 characters (or 512 or 1024) is not big enough to hold any possible input. The fix is not only safe, but way simpler and more comprehensible:
void minorfail(char *s) {
    std::string str(s);
    // Do stuff with str

Sometimes, a piece of clever code relies on some really obscure knowledge to make sense of it. Look at the following JavaScript:

var hardcoreMathFn = function(x) {
    if (x === x) {
        // Do loads of really cool stuff

Because I’m a bit of a clever-clogs myself, I recognize that this is an NaN test (NaN is the only value which is not equal to itself). If you, the maintainer of this obscurantist nonsense, are not aware of that piece of arcane knowledge, then the code looks like this:
if (true) {
    // Do loads of really cool stuff

The correct fix is:
if (!isNaN(x)) {
    // Do loads of really cool stuff

but the more likely one is:
// Do loads of really cool stuff

This sort of uncommented esotericism led to one of the worst bugs ever and I personally hold the smartypants openssl devs responsible, not the well meaning Debian package maintainer.

…Reinvent wheels. Badly

Have you ever seen code that does its own command-line processing? Something like this:

int main(int argc, char **argv) {
    char *file = NULL, *host = NULL, ...
    for (int i = 1; i < argc; i += 2) {
        if (!strcmp(argv[i], "-file")) {
            file = argv[i + 1];
        else if (!strcmp(argv[i], "-host")) {
            host = argv[i + 1];

This is ugly to read, a ballache to maintain and accommodating an option that doesn’t take an additional parameter, such as -verbose will be irksome. Also a variant argument form, -file rather than --file is used. If you don’t know already, you’ll probably guess that DIY command-line processing is quite unnecessary as well since the problem has been solved. The bad programmer is simply too ignorant to know that this is the case or too lazy to figure out how to do it properly.

Bad programmers don’t just reinvent, they invent as well. A sure sign of bad software design is the invention of (oxymoron alert!) a proprietary network protocol. Imagine that we are implementing a client/server system that receives JSON-encoded requests (no, not XML because this is the 21st century) and emits JSON-encoded responses. An exchange might look like this:


Now imagine that our “protocol” is simply that each request and response terminates on a newline so that the actual network exchange is this (“\n” signifies an actual newline character rather than an escape sequence):


Why is this a problem? Because implementing the protocol requires us to work at an uncomfortably low level. We have to get down and dirty with send, recv and select. We have to handle the possibility that the other end might fall over halfway through a message ourselves. If, however, we’d chosen HTTP rather than inventing our own protocol, we could use something like libcurl on the client side, a library that is not only mature and robust but more importantly is maintained by someone else. The server side could be implemented as a simple CGI script.

…Are plain sloppy

Look at my code samples in this article. Even when I’m demonstrating manifestly bad practices, the code is neat and orderly. You may question my opening brace placement since I favour the one, true placement of Kernighan and Ritchie over the deviant form of the heretic Stroustrup but you can’t deny that my coding style is readable and consistent. Bad software isn’t neat. The bad programmer can’t decide whether or not a space should go after keywords like if and while (hint: yes it should). You can generate random numbers from how many spaces go after commas: 0, 1 or other (hint: the correct value is 1). Similar strong randomness seems to govern whether or not there is a space between a function name and its argument list (hint: there shouldn’t be but I’ll let it pass if you’re consistent). And tabs and spaces are intermixed almost whimsically.

You may retort that whitespace isn’t significant, so what’s the problem? I said above that programming is a craft as well as an art. It’s also a discipline, one that requires an orderly thought process and a methodical approach. Code spends most of its time being maintained, not being written. If you don’t take a few minutes to ensure that your indentation matches your nesting how is the person who comes after you supposed to follow your logic? If you can’t pay attention to details how are you going to avoid sneaking in defects? In my experience, the probability of untidy code also being good code is close to 0.

Leave a Reply

Your email address will not be published. Required fields are marked *