Object Calisthenics #1: Elevating Code Quality with 9 Powerful Rules

Pierre Belin
Pierre Belin
Object Calisthenics #1: Elevating Code Quality with 9 Powerful Rules
Table of Contents
Table of Contents

Discover how Object Calisthenics can revolutionize your coding practices. Learn 9 essential rules to elevate your software craftsmanship and produce cleaner, more maintainable code.

In the ever-evolving landscape of software development, the pursuit of excellence extends far beyond mere functionality. Enter the realm of software craftsmanship, a philosophy that elevates coding from a technical task to an art form.

At the heart of this discipline lies a powerful technique known as Object Calisthenics, a set of coding exercises designed to sharpen skills and dramatically improve code readability.

What are Object Calisthenics?

Object Calisthenics, brainchild of Jeff Bay, made its debut in the 2008 book "The ThoughtWorks Anthology." Bay, inspired by the physical discipline of calisthenics, crafted these coding exercises with a bold vision: to challenge developers to break free from conventional coding habits and explore new frontiers of software design.

His goal was to push programmers beyond their comfort zones, compelling them to approach problem-solving from fresh perspectives and ultimately produce more maintainable, object-oriented code.

The 9 Rules of Object Calisthenics, each serving as a pillar of code quality, are:

  1. Only One Level of Indentation per Method: forces developers to break down complex methods into smaller.
  2. Don't Use the ELSE Keyword: encourages to use early returns resulting in cleaner conditional logic.
  3. Wrap All Primitives and Strings: promotes better encapsulation and more expressive domain modeling.
  4. First Class Collections: leads to more cohesive and maintainable code.
  5. One Dot per Line: reduces coupling between objects and improves adherence to the Law of Demeter.
  6. Don't Abbreviate: improves code clarity and reduces the cognitive load.
  7. Keep All Entities Small: encourages single responsibility and easier maintenance by preventing classes from becoming overly complex.
  8. No Classes with More Than Two Instance Variables: forces better decomposition of objects.
  9. No Getters/Setters/Properties: enhances encapsulation and leads to more robust object-oriented designs.

These rules may seem simple and trivial, but they are far from being respected in the world of development.

Before starting, it's crucial to understand that Object Calisthenics are not architectural patterns like Domain-Driven Design (DDD) or Command Query Responsibility Segregation (CQRS). Instead, they're a set of guidelines aimed at improving code readability and maintainability at a more granular level, regardless of the broader architectural choices made in a project.

An important step in fully understanding the meaning of craftmanship is to always question its principles in order to draw out the very essence of its rules. It's naive to think that a rule or principle should be used systematically on all your projects without fully understanding its limits.

Dorra's approach of Object Calisthenics

To illustrate this point, we're going to take a look at a LinkedIn post by Dorra Bartaguiz, who suggests a different order of importance for these rules to simplify their application and quickly align business language and vocabulary with the code. Her approach is closely linked to its DDD-oriented architecture, which is why the creation of business objects close to the domain is a priority.

💎Dorra BARTAGUIZ // on LinkedIn: #object #calisthenics #coaching #legacy #forum #ouvert #calisthenics… | 40 comments
🚀 J'ai décidé de réorganiser les règles des Object Calisthenics et voici pourquoi ! Depuis plusieurs années, j'intègre quotidiennement les règles des #Object… | 40 comments on LinkedIn

This results in:

  1. Wrap All Primitives and Strings
  2. First Class Collections
  3. Don't Abbreviate
  4. No Getters/Setters/Properties
  5. One Dot Per Line
  6. Don't Use the ELSE Keyword
  7. Only One Level Of Indentation Per Method
  8. Keep All Entities Small
  9. No Classes With More Than Two Instance Variables

This order provides a strategic progression, addressing cognitive complexity from the outset. It begins with simpler improvements that immediately reduce mental load, such as clarifying naming conventions and encapsulating primitives. As developers build upon each step, they gradually tackle more complex refactoring, continuously decreasing the codebase's cognitive complexity. By addressing readability and basic object-oriented principles first, it sets a strong foundation for the more challenging rules that come later in the process.

1 method, 2 implementations of Object Calisthenics

In our exploration of Object Calisthenics, we're taking a unique approach. We'll be creating a series of articles that delve deep into each rule, but with a twist: two developers, working independently, will apply these rules to the same codebase. Once complete, they'll compare their results, discussing the reasoning behind their choices and the different paths they took to improve the code.

Our two developers

Our two developers bring diverse perspectives to this exercise:

  • Yoan Thirion, a senior developer and agile coach, brings a wealth of experience in software craftsmanship. His deep understanding of clean code principles and years of practical application provide a seasoned perspective on how to effectively implement these rules.
  • Pierre Belin, while experienced in development, is newer to the concepts of software craftsmanship. His fresh eyes and learning journey offer insights into how these rules are interpreted and applied by someone encountering them for the first time.

This dual approach serves a crucial purpose in our exploration. Too often in software development, solutions are presented as definitive, leading many developers to believe there's only one "correct" way to solve a problem. By comparing two different approaches to applying the same rules, we highlight the creative nature of programming. It demonstrates that even within the constraints of Object Calisthenics, there's room for individual interpretation and multiple valid solutions.

The code base we are going to attack looks like the following:

public class FellowshipOfTheRingService
{
    private List<Character> members = new List<Character>();

    public void AddMember(Character character)
    {
        if (character == null)
        {
            throw new ArgumentNullException(nameof(character), "Character cannot be null.");
        }
        else if (string.IsNullOrWhiteSpace(character.N))
        {
            throw new ArgumentException("Character must have a name.");
        }
        else if (string.IsNullOrWhiteSpace(character.R))
        {
            throw new ArgumentException("Character must have a race.");
        }
        else if (character.W == null)
        {
            throw new ArgumentException("Character must have a weapon.");
        }
        else if (string.IsNullOrWhiteSpace(character.W.Name))
        {
            throw new ArgumentException("A weapon must have a name.");
        }
        else if (character.W.Damage <= 0)
        {
            throw new ArgumentException("A weapon must have a damage level.");
        }
        else
        {
            bool exists = false;
            foreach (var member in members)
            {
                if (member.N == character.N)
                {
                    exists = true;
                    break;
                }
            }

            if (exists)
            {
                throw new InvalidOperationException(
                    "A character with the same name already exists in the fellowship.");
            }
            else
            {
                members.Add(character);
            }
        }
    }

    public void UpdateCharacterWeapon(string name, string newWeapon, int damage)
    {
        foreach (var character in members)
        {
            if (character.N == name)
            {
                character.W = new Weapon
                {
                    Name = newWeapon,
                    Damage = damage
                };
                break;
            }
        }
    }

    public void RemoveMember(string name)
    {
        Character characterToRemove = null;
        foreach (var character in members)
        {
            if (character.N == name)
            {
                characterToRemove = character;
                break;
            }
        }

        if (characterToRemove == null)
        {
            throw new InvalidOperationException($"No character with the name '{name}' exists in the fellowship.");
        }
        else
        {
            members.Remove(characterToRemove);
        }
    }

    public void MoveMembersToRegion(List<string> memberNames, string region)
    {
        foreach (var name in memberNames)
        {
            foreach (var character in members)
            {
                if (character.N == name)
                {
                    if (character.C == "Mordor" && region != "Mordor")
                    {
                        throw new InvalidOperationException(
                            $"Cannot move {character.N} from Mordor to {region}. Reason: There is no coming back from Mordor.");
                    }
                    else
                    {
                        character.C = region;
                        if (region != "Mordor") Console.WriteLine($"{character.N} moved to {region}.");
                        else Console.WriteLine($"{character.N} moved to {region} 💀.");
                    }
                }
            }
        }
    }

    public void PrintMembersInRegion(string region)
    {
        List<Character> charactersInRegion = new List<Character>();
        foreach (var character in members)
        {
            if (character.C == region)
            {
                charactersInRegion.Add(character);
            }
        }

        if (charactersInRegion.Count > 0)
        {
            Console.WriteLine($"Members in {region}:");
            foreach (var character in charactersInRegion)
            {
                Console.WriteLine($"{character.N} ({character.R}) with {character.W.Name}");
            }
        }
        else if (charactersInRegion.Count == 0)
        {
            Console.WriteLine($"No members in {region}");
        }
    }

    public override string ToString()
    {
        var result = "Fellowship of the Ring Members:\n";
        foreach (var member in members)
        {
            result += $"{member.N} ({member.R}) with {member.W.Name} in {member.C}" + "\n";
        }

        return result;
    }
}

The two results will be made available in 2 branches of the github project so that everyone can make up their own mind about which approach they prefer.

goat/object-calisthenics at main · ythirion/goat
Contribute to ythirion/goat development by creating an account on GitHub.

This comparative method offers several advantages for learning and applying Object Calisthenics:

  1. It provides a richer learning experience by showcasing different thought processes and problem-solving approaches.
  2. It encourages critical thinking, as readers can analyze the pros and cons of each solution.
  3. It reinforces the idea that software development is as much an art as it is a science, with room for creativity and personal style within the bounds of best practices.
  4. It demonstrates that improving code quality is not about following a strict set of rules, but about understanding principles and applying them thoughtfully.

As we embark on this journey through Object Calisthenics, keep in mind that the goal isn't to find a single "correct" implementation, but to broaden your understanding of how these rules can be applied in various scenarios. Each article in the series will offer insights into different ways of thinking about code structure and design, ultimately helping you become a more thoughtful and skilled developer.

Through this series, we aim to not only teach the rules of Object Calisthenics but also to foster a deeper understanding of software craftsmanship principles. By seeing how two different developers approach the same challenges, readers will gain a more nuanced view of code improvement techniques and the thought processes behind them.

Remember, the path to mastery in software development is not about rigidly following rules, but about understanding principles and applying them thoughtfully. Object Calisthenics provides a framework for this journey, challenging us to think differently about our code and continually strive for improvement.

The road ahead promises not just better code, but a deeper appreciation for the art and science of software development.

Have a goat day 🐐



Join the conversation.

Great! Check your inbox and click the link
Great! Next, complete checkout for full access to Goat Review
Welcome back! You've successfully signed in
You've successfully subscribed to Goat Review
Success! Your account is fully activated, you now have access to all content
Success! Your billing info has been updated
Your billing was not updated