"Object Calisthenics"


S1 E1

 

What is "Better code saul" aboUt

The vision of "Better Code Saul" is to advocate good code practices and good coding design techniques in order to inspire others to adopt them and increment software's code quality.

 

The season one's mission of "Better Code Saul" is to provide a short-term benefit to your code quality.


The episodes succession is meant to be of incremental difficulty and each episode is useful for the following.

What is "OBject calisthenics"

Calisthenics is a word that derives from the greek and means "exercise" in a sport context.
Object is a word from Object Oriented Programming.

So Object Calisthenics are programming exercises, formalised to be executed following a set of 9 rules.

 

These 9 rules have been written by Jeff Bay

The 9 rules

  1. Only One Level Of Indentation Per Method

  2. Don’t Use The ELSE Keyword

  3. Wrap All Primitives And Strings

  4. First Class Collections

  5. One Dot Per Line

  6. Don’t Abbreviate

  7. Keep All Entities Small

  8. No Classes With More Than Two Instance Variables

  9. No Getters/Setters/Properties

1) Only One Level Of Indentation Per Method

class Board {
  String board() {
    StringBuffer buf = new StringBuffer();
	// 1st level
    for (int i = 0; i < 10; i++) {
        // 2nd level
        for (int j = 0; j < 10; j++) {
            // 3rd level
      	    buf.append(data[i][j]);
        }
        buf.append(“\n”);
    }
    
    return buf.toString();
  }
}

Too many nested levels

1) Only One Level Of Indentation Per Method

A possible fix

class Board {
    public String board() {
        StringBuilder buf = new StringBuilder();

        collectRows(buf);

        return buf.toString();
    }

    private void collectRows(StringBuilder buf) {
        for (int i = 0; i < 10; i++) {
            collectRow(buf, i);
        }
    }

    private void collectRow(StringBuilder buf, int row) {
        for (int i = 0; i < 10; i++) {
            buf.append(data[row][i]);
        }

        buf.append("\n");
    }
}

Suggestion: Make use of ExtractMethod pattern

2) Don’t Use The ELSE Keyword

public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        redirect("homepage");
    } else {
        addFlash("error", "Bad credentials");

        redirect("login");
    }
}

with ELSE

public void login(String username, String password) {
    if (userRepository.isValid(username, password)) {
        return redirect("homepage");
    }

    addFlash("error", "Bad credentials");

    return redirect("login");
}

without ELSE

Suggestion: Use  early returns or flags

3) Wrap All Primitives And Strings

Suggestion: Use  ValueObjects

Principle

If you have a primitive variable that has some behaviour

you need to encapsulate

class Product {
    public function getPrice(): float {
        return $this->price;
    }

    public function setPrice(float $price) {
        $this->price = $price;
    }
}
class Product {
    public function getPrice(): Money {
        return $this->price;
    }

    public function setPrice(Money $price) {
        $this->price = $price;
    }
}

4) first class collections

If you have a class that contains one collection, that class should not contain any other member 

class Order {
    
  public ArrayCollection $products;
  public ArrayCollection $discountsApplied;
    
  function productsCustomMethod() {
    ...
  }
  
  function discountsCustomMethod() {
    ...
  }
}
class Order {
    
  public ProductsCollection $products;
  public DiscountCollection $discountsApplied;
    
}

// ProductsCollection
class ProductsCollection {
  public ArrayCollection $products;
   
  function productsCustomMethod() {
    ...
  }
}

// DiscountCollection
class DiscountCollection {
  public ArrayCollection $products;
    
  function discountCustomMethod() {
  	...
  }
}

not good

better

Suggestion: Make use of SOLID's SRP

5) one dot per line

In C# you have a dot, in JS you have a dot, in PHP you have the arrow operator.

You use this when you want to use a class method or property.
You should not have more than one of these in your code.
This does not apply for fluent interfaces.

// I want to get a certain 
// product price starting from an order

// NOT OK
$order->orderDetail->product->getPrice();
// I want to get a certain 
// product price starting from an order

// MORE OKish
$orderDetail = $order->orderDetail;
$product = $orderDetail->product
$price = $product->getPrice();
// OK Fluent example
$widget
  ->addSectionFoo()
  ->addSectionBar();

6) Don't ABBREVIATe

$gt = $t + $tx - $d;

Never abbreviate variable names or methods names. 

Why do you need to do that?

$grandTotal = $total + $taxes - $discounts;

Why?

Oh ok, now better

Suggestion: If you have no answer from the above question, then you don't need abbreviations. If you I've found an answer your code is probably duplicated or you are crunching code and getting stresses. Ask a collegue for help

7) keep all entities small

An entity here is a class.

All classes should not be longer than 150 lines *.

 

Classes longer than 150 lines are probably doing more than one thing.

 

This is a rule. The principle above is to keep things simple, split responsibilities and keep your classes testable.

 

This is hard, but it will benefit your coding quality.

 

 

* the original rule says 50 lines. 150 is still reasonable tho.
Up to you decide to which one abide to.

8) No classes with more than two instance variables

class Person {
  public string $firstName;
  public string $middleName;
  public string $lastName;
}
class Person {
  public GivenNames $names;
  public FamilyName $lastName;
}

class FamilyName {
  public string $familyName;
}

class GivenNames {
 /** array<string> **/
 public array $names;
}

Suggestion: Use encapsulation 'til death

9) NO SETTERS/PUBLIC PROPERTIES

class Person {
  public string $firstName;
  public string $lastName;
  
  public function setFirstName(string $firstName) {
  	...
  }
  
  public function getFirstName(): string {
  	...
  }
  
  public function setLastName(string $lastName) {
  	...
  }
  
  public function getLastName(): string {
  	...
  }
}

Wrong

9) NO SETTERS/PUBLIC PROPERTIES

class Person {
  private string $firstName;
  private string $lastName;

  public function __construct(string $firstName, string $lastName) {
  	$this->firstName = $firstName;
 	$this->lastName = $lastName;
  }

  public function getFirstName(): string {
  	...
  }
  
  public function getLastName(): string {
  	...
  }
}

Correct

9) NO SETTERS/PUBLIC PROPERTIES

  • Never change an object's state

  • Make objects immutable

  • To init some properties use class' constructor

  • Don't write unnecessary getters

FINAL WORDS

I'd love to see these rules applied in some programming exercises.
Some of them are also doable on refactoring PRs on a real project.

Yes, these are rules for exercises, but they will change your way to write code and you will question yourself when you will write production code.

 

You will produce better quality code and break your old bad habits.

 

Theres no reason to use these rules in a live projects, but there are a lot of reasons to use the principles behind them.

 

As Jeff Bay said, 7 out of 9 of these rules are just making wise use of encapsulation. 

RESOURCES and further readings

THANK YOU

Better Code Saul S1E1: Object Calisthenics

By Kevin Cittadini

Better Code Saul S1E1: Object Calisthenics

  • 790