By designing and writing code, we naturally start to develop our own personal style and habits. The more code we write with the intent to improve, the better we’ll get eventually.

But on our way to mastering the craft, we might pick up some bad habits.


Old Wine in a New Bottle

Much of the work we do as software developers is just addressing the same problem in slightly different packaging. After basically doing the same thing over and over again, we start developing a habit of doing it in a certain way. The problem is, we may not have found the best way of doing it.

If somebody asked us, “Why are you doing it this way?” or “Why did you choose that particular stack?”, what would our answer be?

A few answers come to mind:

  • “I’ve always done it that way.”
  • “It’s the way we do things around here.”
  • “It’s more convenient than other solutions.”
  • “It’s the only way I know how to do it.”

These are all valid responses, and we all have heard or said them before. What’s missing are the questions we should ask ourselves to understand why we gave that particular answer:

  • “Why did I start doing it that way?"
    Every single one of our habits and beliefs about software development started with sound reasoning. Maybe we hit a bug and we had to work around it. The compiler was optimizing our code wrong. A framework made us adapt our style. Or we just didn’t know any better at the time.
  • “Aren’t there better ways to do it (now)?"
    Our languages and tools steadily evolve with new features and paradigms, performance improvements, and bugfixes. Even if we could trust the compiler in the past, is it still valid now? Is the bug which forced us to do something differently finally fixed? When was the last time we checked and validated our previous assumptions or even benchmarked our solution against others?

Without thinking about why we do things the way we do, we end up with superstitious self-coding instead of relying on standard libraries provided by our framework or proven solutions by the community. And in order to not shake our beliefs, we won’t even validate our solution.

Sure, we’ve always done it this way.

It’s our normal.

But have we asked ourselves lately if it’s (still) good?


Doing It My Way

Memory management in C is not easy. Doing it correctly is hard. That’s why many developers create their own style over time, believing they found the perfect way:

1
2
3
4
5
// Habit-Driven-Development
char x[2048];
for(int i = 0; i < 2048; i++) {
    [i] = 0;
}

We could initialize the memory of an array ourselves by iterating and setting everything to zero. Maybe there was even a time when that was the supposed way to do it. But there’s also a standard library way to do it:

// Standard Library
char x[2048];
memset(x, 0, sizeof(x));

Why would we do the initialization ourselves when this shorter, more common way exists?

Maybe we believe our solution is safer or even faster than calling memset(). And that might be true under the right conditions, especially since micro-benchmarking is another really tough problem.

But do we really want a more complex and harder-to-read solution? Why not just use the standard solution that every other programmer understands, is well-documented, and battle-tested by a bazillion lines of code?


Be Smart, Not Clever

Compilers are capable of quite sophisticated optimization tricks and cunning processor and platform-dependent implementations. Did you know that a smart compiler might even replace our for-loop with a memset()?

If we had micro-benchmarked our code (which we didn’t), we might think, “My code is as fast as memset()!”. Even though the compiler replaced our habit by more resilient and concise code.

Our habits and sometimes quirky code styles don’t necessarily result in bad or wrong code. But in the end, all we might accomplish is adding an extra layer of complexity. Especially if we’re not the only ones who have to deal with the code, we should try to avoid adding these.

There are situations where we need to do things ourselves and not rely on the provided standard libraries. But these situations are rarer than we often think. And we should be absolutely sure about what we’re doing and why before doing it.

We need to be smart about our code and not try to be clever.


Truth by Repetition

Habits are reinforced by repetition. The more we build up our habits by repeating a behavior, the more we can rely on them without conscious thought. They will become our automatic truth.

This is true for good habits, but also bad ones.

Almost Correct

An example of truth by repetition is movie quotes. We all know some famous movie quotes and can recite them by heart:

“Luke, I am your father.”
– Darth Vader, The Empire Strikes Back

“Play it again, Sam.”
– Rick Blaine, Casablanca

“Do ya feel lucky, punk?”
– Harry Callahan, Dirty Harry

The Empire Strikes Back | Lucasfilm

Guess what?

Not a single one of these is a verbatim quote, even though most of us are sure they all are.

Over time, the original lines were made into shorter, more memorable nuggets of information. And by repetition in popular culture, it eventually became the truth.

Just because we do something all the time in a particular way, it doesn’t mean it’s the best solution. We don’t have to call every habit in question every time we do it, but we need to re-evaluate from time to time.


Other People’s Habits

Sometimes, habits aren’t even our own!

Any modern IDE provides a lot of code generation for mundane tasks. That’s great for convenience.

But is it the best code possible for the problem?

Enforced coding guidelines, or automatic linting and fixing, is another external factor on our code.

We utilize a lot of different tools to improve our code, like linters, formatters, code generators, build pipelines, and more. They improve our productivity and code quality immensely. But when was the last time we actually dared to question our tools?

I’m not arguing we should not use coding guidelines, linters, or code generation. They are all great additions to our coding toolbelts. But we need to understand that any fixed rule or mindset that’s applied without actually understanding why is crippling our ability to understand our code better and improve on it.


Don’t Start at Zero

Some languages, like Golang, provide hard coding guidelines, including tooling to enforce them. Other languages are more open to the interpretation of what makes coding style good.

We don’t need to reinvent the wheel. But we also shouldn’t accept the truth of others without fact-checking.

Instead of starting from zero, we can leverage the work of others as a starting point. A lot of different people, many much smarter than us — or at least smarter than me — had long and complicated discussions about proper code styling. It doesn’t mean they are right about every little decision. Every team and codebase is different. But we can profit from their results for free.

There are styles guides for languages, sometimes even for single projects or frameworks:

Guidelines should improve our code, not force our code to adapt just to make the linter happy.

Know when to break or change the rules.


Finding Your Own Style

At my company, all the developers sat down together to go through different style guidelines that were suitable matches for our projects. We adapted them to our team and codebase through discussion and by voting on them.

Photo by Amanda Dalbjörn on Unsplash

Taking an established guideline as a starting point is always a good idea. But whatever custom guidelines you come up with, never treat them as a fixed ruleset. They need to be re-evaluated regularly, evolve, and adapt to new requirements. Guidelines should never be too strict, or it will become hard to deviate from them when needed.

For example, one kind of rule we always adapt to our previous way of doing things: verbosity.

As a Java shop, we are used to verbose code — and we firmly believe that verbosity can be a good thing. Many bugs can be avoided by adding a little verbosity from time to time. Code is more often read than written:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
boolean result = ...;
if (!result)
    handle();

// VS.

boolean databaseAvailable = ...; 
if (databaseAvailable == false) {
    handleConnectionFailure();
}

This is an exaggerated example, but it’s a valid style choice:

  • Every variable deserves a proper name.
  • Explicit == false might be unusual, but we won’t forget ! by accident after the first parenthesis.
  • Methods also deserve proper names.
  • Providing curly brackets prevents multiple types of errors.

But be aware that overdoing it might add more “noise”, compared to the original “signal”.


Conclusion

We often don’t need to use the latest and greatest nightly build of the newest software stack available. But we shouldn’t trap ourselves in the past by not trying to improve either.

Just because we’ve always done something in a particular way doesn’t mean we can’t change and improve. Sometimes, we need to think outside the box, but we also need to know when to stay in our cozy box.

Our profession demands constant improvement and lifelong learning. Otherwise, we won’t be able to achieve the best results. We should strive to become better software developers by continually re-evaluating our beliefs.

Technology moves forward, and so should we.

Book Recommendations