When it comes to software development, the road is often paved with good intentions.
As software developers, we often move forward by trial and error, which is how we learn.
But after a while, we begin to learn our lesson the hard way. And one of the most important lessons we learn is that if we don’t take care, we could end up using a few of the most common C# anti-patterns.
You’ve probably heard the term “pattern” in reference to programming design, and “anti-pattern” is the opposite.
Patterns are ideas that solve common problems in a specific context. Anti-patterns are the patterns that developers should avoid using. Programmers often use anti-patterns because they are simple, quick, or even seem to work, but they often cause more problems than they solve.
Read on to know more about anti-patterns in C#, how to identify and avoid them!
1. God Class
The God class (or god object) is an anti-pattern that can be very dangerous since it makes code maintenance hard and risky.
This type of class might have many responsibilities, and the class itself usually serves as a direct parent for other, more specialized classes. As the name implies, when such classes exist in a system, they are considered gods because they often hold many responsibilities and powers they delegate to their children. While this approach might look useful, this can lead to an explosion of complex and difficult code.
Another way to recognize this class is that it’s not covered with unit testing. Usually, the class is tightly coupled with other classes or with many other dependencies. At best, you can test this class with integration tests.
To avoid God class pattern, follow these rules:
- The number of responsibilities of a class should be proportional to its size. If a particular class has more than one responsibility, break it into smaller classes when necessary.
- Please do not use the same name for the class you are creating and its children. They should have different names if their roles are clearly different.
- Always think about whether the conditions under which a particular class exists or its responsibilities are important to the overall system. Take care to ensure that these conditions and responsibilities do not become jammed in any way, so they can be easily processed when needed.
2. Spaghetti code
The term “spaghetti code” is used to describe a large tangled mess of code due to its complexity or lack of structure. It consists mainly of many classes and methods with many inter-dependences between them. This design smell can also happen if you don’t use proper design patterns to separate business logic from the UI code.
You can avoid this anti-pattern by identifying what each class should do. You can also avoid this problem by designing each class so that their methods are related to each other, while its functionality is easy to understand and maintain. The simplicity of code should be the main goal.
You can also avoid this anti pattern by using dependency injection. When using a DI container, classes can be injected with their dependencies. This makes the code more readable and maintainable.
3. Magic numbers and strings
When you use strings in your code, you need to define them as constants or variables throughout the different methods of your framework. This will help other programmers reading your code to understand its purpose, as well as other questions related to that particular section.
By using magic numbers, you’re making your codebase hard to understand and maintain. When any value is changed, you will have to go through all your codes and make the necessary changes.
For example, let say you are comparing a local variable with the number 5. If the condition is satisfied, then you execute a particular code inside the if block. The problem here is to understand what the number 5 means in this context. Sure, you will know what it means at the time of writing the code. But, if you come back to the code in few months, the chances are that you will forget what that value means. Then the only way to remember will be to spend some time debugging the code.
To avoid this bad practice, use symbolic constants for such magic numbers.
4. Yoda conditions
Yoda conditions occur when you reverse the usual order of parameters in the if condition. I have seen programmers who do this to make their code more concise.
if (5 == numberOfOrders) { //do something }
As the name implies, these “Yoda conditions” make your code look like it has been written by Yoda, the Jedi Master from Star Wars. It makes your code harder to read and understand. It will also confuse a developer who will come in the future to change that code.
There might be exceptions to this rule, but it is still advised that you avoid using Yoda conditions.
5. Interface bloat (fat interface)
Interface bloat is a design anti-pattern that leads to an interface that is unnecessarily cluttered, confusing, and hard to use.
The root cause behind interface bloat is a lack of prioritization. The designers behind the interface didn’t decide which features were important and which ones weren’t, so they added every feature and then some. When you combine lots of features with a lack of clarity, you end up with the opposite of an intuitive interface.
To avoid this pattern, prioritize features by analyzing the features that are most important to your interface. Then remove features that are less important or much less used. You can also use the interface segregation principle to simplify the code.
6. Overuse of the singleton pattern
Singletons are used when you want to ensure that a class has only one instance.
The singleton design pattern is “overused” when this concept is applied to all or most of the classes in an application. In this case, singletons can make it hard to get started with your code because you have just one instance, and so many things can go wrong.
A good example of this anti-pattern is Singleton Pattern for Dependency Injection Containers, aka ServiceLocators. If you use it throughout the code, this means you cannot inject test doubles in your unit tests.
If you ask an experienced developer, he might tell you that any code that has a singleton pattern in it is bad code. I can’t entirely agree. Singleton pattern has a bad reputation since it is not used most of the time properly. However, if used correctly and sparingly, it can improve your code.
7. Primitive Obsession
A primitive obsession anti-pattern occurs when developers create classes containing only primitive data types such as integers or strings. Instead of using primitive data types, developers should use object-oriented classes to group the primitive properties. This is because they provide a greater degree of flexibility.
If you notice that a group of primitive values is used repeatedly throughout your code, you can avoid this duplication using complex data types instead of primitive ones in your source code instead of overloading them with more primitive values. By having small classes representing data in your code, you can easily improve your software design with small wins such as this.
8. Poltergeist
The Poltergeist anti-pattern occurs you have a class in your application that delegates the whole work to other classes.
I occasionally see this type of class during a code review. It’s usually because the code has multiple layers. But in this case, one layer doesn’t do anything else than delegating work to another layer. This can quickly become a recurring problem, but it’s important to identify it as soon as possible.
For example, let’s take a look at the following code.
public class Chef
{
private Cook cook;
public Chef(Cook cook)
{
this.cook = cook;
}
public void CookSoup()
{
cook.cookSoup();
}
}
The “Chef” class delegates its work to another class, the “Cook” class, and so there is little point in having a separate “Chef” object.
To get rid of the Chef class in your code, you should use the Cook class in places where you use the Chef class. Once you have identified and change all those places, remove the poltergeist class.
9. Premature generalization
Premature generalization is an anti-pattern in software development where you create classes that are overly generic and too flexible. You make these classes generic to reduce code duplication, but this often makes your code hard to understand and maintain.
To avoid this, don’t make your classes generic too early. Make them generic only when you notice that the logic for manipulating types is repeating itself. This will make your code easier to maintain, understand and extend.
This anti-pattern is a type of code smell called premature optimization.
Premature optimization is the act of applying optimization techniques or tools in the development of code or other processes before there is a problem to solve. In other words, attempting to improve the performance of a program or some other action before it is known to be slow.
10. Copy and paste programming
Copy-paste programming is a technique used by lazy programmers who want to save time and effort on writing the same code multiple times.
This is usually done by taking previously written code and copying and pasting it into new locations of the program. After the new code has been pasted, there are only minor changes to the new code. The developer may only change the name of the variables and methods or do small changes to the logic.
Copy-paste programming can result in code that is difficult to maintain. If you find a bug in the copy/pasted code, you need to change that code. But also remember to change all other places where that code was pasted.
You will always remember to do that, right?
To avoid copy-paste bugs, create methods that accomplish a specific task and keep your methods small.
11. Shotgun Surgery
Shotgun surgery is a code smell in software engineering that means making changes in lots of places at once to implement a feature. The changes themselves might seem minor, but they must be applied in multiple places in the code.
It is called shotgun surgery because making so many changes at once is a lot like the quick-and-dirty approach to solving a medical problem with a shotgun, such as when a doctor does not have time to diagnose the issue fully and performs several diagnostic tests all at once.
The difference between using a shotgun to fix an injury and using a shotgun to implement a feature is that the first approach could kill you, and the second one could be bad for your code, but it might not.
When you perform this kind of surgery on your code, it can quickly become difficult to tell where the changes are located. So you need to remember all of them, which becomes increasingly difficult as a project gets larger and more complex.
The solution is to make each change in one place only. And you can do that by refactoring your code.
12. Golden Hammer
Golden hammer is a term used in object-oriented programming to describe a popular tool that is overused to the point of becoming ineffective. In short, a golden hammer is a tool that is used repeatedly for tasks for which it is not well suited.
For example, if you are a programmer and always try to use the same technology to address different problems, you may be using a golden hammer.
When a programmer uses this method, he sees the solution to a problem as already being known and tried.
The anti-pattern of the golden hammer is not limited to one technology.
To avoid this pattern, you should use the right tool for each job.
For example, when programming, you should not use Google Drive or Dropbox as your source control system.
Instead, for storing your source files, you should use GitHub or some other similar system.
13. Cargo Cult Programming
Cargo Cult Programming is an anti-pattern in which programmers use solutions they don’t really understand.
Programmers following this antipattern will often use a design pattern in places where that pattern shouldn’t be used. One example of this antipattern would be to use 2-3 design patterns to write a simple Hello World application.
Can this be done? Sure.
Should it be done? Absolutely not.
You often see Cargo Cult Programming when someone uses an external solution without understanding what that solution does exactly. Then he copies it and tries to apply it somewhere else but doesn’t know why and how this solution works.
To avoid this, you must understand why a solution works and what features it provides. Don’t just copy things without knowing the purpose.
14. Error Hiding
Error Hiding is a programming anti-pattern in which you don’t provide enough information to the client, so they know how to use your system successfully.
This happens when you catch an exception within the system, but you don’t provide enough information to the user. Catching an exception but not doing anything with it is an antipattern.
If the user tries to use a system and the system doesn’t respond, that’s not a good sign.
You should always provide explicit information about how to use the system because sometimes it can be tough for clients to understand the “right way” to use your products.
You should also always provide information about how a system can be misused.
15. Manual Memory Management
This anti-pattern occurs when you manually deallocate objects instead of allowing the garbage collector to do this job for you.
C# is a programming language that has many built-in features to remove objects from memory automatically:
- using keyword
- IDisposable interface
- Garbage collector
If you don’t use them, objects are not garbage collected, and so they stay in memory for too long.
Also, when you do manual memory management, you need to know all the variables that can be referenced by other objects and so if one of these isn’t garbage collected, then it still is being referenced somewhere, and it won’t be garbage collected.
To avoid this pattern, always use a garbage collector to help you with object deallocation.
Master design patterns, but also learn anti-patterns
Master design patterns, but also learn anti-patterns.
This is because understanding both of these concepts is essential for your software development career. They are not in any way opposites; they are different ways to achieve the same goal, so you need to know and understand them both in order to be the best developer possible.
This is one of the most important things to remember when learning about SOLID principles and design patterns: don’t get hung up on them as inspiration. These can be used as a guide if you’re stuck or struggling with a problem—but they may not apply perfectly to your situation or may not be necessary at all.