Criando Exceptions para impressionar no Teste Técnico
Tags: oop, php, braziliandevs, beginners
Published: 2023-10-10 01:03:22

Exceptions sempre vai ser um assunto constante quando o tópico for **Orientação à Objetos** e hoje vamos descobrir como criá-las como um artesão de software!
## Tabela de Conteúdo

* [1. Prólogo](#1-prólogo)
* [2. O que gostariamos de EVITAR](#2-o-que-gostariamos-de-evitar)
* [3. Refatoração 1: Criando Exceptions](#3-refatoração-1-criando-exceptions)
* [4. Design Patterns: Factory Pattern](#4-design-patterns-factory-pattern)
* [5. Refatoração 2: Refinando as Exceptions](#5-refatoração-2-refinando-as-exceptions)
* [6. Conclusão](#6-conclusão)

## 1. Prólogo

Quando eu comecei a estudar programação, um dos assuntos que sempre me assustava eram "erros" ou qualquer coisa que se relaciona à isso, porém após começar a estudar com mais frequencia, eu entendi que os erros e/ou **Exceptions** são muito mais amigas do que inimigas. Mas é claro que pra isso, você precisa entender como utilizar de um jeito interessante pro seu projeto.

No meu caso, acabava usando o trecho `throw new Exception()` pra literalmente qualquer coisa e me perdia facilmente na codebase, por conta de uma Exception genérica espalhada no meio de tantas outras. No início não tinha problema, eu ainda não trabalhava com time que tinha observabilidade, então tá tudo certo.

Passado o tempo, entrei em mais empresas FODAS e me deparei com excelentes implementações de Exceptions, principalmente o `Factory Pattern`. Esse método me deixou maravilhado em como as coisas podem ser `simples e elegantes`, mesmo quando se trata de erros.

E hoje vou mostrar pra vocês como criar um certo **gosto** em escrever exceções elegantes pra não encher seu código com 2km de mensagem de erro dentro da regra de negócio. 

## 2. O que gostariamos de EVITAR

Vamos começar dando um pouco de contexto para esse tutorial: imagine que você tá desenvolvendo um sistema de RPG e nele você precisa criar um inventário simples pro seu personagem. 

```
src
├── Item
│   └── Item.php
└── Player
    ├── Inventory.php
    └── Player.php
```

Dentro desse contexto, imagine que você está tentando equipar um item no seu personagem. Porém, é lógico que nós vamos colocar algumas regras de validação com suas devidas `Exceptions`.

```php
namespace DanielHe4rt\Player;

use DanielHe4rt\Item\Item;

class Player {

    public function __construct(
        public string $username,
        public int $level,
        protected Inventory $inventory,
    ) {

    }

    public function equipItem(Item $item): void
    {

        if ($this->inventory->hasItem($item)) {
            throw new \Exception(
                'Você não possui o item "' . $item->name . '". '
            );
        }

        if ($item->minLevel > $this->level) {
            throw new \Exception(
                'Você não pode equipar o item ' . $item->name . 'pois o nível minimo é ' . $item->minLevel 
            );
        }

        $this->setupItem($item);
    }


    private function setupItem(Item $item): void 
    {
        // faça coisas iradas
    }
}
```

Vimos que existem duas regras de validação que jogam exceções diferentes pro nosso cliente. E pasmem: isso funciona (num cenário que o código tá completinho) e cumpre o papel de validação... MASSSSS, depois que eu aprendi que em testes de emprego o que é visto é a **QUALIDADE DA ENTREGA** e não a agilidade que foi criado, meu mundo deu uma leve mudada para entender como transformar coisas que parecem "estranhas e feias" em coisas "simples e elegantes".

Nesse caso, eu gostaria muito de evitar duas coisas: 
* Exceptions genéricas;
* Exceptions que tomam conta de locais para regra de negócio.

Não entenda errado, as exceptions vão continuar onde elas estão, porém vamos melhorar a legibilidade do código.

## 3. Refatoração 1 : Criando Exceptions

```
src
├── Item
│   └── Item.php
└── Player
    ├── Exceptions
    │   ├── PlayerException.php
    │   └── PlayerInventoryException.php
    ├── Inventory.php
    └── Player.php
```

Beleza, agora passamos para parte que criamos nossa primeira `Exception` "customizada", onde só estendemos a Exception base para uma nova classe. Não é nada de outro mundo, mas já melhora nossa legibilidade e entendimento do código em alguns vários pontos.

```php
namespace DanielHe4rt\Player; 

class PlayerException extends \Exception
{}

class PlayerInventoryException extends \Exception
{}
```

E faremos uma refatoração simples na nossa função `equipItem()`, reposicionando as exceptions padrões pela nova exception que criamos.

```php

namespace DanielHe4rt\Player;

use DanielHe4rt\Item\Item;
use DanielHe4rt\Player\PlayerException;
use DanielHe4rt\Player\PlayerInventoryException;

class Player {

    public function __construct(
        public string $username,
        public int $level,
        protected Inventory $inventory,
    ) {

    }

    public function equipItem(Item $item): void
    {
        if (!$this->inventory->hasItem($item)) {
            throw new PlayerInventoryException(
                'Você não possui o item "' . $item->name . '". '
            );
        }

        if ($item->minLevel > $this->level) {
            throw new PlayerException(
                'Você não pode equipar o item ' . $item->name . 'pois o nível minimo é ' . $item->minLevel 
            );
        }

        $this->setupItem($item);
    }


    private function setupItem(Item $item): void 
    {
        // faça coisas iradas
    }
}
```

Com as novas Exceptions, agora sabemos exatamente do que se trata e principalmente onde buscar na nossa codebase quando essa Exception estourar.  É literalmente um `CTRL + ALT + F ` e pesquisar o nome **"PlayerInventoryException"**. Facilita sua vida, a vida do DevOps que vai meter isso num NewRelic/DataDog da vida e assim seguimos. 

Porém algo ainda me incomoda muito... Por quê essas mensagens gigantes estão no meio da regra de negócio? Misturar pt-br com en desse jeito é triste d+ pra mim desculpa amigos!! Vamos aprender um jeito de por isso debaixo dos panos, porém antes precisamos passar num tópico de Design Pattern chamado Factory!


## 4. Design Patterns: Factory Pattern

Se você já ouviu falar de **Design Patterns**, provavelmente já entende um pouco sobre o que isso resolve. Mas caso não, eu te explico!

> "Design Patterns são soluções genéricas para problemas genéricos." - Alguém por ai

Nessa ideia de problemas genéricos, uma galera se reuniu e começou a criar alguns principios de Design de Software pra você resolver problemas do dia a dia com uma certa agilidade. Os Design Patterns são divididos em três tipos:

- Padrões Comportamentais;
- Padrões Criacionais;
- Padrões Estruturais.

e você pode ler mais sobre eles no site [https:/refactoring.guru](https:/refactoring.guru) e eu recomendo MUITO pra qualquer pessoa desenvolvedora explorar essa documentação e se auto desenvolver. Ok, mas vamos focar nele, o tal do [Criacional de Fábrica (ou Factory Method)](https://refactoring.guru/pt-br/design-patterns/factory-method). 

A ideia desse padrão é você criar objetos sem ter que instaciar mil coisas em classes diferentes, você literalmente fabricar alguma instância de algo e só receber numa chamada simples de alguma função. No mundo da programação existem centenas de milhões de chamadas como `Models::make()`, `Exception::create()`, `ApiQualquer::factory()` pra você não ter que acessar o método construtor de uma respectiva classe.

Dando o exemplo de um Client de API, onde deixamos o construtor modular pra caso precisemos trocar a chave e segredo PORÉM ainda damos a possibilidade de uma chamada rápida fabricando o objeto final: 

```php

class GithubClient {

	public function __construct(string $clientId, string $clientSecret) 
	{
		$this->client = new Client([
			'clientId' => $clientId,
			'clientSecret' => $clientSecret,
		]);
	}

	public static function make(): self
	{
		return new self(
			env('github.client_id'),
			env('github.client_secret'),
		);
	}

	public function getUserByUsername(string $username = 'danielhe4rt'): array 
	{
		// faça uma chamada pro github..
	}
}

// Chamando sem fabricar o objeto

$client = (new GithubClient('client-id-foda', 'client-secret-foda'))
	->getUserByUsername('danielhe4rt');

// Chamando usando o Factory 

$client = GithubClient::make()
	->getUserByUsername('danielhe4rt');

```

Nós fizemos uma chamada estática fabricando todos os parâmetros de um jeito sucinto. Esse "make/factory" ou o que você quiser chamar, pode ser um método bem extenso dependendo do que você for injetar mas isso não chega a ser problema.

Mas de qualquer forma, vimos que a legibilidade usando o Factory Pattern foi melhorada, claro que você pode colocar melhores nomes pras funções mas na base é isso. Agora voltemos para nossas exceptions!
## 5. Refatoração 2: Refinando as Exceptions

Show, aprendemos um pouquinho sobre o factory, agora vamos aplicar. 

Criaremos um método de factory para nossa exception que faça sentido com o contexto do que tá acontecendo. Pois é, nada de usar "make" ou "create" nesses momentos. Exception precisam contar minimamente uma história pro usuário ou pro desenvolvedor do quê tá acontecendo e vamos focar nisso.

Depois de uma leve refatoração na nossa `PlayerInventoryException`, temos o resultado de:

```php
class PlayerInventoryException extends \Exception 
{
    public static function itemNotFound(string $itemName): self
    {
        $message = sprintf('Você não possui o item "%s".', $itemName);
        return new self(
            message: $message,
            code: 403 // Forbidden
        );
    }
}
```

E após chamarmos essa factory em nosso código, podemos perceber uma melhora de leitura e mantenibilidade já que isolamos as informações da Exception dentro da mesma.

```php
public function equipItem(Item $item): void
{

	if (!$this->inventory->hasItem($item)) {
		throw PlayerInventoryException::itemNotFound($item->name);
	}

	if ($item->minLevel > $this->level) {
		throw new PlayerException(
			'Você não pode equipar o item ' . $item->name . 'pois o nível minimo é ' . $item->minLevel 
		);
	}

	$this->setupItem($item);
}
```

Agora refatorando a próxima, temos a mesma ideia de trocar a PlayerException. 

```php
class PlayerException extends \Exception 
{
    public static function lowLevelForThisEquipment(string $itemName, int $itemLevel): self
    {

        $message = sprintf(
            'Você não pode equipar o item %s pois o nível minimo é %s.',
            $itemName,
            $itemLevel
        );

        return new self(
            message: $message,
            code: 403 // Forbidden
        );
    }
}
```

e agora nossa `equipItem()` tá ó, uma maravilha!

```php
public function equipItem(Item $item): void
{
	if (!$this->inventory->hasItem($item)) {
		throw PlayerInventoryException::itemNotFound($item->name);
	}

	if ($item->minLevel > $this->level) {
		throw PlayerException::lowLevelForThisEquipment($item->name, $item->minLevel);
	}

	$this->setupItem($item);
}
```

Tá uma maravilha? Tá. Mas ainda tem algo me incomodando... Por quê passar os tipos primitivos sendo que essas exceptions estão se "comunicando" com classes?

Ficaria bem mais limpo se passarmos a referência do objeto inteiro pra Exception e lá dentro ela resolve o que precisa usar. Afinal, vai que precisamos de mais algo num futuro e não custa nada já deixar bonitinho, né?

```php
namespace DanielHe4rt\Player;

use DanielHe4rt\Item\Item;

class PlayerException extends \Exception 
{
    public static function lowLevelForThisEquipment(Item $item): self
    {

        $message = sprintf(
            'Você não pode equipar o item %s pois o nível minimo é %s.',
            $item->name,
            $item->minLevel
        );

        return new self(
            message: $message,
            code: 403 // Forbidden
        );
    }
}

class PlayerInventoryException extends \Exception 
{
    public static function itemNotFound(Item $item): self
    {
        
        $message = sprintf('Você não possui o item "%s".', $item);

        return new self(
            message: $message,
            code: 403 // Forbidden
        );
    }
}

```

E o resultado final do nosso método fica só o charme, tendo exceptions encapsuladas e ainda vai te gerar ótimos feedbacks na sua entrevista de emprego.

```php
namespace DanielHe4rt\Player;

use DanielHe4rt\Item\Item;
use DanielHe4rt\Player\PlayerException;
use DanielHe4rt\Player\PlayerInventoryException;

class Player {

    public function __construct(
        public string $username,
        public int $level,
        protected Inventory $inventory,
    ) {

    }

    public function equipItem(Item $item): void
    {
        if (!$this->inventory->hasItem($item)) {
            throw PlayerInventoryException::itemNotFound($item);
        }

        if ($item->minLevel > $this->level) {
            throw PlayerException::lowLevelForThisEquipment($item);
        }

        $this->setupItem($item);
    }


    private function setupItem(Item $item): void 
    {
        // faça coisas iradas
    }
}
```


## 6. Conclusão

Exceptions são de longe uma das coisas mais "chatas" de se lidar. Afinal ninguém quer erro estourando na tela do cliente, mas no geral elas só precisam ter uma boa escrita e adicionar um pouquinho de charme com chamadas estáticas e **PLAU** tu ganha um elogio e ponto positivo na entrevista de emprego.

Espero que vocês tenham curtido o conteúdo e não esqueça de me seguir nas redes sociais!


Referência: [Formatting Exception Messages](https://www.rosstuck.com/formatting-exception-messages)