[from chapters 1–4]
I am not a professional Ruby programmer. I am not even a Ruby programmer at all. I learned coding in high school, and as an engineering graduate I had a couple of programming classes in C, and assembly (for microcontrollers… a long time ago*). And I do code for fun. I taught myself Python (edX), and Octave (coursera), among other languages. I’m very thankful that I live in the internet age and that there are people who graciously give free world-class education to everyone.
I recently watched one of Sandi Metz’s talks called “Nothing is Something”. It blew me away. She talked about her insights — how to think about code using a “message-centric” philosophy. The goal is to make the code simpler which makes your application better and your life better. I have read about Agile, and practiced a little bit of object-oriented programming, but my mind was still heavily programmed to think in functions and procedures. Being an amateur coder I had little experience in “maintenance nightmares”, but her talk made me realize that Agile can be useful for me too. Writing code with a solid design philosophy would also be beneficial to my personal projects (and my sanity!) even if I am not a coder by profession. Learning to write simpler, cleaner code is not only a practical skill, it is also a thinking philosophy. It’s useful in structuring thoughts and expressing them clearly.
Having no background in Ruby at all — not even a single line of “hello world”!, I got her book and started reading it. After about an hour of reading, by the end of chapter four, I decided to put the book down. I should internalize the things I’ve learned, maybe apply them in my own small way. I’m so happy that I decided to read this book.
In the next few paragraphs, I will be writing about the key concepts I learned in the first four chapters — the concepts which resonated the most with me. Each chapter contains so much valuable information, I would not be discussing them all.
Read the book! Of course, other ideas might resonate with you more which you might deem more important, but I am writing about my personal experience in reading this book.
1. A CLASS SHOULD HAVE ONE AND ONLY ONE JOB
A class, as much as possible, should do the smallest possible useful thing. A class has responsibilities that fulfil its purpose. This matters because applications that are easy to change consist of classes that are easy to reuse. A class with with various responsibilities is difficult to reuse. This is because if you want to reuse some but not all of its behavior, it is almost always impossible to get only what you need. Having more responsibilities means that the class is also likely to be entangled with other code. This is not a good thing for reasons I will discuss later.
Classes that do one thing which isolate that thing from the rest of your application allows change without consequence and reuse without duplication.
There are a few ways to determine whether a class has a single responsibility or if it contains behavior that belong somewhere else. One way is to pretend that it’s sentient and interrogate it. Rephrase each of its methods into a question and determine if asking such question makes sense. As described in the book, for example, “Please Mr Gear, what is your ratio?” seems to be reasonable, while “Please Mr Gear, what is your gear-inches?” seems to be on shaky ground, and “Please Mr Gear, what is your tire size?” is downright ridiculous.
Another way is to try to describe that class in one sentence. If it is only responsible for one thing then it should be simple to describe. If it uses the word ‘and’ or ‘or’ then it is likely that you fail this test. A class is highly cohesive when everything inside it is related to its central purpose.
2. A CLASS SHOULD KNOW ENOUGH TO DO ITS JOB AND NOTHING MORE.
ALSO, DEPEND ON THINGS THAT CHANGE LESS OFTEN THAN YOU DO.
Interaction between objects are inevitable. A single object doesn’t know everything so it has to talk to others. Because we have designed objects with single responsibilities, each object work with one another to accomplish more complex tasks. To work with each other, objects must know things about others. However, knowing creates dependencies. An object is dependent on another if whenever one object changes, the other might be forced to change with it. The more dependencies objects have, or the more they know each other, the harder it is to change a class without affecting other classes. It is also harder to predict the behavior of the application.
There are many ways to recognize dependencies. An object is dependent if:
- It knows the name of another class and expects it to exist.
- It knows the name of a message (function) it intends to send to someone other than itself (and/or the arguments of this message, and/or the order of those arguments)
Of course we cannot help but have some degree of dependency, but a lot are likely unnecessary. There are techniques to minimize dependencies such that a class should know enough to do its job and not one thing more. There are several ways to minimize dependencies, and to code in such a way that objects can quickly adapt to unexpected changes. Discussed below are some of them which are the most important in my opinion.
Injecting dependencies can mean moving the creation of an instance of a class B that class A interacts with outside of the class. That way class A only knows that it holds a class that responds to a certain message M. Class A doesn’t care if it holds an instance of class B or C or D, only that this instance responds to a certain message M. Just because the class knows it needs to send the message M to the instance doesn’t mean it needs to know what kind of class this instance is.
Isolating dependencies can mean making the creation of an instance of class B on its own explicitly defined method inside class A. It can create this instance lazily when it needs to to do something that requires interacting with it.
You can also isolate vulnerable external messages (message sent to someone other than itself). Define methods inside class A that solely interacts with messages to other classes and call this method instead of sending external messages inside a method with a different purpose.
You can also reverse the direction of the dependency, making B dependent on A instead of A dependent on B. This is better if A is a class that you judge to be more likely to be reused in the future or if it is B that is more likely to change.
3. ASK FOR “WHAT” INSTEAD OF TELLING “HOW”
OBJECTS SHOULD BLINDLY TRUST AND BE TRUSTWORTHY. OBJECTS SHOULD BE ABLE TO COLLABORATE WITH OTHERS WITHOUT KNOWING WHO THEY ARE OR WHAT THEY DO.
In chapter four, Sandi Metz emphasizes that although an application is made of classes, to create code that is easy to change and maintain, we must be more concerned with how the objects talk to one another (Message-centric design). This is revealed through communication patterns. An application where the message patterns are visibly constrained (as the one on the right) is better. To accomplish this, we classify the methods in a class into two: public or private. Public methods should reveal its primary responsibility and are the ones which are expected to be invoked by others. As such, they should not change on the whim which makes it safe for others to depend on. The private methods on the other hand, handle the implementation details and as such are not expected to be used by other objects. Sandi Metz proceeds to give us a lot of good advice on how to find and define these methods including using sequence diagrams (Unified Modeling Language Diagrams) .
One of the key points that I love about this chapter is that objects should interact with each other based on trust. If we call the things that objects know about each other “the context” then we should seek context independence as this just makes objects more entangled with one another. It should be possible for an object to collaborate with others without knowing who they are and what they do. Objects should ask for “what” instead of telling “how”.
The example she gave is interesting. A trip is about to depart and it needs to make sure all the bicycles scheduled to be used are in mechanically sound. A TRIP object can interact with a MECHANIC object to do this.
The three diagrams (from the book) below depict three possible ways Trip and Mechanic could interact in order 0f increasing “object-oriented-ness” .
In the first diagram, the Trip is telling Mechanic “I know that I want and I know how you do it”. In the second, “I know what I want and I know what you do”. In the last diagram, “I know what I want and I trust you to do your part”.
This blind trust in the last diagram allows objects to collaborate without binding themselves into context.