Programming paradigms

Posted on 2021-12-05 byVlad Călin

Reading time of 1 minutes

As software developers, we all write code day in and day out. Import some libraries, instantiate some classes, write some functions, write some unit tests, etc. At the surface level, it all seems the same. All our code is composed of the same building blocks we all know and love: if, for, while, variables, functions, classes.

But at a higher level, we need to think to about the architecture or the flow of the code. Which component talks to which component? What is a component? Can I reuse it? Should I reuse it? How does this communication work? What if communication breaks down? How are errors handled?

A lot of questions that seem without answers without a concrete case, but these questions appear again and again in the software development world. A lot of developers face them, and answer them again and again with their code.

I want to talk a little about paradigms. It may seem some kind of esoteric term that people higher up in the foodchain seem to like to throw around to sound smarter. But they are not. As software developers, it is important to know how to program, aka to assemble simple instructions into more complex flows to fulfill the business requirements, but it is more important to know how to structure our programs at a higher level and how to reason about the code and the inevitable changes.

What is a paradigm?

Although there are many definitions for it, I have my own which is sufficient for me: a paradigm is a way to think about some program and its moving parts. It is worthwhile to think about how the data and instructions flow through code before we write a single line of code.

Next, I'm going to write a little about my favourite programming paradigms that I used in my day to day job and in my personal projects, that helped me reason more cleanly about the code I write and keep everything tidy and robust.

Procedural programming

In a procedural programming style, you write procedures. You structure your code in a sequence of instructions that execute in a fixed order to achieve the desired behavior. It is the kind of programming we all know and we write procedural code without even thinking about it.

Each block of code which reads like "do this, then do that, and then do that, if this, do that, and then to this other thing" is procedural code.

Some instructions call other procedures (functions, classes), which in their turn call other procedures as well. Our whole execution flow becomes a kind of a tree graph of procedure calls.

When a procedure is called by a callee (another procedure), it executes its code, and the end returns a value (or not), and the control is returned to the original callee.

It's nothing much more to say here, because it's pretty obvious what it is.

Functional programming

Functional programming is an interesting paradigm I became familiar with pretty recently. I wrote functional code without realizing it before, and because of that, I wasn't aware of the advantages it offers.

Long story short, functional programming means writing all your code as small pure functions that call each other.

In essence, it seems very similar to the procedural programming paradigm I talked before, and in fact they even are very similar.

The key aspect here is that we need to keep our functions/procedures pure.

A pure function is a function that for a specific set of inputs, produces the same output. In other words, they have no state, no side effects (eg. return different values based on some variable stored in Redis), and for the same inputs they always return the same thing (aka they are reproducible).

This kind of programming comes with some advantages that most beginner programmers disregard because it's easier and fancier to write classes and big complicated code. Those advantages are simplicity, more maintainable code, eventually you end up with a powerful library of small functions you can reuse at will, and better testability (having small pure functions that only have inputs/outputs, it is way easier to test the desired behavior with unit tests).

Object oriented programming

Object oriented programming is a way to structure your code using classes and objects. Related information are kept together and isolated, the state of an object is encapsulated inside certain instances.

An advantage is that it is widespread and known by a lot of developers. Familiarity is a powerful force in a team, because the terminology and vocabulary is already established.

Another advantage is the possibility to structure your code more elegantly, having more clearly defined components in classes, which are easier to be reused. The possibility to use specific design patterns to solve usual problems about how you structure, create or extend class hierarchies is another big plus (although it can be a big minus in the hands of inexperienced team members, or even experienced but overly zealous ones).

Declarative programming

Declarative programming is a special kind, which trades off control for simplicity.

In a declarative programming environment, you write what you want to be achieved. In other words, you declare the state of the system in a language that is expressive enough to allow you to implement the business logic you need.

This kind of programming is not actually programming, but I look at it more like being configuration. You configure a system to behave in a certain way, by declaring what you want to be achieved.

Some examples of these styles are YAML based configurations for all kinds of infrastructure as code tools, where you declare the state of the infrastructure you need. Afterwards, the tool will compute what changes need to be done to achieve that state starting from the current state.

Another example, which is closer to actual programming, is writing JSX code in a React project. You are declaring the state of the component hierarchy that you want to build, so that at runtime, the React engine will assemble that hierarchy using its React.createElement calls.

It's a powerful paradigm because it abstracts away the actual implementation from the result, so that the person in charge can focus only on the desired result.

Reactive programming

Reactive programming is a subset of the declarative programming paradigm as it is involves structuring the way components talk with each other using callbacks (or in newer code, coroutines, async, await).

What it says is basically to write your code so that everything that happens is in response to something else happening.

Usually, the kind of application that requires such a paradigm are the applications that involve a lot of user interaction (web interfaces, games, mobile applications) or distributed systems that talk through event buses.

By employing this paradigm, your code will read like "when this happens do this, when this other thing happens, do this other thing". It will become a chain of events that happen, handlers for those events and callbacks.

By writing the code this way, we can react to events as soon as they happen, without polling or having other mechanisms inplace.

A major drawback of this paradigm is that synchronization between components is very complex, and implementing such a thing will require a lot of custom code and "hacks".

Example of code that uses this paradigm extensively is a React project, where you bind certain events with some callbacks, such as onClick, onMouseEnter, onSubmit or even useEffect (binding to events in the lifecycle of the components rather than DOM events).

Conclusion

There are plenty of other programming paradigms, such as asynchronous programming (or concurrent programming), parallel programming (thrading, multiprocessing), logic programming (good ol' times in the faculty when this was a thing. In real life I have not met a case when this is needed), probabilistic programming (where you compute results based on certain chances and approximations rather than reproducible math), etc.

We all structure our code using one paradigm or another, most of us without thinking explicitly about what we are doing. When you are actively thinking about the pradigms you are using, you will have end up with a more organized program, which is also more maintainable and testable.

It's important to not mix the paradigms too much, because you lose the advantages one offers for no real gain. We all dislike messy code, and keeping your code in written in a certain style will help you keep it tidy and easier to reason about.