Code Smell | Primitive Obsession
Tags: codequality, refactorit
Published: 2021-11-29 16:57:54

Hello, today I am writing again and this time I am going to introduce you to how we incur in a very common code smell called ``Primitive Obsession``, this code smell is given by the abusive use of primitive types when modeling our classes, was it not very clear? let's go with a reduced example:

```ts
class User {
  #locale: string;
  #age: number;
  #email: string;

  #SPANISH_LANGUAGE: string = "es";
  #UNDERAGE_UNTIL_AGE: number = 18;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  constructor(locale: string, age: number, email: string) {
    this.#locale = locale;
    this.#age = age;
    this.#email = email;

    if (!this.isValidEmail()) throw new Error("Invalid email format");
  };

  understandSpanish(): boolean {
    const language = this.#locale.substring(0, 2);

    return language === this.#SPANISH_LANGUAGE;
  };

  isOlderAge(): boolean {
    return this.#age >= this.#UNDERAGE_UNTIL_AGE;
  };

  isValidEmail(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };
}
```

```ts
const user = new User("es", 18, "test@email.com");

user.understandSpanish(); // true
user.isOlderAge(); // true
user.isValidEmail(); // true
```

You might be thinking, Well, it's not so bad, right?

This example, being small, can be a bit misleading, but as the code of our ``User`` class begins to grow, we will begin to see more clearly that there is still some logic that we have within the class that we could abstract so that our class looks much better

___

## Our best friends: Value Objects

A value object is simply a modeling of a primitive type, let's see an example:

```ts
class Email {
  #email: string;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  constructor(email: string) {
    this.#email = email;

    if (!this.isValid()) throw new Error("Invalid email format");
  };

  isValid(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };

  value(): string {
    return this.#email;
  };
}

new Email("suso@gmail.com").value(); // "suso@gmail.com"
new Email("susogmail.com").value(); // Error: Invalid email format
```

As we can see, we are simply creating an abstraction of a primitive ``string`` type, within which we are adding logic to validate that the email we receive is valid, in this way we can reuse this ``VO`` in different parts of our application


### What advantages does a VO offer me?

- Immutability

- Greater robustness in validations

- Greater semantics, better readability in the class signature

- Logic magnet

- Helps IDE/editor autocomplete

- They simplify the API

- They can be reused in various parts of our application as they are not coupled to any class

___

## Refactoring time

Initial state:

```ts
class User {
  #locale: string;
  #age: number;
  #email: string;

  #SPANISH_LANGUAGE: string = "es";
  #UNDERAGE_UNTIL_AGE: number = 18;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  constructor(locale: string, age: number, email: string) {
    this.#locale = locale;
    this.#age = age;
    this.#email = email;

    if (!this.isValidEmail()) throw new Error("Invalid email format");
  };

  understandSpanish(): boolean {
    const language = this.#locale.substring(0, 2);

    return language === this.#SPANISH_LANGUAGE;
  };

  isOlderAge(): boolean {
    return this.#age >= this.#UNDERAGE_UNTIL_AGE;
  };

  isValidEmail(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };
}
```

___

Split ``User`` class code into three value objects: ``Locale``, ``Age`` and ``Email``

``Locale``

```ts
class Locale {
  #locale: string;
  #SPANISH_LANGUAGE: string = "es";
  
  constructor(locale: string) {
    this.#locale = locale;
  };
  
  understandSpanish(): boolean {
    const language = this.#locale.substring(0, 2);

    return language === this.#SPANISH_LANGUAGE;
  };
  
  value(): string {
    return this.#locale;
  };
}
```

``Age``

```ts
class Age {
  #age: number;
  #UNDERAGE_UNTIL_AGE: number = 18;
  
  constructor(age: number) {
    this.#age = age;
  };
  
  isOlderAge(): boolean {
    return this.#age >= this.#UNDERAGE_UNTIL_AGE;
  };
  
  value(): number {
    return this.#age;
  };
}
```

``Email``

```ts
class Email {
  #email: string;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  
  constructor(email: string) {
    this.#email = email;
    
    if (!this.isValid()) throw new Error("Invalid email format");
  };
  
  isValid(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };
  
  value(): string {
    return this.#email;
  };
}
```

Finally the ``User`` class, it would stay like this:

```ts
class User {
  #locale: Locale;
  #age: Age;
  #email: Email;
    
  constructor(locale: Locale, age: Age, email: Email) {
    this.#locale = locale;
    this.#age = age;
    this.#email = email;
  };
  
  understandSpanish(): boolean {
    return this.#locale.understandSpanish();
  };

  isOlderAge(): boolean {
    return this.#age.isOlderAge();
  };
}
```

As you can see in this way, we manage to encapsulate each functionality in its corresponding ``value object`` in such a way that the user class is not responsible for carrying out any validation, so that we will achieve a more readable and maintainable code over time

Using new refactored ``User`` class:

```ts
// with valid email
const user1 = new User(
  new Locale("es"), 
  new Age(18),
  new Email("suso@gmail.com")
);

user1.understandSpanish(); // true


// with invalid email
const user2 = new User(
  new Locale("es"), 
  new Age(18),
  new Email("susogmail.com")
);

user2.understandSpanish(); // Error: Invalid email format
```

___

### Warning!

Not all are advantages, below we will see some disadvantages that we must consider when applying these abstractions:

- In some cases we can incur in a premature optimization, especially in small projects that do not need much maintenance

- They can have many classes if the project has considerable dimensions

___

Thanks for reading me 😊

![thanks](https://media.giphy.com/media/BYoRqTmcgzHcL9TCy1/giphy.gif)