Como mantemos a qualidade de software nos projetos do Luizalabs

No luizalabs, trabalhamos em centenas de projetos diariamente para atender as demandas que surgem. Estas acabam constantemente se tornando alterações em projetos e precisamos garantir, ou ao menos, conseguir diminuir de forma considerável que novas funcionalidades conforme forem entregues ao longo do tempo não quebrem as que já existem e continuem funcionando perfeitamente.

Sabemos que um software em geral, o tempo e esforço gasto é muito maior para manutenção e evolução do produto, seja por correção de bugs ou entrega de novas features do que a criação em si.

Isso ocorre porque possivelmente quando os desenvolvedores alteram uma aplicação, acabam introduzindo algum bug ou complexidade adicional no projeto, incrementando cada vez mais débitos técnicos.

Custo do software aumenta conforme degradação da qualidade de código. Fonte: https://image.slidesharecdn.com/cleancode-170530131947/95/the-art-of-clean-code-6-638.jpg?cb=1527338523

Não é incomum vermos por aí sistemas legados em que o custo de manutenção fica extremamente caro (em termos de esforço) ao ponto de inviabilizar quaisquer alterações, devido ao software se tornar muito “frágil”.

Contextualizando a palavra “frágil”:

  • O sistema não possui cobertura de testes;
  • Não é feito refactoring (melhorias) de forma constante;
  • As alterações realizadas não são revisadas por nenhum outro membro da equipe;
  • Não possui padrões de arquitetura a serem seguidos.

Por esta razão, os times do luizalabs são orientados a manterem princípios no desenvolvimento de aplicações. Alguns deles envolvem: escrita de código, cloud first, segurança, performance dentre vários outros.

O motivo é facilitar que novos desenvolvedores ou próprios membros da equipe levem um tempo menor para curva de aprendizado e se sintam para realizar contribuições em projetos.

Seguem abaixo alguns deles:

  • Testes unitários na entrega de novas funcionalidades e correção de bugs;
  • Revisão de código (code review) para entrega de novas funcionalidades por outros membros da equipe;
  • Testes de integração;
  • Integração contínua;
  • Ambiente pré-prod (sandbox);
  • Monitoramento de aplicação por meio de logs e dashboards;

Vamos explorar cada uma delas de forma resumida, pois no detalhe cada um destes tópicos dariam um post.

Vale lembrar que cada time (squad) dentro do luizalabs possui autonomia para escolher as ferramentas que melhor se adequarem a estes princípios.

Testes Unitários

Por meio do TDD (Test Driven Development), deixamos os próprios testes guiarem o desenvolvimento e design da aplicação.

Ao escrever testes previamente à funcionalidade em si, temos uma ideia clara sobre o propósito do código que estamos desenvolvimento.

Outro ponto que vale citar: este ciclo de feedback é feito de forma constante para o desenvolvedor. Por exemplo: se percebermos que uma determinada funcionalidade estiver começando a se tornar complexa para testar, o que isso pode nos dizer? Provavelmente é um indício de más práticas e/ou problema da forma que o design da aplicação está sendo desenvolvida. Em outras palavras: o TDD força o desenvolvedor a ter coesão e menor acoplamento no código.

O ciclo de feedback citado acima é feito de forma iterativa:

  1. Teste o resultado esperado ao executar o comportamento (irá falhar, pois ainda não foi implementado/corrigido nada);
  2. Arrume a implementação do comportamento;
  3. Teste o resultado esperado (desta vez irá passar);
Ciclo de feedback no TDD. Fonte: https://cdn.softwaretestinghelp.com/wp-content/qa/uploads/2012/11/Unit-testing-cycle.jpg

Resumindo: os testes unitários garantem que cada componente está funcionando da forma que se espera. Como em tese iniciamos sempre pelo teste, garantimos que nenhum código escrito em produção entre sem cobertura.

Com a cobertura de testes, os processos de refactoring e melhorias no projeto se tornam muito mais seguros: a equipe se sente muito mais confortável em aprimorá-lo, visto que basta rodar os testes e verificar se nenhum teste unitário falhará.

Também servem como uma documentação do projeto para os desenvolvedores, já que ajudam a entender o comportamento do sistema em questão.

A maioria das linguagens de programação possuem um framework que facilitam a criação de testes unitários.

Toda funcionalidade entregue em nossos projetos precisam no mínimo conter testes unitários.

Code Review

A cultura de code review é extremamente importante para identificar possíveis problemas e melhorias no código. Fonte: https://smartbear.com/learn/code-review/what-is-code-review/

Geralmente enquanto estamos desenvolvendo algo, precisamos ter um nível alto de concentração. Porém de forma involuntária, podemos deixar alguns problemas desapercebidos enquanto codificamos.

Por isso, um desenvolvedor não simplesmente codifica e entrega a funcionalidade. O resultado é submetido para que outros membros da equipe revisem o que foi feito. Alguns problemas mais comuns que podem ser encontrados no processo de revisão, podemos citar:

  • Bugs introduzidos;
  • Melhorias e refactoring;
  • Cobertura de testes unitários;
  • Funcionalidade não estar de acordo com a proposta inicial.

Este processo é conhecido como code review.

Aqui no luizalabs, temos uma cultura forte de code review. Os desenvolvedores estão sempre abertos à críticas construtivas em seu código. Vemos isso como uma oportunidade de aprendizado tanto para quem dá, tanto para quem recebe um feedback no processo de revisão.

Testes de Integração

Enquantos os testes unitários focam em geral uma função ou uma “unidade” da aplicação, os de integração visam garantir que uma funcionalidade envolvendo mais partes do sistema está funcionando conforme o esperado. Em outras palavras: cada tipo de teste tem um propósito diferente.

Podemos dizer que os testes de integração estão a um nível acima dos testes unitários, pois garantem que as unidades ou pequenos comportamentos da aplicação quando trabalham em conjunto, estão funcionando da forma esperada.

Integração Contínua

Automatização de tarefas por meio de integrações contínuas

Ao final do desenvolvimento, é necessário que algumas premissas estejam válidas para que o código possa entrar em produção. Caso contrário, podemos cair no problema que citamos no início desse post: o código se tornar muito custoso para alterá-lo. Alguns exemplos são:

  • Padrões de escrita;
  • Testes unitários passarem;
  • Não haver vulnerabilidades de segurança;
  • Testes de integração passarem;
  • A cobertura de testes atingir um determinado nível.

Explicamos acima a prática de code review e possíveis problemas que podem ser detectados durante o processo.

Porém humanamente falando, seria inviável pessoas da equipe verificarem e revisarem cada passo citado acima. Ainda pior: imagine que seja necessário alguma alteração em algo que está sendo revisado e você tenha que executar cada step toda vez.

Essas tarefas podem ser automatizadas de forma relativamente fácil hoje em dia: basta configurar e descrever estes passos rotineiros num processo de integração contínua.

Neste caso, toda vez que alguém enviar quaisquer códigos ao repositório, o processo de integração contínua será acionado e executado.

No luizalabs, utilizamos ferramentas de integração contínua junto ao versionador. Isso significa que toda vez que um desenvolvedor (como utilizamos principalmente o github, fazemos isso por meio de pull requests, que basicamente é uma solicitação de introduzir algum código no repositório) envia um código, um novo processo de integração contínua é disparado e passando por cada passo de forma automática. Caso algum dos passos falhem, o desenvolvedor será alertado com os detalhes do motivo do build não ter tido sucesso.

Exemplo de arquivo de configuração com os passos que são executados em uma integração contínua
Exemplo de processo de integração contínua que teve problemas
Exemplo de processo de integração contínua que se comportou conforme o esperado

Features novas só são aceitas caso (além do processo de code review citado acima) o processo de integração contínua esteja totalmente ok.

Ambiente Pre Prod (staging)

Ao terminar de desenvolver uma nova funcionalidade, precisamos simular de alguma forma seu comportamento como se estivesse em produção.

Daí a importância do conceito staging: um ambiente com menos dados, porém suficiente a garantir (com exceção de performance) que a funcionalidade em si está se comportando conforme o esperado antes de ir de fato para produção e entregar valor ao negócio.

Após estes passos, podemos realizar o deploy da aplicação. A maioria de nossos projetos utilizam o Teresa: uma ferramenta PaaS open source desenvolvida internamente que roda em cima do Kubernetes para deploy de aplicações por comandos (similar ao Heroku).

Monitoramento

Uma coisa é fato: as coisas falham. Independente da origem do problema ser infraestrutura (rede, por exemplo) ou a própria aplicação em si, mas com certeza falhará em algum momento.

Expomos a maioria de nossos serviços via APIs e estas se comunicam umas com as outras via rede. Caso alguma delas falhe, podemos ter um impacto em no fluxo de compra e impactar o cliente.

Por isso um monitoramento efetivo é vital: por meio de logs, dashboards e alertas em tempo real, os times conseguem verificar quando algum sistema não estiver se comportando conforme o esperado e atuarem sem maiores impactos no negócio.

No luizalabs, utilizamos algumas ferramentas e métodos tais como:

  • Healthcheck de aplicações;
  • Sistema de análise de logs e aplicações;
  • Alertas em tempo real para o time responsável pela aplicação com problemas;
  • Dashboards temporais e tempo real com métricas da aplicação.

Ferramentas utilizadas

Por fim, vamos encerrar o post com algumas das ferramentas que nos facilitam no dia a dia a implementar todo o processo de desenvolvimento e estão alinhadas com os princípios que citamos acima.

Como já foi citado, mas vale lembrar, cada squad tem autonomia para atuar com ferramentas que auxiliem no processo de desenvolvimento.

Pytest

O pytest é um framework open-source para criação testes unitários utilizando a linguagem Python. É altamente escalável e a curva de aprendizagem é relativamente baixa. Utilizamos fortemente o pytest em nossos projetos para testes unitários.

Link: https://docs.pytest.org/en/latest/

Logentries

O logentries é uma ferramenta que permite a análise e criação de dashboards baseada em logs coletados na aplicação. Sua configuração na aplicação é relativamente simples.

Por meio dela, conseguimos ter um melhor entendimento de como a aplicação está rodando, bem como possíveis erros a serem verificados.

Link: https://logentries.com/

Grafana

É uma ferramenta open-source capaz de conectar em diversas bases de dados e realizar criação de gráficos de forma temporal.

É possível obter e visualizar diversos tipos de métricas tais como: tempo de resposta, I/O, basta apenas que a fonte de dados disponibilizem estes dados.

Aqui no luizalabs, utilizamos para criar gráficos que nos dizer dados da aplicação envolvendo:

  • Performance (tempo de resposta);
  • Tráfego (nº de requisições);
  • Total de instâncias;
  • I/O;
  • Armazenamento.

Link: https://grafana.com/

Github

Ferramenta para versionamento de código em cloud. Também é possível integrar softwares terceiros a fim de melhorar o processo e qualidade do software.

Aqui no luizalabs, utilizamos softwares de terceiros para integração contínua.

Link: https://github.com/

CircleCI

Ferramenta que permite criar processos de integração contínua em projetos hospedados no github. A integração e criação do processo é relativamente simples: para configuração dos passos basta fornecer um arquivo yaml contendo os passos necessários.

Link: https://circleci.com/

Teresa

Como já citado, o Teresa é uma ferramenta feita pelo próprio labs para fazermos deploy de forma simplificada das aplicações, sem preocupações envolvendo infraestrutura.

Caso tenha interesse em conhecer mais, o Diego Garcia descreve muito bem em seu post como o Teresa funciona e a sua adoção no luizalabs:

https://medium.com/luizalabs/como-aceleramos-a-ado%C3%A7%C3%A3o-do-kubernetes-em-larga-escala-5ea0e6b71cf8

Link: https://github.com/luizalabs/teresa