How to Spot Primitive Obsession Before It Becomes A Headache

Primitive obsession is typically noticeable when it’s already a pain to deal with. Luckily, there are some things we can think about to spot it in time.

Giedrius Kristinaitis
Level Up Coding

--

Primitive value
Photo by Brett Jordan on Unsplash

Primitive obsession is a code smell where primitive types (like integers, floats, or strings) are being used too much, be it as return values, function parameters, class members, or anything else.

Problems Caused by Primitive Obsession

First, whenever the type that is represented by a primitive changes, a lot of places in code need to be changed as well, so it adds additional work to do.

Second, primitive types are not as expressive as structured types, it’s harder to understand what’s going on just by looking at a few primitives that don’t have a name attached to them as a group, and what’s worse, it’s harder to understand if they’re actually related or not.

Third, if primitive types are being used in the form of an array or any other dynamic type, there’s nothing that enforces the structure of said dynamic type, for example, if there’s a dictionary that contains a bunch of primitives, and they’re accessed by using a string key, then the compiler or any other code checking tool can’t point out any violations in the way that the primitives are accessed, for example, if a passed key doesn’t really exist in a dictionary. That means that potential problems can avoid our radars and cause problems in production.

Fourth, the knowledge about primitive types, and other values that come together with them gets ingrained into the codebase when it doesn’t need to be aware of that, thus breaking encapsulation.

How to Spot Primitive Obsession

Below are some guidelines that I use when I want to spot primitive obsession before it becomes a headache.

Primitives that break encapsulation

When a primitive breaks encapsulation it’s a good sign that it should be turned into an object.

For example, imagine a scenario where you have to create a shopping cart. Let’s say you decide to just return a URL that leads to the cart after you create it.

That’s something that should not be done, because the return value leaks information about the way that the created cart is consumed. Now any code that sits between the consumer of the shopping cart and the creator knows that a cart has a URL, and, for that matter, is nothing more than an URL. The internals of the shopping cart are not encapsulated.

A primitive that breaks encapsulation should be turned into an object.

Arrays are primitive

Passing primitive values around in the form of arrays or dictionaries is also a form of primitive obsession. However, that doesn’t mean that every array should contain only objects.

Arrays/dictionaries become primitive obsession when specific keys are used to access values. For example:

$data = [
'position' => 5,
'value' => 'something'
];

This is problematic, because the structure is not enforced in any way. A primitive value is used to access another primitive value, even though it’s passed around as a single type, it’s still fragile, so an object would make more sense.

On the other hand, if there’s an array that doesn’t have a structure and is just an iterable collection of values that have the same type then it’s ok, after all, that’s what arrays exist for.

Parameter lists

Parameter lists can also be a form or primitive obsession, especially when all the parameters that are passed around are used together to form something.

For example, let’s say that we’re working with a 3D rendering engine. Instead of passing 3D points as 3 x;y;z coordinates we should use an object.

Incomplete concepts

It’s important to understand what concept is represented by a primitive type. If primitive types are representing a part of something, then it’s not a good idea to use a primitive.

I think spotting parts of objects that primitives are trying to represent is the most difficult thing to do, but, really, we should try to think about the bigger picture, because if we focus on the primitive alone, then we’re likely to fall into the primitive obsession trap.

Whenever we decide to use a primitive value for something, we should think of all related things that come to mind, and based on that we should decide what should/might go together with the primitive in the form of an object.

For example, let’s say we have a function that returns some kind of statistic, like an average value of something. Returning just a plain number could cause problems later, because it doesn’t have any information attached to it, and it would be problematic when there’s a need to attach it.

A simple primitive tells nothing about the average, like the unit and the data set’s size. Depending on the context, an average can be more than just a simple number, therefore it’s an incomplete part of something.

If we decide to pass an average value around as a simple number, then other information related to it will be hardcoded into other parts of the code. While it may not be a problem initially, after some time it could add places you need to modify when something about the average changes, but it all depends on the situation.

Primitive values rarely travel alone, so it’s not going to be the end of the world if we turn them into an object.

Can the primitive do something?

Sometimes we end up having functions that do something to a primitive, for example, let’s say we have a word, which is represented by a string, and we have a function that reverses it. In this case, instead of having the word be a primitive, we could have it be an object with a reverse method.

Getting something is also a form of doing something. If we’re dealing with money, we also need to know the currency, so using primitives to pass around money is not a good idea.

When there’s a function that does a simple modification to a primitive or retrieves some information about it, then it’s a good idea to consider turning the primitive into an object.

How many places depend on the primitive?

If you still have trouble deciding whether to create an object around a primitive or not, I think the number of different places in code that depend on/use the primitive should be a tie-breaker.

A couple of places that depend on a primitive can still be managed with ease, however, when the number increases to more than 2, then it’s a good idea to turn a primitive into an object.

Turning a primitive into an object will protect a big chunk of the code from changes to the primitive.

Spotting primitive obsession in time could be difficult, and there’s no rule you can apply to accurately identify the code smell.

More often than not, it’s not all that harmful to use an object instead of a primitive value.

The most important thing is that whenever we spot a candidate, we should not turn a blind eye to it and refactor it at that moment.

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--

I’m a software engineer who loves to explore ways to make development more effective and enjoyable. Love to call out bad practices and misunderstandings.