Object Calisthenics em PHP
Tags: programming, objectorientation, objectcalisthenics, php
Published: 2022-09-09 19:18:31

*Object calisthenics* são um conjunto de boas práticas em orientação a objetos, propostas por Jeff Bay, que visam melhorar a “forma” dos nossos objetos. Ao todo são nove regras que nos ajudam a aumentar a qualidade nossos projetos, melhorando aspectos como manutenibilidade, extensibilidade, testabilidade e coesão.

Os exemplos a seguir são elaborados em PHP, mas a regras se aplicam a qualquer linguagem com suporte a orientação a objetos.

Resumo da ópera:

- Apenas um nível de indentação por método
- Não use *else*
- Envolva seus tipos primitivos
- Envolva suas coleções
- Use apenas uma chamada por linha
- Nunca utilize abreviações
- Mantenha classes e pacotes pequenos
- Tenha no máximo dois atributos por classe
- Não use *getters* e *setters*

Então vamos lá colocar nossos objetos para se exercitarem. 

## Apenas um nível de indentação por método

A primeira regra nos orienta a ter apenas um nível de indentação por método, ou seja, não devemos utilizar estruturas aninhadas. 

Veja a seguir que o método `hasAccess` verifica se o usuário tem acesso liberado caso tenha sido confirmado e o seu último acesso tenha acontecido nos últimos 90 dias:

```php
public function hasAccess(): bool
{
    if ($this->isConfirmed) {
        $today = new DateTimeImmutable();

        if ($this->lastAccess->diff($today)->days >= 90) {
            return false;
        } else {
            return true;
        }
    } else {
        return false;
    }
}
```

Podemos evitar o aninhamento de estruturas extraindo trechos em métodos privados que façam sentido, utilizando a estratégia de *early return* ou *fail first*. Melhorando o trecho apresentado anteriormente, ele fica assim:

```php
public function hasAccess(): bool
{
    if (!$this->isConfirmed) {
        return false;
    }

    $today = new DateTimeImmutable();

    return $this->lastAccess->diff($today)->days >= 90;
}
```

Neste exemplo representamos apenas `if` aninhados, mas a regra também se aplica a outras estruturas condicionais, laços de repetição e ao que mais poder ser aninhado.

## Não use *else*

Essa regra parece intimidadora e não fazer sentido, mas na verdade é muito fácil aplicá-la e realmente melhora a legibilidade do código — pudemos ver isso no exemplo anterior.

Sua aplicação é tão simples quanto é descrito: simplesmente não usamos *else*, em lugar nenhum. Em vez disso, podemos fazer uso das estratégias usadas anteriormente, como *early return* e *fail first*. O exemplo apresentado anteriormente também aplica essa regra.

## Envolva seus tipos primitivos

Quando as propriedades de uma entidade possuem algum comportamento (regra ou validação), é uma boa prática encapsulá-las em classes independentes, o que aumenta a coesão e possibilita o reaproveitamento de código.

Veja no exemplo a seguir, temos uma classe que possui um atributo `email`, que é validado ao ser atribuído no método construtor:

```php
class User
{
    private string $email;

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

    private function setEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid e-mail address');
        }

        $this->email = $email;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}
```

refatorando isso, temos:

```php
class Email
{
    public function __construct(private string $email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid e-mail address');
        }
    }

    public function __toString(): string
    {
        return $this->email;
    }
}
```

e:

```php
class User
{
    private Email $email;
    private string $name;

    public function getEmail(): string
    {
        return $this->email;
    }
}
```

Desta forma a class `User` não fica sabendo das regras relacionadas ao comportamento de `Email`, que também pode ser reaproveitado em outras entidades.

## Envolva suas coleções

A quarta regra nos recomenda envolver coleções em classes específicas que contenham apenas o comportamento relacionado à sua manipulação. Assim, também teremos maior coesão e encapsulamento das nossas classes.

Observe o seguinte cenário, em que a classe `User`, além de tratar seus próprios atributos, precisa lidar com o comportamento da lista de emails:

```php
class User
{
    private SplObjectStore $emails;

    public function __construct()
    {
        $this->emails = new SplObjectStore();
    }

    public function someMethod(): void
    {
        // do something...
    }

    public function addEmail(Email $email): void
    {
        $this->emails->attach($email);
    }

    public function removeEmail(Email $email): void
    {
        $this->emails->dettach($email);
    }

    public function countEmails(): int
    {
        return $this->emails->count();
    }
}
```

Ao extrair a coleção para sua própria classe, temos:

```php
class Emails
{
    private SplObjectStore $emails;

    public function __construct()
    {
        $this->emails = new SplObjectStore();
    }

    public function addEmail(Email $email): void
    {
        $this->emails->attach($email);
    }

    public function removeEmail(Email $email): void
    {
        $this->emails->dettach($email);
    }

    public function countEmails(): int
    {
        return $this->emails->count();
    }
}
```

Por fim, a classe `User` apenas faz uso da lista, sem se preocupar com a sua implementação:

```php
class User
{
    private Emails $emails;

    public function __construct()
    {
        $this->emails = new Emails();
    }

    public function someMethod(): void
    {
        // do something...
    }
}
```

Utilizar esta técnica em linguagens dinâmicas, que não suportam tipos genéricos, caso do PHP, é uma ótima forma de garantir a consistência de tipo dos itens manipulados pela coleção.

## Use apenas uma chamada de atributo ou método por linha

Essa regra nos diz que devemos utilizar apenas uma chamada de atributo ou método por linha — “ponto” na marioria das linguagens, “→” no PHP. 

Se o nosso código estiver realizando várias chamadas em sequência, isso é um forte indício de que uma parte precisa conhecer muito a outra para realizar alguma ação, violando o encapsulamento das entidades.

No próximo exemplo, temos uma classe que recebe um usuário e realiza alguma ação se ele tiver mais que 18 anos. Para isso, o atributo `birthdate` da classe `User` precisa ser acessado:

```php
class User
{
    public function birthdate(): DateTimeInterface
    {
        return $this->birthdate;
    }
}
```

```php
class SomeAction
{
    public function execute(User $user): void
    {
        $today = new DateTimeImmutable();
        $age = $user->birthdate()->diff($today)->y;

        if ($age < 18) {
            return;
        }

        // do something...
    }
}
```

O trecho `$user->birthdate()->diff($today)->y` possui três níveis de chamada, violando a regra. Para melhorar isso, a classe `User` pode prover um método que retorne a idade, assim a classe `SomeAction` pode utilizá-lo sem depender da sua implementação:

```php
class User
{
		public function age(): int
    {
        $today = new DateTimeImmutable();
        
        return $this->birthdate->diff($today)->y;
    }
}
```

```php
class SomeAction
{
    public function execute(User $user): void
    {
        if ($user->age < 18) {
            return;
        }

        // do something...
    }
}
```

## Nunca utilize abreviações

Usar abreviações, para muitos, é algo corriqueiro e “inocente”. Mas é uma má prática. Nós, desenvolvedores de software, precisamos lidar com uma grande carga cognitiva todos os dias, então quanto mais simples as coisas forem, melhor. E usar abreviações é algo que dificulta, e muito, as coisas!

Imagine ter que lidar com regras de negócio complexas e ainda ter que decifrar o que cada atributo ou método faz porque o nome está abreviado… isso é algo que pode, e deve, ser evitado. Novos membros da sua equipe, e até mesmo você, quando for mexer no seu próprio código tempos depois, irá agradecer se não usar abreviações.

Portanto, sempre dê nomes completos e elucidativos para atributos e métodos. Evite coisas do tipo:

```php
class User
{
    private string $fName,
    private string $lName;
    private DateTimeInterface $bd;
}
```

Prefira isto:

```php
class User
{
    private string $firstName;
    private string $lastName;
    private DateTimeInterface $birthDate;
}
```

## Mantenha classes e pacotes pequenos

Originalmente essa regra diz que devemos manter classes com no máximo 10 linhas e pacotes (*namespaces*) com no máximo 10 classes. Essa é uma das regras mais desafiadoras e difíceis de ser seguida. 

Mesmo que não seja sempre seguida à risca, é importante sempre procurar manter as classes e pacotes com o menor tamanho possível. A extração de tipos primitivos e coleções pode ser seu aliado na aplicação desta regra.

## Tenha no máximo dois atributos por classe

De forma semelhante a regra anterior, essa regra diz que devemos ter, no máximo, dois atributos por classe.

Mesmo que nem sempre seja possível aplicar isso à risca, é uma boa prática manter a menor quantidade de atributos numa classe. Novamente, a extração de tipos primitivos e coleções pode nos ajudar. Veja este exemplo:

```php
class User
{
    private string $firstName;
    private string $lastName;
    private DateTimeInterface $birthDate;

	  public function fullName(): string
    {
				return "{$this->firstName} {$this->lastName}";
		}
}
```

Os atributos `firstName` e `lastName` têm como finalidade representar um **nome**, então faz sentido envolver esses tipos primitivos numa classe específica e enxugar a classe `User`:

```php
class Name
{
    private string $firstName;
    private string $lastName;

    public function __toString(): string
    {
        return "{$this->firstName} {$this->lastName}";
    }
}
```

```php
class User
{
    private Name $name;
    private DateTimeInterface $birthDate;

	  public function fullName(): string
    {
				return "$this->fname";
		}
}
```

Refatorações e análises constantes da nossa base de código é o caminho!

## Não use *getters* e *setters*

Para encerrar com chave de ouro: essa regra que aparentemente bizarra. 

*“Como assim? Eu aprendi que em orientação a objetos devemos encapsular nossos atributos usando getters e setters!”*

É verdade, devemos prezar pelo encapsulamento, e este é um dos pilares de *object calisthenics.* Acontece que o uso irrestrito de *getters* e *setters* não nos provê absolutamente nenhum encapsulamento. Ter todos atributos públicos ou ter todos atributos privados com métodos *getters* e *setters* é exatamente a mesma coisa.

O que fazer então? Devemos utilizar métodos específicos para situações específicas, apenas quando for necessário. Neste exemplo vemos o que, talvez, estamos acostumados a fazer:

```php
class Video
{
    private bool $visible;

    public function getVisible(): bool
    {
        return $this->status;
    }

    public function setVisible(bool $visible): void
    {
        $this->visible = $visible;
    }
}
```

Uma pequena alteração nos nomes dos métodos já é o suficiente para enriquecer nosso domínio e deixar as regras mais claras:

```php
class Video
{
    private bool $visible;

    public function isPublic(): bool
    {
        return $this->visible;
    }

    public function publish(): void
    {
        $this->visible = true;
    }
}
```

Seguindo essa linha de raciocínio, se nenhuma entidade externa precisar saber o estado de um `Video`, o método `isPublic` simplesmente deve deixar de existir.

De maneira semelhante, se outra ação precisar ser executada sobre o atributo `$visible`, outro método com nome esclarecedor deve ser criado. Se estivéssemos utilizando o método `setVisible`, toda a regra de negócio seria resolvida externamente, e o encapsulamento completamente violado.

## Considerações finais

Vimos algumas regras mais simples, outras mais difíceis de serem aplicadas, mas a regra máxima que devemos seguir é: utilize o bom senso para definir quando alguma delas deve ou não ser utilizada. Realizar revisões e refatorações constantemente deve ser um costume, para que nós, como profissionais, e os nossos produtos, estejam em constante evolução. 

Se encontrou algum erro, tem alguma sugestão ou dúvida, não deixe de se reportar nos comentários ;-)