Quando trabalhamos com sistemas grandes, com diversas integrações, camadas, recursos e/ou que possuem muitos usuários, invariavelmente acabamos aprendendo algumas lições. Descobrimos que qualquer mínima alteração pode causar sérios problemas e comprometer todo o projeto. Uma linha de código alterada em uma camada pode influenciar em outra distante de uma forma que você jamais seria capaz de imaginar. Um pequeno novo recurso implementado pode derrubar toda a sua Infraestrutura em questão de minutos ao ser entregue para milhares de usuários.

Com isso em mente, a comunidade evoluiu ao longo dos anos desenvolvendo diversas técnicas e metodologias para lidar com essas questões. Começamos a melhorar nosso processo de desenvolvimento, realizando entregas menores, é mais seguro entregar ao usuário pequenas alterações toda semana do que 6 meses de código acumulado de uma vez.

Passamos a automatizar nossos testes para garantir que uma alteração feita não prejudique alguma parte do sistema. Incluímos profissionais de QA para ajudar na qualidade da entrega, realizamos testes de carga para ver se o servidor vai aguentar o tranco, criamos ambientes de homologação que simulam produção para testes mais confiáveis, e por aí vai…

Tudo isso com certeza diminuiu os riscos e tornou o trabalho com grandes sistemas infinitamente melhor. Mas… sejamos francos: Ainda assim enviamos para produção códigos que geram problemas.

E as questões que sempre ficam são:

  • Por que isso acontece?
  • Como podemos melhorar para que isso não aconteça?

Depois de muitas conversas, muitos anos trabalhando e diversas experiências, cheguei a algumas conclusões.

Navegue pelo índice

    O resultado da combinação de Software com Seres Humanos é imprevisível

    Sim, quanto mais rápido aceitarmos isso, melhor. É claro que temos o controle sobre muitas coisas e conseguimos prever muito do que irá acontecer, mas nunca é 100%.

    Podemos escrever 10 mil linhas de testes para o código, testar manualmente a funcionalidade em 50 cenários diferentes e liberá-la inicialmente para apenas 10 beta testers… Infelizmente isso nunca será uma garantia absoluta contra os bugs.

    O motivo é simples: o comportamento do usuário pode sempre nos surpreender e quanto maior o sistema e maior a quantidade de usuários, maiores são as chances de isso acontecer. Alguns exemplos:

    Em sistemas grandes e complexos, diversas camadas e funcionalidades podem conversar entre si e, algum dos seus inúmeros usuários pode criar uma conta X que realiza o cadastro Y que por algum motivo envia o formulário X para a tela Y e… bum! Algo dá errado.

    Algum usuário pode ter uma conta de 5 anos no seu sistema e todas as ações dele acumuladas nesses 5 anos de utilização geram uma combinação de dados e interligações única no sistema que por algum motivo, com o seu novo código, geram algum bug.

    Logo, a conclusão aqui é: Somente quando o código estiver disponível para todos os usuários reais é que saberemos realmente se tudo deu certo ou não.

    Então a solução é simples: Escreva o código e mande para produção! Certo? Quase… Não queremos estragar a experiência do usuário e prejudicar o negócio enviando possíveis bugs para produção. Como resolver então essa situação?

    Bem, é aí que entram duas técnicas que podem ajudar muito a lidar com este dilema: Feature Toggles e Dark Launching.

    Feature Toggles

    Martin Fowler (leitura indispensável para quem trabalha com software) fala bastante sobre Feature Toggles. É uma técnica que permite alterar o comportamento do seu sistema sem a necessidade de alterar código.

    Sendo mais prático, significa conseguir desligar e ligar novos recursos e alterações no sistema.

    O código que realiza o cadastro do usuário foi reformulado e agora está muito mais rápido? Ao invés de simplesmente substituir o código antigo pelo novo, você pode colocar uma “chave” em algum ponto do seu código que dirá qual versão dele utilizar.

    Se estiver aberta, utilizamos o código novo, se estiver fechada, utilizamos o código antigo.

    O mesmo vale para novos recursos. Agora o usuário consegue cadastrar uma foto no seu perfil do sistema? Bacana, podemos colocar uma chave nisso também. Se estiver ligada, o usuário poderá ver e alterar a sua foto do perfil, se estiver desligada, o recurso não existirá, como antigamente.

    A ideia é que com apenas um clique os responsáveis pelo sistema possam ligar e desligar tais alterações ou novos recursos.

    O motivo disso é simples: Temos mais segurança para lançar algo.

    O novo código de cadastro do usuário está com um bug que só conseguimos ver em produção? Tudo bem, desligamos a chave e o código antigo volta em seu lugar para que tudo funcione enquanto trabalhamos na correção do código novo.

    O recurso de fotos no perfil está sobrecarregando nossos servidores mais do que prevíamos e o sistema todo irá cair por conta disso? Sem problemas, vamos desligar a chave e trabalhar na otimização da funcionalidade ou melhoria da nossa infraestrutura.

    Se sabemos que mesmo com muitos esforços algo de grave pode acontecer no lançamento real de algo, colocar uma chave para conseguir ligar e desligar as coisas pode ser a opção mais prudente.

    Outra vantagem que temos ao trabalhar com Feature Toggles é a possibilidade de evoluírmos a técnica e conseguir por exemplo abrir um novo recurso apenas para 10% dos usuários ou então abrir o novo código apenas 2 horas por noite…

    Desta forma podemos testar tudo que for novo aos poucos e com a segurança de conseguir voltar atrás facilmente se algo der errado.

    Mas, tal técnica não resolve um problema: O usuário final continuará tendo uma experiência ruim quando algum erro acontecer. Para lidar com isso então, vamos falar sobre Dark Launching.

    Dark Launching

    Um dos primeiros relatos que conheço sobre esta técnica veio do Facebook, em uma publicação sobre o lançamento de uma nova funcionalidade no site: Hammering Usernames

    Existem diversas interpretações para ela, mas irei trabalhar com a seguinte: É a técnica de lançar algo de forma imperceptível ao usuário, para fins de análise do comportamento em produção com usuário reais.

    Para exemplificar, vamos imaginar que o seu sistema possui 3 áreas:

    • Meus Produtos Favoritos
    • Meu Histórico de Compras
    • Meu Perfil

    Iremos lançar então uma nova área chamada “Sugestões de Produtos”. Tal área deverá listar produtos nos quais o usuário possa ter algum interesse. Para isso, iremos analisar o seu histórico de compras, os produtos visitados, etc. Precisamos também garantir que não iremos mostrar um produto que o usuário já tenha comprado antes.

    Tal área pode conter uma boa quantidade de código e irá variar bastante de acordo com o usuário.

    Ao terminá-la, podemos disponibilizar um novo link no sistema para o usuário acessá-la ou experimentá-la utilizando Dark Launching da seguinte maneira: Toda vez que o usuário entrar em Meu Perfil, iremos realizar uma requisição oculta para a nova área Sugestões de Produtos.

    Tal requisição precisa ter algumas características para atender aos nossos propósitos: Ela não deve deixar o carregamento mais lento, precisamos então fazer essa requisição em uma thread separada por exemplo. Se por algum motivo ocorrer algum erro nesta chamada, não podemos prejudicar a experiência do usuário, tal erro deve acontecer de forma que não interrompa de forma alguma o fluxo normal de navegação.

    Todos os usuário da aplicação irão abrir a nova área, sem saber que estão fazendo isso. Desta forma conseguiremos que a nova área seja testada com usuários reais em produção, sem de fato afetá-los caso algo dê errado.

    E, para que isso seja proveitoso, precisamos coletar métricas sobre tais requisições. Tempo que ela levou para se completar, quantos erros aconteceram, a média de produtos retornados para cada usuário, etc.

    Assim poderemos descobrir por exemplo que nenhum usuário está conseguindo abrir a nova área por conta de um erro, mas tudo bem, na prática nenhum deles sabe disso.

    Poderemos descobrir também que apenas usuários que compraram produtos há mais de 6 meses recebem um erro. E descobrimos isso sem precisar fazê-los passar pela experiência ruim de ver esse erro.

    Podemos descobrir que a área está muito lenta, que está sobrecarregando o servidor, e por aí vai.

    Tudo isso, de forma invisível. O valor de saber como o seu código se comportará diante de todos os usuários reais em ambiente de produção, sem afetar nenhum deles com possíveis problemas, é inestimável.

    Outro exemplo, é na troca de serviços. Se você realiza requisições para uma API que consulta CEP’s e decidiu trocá-la por um novo fornecedor, a mesma técnica pode ser utilizada.

    Deixe tudo como está, mas toda vez que um usuário consultar um CEP no seu sistema, abra uma nova thread e realize a chamada para a nova API.

    Você poderá comparar o retorno da sua API atual com o da nova, vendo se ambas retornam os mesmos dados, como é esperado. Poderá comparar o tempo de execução de cada uma delas e descobrir se a nova realmente é mais rápida com todos os usuários reais utilizando-a. Saberá quantos erros acontecem em comparação com a atual, e assim por diante.

    Tal técnica pode ser utilizada sempre que um novo recurso estiver sendo preparado para ser lançado, para a troca de serviços, para uma versão otimizada de um código, para uma nova classe refatorada, etc.

    Sempre que for trabalhar em algo complexo e arriscado, pode valer a pena lançar tal código com Dark Launching antes de realizar o lançamento real para todos os seus usuários.

    Unindo os mundos

    Para finalizar, algo bacana que podemos concluir é que ambas as técnicas podem trabalhar juntas. Ao lançar algo com Dark Launching, você pode definir quantas vezes por dia o novo recurso será testado, com quantos usuários e em quais horários utiizando-se de Feature Toggles para ligar ou desligar a nova funcionalidade.

    Se o seu experimento invisível ao usuário está sobrecarregando o servidor, gerando erros demais ou algo do gênero, basta desligar o experimento.

    Adotar tais técnicas poderá lhe ajudar a controlar melhor os riscos e incertezas de realizar mudanças críticas em produção. Quanto mais rápido conseguirmos um feedback da nossa mudança, mais preciso será o nosso foco com relação ao que deve ser feito para entregar ao usuário algo de valor sem prejudicar a sua experiência.