How to Write Bad Code: The Definitive Guide, Part II

Daniel Lempesis
9 min readSep 4, 2018

First, a Recap
In Part I, we went over the evils of writing good, clear and flexible code which inevitably lends itself to maintainability, reusability and stability. We covered the benefits of mutating our inputs to achieve confusing, breakable and labyrinthine code, using poor variable naming conventions to confuse both ourselves and others, using no discernible formatting, keeping our code WET, and the joys of infinite method chaining.

We learned that by harnessing these powerful techniques and eschewing silly ideas like “best practices”, we can avoid unwanted side effects like readability, quick development and low levels of interpersonal frustration.

Let’s jump back in!

Learning New Languages

If you are new to a language, do not learn its idioms and best practices. Learn the bare minimum needed to get by, and then force it to resemble your favorite language as closely as possible. Use for loops in lieu of Ruby’s standard each. Forego Python’s list comprehension for map and filter. Import reduce from functools instead of using sum.

This is for the good of the team; once coworkers begin to see how much better everything looks when you do it your way, they’ll almost certainly end up switching to your language of choice, which is better anyhow. I can’t tell you how many die-hard Python fans have made the switch after five minutes of exposure to COBOL with the giddiness of a group of preschoolers on their first Halloween outing.

To quote a grouchy programmer I know: “Trying to write code in the strict style of another language is always guaranteed to be a trainwreck.”

We’re in the trainwreck business.

Testing: Running the Gauntlet

In olden times, “running the gauntlet” referred to a form of corporal punishment in which the guilty was made to run between two rows of soldiers who would strike out at them as they passed by. This is basically what testing does to our code; if it finds a weakness, it will take us down. And given the incredible job we’ve done so far to keep our code error-ridden, this could easily be our downfall.

As you’re likely aware (and if you’re not, skip this section and whatever it is you’ve been doing, keep doing it), testing frameworks are designed to ensure that our app is behaving as expected after we make changes to portions of our code, and even more critically, before we release it. They’re meant to ensure code quality, minimize bugs, and make it much harder for things to go awry without our knowing.

Needless to say, this is a problem. The reasons for this are many, and some are more obvious than others; I won’t go too far into detail, but obviously, programmers are generally only needed when things aren’t working right. If nothing’s ever broken, what do they need you for? Nothing. Don’t foolishly test yourself and your coworkers out of a job. If you can manage to get away with it, don’t use any testing frameworks at all. If not, there are steps you can take to increase your chances of making it through that gauntlet.

Edge cases: Unless your code already passes these, do not implement them. The reasons for this should be obvious, but I’ll touch upon a few of the main ones anyway.

Having to bother correctly handling edge-cases just takes more work. This means more thinking, more typing, and is a recipe for carpel tunnel. This also means less time pretending to code while browsing your favorite kitten subreddit, and more time actually having to code.

“But what about activity monitoring software!? They’ll know we’re goofing off!”

No they won’t. Who do you think wrote that software?

Edge cases are more or less the bread and butter of bugs (…or whatever software bugs eat. I don’t know, I’m not an electro-entomologist). Writing code that’s robust enough to appropriately perform its duties in a wide range of situations is code that’s not likely to break any time soon.

Think of your code as a scientific theory. It aims to answer a specific set of questions, and seeks to answer them to the satisfaction of the scientific community at large. Obviously, you want your theory to be accepted, so what do you do? Do you painstakingly construct a working model to fit and explain the data, through years of grueling research and rigorous testing?

Of course not. That would take too long. You make the data fit the theory, and tweak your own ideas as little as possible. Developing a truly solid model which fully explains something about our world is hard. So is writing flawless code. Lower the bar as much as you can.

Write weak tests: If you find yourself saddled with the unhappy task of writing the tests for your own code, look at it as an opportunity. Yes, you read that right: The next-best thing to no tests at all is writing the tests to fit your code. Do this, and you’re golden.

Fool the tests: Sometimes it just comes down to cheating the tests themselves. Don’t like a particular group of edge-cases? Hard-code them. Have a particularly difficult case where you can’t just hard-code the answer? Find a part of the test that calls on a commonly-used prototype method, and modify it to fit this particular scenario.

Exploit the tests: This is the worst-case scenario, as it’s generally obvious at a glance what you’re doing, especially if your only recourse is to modify the test framework itself. However, since at no point should you be ensuring the integrity of your application, this is sometimes a necessary risk.

With these time-tested techniques, we’ll switch Nightwatch’s night vision goggles with a blindfold, make a joke out of Jest, make Lettuce into rabbit food, and turn Cucumber into a vegetable.

Reinventing the Wheel

I want you to take a moment, and imagine the sort of acclaim the inventor of the wheel must have received. He almost certainly made inroads with the cavemen nobles, and may have even gained traction in his quest to become a cave-owning aristocrat himself (accounts vary).

(What is known with certainty is that he once came to blows with the inventor of the club, after an argument over whose invention had a greater impact. They ended up being unwitting co-inventors of the concept of irony.)

Now ask yourself: Do you want to simply rely on the achievements of others, resigning yourself to humbly stand upon the shoulders of giants in order to peer out across the great expanse of staid, unchanging human knowledge? Or do you want to be the proud recipient of the same praise and adulation prehistoric inventors got by breaking old ground?

(That was a rhetorical question, of course you want the fun one.)

Reinventing the wheel covers everything from writing your own implementations of everyday methods like map, filter and reduce , to more b̵o̵n̵e̵h̵e̵a̵d̵e̵d̵ ̵m̵o̵v̵e̵s̵- impressive feats like insisting on working out mathematical proofs or Dijkstra’s algorithm entirely on your own from the ground up.

Ignore that irritating little voice of reason and lack of confidence in the back of your head. Of course you can do it — you’re a programmer. Do you honestly think Ancient Greeks like Euclid, Archimedes and Pythagoras were smarter than you are? Of course they weren’t. They didn’t even have Wi-Fi. Who knows if thinking was even a thing before the internet?

Imagine the look on your coworker’s face when you proudly tell him you’ve spent the entirety of the last month* working on your own implementation of the Quicksort algorithm, and that it all paid off: it’s 3% faster in Safari than the built-in! Be prepared for some disbelieving stares… you’ve earned them!

*Be sure to emphasize that this was to the exclusion of all else; this proves your dedication and focus, and lets people know you don’t just fool around.

Still need convincing? Compare the following implementations of a simple algorithm meant to sum the numbers of an array whose squares are even below:

Array.prototype.filter = function(func) {
const temp = [];
for (let i = 0; i < this.length; i++) {
if (func(this[i]))
temp.push(this[i]);
}
return temp;
};
Array.prototype.map = function(func) {
const temp = [...this];
for (let i = 0; i < this.length; i++) {
temp[i] = func(this[i]);
}
return temp;
};
Array.prototype.sum = function() {
let total = 0;
for (let i = 0; i < this.length; i++) {
total += this[i];
}
return total
};
function sumNumsWithEvenSquares(arr) {
const evenSquares = arr.map(n=> n*n).filter(n=> n%2 === 0)
, evens = evenSquares.map(n=> n**0.5);
return evens.sum()
};

Vs.

function sumNumsWithEvenSquares(arr) {
const evens = arr.filter(n=> n*n % 2 === 0)
return evens.reduce((a,b)=> a + b)
}

Our first one is downright impressive. It’s 30 lines of code, overwrites two built-in prototype methods, and takes up so much real estate you’d have to start paying rent if it weren’t all virtual. The second example is just sad. (Although even it beats the realization that any even number squared is even, leading to code like arr.filter(n=> n%2 === 0).reduce((a,b)=> a+b).) This sort of practice is guaranteed to shrink your codebase to the point where you’re embarrassed to talk about it, impressing exactly no one.

Moral of the story? Reinvent the wheel.

Running Commentary: An Internal Monologue

Comments help clarify abstruse, complicated, or otherwise-confusing code. They cue other programmers in to what the heck is going on without intense, unnecessary effort. Some languages are designed to be readable to the point where they don’t need comments, and commenting your code isn’t generally expected. If this happens to be the case, obfuscate your code and fail to comment.

If comments are expected of us, we want to make them as unhelpful as possible. A great place to start is by writing in a stream of consciousness format. Whatever happens to be going on in your head at that particular moment, just write it down. Context isn’t important. Thought-fragments that won’t make sense to you by the time you get back from your coffee break are also sure winners. You can also make your code appear more thoroughly commented by peppering it with unnecessary comments in places where no clarification is needed (in fact, excessive comments can actually make your program more difficult to read, while simultaneously bestowing upon it the semblance of thoughtful code, so long as no one looks too closely).

Now, this next one is something that very few people actually do. To be honest I’m a little hesitant to even let you in on this little secret, but it’s just so much fun.

In your comments, if you absolutely must use them, refer to things with technically correct or almost-correct but relatively confusing terminology. For example: If you’re working with Ruby, refer to arrays as “lists”. Working in JavaScript? Refer to your JS Object as a dict or a hash. Call an arrow function a lambda. Mention how you think a tuple would be a useful data type for the current problem you’re solving if you’re coding in a language that doesn’t support them.

Your comments are useless and your code unreadable? That’s Future You’s problem! For now it’s all about cranking things out as fast as possible, and having to take the time to thoughtfully and meaningfully comment out your code just gets in the way of that.

Before We Wrap Up

I’m going to leave you with a few extra points intended to guide you on your path to greatness. These don’t necessarily fall into any of the above categories, but that doesn’t make them less important.

There’s an old Buddhist saying you may have heard before: “First thought, best thought.” Whatever the first thing that comes to mind is almost certainly the best way to do something. So why would you refactor? This will only detract from the quality of your work. Refactoring is one of the purest forms of entropy.

If you must refactor, never, ever get rid of old code. You worked hard on that. Leave it as as testament to your S̵i̵s̵y̵p̵h̵e̵a̵n̵- Herculean efforts. If you must, comment it out. If it no longer has any function, leave it as is. It’s not hurting anything and it lets people know how hard you worked on the app.

Most of us appreciate cuteness. There’s a reason 93.8% of YouTube is just cats chasing after laser pointers. Similarly, other coders appreciate cute and clever code. Do you see a clever way to accomplish something in fewer keystrokes or reduce the size of your bytecode, even if at some point in the future things might break? Go for it!

Spend a lot of time optimizing parts of your app that don’t need it. Preferably, tweak code of the wrong computational complexity without actually reducing its complexity. Is a modification to how you’re handling an array going to speed up your runtime by 4% rather than switching to a set or hash that would make things orders of magnitude faster? Do it!

In fact, always favor speed over readability, and do so at all costs. Choosing between adequately-performant, concise, readable code, and fast, verbose and buggy code shouldn’t be a decision you have to think about. It’s basically the choice between floating around a pond in a ducky paddle boat and racing Sebulba in Anakin’s pod racer. Yes, there’s a chance you’ll die, but it’ll look a lot cooler, and at least there’ll be fiery explosions and you’ll make the news.

As always, thanks for reading! I hope you had fun, and maybe even learned something :)

--

--