5 Paradigms you should know as a Software developer

5 Paradigms you should know as a Software developer

If you are new to the software development industry or you are a self-taught developer, there are 5 paradigms (or topics) you should study and know about to up your game and get to the next level.

1. Object-oriented Programming/Design

OOP is a concept of programming that models code structure around data and objects. It encourages us to think of every piece of code as an object and treat it that way through classification. For instance, if you want to model an human being, they can have a name, age, sex, height, skin color, language, etc, these parameters are the attributes of the Human class. Then there are different functions (or actions, for the mortals) that humans can do, like talk, eat, move, etc, these functions are then defined as methods of our Human class. That way, we can ask anything that identifies as a member of the Human class for its attributes or ask them to act based on what they can do i.e the function/methods defined in them.

The object-oriented approach makes it easy for us to instruct the computer to solve problems in a way that we humans can understand because we are surrounded by objects. Most popular languages like Java, Python, and JavaScript (though JavaScript is not classified as an OOP language, it still has a way of using objects) support OOP.

If you don't know about OOP, you should learn it today.

2. Clean Architecture

What separates the novice from the pro is not only if they can solve a problem, it is also how they solve it. Clean Architecture is a paradigm of planning, separating, and structuring your software into understandable, modular, and reusable code units. This design thinking was started by Robert C. Martin, popularly known as Uncle Bob, and it aims to provide a cost-effective methodology that makes it easier to develop quality code that will perform better, is easier to change, and has fewer or no tightly-coupled dependencies. By separating your software into different layers, each with different responsibility, you can easily replace parts of your code without the worry of breaking something.

Chances are that you are already following this thinking without even knowing it because most software tools follow this approach by default. Say, for instance, you are building a web application that serves users random jokes, you can separate the application into a User interface (where they can send the request for a joke), the Application interface (where you handle their request, make a call to get the joke and return the response), the Data layer (where you access the joke from the database and model it appropriately) and lastly the database layer (which stores the data). This kind of separation allows for a loose coupling between all these layers, where they can only communicate through platform interfaces, and makes it easy to modify a layer without necessarily affecting the other layers.

Isn't that interesting?

There are rules and guidelines on how to follow Clean Architecture, and there is a book by the same name authored by the pioneer of clean architectural thinking himself, Uncle Bob.

3. SOLID Principles

SOLID is an acronym for Single-responsibility Principle, Open-closed Principle, Liskov Substitution Principle, Interface Segregation principle, and Dependency Inversion Principle, the 5 principles that guide writing maintainable and elegant object-oriented software. I cannot stress the importance of SOLID enough, but I can tell you that following these principles makes your code clean and easy to read. You can find a good number of resources on the SOLID principle on the internet, but you can start here.

4. Design Patterns

Chances are that you hear some of these paradigms together, in the same context, that's because they are closely related and they work together to make your code more maintainable, elegant, and satisfactory (or don't you feel so before you $git push origin update). Design patterns are simply commonly defined, tested, proven, and repeatable ways of approaching some specific software development problems. Some people have encountered some problems in the past and solved them in creative and ingenious ways, and their solutions became popular patterns when the community started adopting them. The most common design pattern is probably the Singleton design pattern, where you forbid the creation of multiple instances of a certain class and allows only one instance of that class to be available throughout the lifetime of your project. This solution lends itself to many use cases that it became very famous. The first set of popular design patterns was created and publicized by 4 engineers that are known in the software world now as The Gang of Four. Apart from the Singleton, there are other patterns like the factory, facade, observable, commander, memento, and adapter patterns.

5. Test-Driven Development

You have probably guessed what this last one would be, right? TDD. I know your code works today, but how do you know when and where something breaks tomorrow if you refactor or add new features? Will you run the app and click every button and watch every interaction to be sure it still works? When you write your to-do apps with few lines, files, and interactions, it is easy to click around your way to your problems. But I can guarantee you that this approach becomes more tedious, time-consuming, and costly as your project grows bigger. When you have thousands of lines of code, interactions, and dependencies, I bet you can spend the whole day trying to find where something is off. That's where testing comes in. Testing is like a virtual dog you train to bark whenever something is wrong anywhere in your code.

Test-Driven Development is a paradigm that encourages software engineers to define the way they write a logic by first defining the way they test the logic, after which they write the code to make the test for the logic pass. Confusing? Let me simplify it.

Let's say I want to write a function that takes a number and returns the square of that number. We will define the function but pend the implementation until we have written a test for the logic.


int squareOfANumber(int n) => throw UnimplementedError();

TDD demands that we think and define the test before even writing the code. So in the TDD world, I'll first of all think of how I will test the function. Let's come up with a few tests for it together.

  1. The function must be able to accept 3 and return 9. Right? Yes. So one test is to compare 9 (which we already know is 3 * 3) with the response from the function when we pass it a 3.

// our test can expect that squareOfANumber(3) should return 9.

const tNumber = 9;
final res = squareOfANumber(3);

// this should throw an error whenever the result is different from the expected
expect(tNumber, res);

Of course, this code will throw an unimplemented error, so we have to find a way to make the test pass, which is to return the multiple of the number with itself.


int squareOfANumber(int n) => n * n;

We can define another test that compares 25 with the response of the function when we pass it 5. Of course, both tests will only all pass when our function executes n * n.


// our test can expect that squareOfANumber(3) should return 9.

const tNumber1 = 9;
const tNumber2 = 25;

final res1 = squareOfANumber(3);
final res2 = squareOfANumber(5);

// this should throw an error whenever the result is different from the expected
expect(tNumber1, res1); 
expect(tNumber2, res2);

Suppose that someone goes into our code tomorrow and for some reason changed the code as below:


// from 
int squareOfANumber(int n) => n * n;

// to 
 int squareOfANumber(int n) => n * 3;

Our virtual dog should bark because this test will fail for squareOfANumber(5) (it will return 15 when we are expecting 25).

Now, we know that every time we run the test, we should know if the squareOfAnumber() function is still correct or not, without having to test the application. We don't need to look through individual codes to know that this function is no longer correct and will behave wrongly in production. Our trained dog will always bark! When you define good tests, you can always be confident that the code will work as long as all the tests are passing. That's the objective of TDD, letting how to test the logic guide the way we write the logic.

Thank you for reading! What do you think about these paradigms and which one do you use most in your projects?