How Primitive Obsession can be an Anti-Pattern

This post was inspired by my own self reflection on code I have recently writtne. I should be doing University coursework, but whatever, there’s always time to write a programming blog. Especially if it’s the uni work that has inspired me to write this article.

What is primitive type obsession?

Primitive type obessesion according to Refactoring Guru “Like most other smells, primitive obsessions are born in moments of weakness. “Just a field for storing some data!” the programmer said. Creating a primitive field is so much easier than making a whole new class, right?”

It’s way easier to use your languages built in primitive types than to create one, but sometimes it’s best for your product, your code base and your team to just make your own custom types.

Consider this piece of Java code:

1
2
3
4
5
6
7
8
9
10
11
12
public class RentalOrder extends ObservableOrder {

private String orderReference;
private String dateStarted;
private String dateFinished;
private String orderStatus;

public RentalOrder() {
super();
this.orderReference = "OSG-" + UUID.randomUUID().toString().substring(0, 7);
}
}

pretty simple right? The whole idea of this is that we have a rental order (Doesn’t matter what for), but I want to target something that seems fairly innocent:

1
private String orderReference;

But what’s wrong with this? It seems perfectly fine, right? That’s because it is to a degree. We’ve told out RentalOrder class to have a value called orderReference and that is going to be a string, and in the constructor we tell it to create a new reference every time the class is constructed by prefixing ‘OSG’ with the first 8 characters of a UUID.

But what if we wanted to validate this order ref before saving it?

We could wrap the order ref inside some logic that would validate it before we save it, yeah that seems okay.

1
2
3
4
5
6
7
/** let's suppose this code sits inside a method called 'saveNewRentalOrder()'
* we can validate our order reference to make sure that it starts with 'OSG' and contains another 7 alpha-numerical characters
*/

if(order.getOrderReference().substring(0 ,3).equals("OSG-") && order.getOrderReference().substring(4, 11)...) { // further logic
this._db.write(order); // yes, I know, it's not this easy but let's assume it is.
}

This (if implemented properly) would make sure that an order reference looks something like this ‘OSG-1234567’, this works as well it does exactly what it’s supposed to,
we could even move the logic into a method called orderReferenceIsValid(String orderReference); and call it like so

1
2
3
if(orderReferenceIsValid(order.getOrderReference())) {
// ...
}

and that cleans the code up a little, it’s always a good idea to use the single responsibility principle where possible in app development.

BUT what if we wanted our order reference to validate itself? This is where things get interesting, what if orderReference wasn’t a string but its own type?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OrderRefernce {

private String _orderReference;

OrderReference(String orderReference) {
if(this._isValidOrderReference(orderReference)) {
this._orderReference = orderReference
} else {
throw new Error("order reference is not valid." +
" It must start with OSG- and have 7 following alpha-numeric characters e.g: OSG-1234567")
}
}

private boolean _isValidOrderReference(String orderRefernce) {
// validation logic
}

private String getOrderReference() {
return this._orderReference;
}
}

That’s much better, the OrderReference is now aware of whether it’s valid at time of construction and will force the code to throw an error. So now when we look back at our Rental Order class, we are now seeing the following code:

1
2
3
4
5
6
7
8
9
10
11
12
public class RentalOrder extends ObservableOrder {

private OrderReference orderReference;
private String dateStarted;
private String dateFinished;
private String orderStatus;

public RentalOrder() {
super();
this.orderReference = new OrderReference("OSG-" + UUID.randomUUID().toString().substring(0, 7));
}
}

Awesome, we now no longer need to validate the orderReference on save or any other action, because we can have confidence in the fact that each orderReference is self-validated.

Why tho?

Primitive types are great when they don’t require any custom logic as they all contain the methods required to most basic operations. Creating our own types ensures that all this logic is encapuslated inside it’s own class. We’ve also also made our lives easier because any problems with the orderReference will also be encapsulated so we know exactly where to go if we ever need to debug.

That said, use Primitive types wisely. Not everything needs to be encapsulated into a new class, developer discression is advised when creating these.