A trabalhosa jornada da qualidade de código
Um dos melhores exemplos de como pequenas atitudes e atenção a detalhes podem fazer muita diferença
What does code quality look like in mechanical terms? Multiple books have been written on the subject, so I won’t attempt to explain in-depth. But to give an overview, high-quality code is code that can be understood quickly. If a programmer can pick a method or class from a codebase at random and understand it deeply in a few minutes—not just its functionality and business logic, but everything it depends on and every way it might be used—without consulting too many other files, then the codebase is probably high-quality. Once this is achieved, the question of whether the code works correctly is far less concerning; it can be changed, fixed, or deleted without much risk or effort.
Source: Code quality: a concern for businesses, bottom lines, and empathetic programmers
Há centenas de boas referências sobre as diversas perspectivas de qualidade no processo de desenvolvimento de software - por exemplo, a maioria das coisas que eu gostaria de escrever sobre este assunto já está no texto acima (inclusive com vários links interessantes, recomendo a leitura). Porém, gostaria de complementar o assunto com um aspecto que não costuma ser abordado nas discussões: a relação entre a maturidade de times e de aplicações.
Cada time possui uma realidade, com complexidades relacionadas à escrita e validação de código, definição de funcionalidades e desenvolvimento das aplicações, e que se refletem na cultura da equipe e da empresa na qual ela está inserida. Por causa disso, não é raro encontrar diferentes processos e estruturas de controle de qualidade de código mesmo entre grupos de um mesmo produto ou empresa; embora quase todos entendam a importância do controle da qualidade na área de desenvolvimento de software, se a adoção de boas práticas não é feita de maneira gradual e consistente, o mais comum é que novas ferramentas e processos se tornem memorabília digital.
Vários fatores contribuem para a construção e manutenção de aplicações de qualidade que, de alguma forma, estão relacionados com a maturidade das equipes. Na base de todo time de desenvolvimento, testes deveriam ser entendidos como uma forma de auditar o código que foi desenvolvido para resolver um determinado problema. Todo código existe para resolver algum problema, mas às vezes a tradução deste problema para uma linguagem compreensível pelas máquinas não ocorre de maneira adequada. A tarefa de elaboração de testes direciona toda a atenção dos profissionais para o domínio do problema, e não da solução.
Essa auditoria pode ter dois níveis principais: unidade, quando queremos garantir que uma determinada instrução do código proposto para a solução do problema está se comportando conforme o esperado; e funcional, quando o objetivo é garantir que o problema para o qual o código foi criado está sendo atendido parcial ou integralmente. Testes unitários estão intrinsicamente associados com automação e, porque são executados o tempo todo durante o processo de elaboração do código, oferecem um feedback rápido sobre os impactos de novas adições à base de código da aplicação. Esta característica é muito bem-vinda especialmente quando a base de código é muito grande e complexa, pois permite que os incrementos sejam feitos respeitando o funcionamento do código já existente. Por isso, técnicas como TDD são populares e recomendadas, embora não seja para todos.
Os testes funcionais são uma excelente maneira de entender como os problemas do mundo real estão sendo resolvidos através de código. Muitos frameworks disponíveis no mercado permitem que os testes sejam escritos de tal forma que a compreensão do que está sendo executado seja simples, e que pessoas envolvidas no processo de desenvolvimento com menor ou nenhum conhecimento de código também assimilem o que está sendo validado (e o que deseja-se avaliar). Esta característica é essencial para permitir que se mantenham conversas de qualidade entre times de desenvolvimento e outros setores, como times de produto e de marketing, por exemplo. De forma resumida: testes unitários são ferramentas de desenvolvimento; testes funcionais são ferramentas de negócio.
Crédito da imagem: aqui
Outro conceito que contribui para equipes de alto desempenho é o processo de revisão por pares (code review), considerado pelos próprios desenvolvedores como a maneira mais eficiente de melhorar a qualidade dos códigos produzidos pelos times. Ler e revisar o código de outras pessoas é uma ótima escola de aprendizado com a prática, e interagir com elas sobre os motivos que as levaram a escrever tal código exercita habilidades de negociação, empatia e análise. Além disso, todo projeto tem uma série de regras não escritas que fazem parte da cultura do time e da empresa, e este conhecimento deve ser transmitido a todos os recém-chegados a um projeto. Anotar essas informações e transmiti-las todas de uma só vez é pouco eficiente e gera desmotivação, e o processo de revisão de código é uma excelente maneira de conduzir esta situação. Uma grande equipe tem como base testes bem elaborados e um forte processo de revisão de código.
Cuidar do que é valioso
Avançando em maturidade, é importante que o time estabeleça processos de validação de código automatizados, além dos processos de revisão manual. Por mais que existam validações automáticas, o processo de revisão de código se encerra com uma análise humana, o que pode ser uma complicação em times grandes e/ou com grandes bases de código. Normalmente, essa automação se materializa por meio de pipelines de CI/CD (em seus diferentes níveis), análise de código estático e execução de testes automatizados. A automação destas etapas resulta na preservação de um dos recursos mais escassos (e, portanto, valiosos) para uma equipe de desenvolvimento de software: o tempo. Rapidamente, a equipe consegue obter uma resposta se um determinado incremento de software está adequado às regras e parâmetros estabelecidos, permitindo uma correção rápida caso algo esteja fora do planejado.
Além disso, como a automação alivia desenvolvedores e testadores do fardo tedioso de realizar tarefas repetitivas, eles são capazes de dedicarem seus tempos e energias a atividades mais produtivas, como decidir sobre métricas de teste avançadas, executar testes que não podem ser automatizados, escrever novos casos de teste e assim por diante. Com a economia de tempo oferecida pela automação de testes e deploy, a equipe gasta menos tempo validando recursos recém-desenvolvidos, estando apta a trabalhar em mais atividades em um mesmo intervalo de tempo.
Finalmente, processos automatizados trazem exatidão, precisão e temporalidade para o controle de qualidade do desenvolvimento de software. Scripts são executados exatamente da mesma maneira todas as vezes, registrando os resultados com o mesmo detalhe e produzindo o mesmo resultado, sempre que são executados. Estes resultados podem ser comparados no tempo (por exemplo, cobertura de código por testes), oferecendo dados para que estratégias sejam criadas visando impedir que a base de código aumente sem o devido cuidado com a qualidade.
Crédito da imagem: aqui
Tudo virou código: mais coisas para validar
Sob a perspectiva de ambientes modernos de infraestrutura como código, a qualidade também se estende a como determinada aplicação irá interagir com o ambiente em que foi lançada. No passado, as infraestruturas eram elementos constantes na equação com as quais os programadores tinham de lidar, capitalizando os pontos fortes e contornando os problemas conhecidos. Hoje, a infraestrutura pode ser tão flexível quanto um algoritmo, e conhecer bem os potenciais e limitações dela pode alavancar aplicativos de alta qualidade.
Por outro lado, essa mesma flexibilidade exige que as aplicações sejam mais maduras em termos de como elas interagem com os demais elementos da arquitetura da solução, visando atingir maiores níveis de confiabilidade, estabilidade, resiliência e tolerância a falhas. No DevOps, essa prática costuma ser chamada de shift-left: programadores e administradores de infraestrutura precisam interagir cada vez mais para que o resultado final fique dentro/acima das expectativas.
Cada vez mais pessoas responsáveis por escrever e auditar um código em determinada linguagem de programação precisam expandir seus horizontes de atuação para além dos códigos diante de seus olhos, interagindo com outras áreas do time e das empresas para garantir que tudo saia como esperado. Dados e métricas precisam fazer parte dos modelos mentais de programadores e testadores, visando construir um ambiente que mantenha-se simples mesmo diante do aumento de complexidade de um código. Por fim, entender todo este cenário e traduzi-lo em relatórios de qualidade para os envolvidos no processo auxilia a aumentar a qualidade das decisões tomadas a respeito dos destinos técnicos e de negócios das aplicações.
Se você gostou deste texto, me ajudaria muito se pudesse clicar no botão de Like e colocá-lo nos seus bookmarks. Estas métricas me ajudam a entender o feedback do texto. Seria incrível se também pudesse compartilhá-lo. Obrigado!