Make your code great again with the

Object Calisthenics

Maxence POUTORD

Object Calisthenics?

Cal • is • then • ics - /ˌkaləsˈTHeniks/
  • Object: OOP context
  • Calisthenics: gymnastics context

Object Calisthenics

In the ThoughtWorks Anthology, Jeff Bay, list 9 rules for writing better Object Oriented code.

Motivation

readability, maintainability, testability, and comprehensibility

...make your OO code great again!

#1: One level of indentation
per method

Code (re)organisation:
Early return


public someFunction(someParameter)
{
    if (firstCondition) {
        if (secondCondition) {
            // Do Something
            // Do Something
            // Do Something
        }
    }
}
      

public someFunction(someParameter)
{
    if (!firstCondition) {
        return;
    }

    if (secondCondition) {
        // Do Something
        // Do Something
        // Do Something
    }
}
      

public someFunction(someParameter)
{
    if (!firstCondition) {
        return;
    }

    if (!secondCondition) {
        return;
    }

    // Do Something
    // Do Something
    // Do Something
}
      

"Single Entry, Single Exit"...?

"Single Entry, Single Exit" was written for assembly languages like Fortran/COBOL

Too much indentation level with loop?

You may need to split your code into multiple functions.
This is the Extract Method (cf. Martin Fowler).


//Class Command
public function validateProductList(array $products): array
{
    $validProducts[];
    foreach ($products as $product)
    {
        if (!$product->price > 0 && length($product->name)) {
            $validProducts[] = $product;
        }
    }

    return $validProducts;
}
      

//Class Command
public function validateProductList(array $products): array
{
    return array_filter($products, 'isValidProduct');
}

public function isValidProduct(Product $product): boolean
{
    if (!$product->price > 0 && length($product->name)) {
        return true;
    }

    return false;
}
      

Remove useless checks


function foo($products)
{
    if (isset($products) && count($products)) {
        foreach ($products as $product) {
            # code
        }
    }
}
      

function foo($products)
{
    if (isset($products) && count($products)) {
        foreach ($products as $product) {
            # code
        }
    }
}
      

function foo($products)
{
    foreach ($products as $product) {
        # code
    }
}
      

Benefits

  • Easier to read
  • Reduce cyclomatic complexity
  • Respect KISS principle
  • Single Responsability Principle ("S" in SOLID)

#2: Don’t use the ELSE keyword

Code (re)organisation


public function login($username, $password)
{
    if ($this->isValid($username, $password)) {
        redirect("homepage");
    } else {
        addFlash("error", "Bad credentials");
        redirect("login");
    }
}
        

public function login($username, $password)
{
    if ($this->isValid($username, $password)) {
        return redirect("homepage");
    } else {
        addFlash("error", "Bad credentials");
        return redirect("login");
    }
}
        

public function login($username, $password)
{
    // defensive approach
    if ($this->isValid($username, $password)) {
        return redirect("homepage");
    }

    addFlash("error", "Bad credentials");
    return redirect("login");
}
        

public function login($username, $password)
{
    // optimistic approach
    if (!$this->isValid($username, $password)) {
        addFlash("error", "Bad credentials");
        return redirect("login");
    }

    return redirect("homepage");
}
        

Not enough?

  • State Pattern
  • Strategy Pattern
  • NullObject Pattern
  • ...

Benefits

  • Easier to read
  • Reduce cyclomatic complexity

#3: Wrap all primitives and Strings

Example


class User
{
    /** @var string */
    private $name;

    /** @var string */
    private $email;

    public function __construct(string $name, string $email)
    {
        $this->name  = $name;
        $this->email = $email;
    }
}
      
//this is ok
$myUser = new User("John", "john@mail.fr");
      
//this is ok
$anotherUser = new User("Max", "0811223344");
      
//this is ok
$whoeverUser = new User("Louise", "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
      
//this is ok
$whoeverUserBis = new User("Lucie", "打才我醫集老電了快洋,西足並良步細主!");
      

Proposition - Value Object


class Email
{
    /** @var string */
    private $email;

    public function __construct(string $email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL) {
            throw new InvalidArgumentException("Invalid format!");
        }
        $this->email = $email;
    }
}
      
// This is ok
$myUser = new User("John", new Email("john@mail.fr"));
      
// This is NOT ok
$anotherUser = new User("Max", new Email("0811223344"));
      
// This is NOT ok
$whoeverUser = new User("Louise", new Email("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."));
      
// This is NOT ok
$whoeverUserBis = new User("Lucie", new Email("打才我醫集老電了快洋,西足並良步細主!"));
      

Should I wrap all primitives and strings?


NO!

Be pragmatic!

only wrap primitive with behaviour:

  • UUID
  • BIC, IBAN
  • email, URL
  • IP address
  • ...

Stop abusing arrays!


$command = array(
    array(
        "user"  => array(
            "id"      => 42,
            "name"    => "Maxence",
            "address" => "Dublin"
        ),
        "date"  => "1989-04-11 07:28:47",
        "beers" => array(
            array(
                "ID"       => "76b44e1a-a8d6-11e6-80f5-76304dec7eb7",
                "name"     => "Tripel Karmeliet",
                "quantity" => 4,
            ),
            array(
                "ID"       => "76b45220-a8d6-11e6-80f5-76304dec7eb7",
                "name"     => "Journeyman's Pale Ale",
                "quantity" => 8,
            ),
            array(
                "ID"       => "76b45356-a8d6-11e6-80f5-76304dec7eb7",
                "name"     => "Duvel tripel hop",
                "quantity" => 15,
            )
        )
    )
);
        
  • Big ball of mud anti-pattern
  • No defined structure
  • Comprehension is time consuming
  • Needless complexity
  • Procedural programming is hard to scale

foreach ($command["beers"] as $beer) {
    if (isset($beer["quantity"])) {
        $itemsCounter += $beer["quantity"];
    }
}

// ...or

$itemsCounter = $command->countItems();
            

Benefits

  • Type hinting
  • Easier to reuse
  • Prevent code duplication

#4: First class collections

Why?


class Company
{
    private $name;
    private $employes;

    # code
}
      

Where can I put filters/contains/... methods?


class Team
{
    private $name;
    private $employes;

    # code
}
      

'employes' collection need to be wrapped in its own class

Example


class EmployeesCollection
{
    private $employes;

    public function add(Employe $employe) {/** ... **/};
    public function remove(Employe $employe) {/** ... **/};
    public function count() {/** ... **/};
    public function filter(Closure $p): {/** ... **/};

    //...
}
      

Benefits

  • Readability
  • Easier to merge collection and not worry about member behaviour in them

#5: One dot per line

...or an arrow (->) if you use PHP!

A.K.A. Law of Demeter (LoD)

"Don't talk to strangers. Only talk to your immediate friends"

What are strangers?


$objectA->getObjectB()->getObjectC();
      

Example


$objectA->getThis()->getThat()->getSomethingElse()->doSomething();
      

What happen if getThat() return NULL?

Benefits

  • Law of Demeter
  • Readability
  • Easier to debug

#6: Don’t abbreviate

Why do you abbreviate?

  • It's repeated many times
    Code duplication
  • The method name is too long
    Violates the Single Responsibility Principle

1st SOLID Principle

S mean Single Responsibility Principle (SRP)

Tips

  • Class and method names with 1-2 words
  • Be explicit!
    Wrong/non explicit names often leads to errors.
  • Naming is too hard? Ask yourself if this class/method/... make sense!
There are only two hard things in Computer Science:
cache invalidation and naming things.
— Phil Karlton

It's all about reading!


foreach ($users as $u) {
    // code
    // code
    // code
    // code
    // code
    $u->sendMail($aMail); // "$u": what does it stand for?
}
      

Avoid tautologies


class Manager
{
    function perform()
    {
        // ...
    }
}
      

You can also replace perform by process()...

Ubiquitous Language
to the rescue!

Benefits

  • Readability
  • Communication
  • Help to reveal underlying problems

#7: Keep all entities small

Avoid God Object

An object that knows too much or does too much

Rule #7: Keep everything small


"Simplicity Comes from Reduction"
— Paul W. Homer

Why?

Long files are hard to:

  • read
  • understand
  • maintain
  • reuse

Code Gravity

"big stuff attracts even more stuff and gets bigger"

≈ theory of the broken window

Benefits

  • Easier to reuse
  • Better code segregation
  • Clear methods and their objectives
  • Single Responsability Principle ("S" in SOLID)

#8: No classes with several instance variables

High cohesion, and better encapsulation

Try to keep 2 5 instance variables (max)


class MyWonderfulService
{
    private $anotherService;
    private $logger;
    private $translator;
    private $entityService;

    // code
}
      

Benefits

  • Loose coupling / Easier to reuse
  • Better encapsulation
  • Short dependency list
  • Single Responsability Principle ("S" in SOLID)

#9: No getters/setters/properties

Why do we use GETTER/SETTER so much?

  • Frameworks (i.e. Symfony's entities)
  • IDE generate them automatically
  • We can change every properties we need
    and also... we are lazy!

Tell, don’t ask principle

"Don't ask for the information you need to do the work; ask the object that has the information to do the work for you"
— Allen Holub

What is better?


// Game
private $score;

public function setScore(int $score): void {
    $this->score = $score;
}

public function getScore(): int {
    return score;
}

// Usage
$game->setScore($game->getScore() + ENEMY_DESTROYED_SCORE);
      

// Game
public function addScore(int $delta): void {
    this->score += $delta;
}

// Usage
$game->addScore(ENEMY_DESTROYED_SCORE);
      

What is the difference between...


class Employe
{
    private $name;

    public function getName() {
        return $this->name;
    }

    public function setName($name) {
        $this->name = $name;

        return $this;
    }
}
          

class Employe
{
    public $name;
}
          

Benefits

  • Avoid to violate the Open/Closed principle (the O in SOLID)
  • Easier to read

Reading

Thank you

Questions?


@_maxpou

Backup slides

No classes with more than two instance variable

Using / Personal POV

#1: One level of indentation
#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 several instance variables
#9: No getters/setters/properties