Desenvolvimento

3 nov, 2017

NullAway Engenharia, uma Ferramenta de Código Aberto da Uber para Detectar NullPointerExceptions no Android

Publicidade

Manter a confiabilidade dos aplicativos móveis da Uber, é crucial para facilitar uma experiência de usuário perfeita e agradável. Ao lado de uma arquitetura de plugin robusto, flags de recursos e validação dinâmica de dados externos, as ferramentas de análise estática desempenham um papel fundamental na garantia de alta confiabilidade do código, detectando potenciais erros antes de as atualizações serem enviadas aos usuários.

Anteriormente, a Uber implantou ferramentas de análise estática de terceiros para detectar possíveis NullPointerExceptions (NPE), uma das principais causas de falhas de aplicativos, e manter a confiabilidade em nossa base de código do Android. No entanto, à medida que nossa base de código cresceu, descobrimos que essas ferramentas não atendiam às nossas necessidades em fornecer verificações fortes e feedback rápido aos engenheiros.

Para resolver isso, a Uber desenvolveu o NullAway, uma ferramenta rápida e prática para ajudar a eliminar NPEs. O NullAway melhorou significativamente a produtividade de nossos desenvolvedores, mantendo a verificação forte que precisávamos para implantar com segurança. E agora, contribuímos com essa ferramenta para a comunidade de código aberto para que outros também possam criar aplicativos mais confiáveis!

Neste artigo, discutimos nossa motivação por trás do desenvolvimento do NullAway, descrevemos como construímos a ferramenta e fornecemos instruções sobre como usá-lo para seus próprios aplicativos Android e projetos Java.

Motivação por trás do NullAway

Falhas no celular podem causar problemas significativos para nossos usuários, como impedir que os passageiros solicitem uma viagem em tempo hábil ou que os motoristas aceitem corridas. Os NPEs, que ocorrem quando um ponteiro nulo é desreferenciado em Java, são uma causa frequente de falhas em aplicativos Android. Nossa estratégia na Uber tem sido usar ferramentas de análise de código estático para impedir falhas do NPE o máximo possível.

Em 2016, a Uber implantou as ferramentas Infer e Eradicate do Facebook para detecção estática de potenciais NPEs. Paralelamente à nossa validação do Mecanismo de Validação de Anotações de Tempo de Execução (RAVE), essas ferramentas reduziram o número de NPEs observados em nossos aplicativos em produção por uma ordem de grandeza.

Para maximizar a confiabilidade, queríamos garantir que nenhum código pudesse ser adicionado em nossos aplicativos até que todos os avisos NPE possíveis fossem corrigidos; desta forma, nosso ramo mestre sempre estaria em um estado “verde” (o que significa que a compilação passou em todos os testes relevantes) com zero avisos. A obtenção dessa garantia exigiu a execução dessas ferramentas na nossa Fila de Envio, a etapa final do nosso pipeline de integração contínua, de modo que qualquer erro das ferramentas bloquearia que novo código fosse adicionado.

Ao aumentar a confiabilidade do aplicativo, executar ferramentas de busca de erros como um verificador de NPE na Fila de Envio provocou dois efeitos colaterais que nos encorajaram a pensar de forma criativa sobre nosso código:

  1. Comentário tardio: Para diffs com problemas de NPE, os desenvolvedores apenas receberam avisos na etapa final de fazer uma mudança de código. Esse feedback tardio poderia levar a uma experiência frustrante: uma mudança de um desenvolvedor poderia passar na revisão do código e em todas as outras verificações, apenas sendo rejeitada na Fila de Envio devido a um problema fácil de corrigir. O desenvolvedor pode muito bem ter mudado para outra tarefa até este momento, e teria que mudar de contexto para retornar ao código e acessar o aviso.
  2. Maior latência, diminuição da produtividade do desenvolvedor: A latência geral da experiência da Fila de Envio aumentou, reduzindo assim a produtividade do desenvolvedor. Uma vez que muitos diffs falharam na Fila de Envio com avisos NPE, eles precisavam ser enviados várias vezes, aumentando os comprimentos gerais da fila.
Figura 1: Existem quatro etapas do pipeline de integração contínua da Engenharia Uber no Android. Antes do NullAway, os desenvolvedores da Uber apenas notariam os erros de NPE reportados na fase de fila de envio. Com o NullAway, eles podem identificar erros muito mais cedo, pois estão criando o código localmente.

A velocidade do nosso desenvolvimento (com muitos diffs em voo ao mesmo tempo) e o longo tempo de execução de verificadores de NPE existentes nos impediram de executá-los mais cedo no processo, então decidimos construir uma nova solução que seria rápida o suficiente para executar com baixa latência em todas as etapas do nosso pipeline de desenvolvimento, mesmo durante as compilações locais. E um benefício adicional de desenvolver nossa própria correção é que poderíamos economizar nos recursos da máquina enquanto continuamos a crescer nossa base de código.

Nossa resposta? NullAway.

Apresentando o NullAway

No seu núcleo, o NullAway é um verificador NPE baseado em código aberto para código Java. Para usar o NullAway, você deve primeiro adicionar anotações @Nullable no seu código sempre que um campo, parâmetro do método ou valor de retorno for nulo. (Nós já tínhamos essas anotações em nossa base de código devido ao nosso uso anterior de Eradicate.) Dadas essas anotações, o NullAway executa uma série de verificações de consistência locais para garantir que qualquer ponteiro que seja desreferenciado em seu código não seja nulo.

NullAway é compilado como uma verificação do plugin para o framework de busca de erros Error Prone. Error Prone executa verificações de código como parte do processo de compilação Java padrão. Esta integração do compilador permite que as verificações reutilizem grande parte do trabalho já feito pelo compilador, como análise de código e verificação de digitação. Além disso, o NullAway e o Error Prone integram-se diretamente nas compilações paralelas rápidas e em memória suportadas por Buck, a ferramenta de compilação que usamos para o nosso código Android. Portanto, o NullAway pode ser executado muito mais rapidamente do que as ferramentas que são executadas fora do processo de compilação normal.

Descobrimos que o NullAway adicionou apenas uma pequena sobrecarga aos tempos normais de compilação (cerca de 10% em nossas medidas). Como resultado, ao invés de apenas executar na Fila de Envio, nós configuramos NullAway para executar em cada compilação única do nosso código Android.

O valor de integrar o NullAway em todas as nossas compilações Java é triplo:

  • Feedback imediato: A integração em todas as compilações permite aos desenvolvedores obter feedback imediato sempre que eles apresentam um potencial NPE, ao invés de ter que esperar a Fila de envio.
  • Nenhum verificador NPE: A integração de compilação significa que não precisamos mais executar um verificador de nulidade como um trabalho separado na Fila de Envio, economizando recursos significativos da máquina.
  • Latências inferiores de Fila de Envio: NullAway permite latências de Fila de Envio significativamente menores, uma vez que as falhas na Fila de Envio devido a avisos NPE tornaram-se muito raras; essa latência reduzida tornou-se ainda mais pronunciada após o nosso movimento para um monorepo.

Usando NullAway

Figura 2: Em três etapas fáceis, NullAway determina se uma expressão “e” no método “m” pode ser nula.

Para ter uma ideia de como o NullAway funciona, consideremos como ele determina se alguma expressão no programa pode ser nula, o que frequentemente é necessário para verificar a nulidade. Considere o seguinte exemplo:

class A {
 @Nullable Object f;
}
static void m(A x) {
 if (x.f != null) {
   System.out.println(x.f.toString());
 }
}

No exemplo acima, o NullAway tenta mostrar que x não é nulo, para garantir que x.f não cause um NPE, e também que x.f não seja nula na chamada x.f.toString(). Para expressões que não devem ser nulas, o NullAway primeiro tenta provar rapidamente que a expressão é obviamente não nula, por exemplo, verificando se o seu tipo não é @Nullable ou se é uma expressão como new Object() que não pode ser nula. Para o exemplo, uma vez que a declaração de x não foi anotada @Nullable, NullAway pode assumir que não é nula, mostrando x.f como segura. NullAway aplica essa suposição verificando que uma expressão @Nullable nunca é passada como um parâmetro para o método m.

Se as verificações rápidas falharem, o NullAway tenta mostrar a não-nulidade usando análise de fluxo de dados, alavancando uma biblioteca existente do Framework Verificador. Um objetivo principal da análise de fluxo de dados é descobrir verificações nulas existentes no código. Para a chamada x.f.toString() no exemplo acima, as verificações rápidas não conseguem mostrar que x.f não pode ser nula, uma vez que o campo A.f é @Nullable. No entanto, nossa análise de fluxo de dados usa a verificação condicional de encerramento onde x.f! = null mostra que a chamada x.f.toString() é segura. Uma vez que a análise do fluxo de dados pode ser cara (requer computação de um gráfico de fluxo de controle e execução de uma computação de ponto fixo), o NullAway executa a análise apenas uma vez por método e armazena em cache o resultado.

Na página GitHub do NullAway, existem instruções detalhadas sobre como executar a ferramenta no seu aplicativo para Android ou em outro NullAway de código aberto e compilado pela Engenharia Uber, nossa ferramenta rápida e prática para eliminar NPEs, para ajudar os outros a implantar aplicativos de Android mais confiáveis. Código Java. Para obter mais detalhes sobre as verificações, mensagens de erro e limitações da NullAway, consulte nosso guia detalhado de implementação.

Próximos passos

Com o NullAway, podemos manter uma forte verificação estática de NPEs por outras ferramentas, além de melhorar a produtividade do desenvolvedor. Esperamos que você tenha achado NullAway útil para melhorar a confiabilidade de sua base de código Java.

Interessado em desenvolver ferramentas de código aberto para melhorar a confiabilidade do aplicativo e a produtividade do desenvolvedor? Candidate-se a um cargo na equipe de Plataforma Móvel da Experiência do Desenvolvedor da Uber.

Manu Sridharan é um engenheiro de software na Equipe de Plataforma Móvel de Desenvolvedores da Uber, levando esforços para aplicar análise estática em toda a base de códigos de Uber. No início deste ano, ele apresentou uma fala sobre tecnologia no Curry On sobre como a análise estática e a arquitetura de software são alavancadas para melhorar a qualidade da aplicação na Uber.

***

Este artigo é do Uber Engineering. Ele foi escrito por Manu Sridharan. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/nullaway/