NDepend Blog

Improve your .NET code quality with NDepend

Code Smell – Primitive Obsession and Refactoring Recipes

December 1, 2020 5 minutes read

Primitives are the real building blocks of your class and its use is obviously inevitable. But the real problem starts when they are not used properly.

When you define Class, you define the placeholders for the data that we want to communicate using goodies of OOP (Object Oriented Programming). So, Class gets real behavior characteristics when defined with primitives like int or string or bool etc. with added methods and functions (using those primitives) per business logic defined and that’s where one gets real value out of Type/Class definition.

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 on using primitives for everything certainly not in a good way. Few examples of primitives are as below:

  • int
  • bool
  • short
  • double
  • char
  • float etc.
  • string…

However, every good pattern could become an anti-pattern if not used correctly. So, the use of primitives within the context of the class definition is very important.

Few notable issues that may cause code smell of type primitive obsession are as below,

  • Use of excessive Primitives for the given class definition and its scope
  • Use of Constants or String constants for field names
  • Use Numeric type code for conditional OR validation statements
  • Use of duplicates primitives

Let’s take an example and understand this. I have a simple class called “Account”. In this class few primitives defined as below.

Pretty simple and neat! Let’s assume I have a few more requirements to add to this class. Now for next future enhancement, this class grows as below,

Now with a habit of adding primitive for every other new need developers tends to assume that’s the right and easy way. But it’s very important to identify behavioral aspects of each new primitive that gets added.

Address details above are just a string?

In the above example, the primitive ‘Address’ is just a string? Let’s say you also need a placeholder for + Zip Code + State etc.

With above understanding developer might add additional few primitives,

Now class definition has increased a bit and it started adding code smell. Here smell is of type primitive obsession.

Primitives defined are more about addresses and locations.

SocialSecurityNumber is just an int type primitives?

SSN number here is tricky as it carries lot of behavioral characteristic.

Most of the time it’s only 4 last digits of SSN which gets used in the business requirements. So often you extract logic to get the last 4 digits from a given social security number. Let’s not ignore the encryption too. We often find encryption an important characteristic while dealing with SSN considering PII (Personally Identifiable Information) management. So, point here is SSN has got few behaviors characteristic due to which it can not be just a primitive. Its an early indication we can handle it differently which we shall see below.

So, you might add extraction logic as below,

So overall are we creating smelling code which will cause lot of issue in the long run,

Code-Smell-Primitive-Obsession-Wrong

Major Issues in the CheckingAccount class

  • Address related primitives are made part of CheckingAccount which violates the Single Responsibility principle (SRP).
  • If the Address details properties are needed in other parts of your application, then the code will be duplicated.
  • Extraction or formatting logic of SocialSecurityNumber owned by CheckingAccount class which differs from class behaviors. You will end up instantiating CheckingAccount class so that you can use GetLast4Digit() method which is absolutely unnecessary.
  • SocialSecurityNumber being part of PII needs to be secured or encrypted. CheckingAccount class will end up adding such logic for any future requirements.
  • If the Address and SocialSecurityNumber properties are needed in other parts of your application, then the code will be duplicated.

Refactoring Recipe for Primitive Obsession

To address the Primitive Obsession code smell you need to group related Data value within Object: validation or extraction logic will become part of ValueObject/Class/SubClass . This will avoid code duplication. As per the single responsibility principle Class should be designed to perform one responsibility at a time.

Let’s now replace SSN and Address details primitive with objects. We shall also move their methods/validation logic.

Simplified Class after applying primitive obsession

Code-Smell-Primitive-Obsession-Ok

Below is the simplified class diagram after addressing primitive obsession.

Code-Smell-Primitive-Obsession-Class-Diagram

Benefits of Primitive Obsession Refactoring

  • Primitive obsession Refactoring recipes lets you bind primitives in the form of Strong Types.
  • Primitive Obsession brings in the low-level domain properties and their validation logic together.
  • You tend to create a Class for each behavior separately helping you decouple code.
  • It helps build a proper domain model and objects become a logical container by packaging data and behavior mutually following the Single Responsibility principle (SRP).
  • Avoid code duplication helping in better maintenance of code following the Don’t Repeat Yourself (DRY) design principle.
  • A developer creates better code architecture by complementing many design and architecture patterns including DDD (Domain Driven Design) or non DDD being used.

Primitive Obsession in the real-world: Path deserves a strong typed model

A string is sometimes not a string. Model it accordingly.

I’ll end up with a very concrete example. The .NET base class library modelizes paths (like file and directories paths) with string and a bunch of helpers around. But actually a path is a set of complex and polymorphic object:

  • It can be absolute “C:\Dir”, relative “..\Dir” or contain some environment variable “%ENVVAR%\Dir”
  • It can be absolute on a drive letter “C:\Dir” or on an UNC server share “\\Server\Share”
  • A path can refer to a file or a directory
  • Many complex operations are surrounding paths: from relative to absolute (and vice-versa), resolve environment variable, get file name or directory name, normalization…

We are all used to System.IO.Path operating on strings but to me, this is a primitive obsession code smell: path deserves a strong typed model. This is why I created the library NDepend.Path that can be browsed and downloaded here. NDepend makes tons of operations on paths and using a strong typed library simplifies – a lot- paths operations. Below is the type diagram of the library. To properly modelize all path complexity, it is definitely not trivial:

NDepend-Path-Class-Diagram

Conclusion

Writing good code is not an afterthought process. It needs to be of good quality on a day when code was written. Today in this article we covered Code Smell aspects of “Primitive Obsession” and also discussed remediation and refactoring recipe to address these smells.