Metaprogramming & Decorators
Todos devem ter passado por momentos em que fazer manutenção no código, implementar novas features, e melhorar as existentes se torna uma tarefa extremamente massante, certo? Se não passou, ainda vai passar, aguarde! (6)
No nosso caso aqui na IDwall, tivemos certa dificuldade com a nossa API: nada muito complexo, mas sentíamos a dor de como tudo foi feito, organizado e pensado na correria de uma startup que está a todo vapor.
A solução? Simples!
Vamos reescrever tudo!
Sim, não estamos falando de uma codebase imensa, com milhões de linhas de código, mas sabíamos que isso iria acontecer eventualmente, então decidimos não colocar no cofrinho mais uma dívida técnica.
Eu ainda não sabia como exatamente iria fazer, mas havia algumas coisas que queria levar em consideração na hora da implementação, e criar uma base sólida para futuros repositórios:
- Melhor organização do código
- Se livrar completamente do callback hell
- Facilidade no acesso e alteração de funcionalidades do sistema
- Maior segurança no código e diminuição de erros em runtime
- Consistência no formato das respostas e mensagens de erro
- Facilidade no desenvolvimento e manutenção
- Documentação boa e consistente
Uma das primeiras coisas que fiz para resolver esses problemas, foi tentar responder a seguinte pergunta:
O que eu preciso para escrever novas funcionalidades, dar manutenção nas atuais, e facilitar a entrada de novos desenvolvedores, sem muita chatice ou perigo de quebrar alguma coisa?
Por ter desenvolvido muito código em C#, Java e Python, a primeira coisa que veio em mente foi utilizar um pouco de metaprogramming (attributes/annotations/decorators em conjunto com reflection).
Mas afinal, o que é metaprogramming?
Metaprogramming is a programming technique in which computer programs have the ability to treat programs as their data. (…) The language in which the metaprogram is written is called the metalanguage. (…) The ability of a programming language to be its own metalanguage is called reflection or reflexivity. Reflection is a valuable language feature to facilitate metaprogramming.
Mas aí fica outra pergunta: como fazer isso em JavaScript?
Apesar de não ser suportado oficialmente, existem propostas de Decorators no ES7, e você já pode brincar com eles se transformar o código com BabelJS. No nosso caso, resolvi utilizar TypeScript por trazer algumas vantagens além dos decorators, e que quando utilizados corretamente, resolve os problemas #2 (async/await) e #4 (type-safety).
Pensando nisso e tentando responder a pergunta, cheguei no seguinte modelo:
Com isso consigo trabalhar com algumas informações que o próprio código disponibiliza. Não vou entrar em detalhes de como decorators funcionam, mas resumidamente, são funções que modificam (ou não) o comportamento de outras funções, classes ou atributos.
No nosso exemplo, o decorator service adiciona a classe HelloService em uma lista de “serviços disponíveis”, e handler adiciona a função hello dentro deste serviço. Todos as informações passadas para os decorators são gravadas utilizando uma biblioteca chamada reflect-metadata (ela também possibilita a consulta posteriormente). Legal né? :P
Na inicialização do servidor eu tenho acesso a todos os serviços declarados e as respectivas informações necessárias para configurar as rotas e os handlers dentro do hapi.js (framework que utilizamos para ser a base do nosso servidor).
Cool! Resolvemos em partes o problema #1 e #6! No que mais esse tal de “metaprogramming” pode nos ajudar?
Nesse novo exemplo, conseguimos resolver mais alguns problemas:
Na linha 11 resolvemos o problema #3 utilizando dependency-injection para conseguir acesso ao banco de dados de forma simples e segura, e o melhor: nada de singletons!
Na linha 22, começamos a resolver o problema #7 descrevendo nossos handlers com informações necessárias para se gerar uma boa documentação (mais sobre isso em um post futuro)
Na linha 27 e 29 utilizamos o async/await do TypeScript para resolver o problema #2 (callback hell nunca mais!)
Também na linha 27, resolvemos parte do problema #5 por forçar a tipagem do objeto (IAuthResult) que deve ser retornado pela função (E sim, o TypeScript vai gritar se você tentar retornar um objeto que não condiz com o tipo informado)
Nada muito diferente nesse, mas na linha 14 podemos ver um novo decorator: esse remove completamente a necessidade de autenticação quando a rota é acessada por algum cliente.
Em resumo, metaprogramming abre várias portas para automatização de coisas que são muito chatas de implementar e verificar manualmente. Montando uma base sólida, sabendo onde e como os decorators podem te ajudar, você pode aumentar drasticamente tanto a produtividade dos desenvolvedores como a qualidade do código, reduzindo também o custo de manutenção!
Todo programador deveria ao menos conhecer e ter brincado com isso uma vez na vida! :D


