DEV Community

Filippe Santos
Filippe Santos

Posted on

Object Calisthenics from Jeff Bay

Jeff Bay introduziu o termo "Object Calisthenics" em seu livro "Thought Works Anthology". É um conjunto de boas práticas e regras de programação que podem melhorar a qualidade do nosso código.

Essas regras foram criadas com base na linguagem Java e nos precisamos adaptar elas para a nossa realidade, nesse post vou fazer uma adaptação para o PHP.

As regras são:

  1. Um nível de recuo por método.
  2. Não use a palavra-chave ELSE.
  3. Envolva todas as primitivas e Strings em classes.
  4. Coleções de primeira classe.
  5. Um ponto por linha.
  6. Não abrevie.
  7. Mantenha todas as classes com menos de 50 linhas.
  8. Nenhuma classe com mais de duas variáveis ​​de instância.
  9. Sem getters ou setters.

1° - Um nível de recuo por método.

Quanto a muitos níveis de indentação dificulta a legibilidade e manutenibilidade. A ideia é que cada método faça apenas uma coisa, assim você pode extrair comportamentos para outros métodos garantindo um único nível de indentação e com isso estaremos aplicando uma técnica de refatoração chamada de **Extract Method**

Benefícios:

  • Facilita a leitura
  • Reduz a complexidade ciclomática
  • Favorece o "Princípio de responsabilidade única"
  • Encoraja o reuso

Ruim

function getReverseCanceledBad($reverses)
{
    $reverseCanceled = [];

    if (isset($reverses) && count($reverses)) {
        foreach ($reverses as $reverse) {
            if ($reverse['status'] === 'canceled') {
                $reverseCanceled[] = $reverse;
            }
        }
    }

    return $reverseCanceled;
}
Enter fullscreen mode Exit fullscreen mode

Bons


function getReverseCanceledGood(array $reverses): array
{
    return array_filter($reverses, 'reverseCanceled');
}

function reverseCanceled($reverse): bool
{
    return $reverse['status'] === 'canceled';
}

//Ou podemos usar função anônima

function getReverseCanceledGoodWithAnonymousFunction($reverses): array
{
    return array_filter($reverses, function ($reverse) {
        return $reverse['status'] === 'canceled';
    });
}

//Ou podemos fazer o uso de collection
**/
* @param $reverses Illuminate\Support\Collection
*\
function getReverseCanceledGoodWithCollection(Collection $reverses): Collection
{
    return $reverses->where('status', 'canceled');
}

//v2
function getReverseCanceledGoodWithCollectionArray(array $reverses): array
{
    $reverses = collect($reverses);

    return $reverses->where('status', 'canceled')->toArray();
}
Enter fullscreen mode Exit fullscreen mode

2° - Não use a palavra-chave ELSE.

Essa regra parece ser muito estranha, mas ela funciona muito bem com o conceito early return, que emprega o uso do “retorne seu valor o quanto antes”, ação que só é facilmente implementada dentro de funções, métodos ou loops.

A base deste exercício é sempre trabalhar com o return (ou continue), sabemos que ao cair em um return/continue o código abaixo não será executado o que ajuda na remoção dos “elses” ao inverter ou até modificar a validação antes usada.

Benefícios:

  • Facilita a leitura
  • Reduz a complexidade ciclomática
  • Evita a duplicidade de código

Em método
Ruim

class login
{
    private function isValid($userName, $password)
    {
        //Validation rule
    }

    private function generateToken($userName)
    {
        //Token rule
        return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
    }

    public function loginBad($userName, $password)
    {
        if ($this->isValid($userName, $password)) {
            return [
                'success' => true,
                'token' => $this->generateToken($userName)
            ];
        } else {
            throw new Exception("Invalid login", 1);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Bom:

class login
{
    private function isValid($userName, $password)
    {
        //Validation rule
    }

    private function generateToken($userName)
    {
        //Token rule
        return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
    }
    //Good Defensive
    public function loginDefensive($userName, $password)
    {
        if ($this->isValid($userName, $password)) {
            return [
                'success' => true,
                'token' => $this->generateToken($userName)
            ];
        }

        throw new Exception("Invalid login", 1);
    }

    //Good Defensive
    public function loginOptimistic($userName, $password)
    {
        if ($this->isValid($userName, $password) === false) {
            throw new Exception("Invalid login", 1);
        }

        return [
            'success' => true,
            'token' => $this->generateToken($userName)
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

Em loop
Ruim

foreach($reverses as $reverse){
   if($reverse->isResolved()){
      $reverse->sendNotification();
   }else{
      $reverse->increasePriority();
   }
}
Enter fullscreen mode Exit fullscreen mode

Bom

foreach($reverses as $reverse){
   if($reverse->isResolved()){
      $reverse->sendNotification();
      continue;
   }

   $reverse->increasePriority();
}
Enter fullscreen mode Exit fullscreen mode

3° - Envolva todas as primitivas e strings em classes.

Quando devemos usar?
Quando os tipos possuem validação, regras de negócio ou comportamento como CPF, email, IP e URL.

Benefícios:

  • Facilita a leitura
  • Reduz a complexidade ciclomática
  • Evita a duplicidade de código

Ruim

class User
{
    private $name;
    private $email;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }
}

new User('André', 'andre@foo.bar'); //pass
new User('Gustavo', '123456789'); //pass
Enter fullscreen mode Exit fullscreen mode

Bom

class Email
{
    private $email;

    public function __construct(string $email)
    {
        $this->valid($email);
        $this->email = $email;
    }

    public function valid(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('invalid format');
        }
    }
}

class User
{
    private $name;
    private $email;

    public function __construct(string $name, Email $email)
    {
        $this->name = $name;
        $this->email = $email;
    }
}

new User('André', new Email('andre@foo.bar')); //pass
new User('Gustavo', new Email('123456789')); //fail
Enter fullscreen mode Exit fullscreen mode

4° - Coleções de primeira classe.

Qualquer classe que contém uma coleção não deve conter nenhum outro atributo.
"A ideia inicial dessa regra diz que se você tiver um conjunto de elementos e quiser manipulá-los, é necessário criar uma classe dedicada (collection) apenas para este conjunto. Assim, ao atualizar aquele valor, com certeza será em sua collection.
Seguindo o comportamento dessa regra, você deixa os comportamentos relacionados.
O maior objetivo dessa regra é Aderir o Single Responsibility Principle (A letra S do S.O.L.I.D.) e High Cohesion."
Benefícios:

  • Facilita a leitura
  • Facilita a operação sobre a coleção

Um bom exemplo do uso de regra é a classe Collection do Laravel

$fruitList = new Collection([
    'Avocado', //Abacate
    'Pineapple', //Abacaxi
    'abyu', //Abiu
    'Apricot', //Abricó
    'Sloe', //Abrunho
    'Açaí', //Açaí
]);

$fruitList->first();
$fruitList->has('Açaí');
Enter fullscreen mode Exit fullscreen mode

5° - Um ponto por linha.

No PHP isso Significa uma seta(->) por linha, não encadear chamadas de métodos.
Isso é baseado na "Lei de Demeter"(Law of Demeter) que diz: "Fale com seus amigos mais próximos e não fale com estranhos".
Ex: $objectA->getObjectB()->getObjectC()
No conceito de orientação a objeto, o objeto "A" não deveria saber quem é o objeto "C", porque o objeto deve ter o conhecimento de quem ele esta conversando, pois do contrários nos estamos quebrando o conceito de encapsulamento.

Porém, existe uma exceção. Ela não se aplica a Fluent Interfaces e a qualquer coisa que se implemente o Chaining Pattern (muito usado por exemplo em Query Builders).

6° - Não abrevie.

Não devemos abreviar nome de classes, métodos ou variáveis pois outras pessoas que forem ler o seu código podem não entender o significado da sua abreviação.
Alguns motivos de termos nomes extensos:

  • Classes ou Métodos pode ter muita responsabilidade ou estar muito grande.
  • Muito código duplicado. No exemplo a baixo veremos que uma função poder ter varias abreviações. Ruins
function calcVolume($h, $w, $l)
{
    return $h * $w * $l;
}

function calcVol($h, $w, $l)
{
    return $h * $w * $l;
}

function calVolume($h, $w, $l)
{
    return $h * $w * $l;
}

function calVol($h, $w, $l)
{
    return $h * $w * $l;
}
Enter fullscreen mode Exit fullscreen mode

Bom

function calculateVolume($height, $width, $length)
{
    return $height * $width * $length;
}
Enter fullscreen mode Exit fullscreen mode

7° - Mantenha todas as classes com menos de 50 linhas.

Essa regras diz para termos classes com 50 linhas ou menos, Mas no PHP é um pouco difícil e podemos mantes uma media de 200 a 300 Linhas. Já para os métodos dever ter um tamanho que não precise usar o scroll para visualizar o seu conteúdo.
Quando não conseguimos implementar essa regra é preciso verificar se a classe ou método está com muita responsabilidade.
Benefícios:

  • Favorece o "Principio de responsabilidade única"
  • Encoraja o reuso
  • Melhora a segregação do código ### 8° - Nenhuma classe com mais de duas variáveis ​​de instância. "Essa é uma regra na qual, por ser muito simples, não precisaremos de um exemplo. No entanto, é uma das mais difíceis a serem implementadas, já que ela depende de um mindset totalmente diferente do habitual.

Ao termos no máximo duas variáveis de instância, isso irá garantir que estejamos respeitando novamente o Single Responsibility Principle e High Cohesion. Se sua classe tem mais que duas variáveis de instância, bem provável que ela esteja fazendo mais de uma responsabilidade."

9° - Sem getters ou setters.

Obedercer essa regra é seguir a "Tell Dont ASK", Que diz: "Não peça informações para realizar seu trabalho, apenas diga para o objeto que possui a informação para fazer o trabalho para você".

Ruim

class ShoppingCart
{
    protected $price;
    protected $item;

    public function getPrice()
    {
        return $this->price;
    }

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


$shoppingCart = new ShoppingCart();

$price = $shoppingCart->getPrice();

$shoppingCart->setPrice($price + 100);

$newPrice = $shoppingCart->getPrice();
Enter fullscreen mode Exit fullscreen mode

Bom:

class ShoppingCart
{
    protected $price;
    protected $item;

    public function getPrice()
    {
        return $this->price;
    }

    public function addPrice($price)
    {
        $this->price = $this->price + $price;
    }
}


$shoppingCart = new ShoppingCart();

$shoppingCart->addPrice(100);

$newPrice = $shoppingCart->getPrice();

Enter fullscreen mode Exit fullscreen mode

Referências:
Livro Thought Works Anthology
https://eminetto.medium.com/object-calisthenics-in-golang-58903ec8ce29
http://ninjadolinux.com.br/3031-2/
https://speakerdeck.com/marcelgsantos/melhore-a-qualidade-do-seu-codigo-com-object-calisthenics?slide=16

Top comments (0)