Today in this article, we will cover Primitive Obsession Code Smell Resolution with an example.
We will cover below aspects,
- What is Primitive Obsession?
- A few examples of C# primitives are as below,
- Getting started
What is Primitive Obsession?
Primitive Obsession is a code smell and type of anti-pattern where you are trying to use primitives for definable basic domain models. It’s an obsession of using excessive primitives and for making the code better this code smell requires remediation efforts.
“Code is read more often than it is written”
“Code is maintained more often than it is written”
A few examples of C# primitives are as below,
- int
- bool
- short
- double
- char
- float etc.
We understand that classes are just dumb templates until they are defined with proper behavior.
The behavior of a class is defined by its properties, fields, and functions.
What we need to understand here is when these primitives are less in number and less in behavioral characteristics, they are manageable.
But the real problem (which we call ‘Code Smell’) starts growing when these primitives grow in number along with their behavioral characteristics.
It gets worse when developers define the same primitives at different places with code duplication without realizing it and code smell gets spread everywhere.
So below are a few characteristics of Primitive obsession,
- Use of Primitives (excessive)
- Use of Constants or String constants for field names
- Use Numeric type code for conditional OR validation statements
Getting started
Let’s take an example and understand this,
public class Employee
{
public Employee(string firstName)
{
FirstName = firstName;
}
public string FirstName { get; set; }
}
As shown in the above example, Class Employee with a single property of type string is defined, very easy indeed ! and all is well.
Now for the next future enhancement, this class grows as below,
public class Employee
{
public Employee(string firstName, string lastName, int iD,string contactCellNo,string ssn)
{
FirstName = firstName;
LastName = lastName;
ID = iD;
PhoneNumber = contactCellNo;
SSN = ssn;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public string PhoneNumber { get; set; }
public string SSN{ get; set; }
}
Is SSN or Phone Number in the above example just a string?
Requirement 1 – The phone number here is currently defined as a string. Let’s say you would like to extract the area code (here ex. 123) then you might need to add extraction logic.
Requirement 2- Additionally, let’s say you have business requirements for extracting the last four digits of your SSN from a given social security number.
You’ll have to write additional logic to extract the last four digits of SSN. Now we will deal with 2 primitive as below,
Let’s see what it takes to extract the area code from a phone number.
var phone = PhoneNumber;
// Extract the area code
var areacode = "";
int index = phone.LastIndexOf("-", StringComparison.Ordinal);
if (index > 0 && index < phone.Length)
areacode = phone.Substring(0,index);
return areacode;
The above logic will be used and repeated at different places as and when there is a need for an area code.
Primitive Obsession Resolution(Partial):
So here developers might tend to add primitive specific validation logic to the Employee class (which is still code smell which we will see below example.. )
The class definition looks like as below after putting validation logic,
public class Employee
{
public Employee(string firstName, string lastName, int iD,string contactCellNo,string ssn)
{
FirstName = firstName;
LastName = lastName;
ID = iD;
SSN = ssn;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public string PhoneNumber { get; set; }
public string SSN { get; set; }
public string GetAreaCode()
{
var phone = PhoneNumber;
// Extract the area code
var areacode = "";
int index = phone.LastIndexOf("-", StringComparison.Ordinal);
if (index > 0 && index < phone.Length)
areacode = phone.Substring(0,index);
return areacode;
}
public string GetLast4Digit()
{
var index = SSN.LastIndexOf("-", StringComparison.Ordinal);
return index >0 && index < SSN.Length
? SSN.Substring(index + 1, SSN.Length - index + 1)
: SSN;
}
}
Issues in the above Employee class:
Here GetAreaCode and GetLast4Digit methods would produce the desired results but there are a few problems as listed below,
- Extraction or formatting logic of SSN and PhoneNumber is owned by the Employee class.
- If the logic is needed in other parts of your application, then the code will be duplicated. You will end up instantiating the Employee class so that you can use the GetArea() or GetLast4Digit () method which is unnecessary.
Primitive Obsession Resolution (Full):
Refactoring Recipes for Primitive Obsession
I took these recipes from Martin Flower’s book “Refactoring: Improving the Design of Existing Code“.
Primitive obsession for the above two issues can be resolved by the below-refactoring recipes.
- Replace Data value with Object( suitable for the above example)
- Replace Type Code with Classes or Subclasses
- Replace Type Code with State/Strategy
Both above techniques concentrate more on replacing primitive type with ValueObject/Class/SubClass.
All validation or extraction logic will become part of ValueObject/Class/SubClass.
This will avoid code duplication.
Let’s now replace SSN and PhoneNumber primitive with objects. We shall also move their methods/validation logic.
public class SocialSecurity
{
public SocialSecurity(string ssn)
{
SSN = ssn;
}
public string SSN { get; set; }
public string GetLast4Digit()
{
var index = SSN.LastIndexOf("-", StringComparison.Ordinal);
return index > 0 && index < SSN.Length
? SSN.Substring(index + 1, SSN.Length - index + 1)
: SSN;
}
}
public class Contact
{
public Contact(string number)
{
PhoneNumber = number;
}
public string PhoneNumber { get; set; }
public string GetAreaCode()
{
var index = PhoneNumber.IndexOf("-", StringComparison.Ordinal);
return index > 0 && index< PhoneNumber.Length
? PhoneNumber.Substring(0,index)
: PhoneNumber;
}
}
See class diagram below for high-level changes,
On the calling side, since behavior is now sticking to a Class of its own, the code will be simplified as below,
- The result of recipes used for Primitive obsession resembles the low-level version of DDD (Domain Driven Design) bringing your domain properties and their validation logic together so that the domain model that gets built over this can be used across your application supporting the DRY (Do Not Repeat) principle. These value objects, however, are not actual business objects or domain models but are simple logical models.
- These refactoring principles like Primitive Obsession or Inappropriate Intimacy are really good friends of any Design Pattern including Domain Driven Design. These principles are where you will start ultimately before you can shape up design or architecture. Code is always supreme of all.
- Objects become logical containers by packaging data with their behavior as new methods/functions.
- There are many built-in types already available that encapsulate primitives and their methods like Ex. class DateTime.
References:
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
Thank you very much Thomas for your encouragement!
Thanks. This was indeed very useful.