The SOLID/STUPID principles
The SOLID principles are a set of five design guidelines that help developers create more maintainable, understandable, flexible, and testable software. Introduced by Robert C. Martin, also known as Uncle Bob, these principles have become a cornerstone of object-oriented programming and design.
SOLID is an acronym, with each letter standing for the first letter of a principle.
The SOLID principles
Single Responsibility
A class should have one, and only one, reason to change. So a class should have only one responsibility.
// ❌ This class does multiple responsibility (instance, save, and send email)class User { public User(String name, String email) { /* */ } public void saveUser() { /* save user to database */ } public void sendEmail() { /* */ }}
// ✅ 1 class = 1 responsibilityclass User { public User(String name, String email) { /* */ }}
class UserDatabaseManager { public void saveUser(User user) { /* */ }}
class EmailSender { public void sendEmail(User user, String message) { /* */ }}
Note: this definition may vary. Some developers prefer to define it as:
Separate code that supports different actors.
Code should have a reason to change based on the actors involved in the project. Therefore, you should organize the code according to the different actors.
Open/Closed
Objects or entities should be open for extension but closed for modification.
// ❌class SpeedCalculator { public BigDecimal calculateSpeed(Vehicle vehicle) { if (vehicle instanceof Car) { // calculate speed here… } else if (vehicle instanceof Airplane) { // calculate speed here… } else if (vehicle instanceof Bicycle) { // calculate speed here… }
// 👎 This code is bad because if we want to add a new vehicle, // we will have to modify this method }}
// ✅ This approach is extensible.// If we want to add a new vehicle, we only have to add one class and that's it !interface Vehicle { public BigDecimal calculateSpeed();}
class Car implements Vehicle { public BigDecimal calculateSpeed() { /* */ }}
class Airplane implements Vehicle { public BigDecimal calculateSpeed() { /* */ }}
Liskov Substitution
Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
In other words: Subclasses should be able to substitute their parent classes without affecting the behavior of the program.
// ❌abstract class Bird { abstract public void fly();}
class Pigeon extends Bird { @Override public void fly() { /* */ }}
// 👎 The principle is violated because Penguin does not support all the methods it inheritsclass Penguin extends Bird { @Override public void fly() { throw new FlyException("Penguins can not fly"); }}
// --------
// This code leads to a bug:class BirdActivityTracker { public void trackFlyingActivity(){ List<Bird> birds = new ArrayList<>(); birds.add(new Pigeon()); birds.add(new Penguin());
birds.forEach(bird -> { bird.fly(); // 👎 BUG ! }); }}
// ✅abstract class Bird {}
abstract class FlyingBird extends Bird { abstract public void fly();}
class Penguin extends Bird {}
class Pigeon extends FlyingBird { @Override public void fly() { /* */ }}
Interface Segregation
A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.
// ❌interface UserOperation { public User createUser(String name); public void deleteUser(int id); public User getUser(int id); public User updateUser(int id); public User[] getUsers();}
class Admin implements UserOperation { /* */ }
// 👎 This class Guest should not support the operations "create/delete/update"// We have to implements ALL these methods even we don't need themclass Guest implements UserOperation { /* */ }
// ✅ Let's split UserOperation in smaller piecesinterface UserCreation { public User createUser(String name);}
interface UserDeletion { public void deleteUser(int id);}
interface UserRetrieval { public User getUser(int id); public User[] getUsers();}
interface UserUpdate { public User updateUser(int id);}
// Here, Admin and Guest implement only the necessary methodsclass Admin implements UserCreation, UserDeletion, UserRetrieval, UserUpdate { /* */ }class Guest implements UserRetrieval { /* */ }
Dependency Inversion
Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
// ❌ The Authentication class is tightly coupled with the MySQLDatabase classclass Authentication { private MySQLDatabase database;
public Authentication(MySQLDatabase database) { this.database = database; }}
// ✅ It is preferable to depend on the abstraction onlyinterface Database { public void connect(); public void disconnect();}
class MySQLDatabase implements Database { /* */ }class PostgreSQLDatabase implements Database { /* */ }
// 👍 The Authentication class can now work with any database typeclass Authentication { private Database database;
public Authentication(Database database) { this.database = database; }}
The STUPID principles
The STUPID principles are a set of anti-patterns in software design that you should avoid.
Singleton
Ensures a class has only one instance, but overusing it can lead to global state and tight coupling, making testing and maintenance difficult.
Tight Coupling
When modules are highly dependent on each other, changes in one module require changes in another, reducing flexibility and reusability.
It refers to Dependency Inversion from SOLID principle.
Untestability
Code that is hard to test, often due to tight coupling or reliance on global state, making it difficult to write unit tests.
Premature Optimization
Optimizing code before it’s necessary, which can lead to complex and hard-to-maintain code without significant performance benefits.
Indescriptive Naming
Using unclear or ambiguous names for variables, methods, or classes, making the code hard to understand and maintain.
// ❌class User { private String n; private String emA; private String ph;}
// ✅class User { private String pseudo; private String emailAddress; private String phoneNumber;}
Duplication
Repeating code in multiple places instead of reusing it, leading to more maintenance work and potential inconsistencies.
Other notes
To be a proficient software developer, it’s essential to understand both the SOLID and STUPID principles.
You may encounter other principles such as KISS (Keep It Simple, Stupid) and DRY (Don’t Repeat Yourself), which stem from the SOLID principles.
Happy coding! 😊
Practice code with the "Quick Sort" algorithm
Enhance your coding skills by learning how the Quick Sort algorithm works!
Create a Docker Swarm playground
Let's create Docker Swarm playground on your local machine
Create an Ansible playground with Docker
Let's create an Ansible playground with Docker
Setup a Kubernetes cluster with K3S, Traefik, CertManager and Kubernetes Dashboard
Let's setup step by step our own K3S cluster !
HashiCorp Vault - Technological watch
Learn what is HashiCorp Vault in less than 5 minutes !
How to internationalize an AstroJS website while maintaining good SEO ?
We will see how to create an implementation of i18n with AstroJS
Database ACID/BASE - Understanding the CAP Theorem
Learn what is the CAP Theorem in less than 5 minutes !
LFTP - Deploy an application in command line
Here we will see how to automatically deploy an application with lftp in command line.