11 Bad Practices in Software Development (and Their Alternatives)

Software developers often have to contend with the consequences of bad practices (either their own, or those of other developers). How many of us have looked at code we wrote a few years ago and thought "What was I thinking?" Or, looked at code someone else wrote and thought the same thing?

Unless you are a perfect software developer who only works with his or her own code, chances are you've been bitten by some of the bad practices listed below...or have inflicted some of them on others (shame!!!!!)! Here's a list of 11 of my least-favorite software development bad practices.

1. Copy/Paste Coding

So you've got some complex code to implement, but it's already been done in your codebase, so rather than doing it all from scratch, you just copy an existing block of code and modify it to suit your needs. This saves time, right? Maybe in the short term, but it can lead to duplicate code (which we'll discuss shortly), unnecessary code (code that was copied but not needed -- this happens a lot with unit tests, and mocks which aren't needed), misnamed code (again, often in unit tests, when someone copies a test and leaves the old description/test name), and a lack of understanding of what the code actually does (i.e. "I just copied this from Joe's code."). In a worst-case scenario, it can lead to propagation of bad patterns and practices. What if the original developer didn't know what he/she was doing? In that case, you could end up with a lot of bad code, copied by developers who went looking for an example of how to do something, and found a bad one.

Alternatives: 
Copy and paste only structure, not code. When you need to reuse existing code, refactor the code in question into something reusable.

2. Duplicate Code

One of the acronyms which gets tossed around a lot in software development is DRY: "Don't repeat yourself." Copy/Paste Coding often leads to duplicate code: Developer A copies Developer B's code rather than following a better practice to produce reusable code. Inheritance, composition, and placing code in a reusable class such as a service are all more maintainable solutions than just copying code, pasting it into a new class, and renaming it. Duplicate code is more difficult to maintain: when you need to make a change, you need to make it in multiple places, instead of just one. On that same note, copying code which contains a bug will have introduced that bug in multiple locations.

Alternatives:
Refactor code into base classes, reusable components, services, or some other manner of code re-use.

3. Cutting Corners

Often in the interest of "just getting it done", developers cut corners. Examples of this include, but are not limited to: only coding for the "happy path", leaving out null checks, no unit tests, and no logging.

Alternatives:
Code for things not working as planned/expected/hoped.
Check for nulls.
Include logging.
Have unit tests to verify code works as expected and continues to do so.

4. Lack of Unit Tests

Developers fall into two camps: those who value the benefits of unit tests, and those who view them as a hassle. My favorite unit testing story is about the time at a previous job where I worked for a lead who told me that unit tests were a waste of time, then months later our company had overcharged subscribers, showing the correct price on the website, but charging them something different under the hood. If proper unit testing had been done, this mistake could have been caught. An additional irony was that, months later, this same lead decided to push for some integration tests which would perform screen scraping to verify values...which would not have caught the problem from months before which had (rightly) angered so many customers.

Unit tests give you the following benefits:
  • They help you ensure your code works as expected. There have been many times I've had simple code that I was sure would work, only to write a test and realize I'd missed something.
  • They help you ensure your code continues to work as expected. Sometimes code is added or changed which, unbeknownst to the developer, breaks existing functionality. If a unit test which used to pass now fails, it's a sign that something has changed. Sometimes it's just a matter of a testing needing an update, but it can also be a matter of a test being broken because now the code that it was testing is.
  • They help reveal design flaws. During the course of writing a unit test, you may realize that code could be simplified or refactored to be better.
Alternatives:
Write unit tests.

5. Non-Descriptive Naming

This can apply to classes, method, variables, you name it. The simplest example I could give would be the following line:

int x = 0;

What does x represent? Without context, there's no way to tell; it would require you to examine the context and figure it out. 

How about the following alternative?

int totalItemCount = 0;

Just a more descriptive name provides more information and makes code more readable and easier to maintain.

Alternatives:
Give your classes, methods, variables, etc, descriptive names which convey intent and/or purpose.

6. Inaccurate Naming

Even worse than non-descriptive naming is inaccurate naming: when something is given a name which is misleading. Being able to convey intent when writing code is important, as it allows the code to be easier to understand and maintain. But inaccurate names act as roadblocks which can slow down and impede a maintaining developer's progress.

Alternatives:
Give your classes, methods, variables, etc, names which accurately describe their purpose.

7. Googled Solutions without Understanding

Again, we've all been there: we need to do something but don't know how to do it, so we Google it. We find a solution online and implement it. But there's a difference between finding a solution and learning how to do something, and just copying a solution. Finding a solution but not taking the time to understand why it's a solution or how it works, puts you at a disadvantage -- for example, what if something goes wrong? If you don't know how or why something works in the first place, how will you be able to troubleshoot a problem when it stops working?

Alternatives:
Take the time to understand a solution once you find it.

8. Overcomplexity

Us developers tend to make things more complicated than they need to be sometimes. Often, there's a simpler way. More time is spent maintaining code than writing it, so the simpler it is to understand, the quicker it will be to make modifications or fixes. Added complexity without added benefit just ends up costing time.

Alternatives:
Remember the old saying: "Keep it simple, stupid!"

9. Retrieval Methods That Also Update Data

Command Query Responsibility Segregation (CQRS for short) has gotten a lot of traction over the past decade or so, but prior to that was the concept of Command-Query Separation. This is basically a pared down version of CQRS which states that methods should either perform an action or return data, but not both. A method which returns data should not update data, and a method which updates data should not return data. This simple concept makes code simpler. If you call a method named GetSomething(), you should be confident that it's not also updating something. Sadly, this is not always the case.

Alternatives:
Adhere to Command-Query Separation.

10. Propagation of Bad Practices

This one is rarely intentional, but is usually caused by habit ("We've always done it this way.") or by following someone else's bad example (Developer A didn't know how to do something, so made their best attempt -- which turned out to be flawed. Then, some time later, Developer B went looking for an example, found Developer A's code, and followed that example). Unfortunately, this can lead to bad practices propagating your codebase.

Alternatives:
When approaching a development task you’re not versed in, do some research.
Try to keep current with technologies and best practices.

11. Incorrect Accessibility of Methods and Variables (or Making Everything Public)

Proper accessibility of methods and variables is a fundamental. Not every variable or method should be public. Expose only what you need to. This is encapsulation. It's a basic principal of object-oriented programming. Incorrect accessibility shouldn't be on this list (in fact, this was originally going to be a "Top Ten" list). Yet, many developers will make everything public. Then there's TypeScript, which defaults to public (unlike most languages which default to private) if the accessibility is not explicitly declared. But not everything should be public.

Alternatives:
Carefully determine what the scope of your variables and methods should be, and only expose what you need to. Be mindful when it comes to proper scope.

In closing, there's a lot that can be done to make code maintainable. Yet, we as developers often feel the unconscious need to shoot ourselves and our fellow developers in the foot through bad practices. But, with a little effort and thoughtful consideration, our code can be readable, extensible, and easier to maintain.

Comments

Popular Posts

Resolving the "n timer(s) still in the queue" Error In Angular Unit Tests

How to Get Norton Security Suite Firewall to Allow Remote Desktop Connections in Windows

How to Determine if a Column Exists in a DataReader

Silent Renew and the "login_required" Error When Using oidc-client

Fixing the "Please add a @Pipe/@Directive/@Component annotation" Error In An Angular App After Upgrading to webpack 4