Developers Bad habits

You don’t want to do that

Jérôme Beau
10 min readFeb 19, 2021

I should make clear that I made all those mistakes myself at least once, and still do a few of them every once in a while. Not because I am a developer, but because I am human: nobody likes to do some tedious things, nobody likes problems to fix, nobody wants to handle others’ problems, everybody… is lazy when it comes to this kind of tasks. But, on the long run, not handling them correctly will worsen the situation.

Here is a list of things we should try to avoid, as good developers.

Copy & Paste

Yeah, your work would be hard without cut & paste, right? We do this all the time, to save time, and it actually has deeper roots: prototyping (creating from a canonical object) is a natural tendency to build concepts (B is a A but something).

There are several use cases that involves cut & paste. Typically what you are developing can be:

  • similar to existing things that would look the same, but you don’t want to re-write the common part. However, if you follow the DRY principle, you should not duplicate things and (hopefully) that’s probably not what you want: you want to edit it afterwards. But nobody’s perfect, and you may forget to do it everywhere it should be, thus leading to a bug. Maybe you should wonder if that common part could be refactored as an object or a function, and the varying parts be arguments for such a function.
  • repetitive and you don’t want to rewrite it every time. However, this must fit one of two cases: either A) it is a long repetition, and you should probably benefit of automating it (through a tool or code) ; or B) it is not long and you should not duplicate things according to the DRY principle (see above).
  • complex and you don’t want to take the time to understand it. However if it is something you need to do, you’d better understand what you are doing. Pasting code or technical commands prevents you to memorize it or even understand what you’re doing. Even if you think you do when pasting it, you won’t memorize it as well as if you typed it.

Questioning the need

Write us what you need, we will explain how to do without it — Coluche

When a fix or a feature is easy to implement, you a less akin to challenge the rationale for it (but you should).

But when things get harder, as a sensible being with a preservation instinct, you start to look for an escape. And the first one is to avoid the obstacle: do we really need to go there? Is it worth suffering for it? So you end up in the anti-pattern of challenging a business need because of technical difficulties.

If you’re developing the feature or fixing it, that is a question that has been settled way before. It is not the time to question it anymore. Face the problem, and make it easier to fix the next time through an agile design refactoring, so that the next discussion can focus on the business need rather than the development cost.

Hushing errors

One way to avoid problems is to hide or hush them. This is what too many developers do in too many situations:

try {
result = canFail()
}
catch (error) {
// Do nothing
}

While this kind of code seems to hide problems, it actually hides the solution: when an error is hushed this way, you will never get a chance to understand what is failing nor how you can fix it. Actually you might search for days why some code is never executed until you find that it is because some unexpected error was absorbed.

This is why the minimum action of error handling should be to display a log message.

Workaround instead of fix

A variant of this is the false fix. Suppose your program crashes because something is null. What it is the solution?

canFail(myVar)   // Crashes because myVar is null!

One of the worst short-minded behavior of some junior developers will be like this:

if (myVar != null)
result = canFail(myVar)

or

try {
result = canFail(myVar)
} catch (nullError) {
// It's ok, continue
}

Did this solve the problem? No, it worked around it. For sure the error won’t show up again, but as much sure is the fact that other problems will arise in the next steps, as we called this function for a reason.

Errors are not things developers should exterminate. They can be very useful, and often it is better to fail with an error than to continue with an inconsistent state that cannot succeed.

Implementing more than one thing at a time

Committing multiple things

This one starts from good intentions: while implementing a feature or fixing a bug, you think about something else (refactoring, another fix or even another planned small feature) that should be done and for some reason (you’re in the right place in the code, it will be quick to do, etc.) you decide in include that secondary work in your current changes.

The problems raised by such a choice are pretty obvious:

  • size: Your changes will get bigger ; so the time to review them ;
  • versatility: The topic of your changes will be diverse, so reviewing your code might require a broader knowledge. Also, depending on your pull request/committing policy (squashing or not), your whole set of change will become atomic, so that one cannot revert only the part of it that is relevant to a single topic.

As the SRP states, you should try to focus on doing one thing at a time. This applies to code, but it is also a good advice for all your activities.

Maybe some exceptions should apply to this recommendation, though:

  • refactoring: we all know that such tasks hardly find their place in a backlog where there is always something deemed as “more important” to do (which is wrong). So moderate refactoring should be allowed as an regular “hygienic” routine that prevents technical debt to grow way out of control.
  • testing: obviously there is nothing wrong in adding or improving tests when you add a feature or fix a bug. Tests should not be postponed or planned for a “later task”.

Bad design

There are other ways of developing multiple things at a time, when you:

  • write non-SRP code that melt multiple concerns.
  • implement some optionality mechanism, which is actually about implementing two contracts at a time (the one when the optional thing is provided, and the one when it is not).

Blaming others

A fraction of humans have a difficulty to acknowledge they did something wrong. There can be many reasons for that, from ego to stress. This can lead to blame the machine, the operating system which “must be bugged”, software tools that “did not do want you wanted” or did it incorrectly (like if they became sentient), or… the users themselves.

That’s a common situation in IT: a bug is reported and you cannot reproduce it on your local environment.

“It works on my machine”

you say.

For sure, if a bug can be reproduced, you can consider it half fixed, as the fix becomes only a matter of development time. But not reproducing a problem is probably one of the worst situation for a developer, who can only guess.

Still, the problem remains very real on the user side, and users don’t care about the fact that everything works fine for you. Keep in mind that you’re building the software for them, not for you.

To avoid those problems, you might want to deliver reproductible environments through app containerization.

“Silly users”

Even if the user actually did something wrong with your software, you have to face the fact that you allowed it. It is your responsibility to prevent such misbehaviors: if you allowed users to do silly things with your software, that should be fixed too.

Loss of rationality

Say you encounter some behavior (a bug, a message, etc) that can’t understand. You really can’t. All explanations have been exhausted, and your brain is — quite rationally actually — starting to look for other variables: Did it work yesterday, even if I didn’t touch that code? Maybe it is working only even days? Could this be a bug of the OS or hardware bug? Could a solar wind event affected my computer?

Stop it, you’re being irrational. You’re just missing something. Walk away, then go back with fresh ideas.

Belief in non-idempotence

A specific case of this when you start to retry things that fails consistently. Idempotence (same causes produce same effects, in short) is more the rule than the exception in software: if you give the same inputs, it is more than likely that you’ll get the same outputs.

Nevertheless, some developers seem to loose faith in that rule when they encounter a bug that shouldn’t be there: they will immediately repeat the faulty operation, as if repetition would imply a different behavior. If you’re not playing with time or race conditions, it won’t.

When a command fails, it doesn’t make sense to type that command again. If an error appears when you click on a button, it doesn’t make sense to click again on it. Reloading a web page usually won’t fix an error in it, and so on. The problem is there, and it’s not gonna disappear until you fix it.

Insanity is doing the same thing over and over again, but expecting different results — Jane Fulton

Another case of irrational behavior is to see a bug, then walk away as if you never saw it.

I thought it was on my machine only

would you say.

Actually the odds are way higher that a program behaves on other machines just like it behaves on yours. It’s an automated thing, after all. So, nine time out of ten, it wouldn’t be specific to you. It is there, and it will show up again, sooner or later. It won’t disappear automagically.

Once again, such a behavior can be understood from a human point of vue. It is a natural reaction, when something doesn’t fit with the expected, to pretend it didn’t exist. Bugs are bad news and, depending on your stress state, you might want to protect yourself by pretending not seeing them. Because that would mean a lot of more stress, because you don’t have time to handle it right now.

If that’s not a critical issue that you’re about to deliver on production, that’s ok. Just note it (in a project task card ideally) so you won’t forget to handle it when the time is right. But don’t pretend not seeing it, don’t try to forget it, because it would be worse if it’s the end user who sees it.

If you see something, say something.

Consider the feature as the only goal

Let’s say you asked a junior developer to develop some feature, and the feature is delivered (possibly on time), fully functional but with unmaintainable code. You complain about it but the developer assumes to invalidate all your remarks with a single argument:

Yes, but it works.

That’s something quite irritating to hear for a senior developer who learned the cost of bad design over years, but that’s quite a natural response from a junior developer: you asked to develop a feature, and the feature is there ; so why do you complain?

As if it was not hard enough, it is likely that people expecting the feature (customers, sales people, or even your manager sometimes) would agree with the argument: all of them just want the feature to work, they don’t care how it is made (and they would be right if the development stopped there).

Then starts the tedious task of justifying good design. It is a most difficult task because you cannot transfer your experience to someone else: either A) they trust you, or B) you impose it ; or C) you try to make them understand that software development is not just about building a black box: making it work is just half of the job, and the other part is to make it maintainable for the box lifetime.

Being maintainable is achieved through design agility (the real one, not the buzz word), that is, the ability to embrace changes over time.

What can you do to prevent this?

  • Programming to mentor and show good practices ;
  • Check the developed code on a regular basis before it is finished.
  • Ask unit tests to be written, emphasising that such tests should allow to test one thing. That should drive the design toward a more granular design.

Uncertainty

Of course you can be sure of some things and uncertain about others ; but in the latter case, you should be able to say:

I don’t know

instead of:

It should work.

Paradoxically, the first sentence is more reassuring that the second one. Because saying that you don’t know something is an unambiguous affirmation, whereas using the “should” adverb is just a confession saying:

My work is not reliable.

If you don’t know for sure if your own work fixed a but or not, who could? This typically mean that you implement something but did not tested it, or did not tested extensively.

Confidence is essential in your relationships with other members of the project team. They rely on you, and so must be able to provide them affirmations — even negative ones — not wild guesses.

That’s it what what I can recall of. Feel free to suggest more, and I may add them to the list.

All of those bad habits share the same trait: laziness. Such a trait is often depicted as a virtue in software development, as it drives you to automate things (through tooling typically) and so improve quality (predicability, stability). Behind laziness is the will to simplify your work, and so your life.

Still, the dark side of laziness remains, to prevent you to do what you should, or to see what you should see.

--

--

Jérôme Beau

Software engineer for three decades, I would like to share my memory. https://javarome.com