PlugRoute: Refactoring, Clean Code, etc.
Tags: php, cleancode, programming, webdev
Published: 2022-08-16 21:26:00

Há aproximadamente 2 anos, resolvi criar uma [lib em PHP](https://github.com/erandirjunior/plug-route) como forma de praticar conhecimento, além é claro, de ajudar outros devs. Na época, não fiz nada muito complexo, a ideia foi fazer uma biblioteca de rotas, parecido com o que os frameworks tem, só que mais simples.

O tempo passou, adicionei mais recursos a ela, também cometi erros ao "abandonar" os testes, um grande pecado diga-se de passagem. A biblioteca não era mais tão confiável. Foi nesse momento que pensei em refatorar-la, e aplicar algumas técnicas que tinha aprendido.

---

## Passo 1: Analisar o código
Os códigos abaixo são referentes a [versão 4.4 da biblioteca](blob). No primeiro exemplo vamos ver como definimos uma rota:
```php
$route->get('/', function() {
    echo 'basic route';
});
```
O código acima é bem parecido com outras bibliotecas e até pode parecer inofensivo, porém se você for analisar com um pouco mais de calma, verá que temos um problema.

Agora te mostro um exemplo mais complexo, passando algumas configurações extras em um agrupamento de rotas:
```php
$route->group(['prefix' => '/sports', 'middlewares' => [OtherMiddleware::class], 'namespace' => 'Controllers\\'], function($route) {
    $route->get('/soccer', function () {
        return 'Index Page';
    })->name('index');
    
    $route->get('/soccer', 'SoccerController@index')->name('soccer');
    $route->get('/f1', 'SoccerController@index')->name('f1');
    $route->get('/boxing', 'BoxingController@index')->name('boxing');
    $route->get('/motorcycling', 'MotorcyclingController@index')->name('motorcycling');
    $route->get('/tennis', 'TennisController@index')->name('tennis');
});
```

## Passo 2: Entender o problema
Eu me incomodava ao ver o código acima, principalmente mas não exclusivamente pelos motivos abaixo:
- O _callback_ de um rota, no caso o segundo parâmetro, pode ser tanto uma função anônima quanto uma classe. Isso trás uma confusão para quem lê e usa o código.
- E o segundo motivo é que as configurações de um grupo de rotas são do tipo array, o que poderia causar confusão com os nomes das chaves ou mesmo informações que acabam dificultando a leitura do código.

Em resumo, eu **queria um código mais legível, testado, confiável. Era necessário refatorar essa biblioteca, tirar comportamentos estranhos, torná-la mais agradável** para quem a usasse.

## Passo 3: Refatorando
Comecei a refatorar a aplicação, aplicando conhecimentos adquiridos após a leitura do livro **Clean Code**, como também conhecimentos adquiridos de outros livros, artigos e vídeos. E aos poucos pude notar alguns ganhos, tanto estéticos quanto funcionais. Vejam como ficou o código na versão mais nova da lib:
```php
$this->plugRoute
    ->get('/people/{id}')
    ->callback(function (Request $request) {
        return $request->parameter('id');
    })
    ->rule('id', '\d+')
    ->name('people.get.id');
```
A primeira coisa que apliquei foi nomear melhor as ações para evitar ambiguidades e confusões, melhorar a tipagem dos parâmetros, e também usar o conceito de **Interface Fluente**, um nome chique para esse aninhamento de chamadas, muito utilizado em **ORMs**.

Um ponto que ficou bem mais legível, e que talvez ainda possa ser melhorado foi em relação ao _callback_ da rota, no exemplo acima tem uma ação para quem quiser usar uma função, e no exemplo abaixo, mostro um exemplo para quem quiser usar uma classe, dessa forma temos métodos diferentes que definem ações diferentes.
```php
$this->plugRoute
    ->get('/people/{id}')
    ->controller(MyController::class, 'method')
    ->rule('id', '\d+')
    ->name('people.get.id');
```

Em relação a definir um agrupamento de rotas, e consequentemente passar algumas configurações extras, foi definido métodos para cada configuração, evitando a passagem de array e tornando o código mais legível:
```php
$route
    ->middleware(MiddlewareA::class, MiddlewareB::class)
    ->middleware(MiddlewareC::class)
    ->namespace('\\MyNamespace')
    ->prefix('/sports')
    ->group(function ($route) {
        $route->get('/xadrez')->controller("PlugRoute\\Example\\Home", "rankingXadrez");

        $route->prefix('/f1')
            ->group(function ($route) {
                $route->get('/ranking')
                    ->controller("PlugRoute\\Example\\Home", "rankingF1");
            });

        $route->middleware('MiddlewareSoccer')
            ->prefix('/soccer')
            ->namespace("PlugRoute\\")
            ->group(function ($route) {
                $route->get('/champions-league')
                    ->controller("PlugRoute\\Example\\Home", "rankingChampions");
                $route->get('//europe-league')
                    ->controller("PlugRoute\\Example\\Home", "rankingEurope");
            });
    });
```

Na minha visão, o código ficou muito mais claro de entender e usar. Claro, toda essa refatoração teve um preço, alguns recursos foram removidos, seja em definitivo ou mesmo para serem adicionados posteriormente.

## Passo 4: Usar recursos da linguagem
Alguns recursos que utilizei podem até ferir o Clean Code, como no caso da passagem de middlewares, prefixos ou mesmo namespaces, em cada método dessas ações, você pode passar apenas um dado, ou vários em uma mesma chamada. Para mim, era um recurso precioso que não poderia deixar de usar nesse caso.

## Passo 5: Usar recursos da comunidade
As **PSRs** estão aí para criar uma padronização e consequentemente melhorar o código para um entendimento geral. Porém optei por não seguir algumas, por exemplo, a **PSR-7** não é implementada única e exclusivamente por motivos pessoais, lembrando que esse componente, inicialmente era algo somente para praticar, e hoje ainda é. Isso não quer dizer que você não possa utilizá-la em uma aplicação profissional, pelo contrário, eu mesmo já utilizei em 2 ou 3 projetos reais.

## Passo 6: Nível de testes
Uma coisa que havia falhado anteriormente era em relação a cobertura de testes, mas dessa vez fiz minha redenção, apliquei um conjunto de testes sobre a aplicação, **chegando a  97%** de cobertura, e com a tendência de que aos poucos e espero que com ajuda, possa chegar a 100%.

---

## E por fim a cooperação
Bem, refatorei a biblioteca, corrigir vários pontos, apliquei uma cobertura de testes minimamente decente, porém isso não é tudo, desde o primeiro commit, esse componente foi pensado em praticar e desenvolver minhas habilidades, mas muito além disso, foi pensando em receber ajuda comunitária, ou pelo menos ajudar devs iniciantes a entender como funcionam alguns recursos de um framework.

Então, esse não é um projeto fechado, se alguém quiser estudar o código, corrigir, melhorar ou adicionar algo, fique a vontade. Por hoje é só e até o próximo artigo.