7 anti-patterns you should avoid in your code

7 anti-patterns you should avoid in your code

·

11 min read

Introduction

Every software developer wants to build simple, efficient, and scalable software. This can only be achieved when best practices are applied during and after development. However, even the most skilled developers can fall into the trap of ignoring best practices, thereby introducing anti-patterns that will hinder their software's progress.

There is no denying that using design patterns in software development is considered one of the best practices to avoid anti-patterns.

Furthermore, identifying and preventing anti-patterns cannot be over-emphasized in software engineering because such knowledge will help software developers build scalable and maintainable software.

In this article, we will learn what design patterns are, the types of design patterns, and why you should use them. We also discuss 7 types of anti-patterns you should avoid, why we should avoid them, and how to avoid them.

Overview of software design pattern

Design patterns are solutions to common problems software developers encounter when building software. They are just like blueprints that can be customized to solve problems in different use cases.

Design patterns were introduced by a group of four C++ engineers popularly known as the Gang of Four (GoF). In the book Design Patterns: Elements of Reusable Object-Oriented Software, they introduced 23 types of design patterns which are grouped into three major categories: Creational, Structural, and Behavioral design patterns.

  1. Creational design pattern: These patterns concern how objects are created and how classes are instantiated to increase flexibility and reuse of existing code. Types of creational patterns are Prototype, Factory Method, Builder, Abstract Factory, and Singleton.

  2. Structural design Pattern: These patterns concern how objects and classes are assembled to form larger structures. They help keep design structures simple and efficient by creating relationships between objects and classes. Types of structural design patterns are Bridge, Adapter, Decorator, Composite, Flyweight, Facade, and Proxy.

  3. Behavioral design pattern: These patterns concern the assignment of responsibilities between objects. Types of behavioral design patterns are Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor.

The benefits of using design patterns

There are several benefits to using design patterns when building and maintaining software. Some of these benefits are:

  • You don’t have to reinvent the wheel when solving problems. All you need to do is to customize these patterns to solve your problem.

  • Applying design patterns reduces the time you spend during development.

  • Design patterns make your code readable, reusable, and maintainable.

  • Design patterns provide a common language for software developers to communicate.

What is anti-pattern?

Anti-patterns are bad programming practices that are ineffective and counterproductive in the long run. They occur when design patterns are neglected or misused when solving problems. Anti-patterns superficially look like the solution to a problem. However, in the long run, they lead to accumulated technical debts, which reduces the efficiency of your software.

Effects of anti-pattern

Some effects of anti-patterns are:

  • They reduce the performance of your software.

  • They make your software too hard to maintain due to accumulated technical debts.

  • They decrease the scalability of your software.

  • They make your code too hard to read, reuse, and maintain.

  • They reduce collaboration between software developers. For example, if the codebase is unstructured and complex, your team will find it difficult to collaborate.

Importance of Identifying anti-patterns

Identifying anti-patterns is very important in software engineering. Some of the importance of identifying anti-patterns are:

  • A good knowledge of anti-patterns will help you recognize and avoid potential problems that could affect the software's performance.

  • A good knowledge of anti-patterns will help you create effective optimization strategies, resulting in efficient software.

  • Understanding anti-patterns will help development teams make design choices with performance and good user experience in mind.

7 types of anti-patterns you should avoid

There are different types of anti-patterns. Below are 7 types of anti-patterns you should avoid in your code:

Spaghetti Code

The Spaghetti Code is the most common type of anti-pattern. They are codes with little or no structure. They are tightly coupled and rigid codes.

The Spaghetti Code anti-pattern also occurs when there is no proper separation of concerns. Files are created randomly and scattered everywhere in the codebase. They are messy and difficult to understand and maintain. When you change something in your code, other things will likely break.

The code below shows a vivid example of a spaghetti code:

// Spaghetti code
const processFoodItems = foodItems => {
for (var i = 0; i < foodItems.length; i++) {
  if (foodItems[i].type === 'fruit') {
    if (foodItems[i].name === 'orange') {
      foodItems[i].price = 1.3;
    } else if (foodItems[i].name === 'cherry') {
      foodItems[i].price = 2.0;
      if (foodItems[i].quantity > 10) {
        foodItems[i].price = 0.8;
      }
    } else {
      foodItems[i].price = 1.0;
    }
  } else if (foodItems[i].type === 'vegetable') {
    if (foodItems[i].name === 'spinach') {
      foodItems[i].price = 0.6;
    } else if (foodItems[i].name === 'cabbage') {
      foodItems[i].price = 0.4;
    } else {
      foodItems[i].price = 0.5;
    }
  } else {
    foodItems[i].price = 5.0;
    if (foodItems[i].name === 'beef' && foodItems[i].quantity > 5) {
      foodItems[i].price = 3.0;

      if (foodItems[i].quality === 'high') {
        foodItems[i].price = 2.5;
      }
    }
  }
}
};

const foodItems = [
{ name: 'orange', type: 'fruit', quantity: 5 },
{ name: 'cherry', type: 'fruit', quantity: 12 },
{ name: 'spinach', type: 'vegetable', quantity: 7 },
{ name: 'cabbage', type: 'vegetable', quantity: 3 },
{ name: 'beef', type: 'protein', quantity: 6, quality: 'high' },
];

processFoodItems(foodItems);
console.log(foodItems);

// [
//   { name: 'orange', type: 'fruit', quantity: 5, price: 1.3 },
//   { name: 'cherry', type: 'fruit', quantity: 12, price: 0.8 },
//   { name: 'spinach', type: 'vegetable', quantity: 7, price: 0.6 },
//   { name: 'cabbage', type: 'vegetable', quantity: 3, price: 0.4 },
//   { name: 'beef', type: 'protein', quantity: 6, quality: 'high', price: 2.5 }
// ]

The code above is a clear example of a spaghetti code. It shows a function processFoodItems that loops through an array of foodItems using the for loop. This function outputs the price of each item based on the name, quality, type, and quantity. Notice that the function does everything, the code is not modularized, and there are multiple levels of if statements and repetition of similar logic. Imagine what would happen if the codebase is filled with unstructured codes like the code above. These will make the codebase too difficult to maintain.

Golden Hammer

The Golden Hammer anti-pattern occurs when you over-depend on a particular tool to solve a problem.

What would happen if you tried to use a hammer to fix every problem in your house even in a situation where an Allen wrench would work better? You will end up not achieving the desired result. This example also applies when you try to use the same tool, concept, or technology to solve every problem, even when it is not the best choice.

For example, your development team is competent with their Golden Hammer, React.js. You have built so many fantastic projects with it and even developed a lot of reusable components. React.js is the leading hammer for your team. Anytime a new project comes, you insist on building it with React.js because you are more comfortable with it, even in projects where Angular or Vue.js would offer better solutions. This may result in the Golden Hammer anti-pattern.

Over-depending on a tool to solve problems stifles innovation and can make software developers too reluctant to find better ways of solving problems.

Boat Anchor

The Boat Anchor anti-pattern occurs when unused codes, third-party libraries, files, components, or features are left in the codebase. They reduce the system's performance because they are too heavy to carry just as a literal boat anchor is heavy to a boat.

For example, your team is working on a big project that initially started with a Material UI library. Over time, your team decided to switch to Chakra UI to meet the application's needs. After switching to Chakra UI, you left the Material UI files in the codebase. This may reduce the system performance because of the increase in the application page load.

Dead Code

A company employed you to manage existing software. While reading the codes, you noticed that a function processData is called in several places in the codebase. This function doesn’t look like it is doing anything in the system. You asked everyone in your team if they could explain what the function is doing but no one knows exactly what it does, and they are scared of deleting it because they fear that it might be useful in certain places even though it doesn't seem visible. This function has been there since the early days of the project and no one is quite sure of what it does. This scenario is an example of the Dead Code or Lava Flow anti-pattern.

The Dead Code anti-pattern occurs when a useless mass of codes exists in the codebase. These codes are not related to the system and no one can remember anything about it.

The Dead Code anti-pattern is difficult to understand and maintain as it is hard to know why the code is there and why it is needed.

Copy and Paste

The Copy and Paste anti-pattern occurs when similar codes are duplicated all over the codebase instead of being reused through abstraction.

For example, you want to use the MUI text field wrapper component in several places in your React project. Instead of creating a reusable component and calling it in places needed, you duplicated it all over your codebase. What will happen if you decide to change the width and height of the input field? You will need to adjust it in multiple places. As the system grows, it becomes difficult to maintain because of the duplicated codes scattered over your codebase.

The Copy and Paste anti-pattern reduces code readability, reusability, and maintainability. It also adds unnecessary lines of code to the codebase.

Magic Number

Magic numbers are numeric values found in the codebase that lack a clear explanation of their meaning. This anti-pattern occurs when numeric values are not given descriptive names.

The Magic number anti-pattern is difficult to debug because you will end up spending time, trying to understand exactly what a numeric value represents and what it does.

The code below shows a clear example of the Magic Number anti-pattern:

//  Magic Number anti-pattern
function calculateShippingCost(weight) {
 if (weight > 10) {
    return weight * 1.5; //1.5 is Magic Number
 } else {
    return weight * 2.0; //2.0 is Magic Number
 }
}
console.log(calculateShippingCost(5)); // Output: 10.0
console.log(calculateShippingCost(15)); // Output: 22.5

The code above shows a function called calculateShippingCost that contains magic numbers 1.5 and 2. You can see that 1.5 and 2 lack context or explanation. What do 1.5 and 2.0 stand for? It is not clearly explained. This becomes a problem for future maintainers, especially if these numbers are found in multiple places in the codebase.

Over Engineering

When you use unnecessary complex architecture, features, or codes to solve a problem, you introduce the Over Engineering anti-pattern.

Out of curiosity for new concepts and technologies, developers may implement features outside the business requirements even when a simpler approach is sufficient.

For example, you were hired to develop a todo app that creates, edits, and deletes a todo. Instead of focusing on the core functionality, you added a social networking feature for following friends and sharing tasks and machine learning for predicting tasks. This results in making the app unnecessarily complex.

The Over-Engineering anti-pattern increases the time spent on development leading to delayed releases. This is because you will spend time and mental capacity trying to understand concepts and technologies to be implemented in the software. They also introduce unnecessary complexities in the codebase.

The best way to prevent the over-engineering anti-pattern is using the KISS principle. KISS stands for "Keep it simple, stupid"

Factors that cause Anti-patterns

Numerous factors can cause anti-patterns. Here are the main causes of anti-patterns:

  • When you ignore or misuse design patterns, you unknowingly introduce poor designs that reduce the scalability and maintainability of the codebase.

  • Lack of a well-structured system or architecture can lead to a disorganized codebase which causes a maintenance nightmare.

  • Developers with little or no experience of design patterns can introduce poor designs and bad programming practices into the codebase.

  • When effective code reviews are not done regularly, inexperienced developers may introduce accumulated technical debts in the codebase.

  • Lack of testing to catch issues early enough for fixes may introduce anti-patterns in the codebase.

How to prevent Anti-Patterns

  • Using design patterns when solving problems is one crucial way of preventing anti-patterns.

  • For easy collaboration and maintenance, ensure that your codebase is properly structured.

  • Code reviews should be done regularly because they are great opportunities to find and fix anti-patterns.

  • Obsolete or unused codes should be deleted from the codebase.

  • Use the right programming language, framework, or library when solving problems, and be open to learning better ways of solving problems.

Conclusion

In this article, you have learned what anti-pattern is, the types of anti-patterns, and how they can significantly undermine the efficiency of your software.

You have also learned why applying best practices such as incorporating software design patterns, learning new technologies, and continuously researching better ways to solve problems is crucial to building functional and scalable systems.

Being mindful of anti-patterns and striving to avoid them results in building efficient, and user-friendly software.