ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinOOP Is not Essential

Overload Journal #153 - October 2019 + Design of applications and programs   Author:
People tend to love or hate Object Oriented Programming. Lucian Teodorescu considers a recent OOP claim.

A recent article from Ilya Suzdalnitski [Suzdalnitski19] complained of the ‘disaster’ that OOP has become, compared to its promise. The article received quite a bit of attention, both on Medium and on Twitter. As the article was rather more ideologic than argumentative, the reactions ranged from very positive to very negative. One particular reaction that caught my attention was from Grady Booch [Booch19].

The tweet from Grady Booch.

Besides being from Grady Booch (a prominent figure in the OOP world), the thing that most caught my eye was the “multitudes of real world systems for which object-orientation was essential” statement. As yet, I have not seen any compelling examples making OOP necessary. Moreover, I believe that such examples do not exist, and the present article is an attempt to show why.

It is far from my intent to pick on some wording that Booch used (in some less-formal context). What I would like to argue against is the common belief that Object-Oriented Programming is the ‘true’ way of writing any software system.

But, by all means, if somebody has such a list of examples of real-world projects in which OOP was/is essential, please share it with me. And, for that matter, of any programming paradigm. Any argumentative explanation of the form ‘software X essentially needs programming paradigm Y’ would most likely advance our studies on software engineering.

The meaning of ‘essential’

In the context of the tweet, we can distinguish three possible meanings for the ‘essential’ word:

  • as in Brooks’ division between essential and accidental [Brooks95] – i.e., there are software systems in which their essential complexity somehow mandates OOP (see below)
  • with the meaning of ‘necessary’– i.e., the software system cannot be built without it
  • with the meaning of ‘it’s much easier with’ – i.e., building the software system is much easier with OOP; it can be built without OOP but with much higher costs

Let’s analyze how OOP can be (or not be) essential in a software system from all three perspectives.

Brooks’ essential

Brooks makes the following division:

[…] to see what rate of progress we can expect in software technology, let us examine its difficulties. Following Aristotle, I divide them into essence – the difficulties inherent in the nature of the software – and accidents – those difficulties that today attend its production but that are not inherent.

He then immediately goes to say:

The essence of a software entity is a construct of interlocking concepts: data sets, relationships among data items, algorithms, and invocations of functions. This essence is abstract, in that the conceptual construct is the same under many different representations. It is nonetheless highly precise and richly detailed.

And then he describes what he believes is the irreducible essence of modern software systems: complexity, conformity (to existing interfaces), changeability and invisibility.

Nothing in what Brooks calls essential is fundamentally attacked by OOP. Furthermore, Brooks has a small section on Object-oriented programming, in which he states that OOP attacks accidental difficulties:

Nevertheless, such advances can do no more than to remove all the accidental difficulties from the expression of the design. The complexity of the design itself is essential; and such attacks make no change whatever in that.

In the ‘“No Silver Bullet” Refired’ chapter [Brooks95], Brooks remarks that, after 9 years since the original claims, OOP has grown slower than people would believe.

Ok, so clearly OOP is not essential for any software system in the way Brooks describes ‘essential’.

‘Essential’ as ‘necessary’

Let us assume Booch intended to say “multitudes of real world systems for which object-orientation was necessary”, with the meaning that the software could not be technically written without OOP. Similar to saying that the complexity of a sorting algorithm is essentially O(nlogn) – that is, in the general case, the order of magnitude for the number of comparisons cannot be less than nlogn.

But that cannot be the case. Any software system that can be built using one programming language/paradigm can be built using another language or paradigm. After all, all the programming languages and, by extension, all programming paradigms are Turing-complete (any programming paradigm can be used to implement Turing-computable functions).

‘Essential’ as a form of simplicity

In the last meaning that we explore, we assume that Booch wanted to say “multitudes of real world systems for which object-orientation makes the problem much easier to solve”. This is starting to sound a bit more plausible.

A statement like “Project X can be solved by team Y with OOP simpler than it can be solved in any other programming paradigm” is a fair statement. I think most of the readers will agree with it.

But, we must argue that we cannot generalize it for all the teams. OOP is not necessarily the simplest way to write (reasonably complex) software. For example, consider people like Joe Armstrong (creator of Erlang, who sadly died this year) [Seibel09], [Armstrong], Linus Torvalds (who expresses so colorfully his dislike of C++/OOP) [Torvalds04,07]], Simon Peyton Jones (designer for Haskell) or Rob Pike (designer for Go). Would they consider that OOP is the easiest method to write software? Definitely not.

Different people and different teams will have different proficiency levels with different technologies/paradigms. Out of all the factors that affect the proficiency of an individual/team, probably education is the most important one. If the industry highly esteems OOP programmers, if most of the formal education encourages people to believe that OOP is the most important programming paradigm, and if most of the software literature teaches OOP, then, of course, people will start to be proficient in OOP, and become biased towards OOP. (It is hard to generalize, but my personal opinion is that OOP is still highly promoted, probably more than it merits.)

The fact that people are biased towards using OOP doesn’t make OOP essentially simpler than other programming paradigms. It’s probably just confirmation bias. People with hammers see nails all around, which strengthens their belief that the hammer is the best tool.

With all these said, we can conclude that even within this interpretation, OOP is not essential in building software systems.

OOP features

Let us now analyze the problem from a different perspective; let us try to answer the following question: Is there some OOP feature that is not present in other programming paradigms and that would help the programmers better tame the complexity?

We will analyze the major features of OOP to answer this question. When I say OOP, I’m mainly thinking of languages like C++, Java, and C#. I will often contrast them with non-OOP languages like C and with functional languages (Haskell, ML)

Objects and classes on top of imperative programming

OOP is an imperative paradigm. Nothing new here. It has classes and objects, but those aren’t necessarily something new.

Classes, in the absence of encapsulation, are just data structures. Similar to C structs; similar to product types in functional paradigms. Objects are instances of these classes – in other words, values. There is nothing new that OOP adds here to help in dealing with complexity.

Please note that OOP has a convention that classes, i.e., data structures, should correspond to things in the real world. I find this a bit disturbing, but that is not the issue here. There is nothing that prevents other paradigms from adopting similar conventions.

Things like class variables are just syntactic sugar. There is nothing here that essentially helps in fighting complexity.

Encapsulation

Encapsulation is the concept that binds together the data and the functions that manipulate that data. As opposed to traditional imperative programming, OOP puts functions inside classes, and calls them methods. But, a method is nothing more than a function that takes the object as a (hidden) parameter. Everything is syntactic sugar.

Nothing prevents a C programmer from placing all the functions that operate on the data near the struct definition. Ignoring the access rights of methods and attributes, this convention produces similar results. With any programming language that supports some sort of package constructs, one can easily emulate encapsulation. Signatures and structures in ML (functional language) [Harper00] behave very similar to encapsulation in OOP.

Again, nothing that cannot be done with simple conventions; at best, improvements that OOP adds here would fall into fighting accidental difficulties.

Now, let me be clear about one point. In general, encapsulation can be seen from two different perspectives:

  • a syntactic perspective, on how OOP languages recommend placing data and functions together
  • a modeling perspective, a way of thinking about programs, that tends to put data and the operations on the data together

The argument here was at the syntactic level. OOP languages add syntactic sugar to easily allow programmers to group data and functions.

The most important part of the encapsulation comes with modeling perspective. That is a form of decomposition that can actually help fight complexity (see below in the ‘What is truly essential?’ section). But again, nothing can prevent a C or an ML programmer from using this way of thinking about problems. So, even though this modeling technique is typically associated with OOP, other non-OOP languages can use it.

Information hiding

Preventing the programmer from accessing some variables/functions can hardly be called an essential improvement for software engineering. If one needs help in hiding that information, one can always rely on packaging systems, on conventions (like the leading underscore in Python) or even code documentation.

Polymorphism

People often claim that OOP is needed to have polymorphic behavior. Nothing can be more false than that.

First, let us acknowledge the existence of multiple types of polymorphism: subtype polymorphism (or inheritance based – the one advertised by OOP), ad hoc polymorphism (i.e., overloading) and parametric polymorphism (as used by functional programming languages, but also for implementing generics/templates in some highly acclaimed OOP languages). And, there is also duck-typing, a form of polymorphism without static types.

There are no technical reasons to believe that subtype polymorphism is superior to parametric polymorphism. On the contrary, I believe the opposite; but I’ll leave that discussion for another time.

Also, the reader should consider that basic polymorphism can be constructed in C with manual vtables. OOP languages just add syntactic sugar on top of this.

As polymorphism is not unique to OOP, we also conclude that, with respect to polymorphism, OOP cannot be essential in building software.

Inheritance

We have already discussed how subtype polymorphism present in OOP is not essential to building software. Without the polymorphism aspect, inheritance is drained out of substance. There are voices that claim that inheritance is abused, and there are a lot of cases in which it can be replaced by simple composition. For example, see item 34 (Prefer composition to inheritance) from C++ Coding Standards: 101 Rules, Guidelines, and Best Practices [Sutter04] and the Inheritance Is The Base Class of Evil presentation [Parent13].

Without polymorphism, inheritance is just syntactic sugar (one that can cause harm if abused).

Dynamic dispatch

Although object-oriented languages provide an easy method of implementing dynamic dispatch (e.g., virtual functions in C++), other languages have different strategies. Languages like C provide function pointers to handle this, while functional languages provide closures to implement dynamic behavior. Essentially, a closure or a function pointer is an interface with a single method, and any object-oriented interface can be decomposed into smaller, one-function interfaces.

Yes, interfaces with multiple (virtual) functions can be slightly more efficient in some contexts, but there is nothing game-changing in having multiple functions per interface. In the worst case, the user can group multiple one-function interfaces into one single data structure.

Again, OOP doesn’t provide a feature that is unique and cannot be matched with a bit more syntactic verbosity; and we already established that is an accidental difficulty, not an essential one.

In summary

There is no single OOP feature that would have significant importance in fighting essential complexity, or in making programmer’s life much more easier. They can be all summed up in the category of syntactic sugar.

Syntactical and modeling – an analogy

Most of the discussion about OOP features revolved around syntactical aspects of OOP. The reader should be guessing by now that, following Brooks, I have a strong position for dismissing syntactic features as solving accidental difficulties, and not being essential to the development process. Yes, it can make you write 10% faster code, but it is not essential.

There is a different story with the modeling perspective of OOP. We’ll tackle this in the next section. But before that, I want to draw an analogy.

Let’s compare OOP with the traditional motor car (with internal combustion). While there are a lot of cars out there, the cars are not essential to locomotion. We can travel by plane, we can travel by boat, we can travel by train, we can travel on a horse and even by foot.

Similar to the syntactical features of OOP languages, we can think of the shape of the car. It is true that the cart alone doesn’t make the car; it just adds marginal improvement to locomotion (faster speeds, better grip, etc.)

The modeling aspect of OOP is analogous to the internal combustion engine of the car. The engine is what makes the car a car. But what it is important here to notice is that there are alternatives to internal combustion engines. We have fully-electric engines, we have hybrid-engines, we have engines based on wind or even on solar power; and let’s not forget horses and locomotion by foot.

The main point is that neither the cart nor the internal combustion engine is essential for locomotion. And internal combustion engines, even though they are most commonly seen on cars, can be present on other locomotion machines.

In the previous sections we went over major OOP features. We concluded that most of them are syntactic features. The modeling aspects that are commonly found on OOP languages (encapsulation, polymorphism) can be present in non-OOP languages. But are these OOP modeling techniques essential?

What is truly essential?

Decomposition. The breaking down of a complex software system into multiple parts that are easier to understand, to reason about and to maintain. Only by decomposition can one hope to tame the complexity.

But beware, decomposition can fall into the same bias as discussed above. We can say that a certain decomposition would make the software system easier to understand for team X, but we cannot say that it will do for any team/individual.

In OOP, people usually follow the so-called object decomposition: we try to break down the system around ‘things’ (as opposed to operations or functions), which will become objects/classes. As these objects/classes will hold state, this type of decomposition typically is a decomposition of state: the state will be scattered (and shared) around all over the software. This is typically a bottom-up approach. See also Booch method [Booch94]

By contrast, functional decomposition as found in functional languages considers functions as basic building blocks. It is more focused on decomposing data flows. The state is typically immutable and isolated (i.e., the inputs of a function are always distinct from the outputs of the same function). The pipes and filters pattern typically employs a functional decomposition. This type of decomposition mostly resembles top-down decomposition.

But, just because these two dominate OOP and functional programming, it doesn’t mean that there aren’t other types of decomposition. Here are some decompositions that can make a lot of sense, but not get that much attention: decomposition based on security levels, based on the distance from the user (think of a web, layered architecture), based on the expertise of different teams/individuals (Conway’s law [Conway68]), etc.

In practice, in one software project, typically more than one decomposition appears. If one decomposition appeals to a group of people, it may not appeal another group of people, at least not at first sight. For example, I believe that a decomposition based on security levels is not something that most of the readers will think of first; on the other hand, I believe there will be other readers who apply it very frequently.

So, to come back to the previous analogy, there aren’t only internal combustion engines. Fully-electric engines are starting to show a lot of potential (can this be similar to functional programming?). Plus, there are hybrid-engines which don’t seem too bad (I find this analogous to C with encapsulation based on conventions). Let’s not forget about non conventional engines, like wind-powered ones (to be associated with less prominent programming paradigms).

Just like engines, the different types of decompositions have pros and cons. Like there is no ‘essential’ engine, there isn’t any programming paradigm that is essential to solving a software problem.

Conclusions

We argue here that OOP is not essential for software systems. It can be easier for certain teams/individuals, but we cannot generalize. The word ‘essential’ cannot be used in this context with the meaning that Brooks attributes to the word, and it cannot mean ‘necessary’. In limited contexts, it can mean simpler; but this simpler is directly dependent on the people for which it is simpler – there is no such thing as simpler for everyone.

To make sure we haven’t missed anything, we also looked at the problem from a different perspective: trying to see if there are some features of OOP that can promise simpler software. But almost all the important OOP features are merely syntactic sugar; all OOP programs can be translated into C with minimal effort.

The most important tool for solving software problems is decomposition. But this is not particularly tied to a programming paradigm. As an industry, we should probably be focusing more on different ways of decomposing a complex software system rather than trying to religiously apply one paradigm or another. There is nothing fundamental that would prevent a programmer from applying good decomposition principles in C as opposed to an OOP language.

But probably there are still readers that believe that C doesn’t allow high-level abstractions. I would urge those readers to carefully analyze the reasons for that belief. I am highly convinced that similar to the content exposed in this article, the main reasons are:

  • the language features that enable those high-level abstractions are just syntactic sugar – accidental difficulties
  • the reluctance of creating high-level abstractions in a language like C comes from internal biases

To overcome the biases I recommend the readers to get exposed to multiple decomposition methods and multiple programming paradigms. With that in mind, I would also recommend the readers to go over Ilya’s post [Suzdalnitski19]; it may be ideologic, it might not have all the proper arguments, but it offers a non-traditional perspective on software construction.

OOP may be helping a lot of people to write good software. But claiming essentialness of OOP is a bit too strong, in my opinion.

References

[Armstrong] Joe Armstrong, ‘Why OO sucks’,https://www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm

[Booch19] Grady Booch (2019), Twitter reply to Ilya, https://twitter.com/Grady_Booch/status/1153176945951068161?s=17

[Booch94] Grady Booch (1994), Object-oriented analysis and design with applications, Addison-Wesley Professional

[Brooks95] Frederick P. Brooks Jr (1995), The Mythical Man-Month: Essays on Software Engineering, Anniversary Edition (2nd Edition), Addison-Wesley Professional

[Conway68] Melvin Conway (1968), ‘How Do Committees Invent?’ http://www.melconway.com/Home/pdf/committees.pdf

[Harper00] Robert Harper (2000), ‘Signatures and Structures’ in Programming in Standard ML, https://www.cs.cmu.edu/~rwh/introsml/modules/sigstruct.htm

[Parent13] Sean Parent (2013), ‘Inheritance Is The Base Class of Evil’ at GoingNative 2013, available from: https://www.youtube.com/watch?v=bIhUE5uUFOA

[Seibel09] Peter Seibel (2009) ‘Joe Armstrong’ in Coders at Work: Reflections on the Craft of Programming, Apress

[Sutter04] Herb Sutter, Andrei Alexandrescu (2004), C++ Coding Standards: 101 Rules, Guidelines, and Best Practices, Addison-Wesley Professional

[Suzdalnitski19] Ilya Suzdalnitski (2019), ‘Object-Oriented Programming – The Trillion Dollar Disaster’, https://medium.com/better-programming/object-oriented-programming-the-trillion-dollar-disaster-92a4b666c7c7

[Torvalds04,07] Linus Torvalds (2004, 2007), Linus Torvalds on C++ (correspondence), http://harmful.cat-v.org/software/c++/linus

Lucian Radu Teodorescu has a PhD in programming languages and is a Software Architect at Garmin. As hobbies, he is working on his own programming language and he is improving his Chuck Norris debugging skills: staring at the code until all the bugs flee in horror.

Overload Journal #153 - October 2019 + Design of applications and programs