My original programming style was what you would call procedural programming. Programs written in a procedural style typically consist of local data structures and module-level functions which operate on that data. This is how you would typically write programs if you weren’t familiar with object-oriented programming.
In my first job my manager once asked me to re-write an entire project into an object-oriented style because he felt my procedural style was less readable and less maintainable. It was confusing at first but he undeniably had a point — throughout my entire career the more object-oriented the software design was, the more readable and maintainable it was.
The primacy of object-oriented programming as a fundamental skill for writing clean, maintainable code is so widely accepted that there is often a special interview format to cover the ability to write code in an object-oriented style (e.g. “design blackjack”). The ability to write clean, maintainable code and proper class design are essentially mutually inclusive.
The importance of object-oriented design is not really that intuitive.
It always stuck me as odd that object-oriented design just so happened to be obviously superior to the procedural style. Object-oriented design is just one of several logically equivalent styles of writing code, the bits still come out the same in the end. But from my own experience it was as if there was some physical law of software that gave objected oriented design magical properties related to maintainability and readability. This is interesting because object-oriented design was never made for this purpose — it was just a way to bundle data and operations on that data in the same data structure.
I now think that object-oriented design actually does have a few, accidental properties which make it especially suitable for human-readable and human-maintainable code.
Object-oriented programming forces you to program to interfaces, not implementations.
In a procedural style, you write your functions according to the structure of the data you are processing. In an object-oriented style, you can only interact with the public interface of the class which often hides the implementation details of its underlying data. This means that as the private implementation of code changes within a class, downstream dependencies need not change.
Coding to interfaces also allows you to make objects swap-able as function arguments provided they are derived from the same base class. In software, this is called polymorphism. Polymorphism is a powerful tool that allows us to use generic programming within class hierarchies.
Object-oriented design maps well to human abstractions
Practically speaking, there is no inherent reason why people tend to prefer object-oriented design over a procedural style if they are logically equivalent. It just so happens that humans are better are recognizing/understanding abstractions and worse at raw information processing. Most people can only keep five pieces of information in their short-term memory but find object-recognition second-nature. Object-oriented design better takes advantage of the kinds of processing the human brain is good at.
Object-oriented design forces modular design
There is a concept called the single-responsibility principle which means the scope/responsibility/function of some module (package of code) should be as narrow as possible. Ultimately this means that any complex system will need to be made up of many independent, interacting modules. It turns out that it is far easier to make and maintain complex systems with simple, independent components than with a single, large, complex module.
The nature of objects naturally lends to modular design, where each object functions independently from the others and is shielded from their implementations as any interactions happen though their public interfaces.
Procedural programming offers none of the advantages of object-oriented programming
The reasons why procedural programming is considered less maintainable are similar to the reasons object oriented programming is considered maintainable.
First, procedural programming forces you to program to implementations, not interfaces. Since functions are operating on the raw data, if there is a change to the data, you must change every function which operates on that data. This could be many functions and these functions could be in many places.
Second, the human brain doesn’t think well in terms of manipulating raw data and keeping a complex workflow in memory. For a logically equivalent program, a procedural style is harder for a person to reason about.
Third, there is no enforcement of modular design in procedural programming. There is no concept of encapsulation. Often all data and functions exist within the same scope or namespace. There is no concept of logically grouping related functions or data because this has no bearing on how to process data. At best, sufficiently modular design in procedural programming will resemble classes but with weaker encapsulation.
As a guideline, use the object-oriented style as the default
Since employees are typically the largest expense in a software company, it’s good practice to optimize your code for human readability and maintenance. Time is money, and more time spent trying to understand code is a real cost.