Object Calisthenics

Sempre perguntamos se podemos melhorar nossa forma de programar, seja ela usando alguma tecnologia, algum framework, mas nem sempre pensamos na nossa escrita, como estamos escrevendo, como as pessoas estão vendo nosso código, ele está passando ao leitor o que se espera, ou deixa a pessoa confusa?

Pois bem, hoje vou falar sobre isso, como escrever melhor, para que possamos passar ao leitor de nosso código, o que se espera dele, vamos falar hoje sobre Calisthenics.

O Calisthenics foi publicado no livro The ThoughtWorks Anthology, e aplicado por Jeff Bay.

Ok, mas o que é Calisthenics? como isso pode ajudar a melhorar o meu código? Calma meu caro leitor, vamos chegar lá já.

A principal inspiração do Object Calisthenics é o SOLID ,então basicamente falando, Calisthenics é a aplicação de boas práticas e algumas regras para aprimorar a qualidade de código.

Ok, mas o que são essas regras e qual o foco delas?

Bom o foco dessas regras estão em, manutenibilidade, legibilidade, testabilidade e coesão/compreensão de código.

No Calisthenics existem 9 regras a serem seguidas, claro, o que estamos falando aqui não é uma receita de bolo, não é necessário aplicar em 100% das vezes, mas é necessário sim, tentar aplica-las em sua maioria, mas enfim, as regras são:

1 - Um nível de indentação por método

Tente escrever de forma mais simples e objetiva possível, com poucas linhas, onde um dev no futuro veja seu código e consiga compreende-lo sem perder muito tempo nisso.

  • Sem obedecer o nível de indentação:
  public function lerDados(): string
    {
        $lines = file ('http://www.example.com/');
        $conteudo = null;

        foreach ($lines as $line_num => $line) {
            $conteudo .= "Linha #<b>{$line_num}</b> : " . htmlspecialchars($line) . "<br>\n";
        }

        return $conteudo;
    }
  • Obedecendo o nível de indentação:
    public function lerDados(): string
    {
        $lines = file ('http://www.example.com/');
        $conteudo = null;
        $conteudo = $this->lerLinhas($lines, $conteudo);
        return $conteudo;
    }

    private function lerLinhas($lines, $conteudo): string
    {
        foreach ($lines as $line_num => $line) {
            $conteudo .= "Linha #<b>{$line_num}</b> : " . htmlspecialchars($line) . "<br>\n";
        }

        return $conteudo;
    }

2 - Não use ELSE

Ao criar uma condicional, evite usar o else, para isso criamos um padrão em tempo de execução, onde o nosso processo será interrompido imediatamente quando a nossa condição "else" for atingida.

  • Sem utilizar o Early Return:
    public function login($user, $pass): array
    {
       if (!empty($user) && !empty($pass)){
           // Executa o processo de login
       } else {
           return [
               "success" => false,
               "message" => "Usuário ou senha inválidos!"
           ];
       }
    }
  • Utilizando Early Return:
  public function login($user, $pass): array
    {
       if (empty($user) || empty($pass)){
           return [
               "success" => false,
               "message" => "Usuário ou senha inválidos!"
           ];
       }

       // Executa o processo de login

        return [
            "success" => true
        ];
    }

3 - Envolva tipos primitivos

Pensando na regra, temos que envolver todos os tipos primitivos no nosso código, em minha sincera opinião, cabe a nós, que estamos desenvolvendo o código, analisar se realmente aquele caso em questão, se é necessário envolver, a pergunta mais básica que eu aprendi durante a minha carreira é, qual a vantagem terei em envolver este tipo primitivo? Quando eu consigo responder essa pergunta, eu realmente envolvo. Confesso que esse "macete", aprendi acompanhando Vinicius Dias, e que me trás grandes vantagens no dia a dia.

  • Não envolvendo tipos primitivos
class Pessoa
{
    private string $nome;
    private string $sobrenome;
    private string $cpf;
    private string $genero;

    public function __construct($nome, $sobrenome, $cpf, $genero)
    {
        $this->nome = $nome;
        $this->sobrenome = $sobrenome;
        $this->cpf = $cpf;
        $this->genero = $genero;
    }

    public function getNomeCompleto()
    {
        return "{$this->nome} {$this->sobrenome}";
    }
}
  • Envolvendo tipos primitivos
class Pessoa
{
    private string $nome;
    private string $sobrenome;
    private Cpf $cpf;
    private string $genero;

    public function __construct($nome, $sobrenome, Cpf $cpf, $genero)
    {
        $this->nome = $nome;
        $this->sobrenome = $sobrenome;
        $this->cpf = $cpf;
        $this->genero = $genero;
    }

    public function getNomeCompleto()
    {
        return "{$this->nome} {$this->sobrenome}";
    }
}

class Cpf
{
    public string $cpfFormatado;
    public string $cpf;

    public function __construct($cpf)
    {
        if (!$this->valido($cpf)){
            throw new CPFInvalido($cpf);
        }

        $this->cpf = $cpf;
        $this->formatar($cpf);
    }

    private function valido($cpf): void
    {
        // Valida o CPF
    }

    private function formatar($cpf): void
    {
        // $this->cpfFormatado = formata o cpf;
    }
}

4 - Coleção

Quando você tem uma estrutura de dados, que possui um conjunto de elementos, ou realiza diversas funções em cima de um elemento, você pode criar uma coleção para tornar mais legível o código, vamos ao exemplo:

  • Não utilizando coleção de dados.
class User
{

    public function assistirVideo(): void
    {
        // Código que retorna o video ao usuário.

        // Define o video como assistido.
        // Adiciona ao contador de visualização.
    }


}
  • Utilizando coleção de dados.
class User
{
    private WatchedVideos $watchedVideos;
    private Map $video;

    public function assistirVideo(): void
    {
        $this->watchedVideos->watchedVideo($this->video);
        // Código que retorna o video ao usuário.
    }


}

class WatchedVideos
{
    public Map $video;

    public function __construct($video)
    {
        $this->video = $video;
        $this->count($this->video);
        $this->watchedVideo($this->video);
    }

    public function count($video)
    {
        // Busca o contador de visualização do video e add 1.
    }

    public function watchedVideo($video)
    {
        // Define o video como assistido.
    }
}

5 - Apenas "1" ponto por linha

A ideia é não encadear métodos, e sim, acionar um objeto que execute exatamente a função que precisamos executar.

  • Sem obedecer a regra
class Videos
{
    public Map $video;

    public function __construct($video)
    {
        $this->video = $video;
    }

    public function limitAge(User $user): void
    {
        $today = new \DateTimeImmutable();
        fn(Videos $video) => $video->getLimitAge() <= $user->getDb()->diff($today);
    }
  • Obedecendo a regra
class Videos
{
    public Map $video;

    public function __construct($video)
    {
        $this->video = $video;
    }

    public function limitAge(User $user): void
    {
        fn(Videos $video) => $video->getLimitAge() <= $user->age();
    }

}

class User
{
    private WatchedVideos $watchedVideos;
    private Map $video;

    public function assistirVideo(): void
    {
        // Código que retorna o video ao usuário.
        $this->watchedVideos->watchedVideo($this->video);
    }

    public function age(): int
    {
        $today = new \DateTimeImmutable();
        $dateInterval = $this->db->diff($today);

        return $dateInterval->y;
    }

6 - Não abrevie nome de variáveis

Quando estiver escrevendo um código, não abrevie os nomes das variáveis, como por exemplo escrever $ln ao invés de $lastName, ter nomes de variáveis bem escrito auxilia bastante na leitura e correto entendimento do código.

A não abreviação de variáveis também está escrita no livro Clean Code: A Handbook of Agile Software Craftsmanship de Robert C. Martin — “Meaningful Names”

7 - Mantenha as entidades pequenas

Toda classe bem escrita, segundo a regra, deve conter até 50 linhas, mas como falei no começo deste artigo, isso não é uma regra de bolo, temos apenas que tentar segui-las ao máximo possível, eu por exemplo, costumo a usar como limitador o máximo de 300 linhas, e acredito que fica bem tranquilo para qualquer dev, compreender o que ela faz. Mas indo ao conceito desta regra é reduzir ao máximo a quantidade de linhas de uma classe.

8 - Não Instanciar duas ou mais propriedades dentro de uma classe

A ideia desta regra é manter o conceito de responsabilidade única do SOLID, garantindo a coesão entre as classes. Uma boa forma de entender essa regra é responder uma pergunta, Essa classe que vou criar irá atender apenas o que ela foi projetada? Se você conseguir responder sim para essa pergunta, e escrever atentamente dentro dessa linha, você conseguirá seguir essa regra.

9 - Não usar Getter/Setter

Essa regra sempre trás discussão, mas é uma regra do Calisthenics, não deixar exposto os dados de uma classe quando utilizamos os getters e setters, podemos trocar os getters e setters deixando as propriedades da nossa classe como pública, claro que pensando em uma propriedade que não tenha uma regra.

Conclusão

Portanto falando sobre as regras, não conseguimos seguir à risca todas, até porque pode tornar um código simples em bastante complexo. Contudo, é bom tentar seguir a maioria delas, pois aplicando 3 regras destas já pode ser um ganho grandioso bem como a qualidade da sua escrita.