How corporate object-orientism kills productivity
Object oriented programming can be great. It is how we think of the world, after all.
A vehicle? An object! What about a car? An actual type of that object! The great thing is that this kind of object-oriented thinking feels natural to us, because it is how the world turns. Even more abstract concepts, such as applications are frequently modeled as objects.
Object-oriented programming is not a problem. It can be very practical. The cult of corporate object-orientism is. It seems to be a virus that has infected many dev-teams all around the globe.
What is object-oriented programming?
OOP is a programming paradigm. The foundational idea is that everything can be an object (e. g. an application) with certain attributes (width, height) and procedures (start_application()) which act upon that object. A computer program then consists of many of those objects, which then in turn act upon each other. It can be quite an elegant concept if done right, actually.
What is object-orientism?
When I say object-orientism, I mean the institutional belief that absolutely everything has to be modeled as objects which interact with each other. It’s about using objects for everything, even when they are the hammer for a screw. It is typically enforced through strict guidelines, code reviews by seniors who have been brainwashed, “best” practices and even the programming language you are programming in.
This belief seems to be untouched by every argument against pure object-oriented programming. It has good effects on specific parts of modeling, but the effectiveness of a team suffers greatly from strictly adhering to OOP.
A paradigm becomes a doctrine.
OOP entered the corporate and academic world with strong promises, one greater than the other: modularity, encapsulation, reuse, and maintainability. They are not wrong, but not unconditionally true either.
Simula is widely considered to be the first OOP-language. (Un)suprisingly, it was developed at the Norwegian Computing Center by Kristen Nygaard and Johan Dahland, used first for simulating nuclear reactors and operations research. It was also later used in the Apollo missions. This unique ability to encapsulate data and procedures into objects (at this time called processes) made it well suited for real-world simulations.
The name class was chosen over type because Nygaard & Dahland wanted to emphasize the concept encapsulation. It was borrowed from biology. In later books about OOP, animals became the de-facto standard example for inheritance.
Smalltalk was the result of extensive research about language design and education by Alan Kay. This then-novel concept was later (1970s) named object-oriented programming, since it is oriented about objects in programming (duh). It was the first purely object-oriented programming language, where everything (except the program itself) was an object.
In the 80s, even more object-oriented programming languages arose from the mind of great programmers. Most notably:
- Objective-C in 1984 by Brad Cox, and
- C++ in 1985 by Bjarne Stroustrup.
These were the early (and most successful) attempts at capturing the fast and hardware-near nature of C while adding OOP to it.
The 90s were drawing nearer and with it came the widespread success of OOP. C++ was now widely used in systems programming and application/game development, in part due to the rise of GUIs, which almost necessitated object-orientation in order to think realistically of a GUI-application with its buttons, text boxes and sidebars.
Finally, in ‘95, our favourite programming language Java was created by James Gosling in Canada with the famous saying of write once, run anywhere (later mocked by programmers as write once, debug everywhere). It was the high-school crush of every corporation which wanted to modernise its infrastructure: No compilation for multiple platforms AND forced OOP? Sign me up!
With Java, the corporate world (and later the academic world) accepted OOP as the one true way of programming big applications and services. Companies started to mandate it in style guides and non-technical higher-ups wanted OOP because it meant they could suddenly understand what their team did without having to learn programming. The result: Teams now spent more time satisfying arbitrary architectural guidelines instead of solving problems. This is where the cult of object-orientism began to form its shape. Managers loved the idea of factories and generic objects which only encapsulated objects because it gave them a sense of understanding the codebase without needing to deal with any of that nerdy stuff.
This dogmatic insistence of modelling absolutely everything as an object leads to bloated and over-engineered (or rather under-engineered?) code bases. Simple functions get buried in dozens of directories and files, just to adhere to some design pattern mandating that everything be an object.
Developers waste hours and hours on end debating about inheritance, the umpteenth interface or yet another wrapper class instead of just writing code.
The pure evil?
No, object-oriented programming is not evil. It can be a powerful tool for modeling complex real-world systems with state and inheritance or interactions between objects; GUIs, games, simulations: All those things can benefit greatly from OOP. The problem is not OOP, but instead it’s the blind insistence on using it for everything.
The structural cost of object-orientism.
Let’s fast forward to today, where this doctrine has infected a fictional but all too real company: Our employer Objectomania.
Objectomania has tasked us with conceptualising a small library for reading and writing CSV so that the other programmers can enjoy this sacred “reusability” and “maintainability” that OOP promises. So, let’s build a concept.
Reading and writing CSV.
As part of the one and only style guide at our company, we need to think purely in objects. Away with functions, we need some real business-programming:
- A
CsvReaderfor reading CSV-files, and - a
CsvReaderFactoryto correctly instantiate theCsvReader.
As writing is of course a whole new operation, we need more objects:
- A
CsvWriterto correctly write CSV-files, and - a
CsvWriterFactoryfor instantiating theCsvWriterobject in order to not expose the delicate internals of it.
The problem.
Right now, we need to create separate objects for reading and writing CSV-files. This does not seem practical for the end user. So let’s employ another design pattern: A CsvProcessor which does all the heavy lifting for us.
Now we don’t have to instantiate a CsvReader via a CsvReaderFactory anymore to read data. We don’t even have to think about which classes we need for CSV parsing, as the CsvProcessor will do everything for us in the CsvProcessor::Read() and CsvProcessor::Write() method!
Along comes Bob, a new intern at our company. He stares at our directory tree for a moment and utters three words:
Why so complicated?
As you are about to lecture him on the very important design patterns of object-orientism, you stop for a second and realise that you don’t need all of this.
The only thing you need is two functions: ReadCsv(String filename) and WriteCsv(String filename).
How to break free.
You don’t need to burn down your current codebase (Although we sometimes want to) to escape object-orientism. Start small and now.
Red flags
- Your
Validatorclass has one methodvalidate()and hardly any kind of state. - You spend more time debating inheritance and the object model than writing features.
- Your PRs are not being approved due to “wrong design patterns”.
- New hires need extensive training on the codebase and object model for a simple task.
The escape plan
There are many ways to break free of this cult, but in my humble opinion, the best way is to just
Question what you are doing.
- Do I really need this abstraction?
- Does this make the life of programmers easier or just more “correct” according to the style guide?
- Will I curse my past-self or thank him in 6 months?
Functions over classes.
You don’t need a
class Validator {
private String input;
public Validator(String initialInput) {
this.input = initialInput;
}
public boolean isValid() {
return this.input != null && !this.input.isEmpty();
}
public String getInput() {
return this.input;
}
public void setInput(String newInput) {
this.input = newInput;
}
}
when you can just have a
public boolean isValid(String input) {
return input != null && !input.isEmpty();
}
The result: Less objects, less files, no loss of functionality.
Strive for simplicity and ease of maintenance.
You don’t need all those fancy patterns to write robust, extensible and maintainable code. Prefer to write simple, clear functions instead of a fancy-named class. If you think in procedures instead of objects, some tasks become stupidly simple. As developers, our job is to do problem solving. Adhering to a single pattern is the enemy of problem solving, because we are depraving ourselves of flexibility in design.
So the next time that you feel like you’re writing code for the PR instead of for the problem, keep in mind:
Write code to solve a problem, not to impress an architecture astronaut.