Object Calisthenics – 9 steps to better software design today – Object Calisthenics?



Object Calisthenics – 9 steps to better software design today – Object Calisthenics?

0 0


object-calisthenics


On Github maxpou-slides / object-calisthenics

Object Calisthenics

9 steps to better software design today

Maxence POUTORD

@_maxpou | maxpou.fr | hello@maxpou.fr

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!

and velocity

#1: One level of indentation per method

Code (re)organisation: Early return

public function register(Request $request): void
{
    $username = $request->get('username');
    $password = $request->get('user_password');
    $confirmPassword = $request->get('confirmPassword');

    if ($username) {
        if ($password) {
            if ($password === $confirmPassword) {
                if (mb_strlen($password) >= 8 && preg_match("regex", $password)) {
                    // Do Something
                    // Do Something
                    // Do Something
                }
            }
        }
    }
}
public function register(Request $request): void
{
    $username = $request->get('username');
    $password = $request->get('user_password');
    $confirmPassword = $request->get('confirmPassword');

    if (!$username) {
        return;
    }
    if (!$password) {
        return;
    }
    if (!$password === $confirmPassword) {
        return;
    }
    if (mb_strlen($password) < 8 || !preg_match("regex", $password)) {
        return;
    }

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

"Single Entry, Single Exit"...?

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

More reading

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 enought?

  • 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

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();
stop abusing arrays in php

Benefits

  • Type hinting
  • Easier to reuse
  • Prevent code duplication

#4: First class collections

Why?

class Compagny
{
    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

Ubiquitous Languageto the rescue!

It's all about reading!

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

Benefits

  • Readability
  • Communication
  • Help to reveal underlying problems

#7: Keep all entities small

Rule #7: Keep everything small

"Simplicity Comes from Reduction" — Paul W. Homer simplicity is the ultimate sophistication

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

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

    // code
}

Benefits

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

#9: No getters/setters/properties

  • http://whitewashing.de/2012/08/22/building_an_object_model__no_setters_allowed.html

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
1.1
Object Calisthenics 9 steps to better software design today Maxence POUTORD @_maxpou | maxpou.fr | hello@maxpou.fr