As some people might now I am a vocal OOP critic. I think it is fair to say that I am on a crusade, actually. :D

Oftentimes, my long online posts explaining what is wrong with OOP meet with a No true Scotsman argument. That I am somehow pointing out to flaws in caricature of an OOP, and the correct OOP is free from these issues. To prove to myself and other people that it is not the case, I decided to go through some classic OOP books, and criticize the OOP examples in them.

My first choice is the Clean Architecture by Robert C. Martin (aka Uncle Bob). I must admit Uncle Bob is not one of my favorite software engineering gurus. But he is a reputable and experience developer, and if he was to write a caricature of OOP, then who are the people who dare to say they do it right?

Turned out that the book might not have been the best choice, because only about one third of it is about OOP. If you have a better suggestion feel free to contact me through some social media channels. You can use https://dpc.pw to find me.

My copy was just recently delivered from Amazon, has a dark blue cover with some galaxy on it. ISBN-13: 978-0-13-44941-6. It is probably best if you had it with you, but I tried to put just enough quotes and context to make it not necessary.

Fell free to leave comments on hackmd.io page.

Foreword

From, page XV:

Building have an obvious physical structure, whether rooted in stone or concrete, whether arching high or sprawling wide, whether large or small, whether magnificent or mundane. Their structure have little choice but to respect the physics of gravity and their materials.

Funny this is mentioned, because after a lot of thinking, my main disagreement with OOP is the way it tries to ignore and abstract away the data model. I postulate that the architecture of a function, program and a larger system have to be a direct consequence of how the data to support computation requirements need to be organized.

To paraphrase: Their structure have little choice but to respect the data organization of their computational requirements.

Chapter 5: Object-Oriented Programming

There we go. After couple of chapters of history and fluff, we get to what I care about.

In section "Encapsulation?", pages 35-36 we are shown how in C a struct Point can be abstracted away:

point.h:

struct Point;
// ...
//...
struct Point {
  double x, y;
};
// ...

At that point I though: this is great - beautifully pointless. It might be just because it is not a well-thought example, but it perfectly illustrate how OOP tends to go over the board with abstracting the data away.

What is the point of hiding details of struct Point?

Are we trying to future proof it? Why? In case mathematicians decided that x and y are not cool names anymore?

To prevent ... user accidentally ... reading x and y directly? Setting it?

The whole thing will make the code harder to write and read, prevent lots of optimizations and so on.

There is a cost associated with every abstraction, and because of that they should be avoided unless they are beneficial. In this case there is little to zero benefit.

I do not want to spend too much time on this particular example, but my point (sic) is: There is usually no reason to "encapsulate" and hide the details of a PoD (plain data types). Or most other data types for that matter.

Much better example here would have been FILE* from stdio.h. There the encapsulation makes sense. Why? Because FILE is not a data-type. It is a logic-type. It hides and abstracts away a behavior and any data it uses internally are not useful to its users.

Later in that section, author shows how struct NamedPoint is built:

struct NamedPoint {
  double x,y;
  char* name;
}

and how that achieves and inheritance-like "trickery" by allowing casting struct NamedPoint* to struct Point*. My comment to that would be that this trickery is terrible, unmaintainable and in its triviality almost indistinguishable from composition:

struct NamedPoint {
  struct Point point;
  char* name;
}

which is simply better.

In section "Polymorphism?" we are shown struct File which is exactly what I have talked about. Here encapsulation and polymorphism make a lot of sense.

The charter ends with "Dependency Inversion" which I agree on. (as long as it is done for logic components and not for data).

Chapter 7: SRP: The Single Responsibility Principle

The "Symptom 1: Accidental Duplication" section, page 63 starts with an example of Employee class with three methods: calculatePay, reportHours, save.

That class is given as an example of a bad OOP, so let's wait for the improvements, but here are my immediate thoughts: Oh dear, what a typical OOP garbage. In OOP code there is this tendency to express any noun from the business language as a class which serves as both a data-type and a namespace for anything remotely related to that noun.

Indeed, author points out this problem and then gives an example that I think completely fails to actually explain the issue well, focusing only on the aspect of code re-use, which is not the root issue.

Let's look at the "solution".

Section "Solutions", page 66 first shows how the Employee should be split into EmployeeData PoD (which I strongly agree with!) ... and ... three pointless classes PayCalculator, HourReporer, EmployeeSaver.

The methods on these pointless classes would be better of being free functions or some static methods (in a OOP-ridden language like Java).

Author then writes:

The downside of this solution is that the developers now have three classes that they have to instantiate and track.

No duh! That is exactly why they should be free functions. Then he provides a "common solution to this dilemma": a Facade pattern.

The original problem was caused by the natural tendencies present in OOP code. And then the solution is a lot of additional boilerplate and confusion. All because of the OOP dogma. A developer with mind not ruined by OOP, using a sane programming language, would start and finish with PoD EmployeeData, three namespaces with free functions in each, all taking EmployeerData as an argument. They would never had the problem in the first place, and would not have to use this "solution".

Free functions (or static methods) operating on public PoD data-types are easy to re-organize, re-group, split, join, refactor, re-use etc. and should be the default for general purpose logic, unless they really fit well or need to become a part of a encapsulated class.

Chapter 8: OCP: The Open-Closed Principle

Finally, something more meaty. We are going to generate a report. On page 72, we get a perfect everything-fully-abstracted-away OOP design to accomplish it.

Do you notice how many interfaces are here? The exact details of what are the generic aspects of the report generation are missing, so it is hard to come up with a simpler counter-example.

What I notice is a typical to OOP code obsession over polimorphism and extensibility. Obviously there will be multiple types of reports that can be generated. So in OOP that has to be an abstract class / interface causing a constrain of having to implement everything through a rigid and hard to evolve formal interface, where often a simple switch or even better a match over a ReportType enumerator (in languages like Rust that support it) would do. I mean ... sometimes an interface like this is beneficial, but that would not be my default. I would convert switch to interface only if I really need it for some reason.

Abstracting away the report data source behind the interface is a good idea (so it can be injected, especially when testing), abstracting away the details of format generation... maybe? There has to be a way to test report generation without bothering with presentation, so having it injected as a class implementing FinancialReportPresenter make sense, but if everything was just a bunch of free functions one could test each step without any interfaces.

Generally, I am OK with that design, but the rationale given:

We want to protect the Controller from changes in the Presenter. We want to protect the Presenter from changes in the View. We want to protect the Interactor from changes in - well, anything.

is not something that resonates with me. Seems to me like a typical OOP-schizophrenia/paranoia, which I usually express with protect your left pocket with a lock pad so that your right hand can not steal from it phrase.

For me, the main reason to introduce interfaces is the same as in mechanical engineering - to have a standardized place to combine interchangeable elements.

BTW, on page 74:

Much of the complexity in that diagram was intended to make sure that the dependencies between the components pointed in the correct direction.

rubs me the wrong way. The goal of any complexity should not be satisfying some abstract rules. The goal is to be able to swap interchangeable components and so on. Nit, I guess. Moving on.

Chapter 9: LSP: The Liskov Substition Principle

Great start:

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

I dare you to come up with a more confusing way to phrase it.

I do understand the definition from the Wikipedia:

if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program

but the version from the book took me a longer moment to compile and understand.

Most of the chapter discusses how easy it is to get inheritance wrong (which is totally true).

Chapter 11: DIP: The Dependency Inversion Principle

I just wanted to say that I use DIP rigorously - but for logical components, and not for data-types.

Chapter 14: Component coupling

At this point the book is mostly about general software architecture, and not really all that OOP specific, so I am not going to be focusing on it that much.

In this chapter the author, among describing many sensible practices and ideas, seems to be overly focused on "separately releasable components". I might be confused, but I can not see why would anyone want to have separate releases for things like Presenters component in Figure 14.1, page 114.

Seems to me like it would have to be a huge system, with many services sharing a lot of common code, where having reusable binary component releases for things like that would pay for the additional work and complexity involved.

BTW. In section "The Stable Dependencies Principle", page 120, it seems to me that the author ignores the fact that some dependencies can be purely an implementation detail. A "stable" component, can use internally "volatile" component, but since it does not leak it through its own interface, it does not affect its own stability from the perspective of its own users.

Chapter 30: The Database Is a Detail

I fundamentally disagree with this statement, and it is the exact same disagreement that I have with OOP as a whole (as in opposition to data-oriented programming).

The chapter starts with:

From an architectural point of view, the database is a non-entity - it is a detail that does not rise to the level of an architectural element. Its relationship to the architecture of a software system is rather like relationship of a doorknob to the architecture of your home.

Let me start by saying that just like the author I always put an interface between business logic and any database querying details. However, just because something is decoupled and isolated through an interface it does not mean it is irrelevant.

The database specifics determine what kind of interface is suitable: what operations it can support and how the whole software using it has to be designed.

Except for quite common but trivial cases like relatively simple CRUD applications, the type of a database to be used in a project is vitally important from the perspective of its architecture. A relational database will be organized and thus operated completely differently from a non-SQL database, file-system, message queue or an in-memory key-value store.

There are many properties that need to be considered: atomicity, scalability, durability, performance, data normalization, supported operations.

A database is not like a doorknob on a front door of a house. It is more like an engine in a vehicle. Depending on the type of a vehicle, a different engine will be needed and a different architecture designed around it. One can not simply replace a jet engine with an engine taken out of an SUV. And it is not only a difference in performance. Everything around the engine would have to be changed too.

In the same way, the OOP code I keep seeing is super eager to abstract away the details of data organization and pretend like it is an irrelevant detail, and perfectly reusable business logic can be created that simply ignores the data model. Usually the most user-visible sign of this problem is the scandalously poor performance.

Chapter 31: The Web Is a Detail

This might be just a nit, but while the author argues that:

(...), the web didn't change anything at all. (...)

The upshot is simply this: The GUI is a detail. The web is a GUI. (...)

The web did change one important things: the scale. Ever heard of "the webscale"?

Before the web software systems had one or a handful of concurrent users. In a web-first world they usually have millions. So the web is not just a GUI. It is a new requirement, forcing everything to be built completely differently.

Though I agree with the author that the web interface is just another interface.

Summary

Well, I am a bit disappointed. The whole book did not have enough OOP examples, so I did not have enough to talk about. As the title suggests - it is mostly about software architecture, and a lot of that is not OOP-specific.

Again, feel free to suggest a better book.

If you would like to read more OOP-related critique and opinions, here are some links:

Ones I wrote myself:

  • https://dpc.pw/the-faster-you-unlearn-oop-the-better-for-you-and-your-software
  • https://www.reddit.com/r/rust/comments/ju5oqp/how_to_avoid_repetition_without_inheritance/gch4gbp/?context=10000

Ones I enjoy:

  • https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/
  • https://www.youtube.com/watch?v=QM1iUe6IofM
  • https://www.youtube.com/watch?v=V6VP-2aIcSc

#oop #software #book