Configurações importantes para projetos grandes em Angular

Projeto grande? Time grande? Será mantido por anos? Conheça algumas configurações para evitar que o projeto se torne uma nhaca

Ricardo Azzi Silva
Nov 7 · 8 min read
https://angular.io/

alguns anos atrás começamos a criar um projeto grande na empresa onde trabalho, uma intranet com Front-End escrito em Angular 2 (hoje migrado para Angular 7.2.0). O time inicial era gigante e acabamos criando alguns problemas por não termos como controlar a qualidade de todo conteúdo entregue (mesmo com uma estrutura de code review).

Hoje temos um time de cerca de vinte pessoas e já são bem mais amadurecidos do que a dois anos atrás quando começamos todos juntos na jornada que o sistema representava, nossos processos de code review também amadureceram, mas hoje temos alguns pontos do software que não nos orgulhamos e que poderiam ter sido evitados na época se tivéssemos o conhecimento de como ajudar o software a se blindar.


TypeScript no modo Strict

O TypeScript por si só é incrível. Ele acolhe o caos e liberdade do JavaScript que foi evoluído em 25 anos de linguagem com a ordem, padrões e conceitos evoluídos de linguagens fortemente tipadas no mesmo período de tempo.

Por consequência, o TypeScript se resguarda de certas características exclusivas, entre estas características estão os Union Types, isto é, tipos de dados compostos por outros tipos de dados:

O objeto Date no JavaScript pode receber argumentos do tipo string ou numérico para criar sua instância, os criadores da linguagem preferiram conceber o conceito de Union Types do que aplicar as técnicas ortodoxas de polimorfismo.
Um único caractere pode ser um tipo de dado isolado. Um conjunto limitado de caracteres pode ser um tipo de dado isolado.

Quando você ativa o modo Strict do TypeScript (não confundir com Strict do JavaScript), os valores null e undefined passam a se tornar tipos de dado independentes, dentro de sua aplicação, e isso te proíbem de efetuar o acesso de dados em um objeto que seja possivelmente nulo ou não definido, evitando desta forma os tão comuns Runtime Exception e impondo aos desenvolvedores envolvidos o uso de técnicas de programação defensiva.

Na imagem, o método é impedido de ser utilizado por ser possivelmente não definido (o modo strict está ativo).
No TypeScript o IF tem efeito de conversão. Dentro do IF é garantido que o tipo será string. Isso também funcionaria se o IF simplesmente validasse a existência do dado em cliente.nome ou para uso de instanceof, além de funções do tipo “type guard” ( https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards).

A configuração do TypeScript strict é simples: duas flags com o valor true devem ser inclusas no arquivo tsconfig.json, são elas: strict e skipLibCheck. Por algum motivo os criadores da linguagem acharam que seria uma ótima ideia efetuar, por padrão, a validação do modo strict nas bibliotecas de dependência de um projeto strict. Isso não faz absoluto sentido e para evitar com diversos problemas que você não possa resolver, habilite a segunda flag citada.


Linter opressor

O linter do TypeScript / Angular é realmente amplo e existem algumas configurações que não vem por padrão em um projeto Angular e algumas dessas configurações são essenciais para evitar perder tempo com defeitos simples ou com manutenções de códigos confusos. Todas as regras citadas são regras do linter e para ativá-las você deve incluí-las nos arquivo tslint.json, dentro do atributo rules.

Imports desnecessários

https://palantir.github.io/tslint/rules/no-unused-expression/, https://palantir.github.io/tslint/rules/no-use-before-declare/ e https://palantir.github.io/tslint/rules/no-empty/

Imports desnecessários e variáveis declaradas não utilizadas só existem para gerar confusão no momento de manutenção, essas duas configurações vão nos ajudar a anular isso. Evitar também métodos criados e não implementados também é importante.

Métodos com retorno implícito

A inferência de tipo é algo que desburocratiza muito a tipagem e tem sido amplamente adotada em outras linguagens, como no Java 10. A ideia é que você não precisa indicar o tipo explicitamente depois de já tê-lo sinalizado de alguma forma do corpo do código, e sendo assim, o TypeScript irá garantir que aquele tipo subentendido seja obedecido para sempre naquela variável.

Apesar de resolver um problema antigo de limpeza de código, a inferência de tipo pode ser um problema em alguns cenários, o mais comum destes é a não explicitação do tipo no retorno de um método.

O tipo do dado retornado pelo método no exemplo ao lado é de string | undefined implicitado, se o meu desejo era um retorno string eu posso estar gerando uma falha dentro de minha aplicação. É bem possível que depois de uma manutenção ou outra o método fique um pouco maior e acabe entrando em fluxos que seu desenho original não previa e, portanto, retornado uma assinatura inesperada. Para ajudar o TypeScript a conduzir os desenvolvedores naquilo que está sendo construído, impor tipo explícito para retorno de funções é conveniente e pode ser feito da seguinte forma:

https://palantir.github.io/tslint/rules/typedef/

Número mágicos

O nome é pejorativo. Se refere a números que foram jogados no corpo do código sem nenhum contexto.

Pode ser que no momento de desenvolvimento aquele número faça absoluto sentido para você, mas acredite, não fará tanto daqui alguns anos e associar um nome a este número é de extrema conveniência.

Pessoalmente, eu acredito que dar nomes para condições grandes contidas em ifs também ajudam na manutenção futura, mas não cheguei a buscar uma forma de impor isso via linter e nem sei se seria realmente saudável.

https://palantir.github.io/tslint/rules/no-magic-numbers/

Esta configuração de números mágicos eu ignoro o número um e zero, por que são comumente utilizados para acesso de primeiro registro de array, incremento, decrementos, valor padrão entre outros usos de dia a dia que são claros a qualquer desenvolvedor.

Complexidade ciclomática

Por último e o mais importante de todos é o controlador de complexidade ciclomática (o do meu projeto está em 18, mas eu mudei rapidinho para 7 só para tirar o print).

Complexidade ciclomática é a medição da complexidade de um método, isto é, quantos loops e verificações existem dentro dele de forma que deixe sua manutenção complexa. Métodos são nomes de trechos de lógica, quanto mais claro e enxuto o nome, melhor será a qualidade de vida para quem for dar manutenção (provavelmente você mesmo).

Trecho de meu código com complexidade ciclomatica em 10.

Pode ser tarde demais quando você rodar o linter de descobrir que seu método ficou complexo demais, então é sempre importante ter instalado uma extensão em seu Visual Studio para medir a complexidade ciclomática do método em tempo real. Eu uso o CodeMetrics, que me dá comentários e cores sobre a qualidade do que estou construindo.

Outros

Vasculhar a documentação do tslint é importante no começo de um projeto. Isso vai ajudar a te dizer para que lado você deseja que a manutenção deste siga.

Há outras possibilidades de lint que não estão funcionando hoje no tslint e que seria necessário a inclusão de um Sonar ou de outro lint no projeto, coisas como tamanho de classes, métodos e medição de complexidade dentro de templates.


Processo de desenvolvimento

O processo de desenvolvimento faz uso de ferramentes que auxiliam a lidar com equipes grandes.

Revisão de código e Git Flow

O processo de git flow pode ser um pouco complicado no começo, mas ele é importante para auxiliar o desenvolvimento de softwares compostos por uma equipe grande.

Em meu ambiente de trabalho são necessárias três aprovações de outros desenvolvedores sobre todos os códigos que eu desenvolvo, mesmo que não estejam relacionados com meu projeto.

Pode parecer muita coisa a aprovação de três entidades técnicas, mas nossa entrega é de pequenos pedaços de código que são fáceis de serem analisados e toda pequena entrega é sinalizada em um canal de Slack que temos na empresa. Um canal exclusivamente para isso.

No começo vai haver muito achismo e revisões que podem ser consideradas bobas demais, ou até mesmo implicação dispensável sobre nomes de variáveis, mas com o tempo as pessoas trabalhando na empresa que estão envolvidas com esta tecnologia em questão vão assumindo certos valores e objetivos que desejam para o software, no nosso caso já registramos um documento com as regras retiradas das solicitações de código mais comum.

Code Review, além de melhorar a qualidade do sistema também ajuda no nivelamento de conhecimento dos profissionais envolvidos e isso ajuda na manutenção.

Integração continua

O processo de integração continua é basicamente um conjunto de scripts que são executados para validação de cada versão gerada de seu software, ou mesmo cada pequena atividade executada por cada desenvolvedor.

O processo de integração continua pode ser executado por um Jenkins ou um Travis. É importante que a validação da build, nos testes unitários e do linter com frequência e de forma automatizada.

Conclusão

Desenvolver software é trabalhar com pessoas, e desejamos sempre trabalhar com qualidade de vida e desejamos sempre que nossas construções sejam elogiadas no futuro. Erro humano é natural, ter como construir um software que irá se blindar dos erros mais bobos e ter uma estrutura que permita que as pessoas da equipe aprendam uma com as outras é de suma importância para software que terá um longo tempo de vida.

Ricardo Azzi Silva

Written by

Programador e Analista de Sistemas nas Casas Bahia desde 2014

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade