Software Development SOLID, LIFT, and FIRST principles
Tags: javascript, solid, software, programming
Published: 2023-05-30 15:40:18

---
title: Software Development SOLID, LIFT, and FIRST principles 
published: true
description: 
tags: #javascript #solid #software #programming
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ydg4ovumxssvts4pc6vu.jpg
---

## SOLID, LIFT, and FIRST principles 

They are all sets of best practices and design principles in the field of software development, intended to improve the maintainability, readability, and scalability of your code. They each serve a different purpose but complement each other, aiming to produce high-quality, manageable, and robust software.

### SOLID: 
This is an acronym for five design principles in object-oriented programming and design. These principles help developers create systems that are easy to maintain, understand, and expand over time.

### LIFT: 
This principle provides a set of guidelines that help in structuring and organising code, especially in large code bases. It helps developers to quickly locate and identify the code they're looking for.

### FIRST: 
This is primarily related to writing good tests, particularly unit tests, in software development. FIRST principles help in creating effective tests that run quickly, can operate independently, provide consistent results, validate themselves, and can be written in a timely manner.

While `SOLID`, `LIFT`, and `FIRST` might focus on different aspects of the software development process, they share the same goal: To improve code quality. Now, when used together, these principles can help create code that is clean, efficient, and maintainable. 

> The combination of good design (SOLID), clear organisation (LIFT), and effective testing (FIRST) can greatly improve the robustness and quality of a software project.

---

## S.O.L.I.D

SOLID Describes the five basic principles of object-oriented software design. It advocates a method of development that allows you to produce software that can easily be extended and is also easier to read. 

• S - (SRP) Single Responsibility Principle
`A Class should only do ONE job`, or "Responsibility"

```js
// Violates SRP
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  constructor(private http: HttpClient) { }

  getUserData(userId: string) {
    return this.http.get(`https://example.com/user/${userId}`);
  }

  saveUserDataInLocalStorage(userData) {
    localStorage.setItem('user', JSON.stringify(userData));
  }
}
```

```js
// Follows SRP
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class UserDataService {
  constructor(private http: HttpClient) { }

  getUserData(userId: string) {
    return this.http.get(`https://example.com/user/${userId}`);
  }
}

@Injectable({
  providedIn: 'root',
})
export class LocalStorageService {
  saveUserDataInLocalStorage(userData) {
    localStorage.setItem('user', JSON.stringify(userData));
  }
}
```

• O - (OCP) Open/Closed Principle
`Software entities (classes, modules, function, etc) should be:` `open for extension (inherit, extend, interfaces of, etc), but closed for modification (it works, do not touch.)`

In Angular (similar to ReactJS), one way to apply OCP is to use dependency injection (DI). Dependency injection allows us to provide different implementations of a service without changing the service's code.

In the `FallbackUserService`, we use the original `UserService` to fetch the user data. If it fails, we provide default user data. This way, UserService remains "closed" for modification, but it's "open" for extension by FallbackUserService.

```js
@Injectable({
  providedIn: 'root',
})
export class FallbackUserService {
  // UserService remains "closed" for changes but "open" for extension
  constructor(private userService: UserService) { }

  getUserData(userId: string) {
    return this.userService.getUserData(userId).pipe(
      catchError(() => of({ id: userId, name: 'Default User' }))
    );
  }
}
```

• L - `(LSP) Liskov Substitution Principle (LSP)`
`Objects of a "BASE CLASS" should be "REPLASABLE" (subtititable) with any of its "SUB-CLASSES" without breaking stuff`, so without altering the correctness of that program, because a "Sub-Class must behaves like its Parent". In brief, `its sub-class should be able to do what its Parent or superclass can do`, now, it's not necessarily true that a superclass should always be able to do what a subclass does.

Now, let suppose we have to support another kind of user data retrieval. 

Example: 
VIP users, who have their own API endpoint and might have more data fields.

Without LSP, we might be tempted to just add another method in UserDataService like getVIPUserData(). However, following LSP, we would instead create a new class extending UserDataService:

```js
@Injectable({
  providedIn: 'root',
})
export class UserDataService {
  constructor(protected http: HttpClient) { }

  getUserData(userId: string) {
    return this.http.get(`https://example.com/user/${userId}`);
  }
}

@Injectable({
  providedIn: 'root',
})
export class VIPUserDataService extends UserDataService {
  getUserData(userId: string) {
    return this.http.get(`https://example.com/vipuser/${userId}`);
  }
}
```

Now, VIPUserDataService is a subclass of UserDataService and they are interchangeable without affecting the functionality of the program. This ensures that any function using a UserDataService can also accept a VIPUserDataService.


• I - `Interface segregation principle (ISP)`
Clients (or in this case, components or other services) should not be forced to depend upon interfaces they do not use. Essentially, it is `better to have many small, "specific" interfaces than one large, general, "multi-purpose" interface`

```js
// Interface for reading user data
export interface UserDataReader {
  getUserData(userId: string): Observable<User>;
}

// Interface for managing user data
export interface UserDataManager {
  createUser(user: User): Observable<User>;
  updateUser(user: User): Observable<User>;
  deleteUser(userId: string): Observable<void>;
}
```

```js
@Injectable({
  providedIn: 'root',
})
export class UserDataService implements UserDataReader, UserDataManager {
  constructor(private http: HttpClient) { }

  getUserData(userId: string): Observable<User> {
    return this.http.get<User>(`https://example.com/user/${userId}`);
  }

  createUser(user: User): Observable<User> {
    return this.http.post<User>('https://example.com/user', user);
  }

  updateUser(user: User): Observable<User> {
    return this.http.put<User>(`https://example.com/user/${user.id}`, user);
  }

  deleteUser(userId: string): Observable<void> {
    return this.http.delete<void>(`https://example.com/user/${userId}`);
  }
}
```

Now, any component or service that only needs to read user data can depend on UserDataReader and won't be forced to also depend on the methods for creating, updating, and deleting user data (which are part of UserDataManager).

This adheres to the ISP, and it makes our code more flexible, easier to understand, and less likely to break from changes.

• D - `(DIP) Dependency Inversion Principle`
`High-level modules (which define the business logic) should not depend on low-level modules (that implement lower-level functionality), instead both should depend on abstractions or interfaces` rather than concretions (tangible implementations of an abstraction or interface). 
In brief, it promotes decoupling between components in a system by introducing an abstraction layer. Example: Dependency injection is one method following this principle.

To implement the Dependency Inversion Principle in Angular with our `UserDataService` example, we will first need to create an abstract service (an interface in TypeScript). This interface will be the contract that any 'user data service' will need to implement:

```js
export interface IUserDataService {
  getUserData(userId: string);
}
```

Then, we make our UserDataService implement this interface:

```js
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class UserDataService implements IUserDataService {
  constructor(private http: HttpClient) { }

  getUserData(userId: string) {
    return this.http.get(`https://example.com/user/${userId}`);
  }
}
```

Now, any component or service that uses UserDataService should depend on IUserDataService (the abstraction), not directly on UserDataService (the detail).

```js
import { Component, OnInit } from '@angular/core';
import { IUserDataService } from './i-user-data.service';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
  constructor(private userDataService: IUserDataService) { } // depends on the abstraction, not the detail

  ngOnInit() {
    this.userDataService.getUserData('123').subscribe(
      data => {...}
    );
  }
}
```

Now, this way, UserComponent depends on an abstraction (IUserDataService), not a detail (UserDataService). If we ever need to change how we fetch user data (for example, if we want to fetch data from local storage instead of an HTTP server), we can create a new service that implements IUserDataService, and UserComponent won't need to change at all. 

---

### `FIRST`

The FIRST principles are a great approach to building clean, maintainable code. It aligns with many of the concepts in SOLID and other best practices in software development. Let's break each principle down. Shall we?

> FIRST Keep components: Focused, Independent, Reusable, Small And Testable.

• `F - every object should have a single responsibility` (This principle aligns closely with the Single Responsibility Principle (SRP) from SOLID)

• `I - make it more `INDEPENDENT and testable` (The goal here is to reduce the dependencies of your components to a minimum)

• `R - Reusable code can save you time and make it portable` 

• `S - Smaller APIs are easier to learn and teach to others. This is generally helped if you do one thing and do it well.` (. This principle also aligns with SRP, since a component that does only one thing will tend to be small.)

• `T - Test your code` (If a component is focused, independent, reusable, and small, it will also easier to test the code as behave predictably, similar to LSP principle)

---

### `LIFT`:

• `L: Locating our code is easy`

• `I: Identify code at a glance`

• `F: Flat structure as long as we can`

• `T: Try to stay DRY`

---

### Final notes:

If you are into clean code practices, I bet you already read the: "Clean Code: A Handbook of Agile Software Craftsmanship"
by Robert C. Martin (2008) it is must read.

---
### :100: <i>Thanks!</i>
#### Now, don't be an stranger. Let's stay in touch!

<a href="https://github.com/leolanese" target="_blank" rel="noopener noreferrer">
  <img src="https://scastiel.dev/api/image/leolanese?dark&removeLink" alt="leolanese’s GitHub image" width="600" height="314" />
</a>

##### :radio_button: Linkedin: <a href="https://www.linkedin.com/in/leolanese/" target="_blank">LeoLanese</a>
##### :radio_button: Twitter: <a href="https://twitter.com/LeoLanese" target="_blank">@LeoLanese</a>
##### :radio_button: Portfolio: <a href="https://www.leolanese.com" target="_blank">www.leolanese.com</a>
##### :radio_button: DEV.to: <a href="https://www.dev.to/leolanese" target="_blank">dev.to/leolanese</a>
##### :radio_button: Blog: <a href="https://www.leolanese.com/blog" target="_blank">leolanese.com/blog</a>
##### :radio_button: Questions / Suggestion / Recommendation: developer@leolanese.com