We recently discussed patterns in software design in a mentoring session. The idea of anti-patterns was brought up. However, we skipped addressing them at the time. What not to do is an excellent area to consider though so let’s take a look at a definition and then some typical software anti-patterns.
First, it is important to note that anti-patterns are not solely a software concern. There are famous anti-patterns that arose in non-technical situations. For example, there is the cart before the horse anti-pattern. Some other common ones are micromanagement, groupthink, and the peter principle. These may not have been called anti-patterns before software patterns were defined, but they pre-existed the term.
Common Software Anti-patterns
Let’s look closer at some anti-patterns that seem to pervade software. These include the following:
- The ninety-ninety rule
- Gold plating
- Race Hazards
- Circular dependency
- God Object
- Accidental Complexity
- Hard Code
- Magic Numbers
- Error Hiding
Some of these may already be familiar or will become so once we provide an example or two. However, all of these are relatively common and often treated as an acceptable approach. In some cases, people will argue for these anti-patterns. Nevertheless, let’s look at these standard strategies and how we can avoid them.
I often feel like I live in this anti-pattern. This is our apparent inability to estimate a task that is “nearly done.” We sometimes see this as moving the finish line or scope creep, but it is closer to a blind spot. I have found entire teams that get to a point where they feel a project is “almost done” even when there is not a well-defined set of requirements. This leads to more optimistic estimates than average and can turn a project into a seeming death march.
The way to minimize this anti-pattern is to do your best to have a complete and thorough definition of “done” for your project. Everyone needs to agree, and all the pieces need to be considered. For example, “done” usually includes shipped or installed on a user’s device. If the build and deploy process has not been considered or created then those finishing touch actions will push out the completion. This includes things like user documentation, online help, and other pieces of a “done” project that are often forgotten.
I recommend tracking all the issues and tasks that are performed from “90%” done to actual completion so you can at least learn from your mistakes. I have always been amazed at the results of this practice.
This anti-pattern is closely related to 90-90 in my mind. In this case, work is done on a project that is not needed. My parents would refer to it as “nit-picking.” It appears in projects as word-smithing error messages, tweaking report alignments, and even adding features. A good example of this is when the “nice-to-have” list of requirements is being delved into rather than pushing for completion. This again is avoided by ensuring that the requirements to complete the project are well-defined and agreed upon by all.
A race condition is a nasty anti-pattern in my experience. It occurs when there is an assumption that two things cannot happen at once, but they do, and they break the solution. A common example is when two users are allowed to change data at the same time. The results can be frustrating and even appear random. The solution can be a problem in itself though. These race conditions can be avoided by making sure actions are adequately wrapped or defined as transactions in a way that precludes more than one at a time. However, this can sometimes lead to deadlocks and poor performance when the operations are not adequately defined or are just too big.
I have not found a silver bullet for this anti-pattern. However, proper design and regular code reviews are a good way to highlight potential race hazards and fix them before they wreak havoc.
This common problem arises when two items refer to each other. It may be a convenient choice, but it can cause all sort of problems. It can also lead to chicken-and-egg problems when initializing or creating objects. A great example is a well-known database management layer that uses the underlying structure to build and populate items. When two tables refer to each other, the system can end up in an infinite loop of building object A then its B reference and then A again as a B reference.
There are documentation and general usage issues that can arise with a circular dependency so avoid it where possible. When objects need to refer to each other, there might be a better solution in a parent or container class.
This is a pattern that I have seen a lot but did not know the name until recently. A God object is one that contains all or nearly all of the application functionality. In a lesser fashion, it occurs when an object or area of the application contains a high concentration of the functionality. It often points to a haphazard approach to design or “duct-taping” functions on in a manner that was not properly considered. Luckily, this is easily avoided by spending some time considering where any new function should “live.” Although functional programming makes it easier to code this way than objects, I have seen the pattern occur in all sorts of environments. I may even be occasionally guilty of this myself, but it has never been proven.
This anti-pattern is becoming more common as the tools and libraries we have available grow at an increasing rate. One could also refer to this as re-inventing the wheel. It is where the implementation codes a solution that is easily solved by the tool or language. As anti-patterns go this is not too bad, but it is often avoided through code reviews. In these cases, the person that created the anti-pattern receives the gift of learning a faster and easier way to solve the problem.
The Hard Code anti-pattern is very common and often a form of technical debt. It is simply easier to hard code values early on in implementation, and we often lack the time or directive to clean them up. The good news is that we can avoid these through well-defined programming standards and code reviews. Hardcoded values can avoid detection all the way through a QA cycle in some cases so the sooner they are removed, the better.
Magic numbers are a sort of hard coding, but the differences make this both better and worse. A magic number is a value that is tightly defined but unexplained. For example, a flag value for an object may be set so it can be 1-4 and the code works based on those values. However, what do the values mean? If they are not defined somewhere easily spotted, then they are magic numbers. This also occurs at times when assumptions are made and value built into the system. Another great example is a phone number international prefix being hard-coded into a system that assumes all numbers will only be within a single country.
Once again the best way to avoid this is through code reviews and good programming standards.
I find this anti-pattern to be one that is often argued for. I have even done so on a few occasions. This is where errors or exceptions in the code are caught and then ignored. No message is passed up the chain, so the system essentially acts like nothing wrong occurred. The anti-pattern is often based on assumptions that raising an error is either “not that important” or always comes from a specific situation. A common example is around writing data to a disk. The assumption is that either the permissions are wrong or a file already exists, but what about when the disk is full or unavailable?
I agree that hiding errors from the user can be a good approach at times. However, we should at least log the errors in case we run into something we did not expect.
Just The Beginning
These are just a few anti-patterns out there. I recommend you spend a little time with these in mind or the next few weeks. Then, once you have cleaned up your applications of these anti-patterns, you can find more with a simple search and continue the process of avoiding common mistakes.