Most of the time, programmers are aware of common code imperfections and intuitively know how to deal with them. Long method, large class, a long list of parameters, duplicated code or code with a lot of comments are well-known problems that are easy to recognize. However, there are some smells that most developers can’t detect intuitively. So in this article, I’ll focus on the Primitive Obsession code smell. I’ll show you how to recognize it, explain why it’s important to fight it and describe ways to avoid or deal with it.
How can code smell bad?
Bad smells in code refer to code quality issues that may indicate deeper problems now or in the future. They’re a diagnostic tool used when you’re considering refactoring, or watching out for warning signs in your own code. Code smells come as list of problems that the code may be dealing with. Additionally, they come with a description of the symptoms, as well as methods and reasons to overcome them.
Primitive Obsession code smells
Before we can start, it’s important to describe what primitive fields are. Primitive data types are basic built-in building blocks of a language. They’re usually typed as int, string or constants. As creating such fields is much easier than making a whole new class, this leads to abuse. Therefore, this makes this smell one of the most common ones.
- using primitive data types to represent domain ideas. For example, using integer to represent an amount of money or string for phone number.
- using variables or constants for coding information. An often-encountered case is using constants for referring to users roles or credentials (like const USER_ADMIN = 1).
- using strings as field names in data arrays.
- Code becomes less flexible because of use of primitives instead of objects.
- Primitive data types are much harder to control. As a result, we may get variables that aren’t valid (supported by the type) or meaningful.
- Primitives are often related to dedicated business logic. Therefore, leaving this logic unseparated may violate Single Responsibility Principle and Open/closed principle.
- When data type logic is not separated in dedicated class, adding a new type or behaviour makes the basic class grow and get unwieldy.
- By using primitives, the developer loses the benefits that come with object-oriented design, like data typing by class name or type hinting.
Replace Data Value with Object
In most cases, a refactoring method called Replace Data Value with Object will cure the code. When the field has its own behavior, associated data or validation rules, creating class to represent it is the first thing to do. Place the old field and its behavior in the new class, then replace the old data value field that occurs in other parts of the code with object instance of new class.
Let’s say we have a User class that stores the person portfolio url.
You can code this as follows, using primitive data type :
Refactoring the above to use object as data type can result in the following code:
|👍 What did developer achieve by separating url logic to its own class? There’s a bit more code, but:
Dealing with type code
|💡 Type code occurs when a developer wants to set allowable values, but instead of creating a separated data type, he or she creates a bunch of numeric or string constants with the purpose to represent all possible values for his/her custom ‘type’.|
When dealing with fields known as type code, the developer needs to consider using one of 3 methods. These are Replace Type Code with Class, Replace Type Code with Subclasses or Replace Type Code with State/Strategy. Each method has its benefits and checks in different cases. Knowledge of the disadvantages and advantages of each solution will allow the developer to choose the best one to suits his/her needs.
Replace Type Code with Class
If the developer doesn’t use values of type code in operator conditions and doesn’t affect the behavior of the program, he or she can use Replace Type Code with Class to get rid of the smell. To do so, the programmer needs to create a new class and use its objects instead of the type code values.
An example may be the User class, which contains information about the status of relationship.
After full refactoring, the logic mentioned above can be coded as below:
|👍 What did the developer gain from refactoring?
You shouldn’t use this solution when values of a coded type aim to control the behavior of the program. If you want to determine application flow (conditions) with them, I recommend one of the following solutions.
Replace Type Code with Subclasses, State or Strategy
When dealing with type code that directly affects program behavior, creating a subclass for each value of the coded type or implementing a State or Strategy pattern may be the right solution. All of the mentioned methods of refactoring have a lot in common, but each of them has different advantages and disadvantages. The developer needs to decide which one will better suit his/her needs. The choice of solution mainly depends on how often class changes its type and whether subclassing is available (due to an already existing hierarchy).
Below, I will focus on showing how you can use the State pattern to remove the smell. You can find the implementation methods of other solutions and a detailed description of when to use them in Martin Fowler’s book about refactoring or on his blog.
When subclassing isn’t available and/or object changes its state (type) often, Replace Type Code with State method may be the best solution.
To show how you can use it I’ll assume that we have an Offer class with status field. Just like the one below:
At the end of refactoring, the code I’ve shown above can look like this:
|👍 What are the benefits from refactoring?
Replace Array with Object
When strings are used as field names (keys) in data arrays, it is highly possible that developer will have to switch to objects. So, let’s say that we have an array that represents a todo list like this:
After refactoring, the you can code the logic shown above like this:
|👍 What did developer gain from refactoring?
Endnotes – code smells and refactoring
The definitions and examples presented in this guide explain what Primitive Obsession is and what its consequences are. Knowing how to recognize a problem – we can avoid it. Creating separated class/classes requires a bit more effort at the beginning than when using primitives. However, in time, it will surely pay off. If a developer recognizes a problem in existing code, he or she can solve it with one of the suggested techniques. I hope my tips will improve your code quality!
Refactoring is, however, a process that entails following multiple steps to achieve the desired result. In Martin Fowler’s book ‘Refactoring Improving the Design of Existing Code’ you’ll find exact instructions for the transformations that you need to do in order to maintain compatibility with code that hasn’t been refactored yet.
You can see my gist files right here.