⏩ Introduction
Hey there, code enthusiasts! 👋✨
I think we all agree if we say that Object Oriented Programming (OOP) is pretty hard! 🤔 After all these years, we're still debating its intricacies, and this is very important feedback about the fact that is complicated to be understood and there is something that didn’t work about the spread of OOP concepts! 💥
The truth is, OOP might not be as impenetrable as it seems. The real challenge lies in how it's been taught all along. 📚😱
In this exciting issue of the #LAP newsletter, we're about to turn the tables on OOP and shed light on a game-changing approach: Object Calisthenics! 🏋️♂️✨ These powerful rules follow the heuristics of OOP, leading the developers to avoid code smells and reach a better design. That’s the key that unlocks the door to a true, deep understanding of OOP! 🗝️🚀
Object Calisthenics is a fascinating concept showing how OOP can become a cool journey rather than an enigma. 🌟🔍
❌ OOP is full of misconceptions 🙅
A lot of people see OOP in the wrong way, and it’s not their fault: for some reason, in the history of software development, a lot of misconceptions and wrong concepts about Object-Oriented approaches spread into the world.
The original definition of Object-Oriented Programming is to build a system made of objects that communicate with each other through messages.
A lot of misconceptions bring developers far away from the original concepts - here are some of those:
-
Equating OOP with the use of classes: while classes are a fundamental concept in OOP, they are not the only aspect. OOP is a broader paradigm that includes encapsulation, inheritance, and polymorphism, among other principles.
-
Treating objects as mere data structures: OOP emphasizes the behaviour and interactions of objects. A common misconception is to treat objects as simple data structures.
-
Overuse of inheritance: Some developers tend to rely heavily on inheritance, creating deep class hierarchies that can become difficult to maintain and understand. Inheritance represents an “is-a” relationship (“Labrador” is a “Dog”), while composition represents a “part of” relationship (“Eyes“ is part of “Head“ which is part of “HumanBody”).
-
Ignoring the SOLID principles: The SOLID principles (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion) provide guidelines for writing maintainable and extensible code in OOP. Ignoring these principles or failing to understand their importance can lead to code that is hard to maintain, tightly coupled, and difficult to test.
-
Using getters, setters, and public properties: in general, making objects communicate with data is a huge mistake that doesn’t respect OOP principles (communicates through messages, not data) and leads to a coupled design.
A lot more misconceptions exist, but in general, I think that most of them are the consequence of the fact that a lot of people forget the “communicates through messages” concepts and start sharing data among objects in the wrong way.
❓ Why Object Calisthenics? 🙋♂️
Object Calisthenics was introduced by Jeff Bay in The ThoughtWorks Anthology, and it consists in a set of 10 rules to respect when writing your code.
Object Calisthenics rules were born with the objective of preventing Code Smells. Code smells are characteristics you can recognize in a codebase that indicates deeper problems in the design, like a violation of fundamental design principles. The terms come from the idea that these pieces of code are recognizable, like if they stink.
There is a strong correlation between SOLID principles and the practice of Object Calisthenics. The objective of Object Calisthenics is to avoid code smells bringing to a bigger target: providing a practical step-by-step solution to apply SOLID principles, to obtain designs that are easier to understand, maintain, and extend.
In order to achieve this, the rules are based on the same heuristics that OOP was born and based on:
-
Tell, don't ask: tell objects to perform actions instead of asking them for data to be processed outside of it; this comes from the original idea of Object Oriented programming of having objects communicate with each other via messages
-
Law of Demeter: aka "don't talk with strangers", each component should only talk to its close friends in order to favour simplicity
The term Calisthenics refers to a form of strength training; the name comes from two greek words: "kalòs", which means beautiful, and "sthènos", which means strength. Main characteristics of this sport are that it can be practiced with none or very minimal equipment and its difficulty can be progressively increased to adapt to different level of training. In software design, the parallelism is between our body and the design of our code: thanks to Calisthenics, our body will become stronger and more beautiful; thanks to Object Calisthenics, the design of our code will become stronger and more beautiful.
Object Calisthenics is about constraining software design decisions: it's not about what you can do, but more about what you cannot do. You will notice in the rules that follow and in their examples that those rules restrict the choices about software design in order to help you avoid pitfalls and bad design choices.
Why do we need to consider design important?
-
The “Don't Repeat Yourself” (DRY) principle is not enough: refactoring efforts cannot be put only on removing duplication, we need more than that
-
Great practices punish you if you don't understand the design: TDD, writing tests in general, CI/Trunk-based, etc… all of these top-notch practices become very hard if you don’t take care of design - they will not take care of it for you
I strongly believe that learning Object Calisthenics rules is the best way to start learning OOP.
Let’s discover the rules ⬇️
1) Only one level of indentation
You should only keep one level of indentation per method, avoiding the nest of code blocks. This helps to ensure that a method focuses on doing only one thing and reduces the size of methods, enabling easier reuse. This approach favours readability and simplicity. To achieve this objective, extract code blocks in methods to give that piece of behaviour a name.
Wrong Example ❌
class WrongExample {
public function doSomething(int $a) {
if ($a > 0) {
if ($a < 10) {
return true;
}
}
return false;
}
}
Correct Example ✅
class CorrectExample {
public function isBetween1And10(int $a) {
if ($a > 0) {
return $this-> checkIfLowerThan10($a);
}
return false;
}
public function checkIfLowerThan10(int $a) {
if ($a < 10) {
return true;
}
return false;
}
}
2) Don't use the "else" keyword
Avoiding the else keyword promotes a main execution line with special cases handled. It suggests polymorphism to handle complex conditional cases, making the code more explicit. We can use the NULL object pattern to express that a result has no value or use guards to create an exit for special cases.
Wrong Example ❌
class WrongExample {
public function doSomething(int $a) {
if ($a < 0) {
// do something
} else {
// handle special case
}
}
}
Correct Example (with guard) ✅
class CorrectExample {
public function doSomething(int $a) {
if ($a > 0) {
// guard to handle special case
return;
}
// do something
}
}
3) Wrap all primitives and string
No arguments of public methods should be primitives, except constructors. Also, no return value should be a primitive, for public methods. Instead of primitives, we must create a class to describe the concept and contain its behaviours.
Wrong Example ❌
class WrongShop {
public function buy(int $money, string $productId) {
// do something
}
}
Correct Example ✅
class CorrectShop {
public function buy(Money $money, Product $product) {
// do something
}
}
class Money {
public function convert(Currency $from, Currency $to) { /** ... */ }
public function __toString() { /** ... for example a specific format like "$ 1.000,00" */ }
}
4) First-class collections
No arguments of public methods should be primitive collections (array, hash, tables, etc.). We must create a class to handle that collection and the behaviour of going through its values.
Wrong Example ❌
class WrongLeague {
public function newParticipants(array $newParticipantsList) {
// do something
}
}
Correct Example ✅
class CorrectLeague {
public function newParticipants(Participants $newParticipants) {
// do something
}
}
// Participants class allow us to create a list of participants validating data and offer behavior of order and go throught all the list
5) No getters/setters/properties
We follow the original idea of OOP as a network of entities collaborating by passing messages to each other. Don't ask for data and then act on it; instead, tell the object what you need it to do for you. Data Structures and Objects have different responsibilities.
Wrong Example ❌
class WrongUser {
public Id $id;
public Email $email;
public Password $password;
public function setId(Id $newId) { /** ... */ }
public function setEmail(Email $newEmail) { /** ... */ }
public function setPassword(Password $newPassword) { /** ... */ }
public function getId() { return $this->id; }
public function getEmail() { return $this->email; }
public function getPassword() { return $this->password; }
}
Correct Example ✅
class User {
private Id $id;
private Email $email;
private Password $password;
// you can only set them in constructor, then we only expose behaviors
public function login() { /** ... */ }
// ...
}
6) One dot per line
Avoid situations like dog→body()→tail()→wag()
with a chain of calls, because that's strictly coupled with classes very far from the caller. The caller here know only the Dog class and should talk only to it, so for example we could have a method dog→expressHappiness()
to encapsulate that behaviour.
Wrong Example ❌
class WrongDog {
private WrongDogBody $body;
public function body(): WrongDogBody { return $this->body; }
}
class WrongDogBody {
private WrongDogTail $tail;
public function tail(): WrongDogTail { return $this->tail; }
}
class WrongDogTail {
public function wag(): void { /** wag the tail action */ }
}
// used somewhere
$dog = new WrongDog();
$dog->body()->tail()->wag();
Correct Example ✅
class Dog {
private DogBody $body;
public function expressHappiness(): void { return $this->body->wagTail(); }
}
class DogBody {
public function wagTail(): void { /** do something */ return; }
}
// used somewhere
$dog = new Dog();
$dog->expressHappiness();
7) Don't abbreviate
Always make the names explicit, even if it cost a long name: no need to save characters. Abbreviations can only lead to misunderstanding and a code hard to read.
Wrong Example ❌
class Calc {
public function calcSumTwoNums() { /** */ }
}
Correct Example ✅
class Calculator {
public function calculateSumOfTwoNumbers() { /** */ }
}
// sum would be a good name for this method, just made an example to show problems with abbreviations so I didn't care about choosing the best name possible.
8) Keep all entities small
Small classes tend to be focused on doing just one thing, improving single responsibility and the reusability and readability of that code. Use packages/namespaces to cluster related classes. Also, packages should be small in order to have a clear purpose.
9) No classes with more than 2 instance variables
The more instance variables, the lower the cohesion within the class. Classes with more than one parameter are usually orchestrators, those with only one are actuators.
Wrong Example ❌
class Example {
public function __construct(string $firstName, string $lastName, string $email, string $password) { /** ... */ }
}
Correct Example ✅
class Example {
public function __construct(User $user) { /** ... */ }
}
10) All classes must have state
No static methods should be used, to avoid creating utility classes that collect some random behaviours together. Create classes with clear responsibility and a state to maintain. This will force you to create a network of collaborators that expose the required behaviours but hide their state.
Wrong Example ❌
class Utilities {
public static function log() { /** ... */ }
public static function translate() { /** ... */ }
}
Correct Example ✅
class Logger {
public function log() { /** ... it's state might include the logger technique and some persisted logs ... */ }
}
class Translator {
public function translate() { /** ... it's state might include the translations and languages ... */ }
}
Until next time, happy coding! 🤓👩💻👨💻
Dan’s take 🙋🏻♂️
To me, Object Calisthenics has been a true revelation: imagine struggling for years to understand OOP, having the illusion that I was starting to really get it - then meeting something that flips everything around and makes you understand that all you were taught was basically wrong.
I remember when I first started working as a developer: I was used to procedural PHP, and my first company paid for an OOP Course for us with coaches from Zend, a big company in the PHP world.
Learning OOP seemed something really really hard and complicated; all they talked about was what an Object and a Class are, Encapsulation, Inheritance, and SOLID Principles. The gap seemed simply too much for me, and my first imposter syndrome came up.
In the following years, I struggled a lot to start understanding a bit of how OOP worked, and I felt a lot of misconceptions from the previous section: I thought that getters and setters were good practices, for example. I had the feeling that learning OOP was too much hard for some reason I couldn’t understand, then I finally meet a couple of Senior Backend developers that opened my eyes.
In particular, in a book they suggested to me (Agile Technical Practices Distilled), I found a chapter dedicated to Object Calisthenics rules - and it was the first time I heard about those rules; it was also the first time I was truly understanding what OOP is about.
I truly believe that everyone should know Object Calisthenics rules, and that those rules are the best starting point for learning OOP.
Object Calisthenics might seem nothing so important at first look, but I think it’s a really great way to learn the foundation of OOP in a clear, non-misunderstandable way.
I think they are like stopping and passing the ball in football: it’s just the basic of all the technical skills of this sport, but they are a fundamental starting point not only to start understanding how to control the ball, but also how the full game works - it’s not just about controlling the ball, but also about the body position, look at your teammates, etc.
Do not underestimate this approach: before learning to run, you must learn to walk, otherwise, you will probably fail or, at most, invest a lot more time than needed to learn to run - and you will probably do it wrong.
🛃 Apply them strictly first, then make them natural. 🌟
The rules structure it’s really helpful at the beginning: You can start applying those rules blindly and you’ll notice that they work, even if you still don’t fully understand why they work. Then your learning journey can start, and you can focus on learning what code smells are, why the rules help avoid them and how they bring you close to SOLID Principles.
You don’t have to memorize all the rules, code smells, and SOLID principles and be able to pronounce them at any moment. You have to understand the core of those rules and principles to make them natural - you can always check the catalogue of rules and code smells when you need to.
In general, I think it’s a good idea to avoid learning this kind of thing by memorizing every detail: at first, try to apply them precisely, then you have to focus on learning the core. Creating a habit and a personal approach to learning is fundamental for being able to face complex topics like this.
❌ Object-Oriented Programming is one of the most misunderstood topics in software. 🏅
It’s incredible how many misconceptions are out there about OOP - books, articles, talks, and any other kind of content about programming are all full of examples with getters and setters, broken single responsibility principles, and much more problems.
It’s hard to understand how we reached this point, but it’s a fact that something went wrong and the original concepts from OOP went lost.
OOP is about creating a system of objects that communicates with each other through messages.
With this simple statement, it’s easy to see that there is something wrong when we see a class getting data from another and doing things.
To face this issue, the best advice I can give you is to search for authorities in the field and read their books and blogs. Kent Beck, Martin Fowler, and Robert Martin - start from their content.
Do not trust ORMs, Frameworks, etc: those are typically very opinionated - and their objective is to hide complexity, typically with a lot of trade-offs on design.
Learn Object Calisthenics and Code Smells: those two are the foundation for learning SOLID Principles, 4 Elements of Simple Design and OOP.
Remember: if something doesn’t convince you or causes you more problems than it should, deep dive and discover why - there is probably something more to learn about it, and you will become better.
Go Deeper 🔎
📚 Books
-
Agile Technical Practices Distilled - Learning the common fundamental software development practices can help you become a better programmer. This book uses the term Agile as a wide umbrella and covers Agile principles and practices, as well as most methodologies associated with it. The only book I know that covers Object Calisthenics!
-
Clean Code: A Handbook of Agile Software Craftsmanship - Even bad code can function. But if code isn’t clean, it can bring a development organization to its knees. Every year, countless hours and significant resources are lost because of poorly written code. But it doesn’t have to be that way.
-
Refactoring: Improving the Design of Existing Code - Martin Fowler provides a catalogue of refactorings that explains why you should refactor; how to recognize code that needs refactoring; and how to actually do it successfully, no matter what language you use.
-
Growing Object-Oriented Software, Guided by Tests - this book explores the relationship between TDD and good OOP design
📩 Newsletter issues
-
Harry Potter and The Object-Oriented Programming Paradigm [The Python Coding Stack by Stephen Gruppetta]
-
Object-Oriented Design [Winds of Waltz by Danna Waltz]
📄 Blog posts
🎙️ Youtube Videos
👨🏻🏫 Online courses
- Object Calisthenics Rules [GitHub repository]