Elegant Objects By Yegor Bugayenko
I'm not an Object Oriented programmer. But, I really wish that I were. I think there's something magical about being able to "think in objects" and to let objects to the heavy lifting for you. Which is why I really enjoy thought-provoking books like Elegant Objects by Yegor Bugayenko. Elegant Objects is an easy read that outlines 23 practical recommendations for object oriented programmers to help keep their code from becoming "pure evil" (as Yegor would put it).
|
|
|
||
|
|
|||
|
|
|
While a number of the concepts in the book did go straight over my head, the book, itself, is a really easy read. I got through it in about two days. At no point is the reading tedious; it's actually quite concise, engaging, and even entertaining at times. I'll likely read it again soon in an attempt an wrap my head around the parts that I missed on the first pass.
Each chapter is associated with a blog post on Yegor's personal website; which is really cool because you can go and pose questions directly to the author. For example, I asked Yegor to clarify something about his concept of Object Immutability and he actually got back to me within a few days. This is such a helpful feature - one that I'll take more advantage of on my next read of the book.
Yegor's take on Object Immutability was very interesting. But, it's definitely different from the concepts of immutability that seem to be sweeping the JavaScript world (I'm looking at you Immutable.js). While some JavaScript programmers look down on any mutation, except at the outer limits of the application, Yegor looks at immutability from an object identity standpoint; that as long as an object's core identity doesn't change, it's ok if the objects that it encapsulates change internally.
My point is that memory, conceptually, must be treated exactly the same way we treat a disk, network, or any other "external" storage.
(Page 163)
Don't ask me to explain that any further as I barely understand it myself.
Let me just leave you with some additional excerpts from the book that I found especially interesting.
1.2 Make One Constructor Primary
If you design your classes right, as explained in further chapters, they will have many constructors and just a few methods. That's right. You will have more constructors than methods in your classes. I'm aware that not all languages allow us to have many constructors in a class, due to the absence of a method overloading feature. We'll discuss that limitation in a minute.
Thus, two to three methods and five to 10 constructors. That is what a perfect class, in my opinion, should look like. Of course, it is not an exact science and these numbers are just made up.... My point here is that a cohesive and robust class will have a small number of methods and a rather big number of constructors.
(Page 27)
1.3 Keep Constructors Code-Free
The initialized of an object must be "code-free" and must not touch the arguments. Instead, it must wrap them, if necessary, or encapsulate them in a raw form.... The first step is to instantiate an object; the second step is to allow him to work for us. These two steps should not overlap. A constructor should not ask its arguments to do anything, because the constructor itself wasn't asked to do anything yet. In other words, a constructor should be code-free. It should only contain assignment statements.
(Page 33-36)
2.1 Encapsulate As Little As Possible
I recommend you encapsulate four objects of less. If you need to encapsulate more, there is something wrong with the class, and it needs refactoring. No exceptions. Four or fewer. I'm just making this number up, there's no scientific proof behind it, but I will explain why in the next few pages.
(Page 42)
2.8 Don't Mock; Use Fakes
Mocking is a terrible practice.... Moreover, most mocking frameworks give us the ability to verify whether certain interactions happened with the mock object and how many times. This may look convenient, but it's a very bad idea, for the same reason. By making unit tests dependable on interactions, we make refactoring painful and sometimes impossible. We must not check or test how the object works with its dependencies. This is the information for the object to encapsulate. In other words, to hide form us. It's a secret.
(Page 105)
3.2 Don't Use Static Methods
The use of static methods, in any context, is a perfect indicator of a bad programmer who has no idea what OOP is. There can be no excuse for a static method in any situation.
(Page 118)
... and now I'll feel like a horrible programmer every time I use Lodash's static methods.
3.6 Don't Use New Outside Of Secondary Constructors
I suggest a simple rule that will ensure good design on all your objects: don't use "new" anywhere except in secondary constructors.... If you entirely prohibit yourself from using "new" anywhere else, your objects will be fully decoupled from each other, and their testability and maintainability will be much higher.
(Page 177)
4.2.1 Don't Catch Unless You Have To
It is an obvious choice we have to make when designing a method - to catch all exceptions here and now, making the method look "safe" for its users, or escalate problems. I am in factor of the second option. Escalate them as much as you can. Every catch statement has to have a very strong reason for its existence. In other words, don't catch unless you really have to do it, and there's no other choice.
(Page 202)
4.2.2 Always Chain Exceptions
If this chaining happens many times, the exception that floats up will look like a soap bubble with a bubble inside it. That bubble will also contain a bubble, etc. There will be many layers. The catch statement that finally decides to do something about the problem and rescue the situation will burst the bubble and take all other bubbles out of it. How catch will handle the situation and report the problem to the user doesn't really matter. What is important is that we bring the low-level root cause of the problem to the highest level of the entire software.
(Page 206)
... this one, in particular, rings true with me and is something I experimented with in Node. I called it "Russian Doll" error reporting.
4.2.3 Recover Only Once
What I'm saying is that there are just a few legal places for recovering in any software. Everywhere else, we must catch and rethrow or not catch at all. The first option is preferable. Always catch, chain, and rethrow. Recover only once at the highest level. That's it.
(Page 209)
4.3 Be Either Final Or Abstract
Inheritance, intuitively, is a top-down process, where child classes inherit code from parent classes. Method overriding makes it possible for a parent class to access the code of child class. Such reverse thinking goes against common sense, so to speak.... However, there is a solution. Just make your classes and methods either final or abstract, and the very possibility of a problem fades away.
If you follow this principle and make all your classes final or abstract, you will almost never use inheritance. But sometimes you will, when it makes sense. When does make sense? Only when you need to refine class behavior, not extend, but refine. There is a difference. Extending means that an existing behavior is partially supplemented by a new one. Refining means that partially incomplete behavior is made complete.
(Pages 214-217)
And that's really just the stuff that I could understand. There's was a bunch of stuff in the book that really went over my head, like getting rid of "If" conditions as language constructs and turning them into Objects. Say whaaaaat?! That said, I definitely enjoyed this book and will be re-reading it again soon.
I still haven't given up hope that I'll understand Object Oriented programming one day.
Reader Comments
Thanks for the review. As for eliminating the `if` statements it reminded me of this talk: https://youtu.be/9lv2lBq6x4A
Is the 'if' stuff related to using something like a Strategy pattern to encapsulate different sequences of actions within an object?
@Anton,
Thanks for the link - I don't know that talk, but I'm a big fa of Sandi Metz. I'll check it out.
@Sam,
I don't think I can even explain it quite well. Here's a snippet of what was in the book:
float rate = new If(
. . . new GreaterThan( new AgeOf( client ), 65 ),
. . . 2.5,
. . . 3.0
);
He explains:
> This is pure object-oriented and declarative code. It doesn't do
> anything, but only declares what rate is.... My point is that in pure
> OOP, we don't need operators inherited from procedural
> languages like C. We don't need if, for, switch, and while. We need
> classes If, For, Switch, and while. See the difference? .... We haven't
> arrived at that language yet, but we'll get there sooner or later.
(Page 144)
Honestly, this part goes way over my head. Even if I could buy for a moment that these operators could be replaced with classes, I still don't see how they could work internally without operators. At some point, a comparison has to be made and an operand has to be chosen, right? This is light years beyond how my brain thinks.
@Ben,
Yeah, it seems a bit dogmatic, but perhaps it makes more sense in the world of language design.
-Sam
Ben, thanks for buying, reading, and reviewing! :)
@Yegor,
My pleasure good sir - I've seen a couple of people on Twitter follow suite, so just happy to help spread the word :)
My suggestion is that you treat what he states somewhat cynically. Much of what he suggests doesn't represent a real, accepted and academically sound object-oriented approach. He promotes ideas that are not based on project success, but rather what he used to implement a single framework ("Takes").
What do you think is the main reason most don't "get" object-orientation?
@Ben,
Here's a very good talk by Sandi Metz where she touches on this subject https://www.youtube.com/watch?v=OMPfEXIlTVE
"... and now I'll feel like a horrible programmer every time I use Lodash's static methods."
I am one of the say-no-to-static guy but there is one exception when I think static can be preferred. There is this thing called "pure function" which can be static. And as for Lodash, most of its functions, if not all, are pure functions I think.
@Mohayeminul,
Ok, I can buy that -- static methods are OK in cases when they are not acting on any state. But, then again, isn't that really what most "static" methods are? Unless they are "singleton" type things, where they are acting on some shared instance, aren't most static methods pure?
That said, some static methods make up for a lack of native functionality. For example, in JavaScript, there's no "flatMap" method on arrays. So, we can use some flatten() Lodash implementation. But, if JavaScript had it, we wouldn't need lodash's static version.
Thought, Yegor might argue that I should create a typed-collection that has the flatten method built in? I'm not really sure. Frankly, OOP is just over my head in all by utility-oriented use-cases.
@Guilherme J.,
Very cool -- I love Sandi Metz, will take a look :)
@John,
At this point, all of my "project success" still revolves around Procedural Scripts that tap into some object organization. So, I am not gonna hate too much on the hand that feeds me -- Scripting has given me a lot of value. I just with I had a better mental model for how it all fits together.