Primitive Obsession — code smell that hurt people the most

arpit jain
Jan 30 · 6 min read

Most of the time, developers are aware of common code imperfections and most of the time know how to deal with them like long method, large class, a long list of parameters, duplicated code or code with a lot of comments. They are well-known problems that are easy to recognize. However, there are some code smells (imperfections) that most developers can’t detect intuitively. So in this article, I’ll focus on the Primitive Obsession code smell and I’ll show you how to recognize it, explain why it’s important to handle it and describe ways to deal with it.

Before jumping into the primitive obsession directly, let me describe what primitive fields are. Primitive fields are basic built-in building blocks of a language. They’re usually typed as int, string or constants etc.

Primitive Obsession is when the code relies too much on primitives. What this means is that a primitive value controls the logic in a class and this primitive value is not type safe. So primitive obsession is when you have a bad practice of using primitive types to represent an object in a domain. An example of this is representing a website’s URL. You normally store it as a String. But unfortunately a URL has more information and specific properties compared to a String (e.g. the scheme, query parameters, protocol), and by storing it as a String you can no longer access these URL-specific items (the domain concepts) without additional code.

Let me try to give some examples so you can relate what I am trying to say here

Example of Primitive Obsession

I am pretty sure that you as a developer have worked a lot on authentication and registration module and typically it has few requirement like

  • User Signup, Authentication and Authorization API and so on…

As you can see, this is pretty common in most applications. Central entity in this application is User and we have a UserService to expose its API. Now we will have 2 main methods that I want to focus on.

The first one is:

public boolean authenticate(String userName, String password) {
...
}

Even before the authenticate() method gets called, it could have basic validations like

  • Non-empty password,
  • Length and characters combinations of the password
  • and so on …

Some of these checks you might put in a separate method on a PasswordUtil class and some you might in StringUtil class. Also, you will have a function in HashUtil class which will be responsible for generating a hash for given password so that we can compare the 2 hashes for verification and return the result.

The second method is:

public User create(final UserDTO userDTO) {
...
}

Before the create() method is called, we validate all the fields inside the UserDTO. During this validation, we do the exact same validations on password as we do before the authenticate method. If all the fields are valid, then inside the create method, we make sure no one else has the same user id. Then we take the raw text password and hash it so that we can store it in our DB.

Now you can easily see here what are the pain points. Few are below:

  • There are data duplication in multiple places
  • It is not easy to guess where I can find some logic (password logic seems to be sprayed all over the place)
  • How do you make sure that password value is not accidentally modified at a later time?
  • What if a new developer on the team doesn’t know about the appropriate method to use?

This is an example of Primitive Obsession. Here you can see we are representing our domain concept password with a primitive String which is causing pain for us. There are enough points to say that primitives are not good enough for modelling objects in your domain.

Now let’s come to the point how we will solve it. We can solve this problem by creating a Value Object. A Value Object represents a typed value in your domain. Here it is Password for example.

It has three fundamental characteristics: immutability, value equality and self-validation.

Immutability

A Value Object should be immutable. like below

final class Password {
private String value;
public Password(String value) {
this.value = value;
}
}

but why value object should be immutable. Let me try to make you understand by example.

var kg = new Weight(60);
Jim.Weight = kg;
Jack.Weight = kg;

So let’s say you have Weight as value object and you assigned 60 as its value. Now you have 2 person Jim and Jack. Initially they have equal weight. But after some time Jim gain some weight — 80

Jim.Weight.Value = 80;

But it doesn’t mean Jack has also gain weight. As you can see If I change the value for Jim it would change the weight of Jack too. Ideally it should not happen.

Also, you can share any Value Object by reference because, being immutable, it won’t be modified in another part of the code. This is very important especially when your code will run in a multi-threaded environment

Value Equality

Two Value Objects with the same internal values are considered equal. This means you can test for equality by checking if their internal values are all respectively equal.

Self Validation

A Value Object only accepts values which make sense in its context. This means you are not allowed to create a Value Object with invalid values.

final class Password {
private String value;
public Password(String value) {

if(value.length() < 6){
throw new InvalidLength(value);
}
this.value = value;
}
}

So If we can create a Password as a value object. Then

  • Password class constructor can do the validations for you. But keep in mind that the constructor should not do all validations, except some basic sanity checking shared by all context.
  • You can give it another password and ask if they match. This will hide all the hashing and rehashing logic from you
  • You can kill all those 3 Utils classes (PasswordUtil, StringUtil, HashUtil) & move the logic in the Password class where it belongs
  • When Relationship logic extends, it will be placed in one place that’s dedicated to it.

So once we are done, we have the following method signatures:

public User findUser(UserId id, Password password) {
...
}
public User create(final User newUser) {
...
}

Start thinking as Value Objects

In the beginning, it is hard to see Value Objects in your domain, but then there is an easy trick that you can follow.

  • You have special validation logic that has to be applied everywhere the type of information is used (e.g. phone numbers, email addresses).
  • You have special formatting logic that is used to render for humans to read (e.g. IP4 and IP6 addresses)
  • You have a set of functions that all relate to that object type (like start date and end date, first name and last name), and try to model them as Value Objects.
  • You have special rules for comparing similar value types

Above are not the basic criteria to select a property as a Value Object, that can vary application to application. But you will see most of the time it is applicable.

Once you start using more and more Value Objects you will naturally get good at it and be able to start to see more of it in your domain.

Conclusion

Something that I find interesting about primitive obsession is that it’s not obvious that primitive obsession is a bad thing. Depending on a primitive, having a method that depends on a string feels less heavy than having a method that depends on some kind of complex object that you’ve built yourself. But when you’re depending on a particular class, you may, in fact, be depending on a whole series of classes or tree of classes.

In my opinion, the problem here is that by coupling to a primitive rather than a complex object you haven’t actually eliminated dependency, you have hidden the dependency, you have made the dependency implicit rather than explicit. So if you think about a typed language, for example, you can’t ensure whether you’ve satisfied all the dependencies from a type perspective. Because as long as you pass it as a string or an int, for example, the compiler or the interpreter would say that it’s okay. But it could of be that your string or int in this particular scenario wasn’t actually a valid value and this is where the key lies. So again think about the domain of possible values or the range of values that are allowed for an object or for an instance of a type at any given time.

Basically, if you have a bunch of rules for how a value is to be treated that you want to be used consistently throughout your project, create a class. If you can live with simple values because it’s not really critical to functionality, use a primitive.

Hope you will like my article., If Yes, Please like and share it so it can reach to the larger audience.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade