Back when I got into programming one of my biggest hurdles was learning about Object-Oriented Programming (OOP). In short, object-oriented programming is a computer programming model (or paradigm) that puts data at the core of your software. OOP has been the ruling paradigm in programming circles since the 1970s and the most popular programming languages of the last decades have been built from the ground up with object-oriented programming in mind.
And yet, as Jon Kalb said on his C++ 2019 lighting talk, it seems that for the last few years the cool kids have either barely spoken about OOP or are outright criticizing it in favor of functional programming and procedural programming. Does this mean that we are on the verge of a paradigmatic transformation? Will all OOP software stop working right this minute? Of course not.
What it means is that more and more people are starting to realize that there is more than one way to make software, and while that may mean little to the end-user (after all, who cares if code is declarative or imperative as long as a click on my screen gets me what I want?), it poses new challenges and opportunities for software developers.
What is a programming paradigm?
What if I were to ask you to write a set of instructions to get the number 10? Reaching the number 10 is a problem, and the set of instructions to reach that number is a solution.
Odds are that you could make up a dozen different ways to reach the same result, but most would think first of multiplying 2 and 5 or adding 5 twice. The fact that most of us will probably think about using the same procedures to approach this as a mathematical problem means that we share something in common: a paradigm.
In that sense, we can define paradigms as a shared set of beliefs, practices, and styles, though different areas use the term in slightly different ways. In programming, a paradigm is often used to describe a set of assumptions shared by a group of programming languages. As such we can say that, for example, Python, JavaScript, and C# are object-oriented while C is procedural and LISP is functional.
Don’t worry if you don’t understand what OOP and functional mean at this point (we’ll get to that). What’s important is that you understand that while two programming languages might differ in syntax and logic, they share some common assumptions about how the programmer is going to define and solve a problem.
Understanding the differences between OOP and functional programming
At the core of OOP lies the notion of an “object”, that is, an element that has certain characteristics (attributes) and things that it can do (methods). Methods can be used to change the attributes of an object, but objects are supposed to be independent, as in, no one outside the object should be able to transform it in ways it wasn’t intended.
For example, let’s say we have a cake object. Attributes are the inherent qualities of the cake (size, shape, taste, color) and methods are things you can do with the cake (eat it, give it away, put candles on it). There is a lot more nuisance involved like classes and inheritance, but for the sake of simplicity let’s stick to our cake example as is.
Another thing to keep in mind with OOP is that data is mutable. Each data in itself is an object, and it can change like our cake when we take a slice. So each cake in a bakery is its own object and when you sell a piece you run the slice function to change that specific cake, without changing any other cakes, or tables, or the coffee.
In contrast, in functional programming, we don’t focus on the data itself (which, in the end, is what OOP is all about), but we instead focus on the functions. In other words, we focus on the things we can do with the data.
So, it’s not really about the cake, it’s about the process by which we create a slice of a cake. It really doesn’t matter what kind of cake you pass by the function. As long as the data has certain properties, the function will produce the intended product.
In functional programming, data is immutable, as in it’s not intended to change. Just like in math, I would get the same result each time I pass a 2 by an equation. It really doesn’t matter if it’s two apples, or cakes, or cars. Also, the number 2 never changes, the equation creates something new, but the 2 is still there.
So, why is functional programming making a comeback?
Back in the early 60s, as computers improved, so did the complexity of the problems they could solve, but as complexity increased, so did the code needed. It was one thing to check a hundred lines of code for a bug and quite another having to comb millions of lines of code for a missing comma.
OOP offered a solution to this problem. If we compartmentalized our code by data types and the things we do to it, then we know that when our piece of the cake comes out wrong we have to check the part of our code that deals with cakes.
So at the time, OOP was a very elegant solution for a very real problem. But as time went on it became the standard for how we think about code and how we organize it. And as with any standard, when you start applying it to everything the seams burst.
On one hand, thinking in OOP terms requires abstraction. For example, if I want to add 2 and 2 together in an OOP way I would have to create a class called the number, of which the number 2 is an instance, and then we need a method to add it twice.
That’s the reason why authors like Ilya Suzdalnitski believe that OOP is counterintuitive to the way our brains work. As he puts so eloquently, we tend to think about the world in terms of things we do: if I’m hungry I eat something, if I want to be healthier I take a walk. So, in a way, we are more attuned to the way how functional programming represents problems.
Another point critics make is that OOP became the monster it tried to slain, as programs grew in complexity, code organization became more difficult, especially for long-term projects. As more and more classes get piled on, and as more changes are made to their methods down the pipeline, the odds of something breaking increase exponentially.
In contrast to the dynamical nature of OOP, functional programming aims for stability, if you need to do something you write a function to make it happen, that function stays there, unchanged, until you need it again. As long as you pass it the right kind of data it will turn out the same reliable results.
As many quickly point out, the functional programming paradigm is more restrictive, and that’s something we can all agree on, but sometimes a restriction is precisely what’s needed. As Suzdalnitsk succinctly puts it, a bad developer will always write bad software, but a restrictive framework will limit how much damage they can do.
In theory, by decoupling data from functions, we create a more predictable environment that is easier to manage in the long term. Let data be data and let functions be functions and let them interact when they have to interact.
More and more developers are finding it easier to think in functional terms rather than OOP terms, and some problems are easier to tackle depending on the paradigm you position yourself.
Is this a competition?
Of course not. Sans the obnoxious project manager who thinks that their paradigm is the end all be all of the world of coding, having more tools at your disposal is a great thing. I mean, most people I know use both OOP and functional programming in their code (a bad practice, but not an uncommon one).
As functional programming (and procedural too, for that matter) become more popular, software development companies have a new challenge on their hands, either finding people who fit their paradigm or having multi-paradigmatic teams who can adapt and build solutions based on the nature of the problem at hand.
Personally, I believe that revisiting these paradigms is necessary, and I do share some of the criticism of OOP even if I don’t fully agree with the doomsayers who prophesize its end. We, as professionals, have to get ready for a world where our standards and coding practices will be challenged, and for me, that’s a good thing.