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)