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:
- Um nível de recuo por método.
- Não use a palavra-chave ELSE.
- Envolva todas as primitivas e Strings em classes.
- Coleções de primeira classe.
- Um ponto por linha.
- Não abrevie.
- Mantenha todas as classes com menos de 50 linhas.
- Nenhuma classe com mais de duas variáveis de instância.
- 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; }
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(); }
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); } } }
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) ]; } }
Em loop
Ruim
foreach($reverses as $reverse){ if($reverse->isResolved()){ $reverse->sendNotification(); }else{ $reverse->increasePriority(); } }
Bom
foreach($reverses as $reverse){ if($reverse->isResolved()){ $reverse->sendNotification(); continue; } $reverse->increasePriority(); }
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
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
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í');
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; }
Bom
function calculateVolume($height, $width, $length) { return $height * $width * $length; }
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();
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();
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)