(Transfer)
The last few years I spent in the study and experiments with many programming languages. In particular, I began to use Scala as the main language, I try to use the functional style wherever possible. I was also very interested in Haskell (pure functional language) and Clojure (modern Lisp dialect).
Thus, I gradually abandon the object-oriented paradigm, despite the fact that I used mainly its last 17 years of my professional activity. I have a feeling that objects are what prevent us from writing lapidary, structured and reusable code.
')
When I think about this topic, I understand that in my thinking there was no sharp shift. The advantages of the objects slowly declined for me for a long time. The way I use objects now is significantly different from how I used them when I was young and naive. In this post I would like to reveal these changes in my understanding of the PLO.
PLO Promises
In the early nineties, the objects were new and intriguing. Adherents of this new paradigm promised the possibility of creating reusable classes and patterns, and this sounded tempting. The ability to combine these classes into reusable and customizable business components seemed the holy grail of the software industry. Developers of new languages, such as C ++ and later Java, supported the promotion of OOP as a way to make cool software.
Business components cannot be reused
It soon became clear that the ability to create reusable business components is a fallacy. Every business is different from all others, even in one industry. Each similar project works on too specific business logic.
The only way to make reusable business components at this level is to make them super-customizable by adding things like rule engines and embedded languages. I would not call such a model a component model. Rather, the model of the next instance of bloatware. The promise of reuse is gone, people either bought huge bloatware systems (losers), or developed their own special business objects in their own projects.
With patterns do not make structured software
The next thing we understand is that hopes for design patterns are not met. Patterns do not lead to good structuring. On the contrary, they lead to excessive complication of programs, difficulties in understanding the code and the high cost of maintenance. Some patterns have even become anti-patterns (for example, the singleton pattern makes it impossible to create unit testing).
Only recently, programmers have learned to use patterns meaningfully. It is much easier to write code the way you understand the model yourself. And do not try to abstract it to some generalized pattern from the book Alexandrescu.
Class and component frameworks are not very efficient to reuse.
Another promise of the paradigm of objects is developed frameworks of closely related classes used together with each other. The paradigm promised to significantly simplify the construction of applications from reusable components, hiding absolutely all the technical difficulties and subtleties of implementation. I mean things like EJB. However, experience has shown that these frameworks do not work. They are just too bulky and prevent people from doing what they want to do.
Such heavy frameworks quickly die and are replaced by more lightweight libraries and sets of lightweight libraries. It became clear that it is easier to build programs from sets of not so closely related classes that you can group together depending on your needs. You really don't need tight integration of different classes.
Inheritance leads to fragile software.
The ability to inherit the interface and implementation is one of the basic principles of OOP. You can select common code and behavior in the base class, while future abstractions can use this code and add logic based on it.
Unfortunately, this does not work. Each subclass is slightly different from its parents. Which leads to a lot of changes in the child class. Or to try to make the base class more general. In the end, we get a fragile code. In which a small change in the base class leads to the breakdown of most, if not all, generated classes.
Encapsulation violation
Another basic principle of OOP is the ability to encapsulate the state and provide behavioral methods for working with the state. Unfortunately, it turned out that in most cases we need the entire object data, and not the methods of working with them.
For example, it is ridiculous to ask an object to render itself to HTML. The knowledge of rendering HTML in this case is spread over the project and a small change in the rendering code entails a change in the set of classes. It would be more correct to transfer the object to a separate HTML rendering component. And this component will first of all extract all its interesting values from the object.
There are even antipatterns that allow you to create objects with an encapsulated state without behavioral methods (Java Beans, Data Transfer Objects, etc.). If we make such objects, why not just use structures instead of objects?
Changeable state - cause of problems
Another apparent advantage of encapsulation is the ability to change the state of an object without affecting the components that use this object. However, any developer of a large object-oriented project can tell stories about enumerating a huge amount of code in order to find a place in which the state of an object changes to an erroneous one, which leads to a program crash in a completely different place (usually at the moment when you output or save object status).
The more we show interest in the immutable state and stateless services, the more we are convinced that these problems are not there. Another advantage of the immutable state is the simplicity of creating parallel systems to increase the efficiency of using modern multi-core processors. This is much easier and safer than trying to work with threads, locks, and thread-safe data structures.
Life without objects
So, is it possible to discard your 17 years of experience and think about life without objects? I'm not sure yet that I have come to a complete enlightenment on this subject. But the use of the Scala multi-paradigm language allows me to bypass many of the limitations of OOP.
For example, the presence in Scala of such features as mixin traits allows you to completely abandon the inheritance of the implementation. The use of non-mutable values and collections makes the code easier to understand and debug.
Functions as generalized abstractions and type classes for extending behavior allow you to write well-structured and reusable code. The principle of single responsibilities in this case is performed perfectly.
In fact, I realized that I tried as much as possible to use simple objects in the form of case classes to represent data structures. There are few methods for such classes, they are intended only to facilitate the work with data. To group functions, I use mixin traits. The components I design simply assemble various related functions that convert data from one form to another.
Probably, I have gone much farther from a clean OOP than it seems to me. I can confidently say that now I am writing more compact, more understandable and better structured code than before.
(Translator's note: in this article I would like to focus on the attitude of an experienced OOP programmer to OOP. I am not familiar with the Scala language, and accordingly with the Scala-specific terms.
Increasingly, I see situations where experienced programmers talk about how they have become disillusioned with OOP.
About the possibilities of Scala. Apparently, they really cover the whole range of cases that have to be dealt with in the PLO. I would like to add that the possibilities of Erlang or Haskell make it just as good.
)