• Nenhum resultado encontrado

CrashAwareDev: apoiando o desenvolvimento a partir da mineração e análise de crash reports

N/A
N/A
Protected

Academic year: 2021

Share "CrashAwareDev: apoiando o desenvolvimento a partir da mineração e análise de crash reports"

Copied!
82
0
0

Texto

(1)

Departamento de Informática e Matemática Aplicada Programa de Pós-Graduação em Sistemas e Computação

Mestrado Acadêmico em Sistemas e Computação

CrashAwareDev: Apoiando o

Desenvolvimento a partir da Mineração e

Análise de Crash Reports

Leandro Dias Beserra

Natal-RN Janeiro/2019

(2)

CrashAwareDev: Apoiando o Desenvolvimento a

partir da Mineração e Análise de Crash Reports

Dissertação de Mestrado apresentada ao Pro-grama de Pós-Graduação em Sistemas e Com-putação do Departamento de Informática e Matemática Aplicada da Universidade Fede-ral do Rio Grande do Norte como requisito para a obtenção do grau de Mestre em Siste-mas e Computação.

Linha de pesquisa:

Engenharia de Software

Orientadora

Prof

a

. Dr

a

. Roberta de Sousa Coelho.

PPgSC – Programa de Pós-Graduação em Sistemas e Computação DIMAp – Departamento de Informática e Matemática Aplicada

CCET – Centro de Ciências Exatas e da Terra UFRN – Universidade Federal do Rio Grande do Norte

Natal-RN Janeiro/2019

(3)

Beserra, Leandro Dias.

CrashAwareDev: apoiando o desenvolvimento a partir da mineração e análise de crash reports / Leandro Dias Beserra. -2019.

81f.: il.

Dissertação (Mestrado) - Universidade Federal do Rio Grande do Norte, Centro de Ciências Exatas e da Terra, Programa de Pós-graduação em Sistemas e Computação. Natal, 2019.

Orientadora: Roberta de Souza Coelho.

1. Engenharia de software Dissertação. 2. Crash report Dissertação. 3. Falha Dissertação. 4. Padrão de erro

-Dissertação. 5. Eclipse - -Dissertação. 6. Java - -Dissertação. 7. Ferramenta - Dissertação. I. Coelho, Roberta de Souza. II. Título.

RN/UF/CCET CDU 004.41

Catalogação de Publicação na Fonte. UFRN - Biblioteca Setorial Prof. Ronaldo Xavier de Arruda - CCET

(4)
(5)

Primeiramente agradeço a meus pais, Flávio e Antônia, que dedicaram uma vida inteira à minha educação e de meus irmãos.

A minha esposa, Márcia, que me apoiou do início ao fim desta jornada e me incentivou nos momentos de maior dificuldade e não me deixou desistir em vários momentos. Te dar orgulho foi a minha principal motivação para concluir este curso. Obrigado por ser essa grande mulher e que possamos juntos atingir objetivos muito maiores.

A Professora Roberta, que foi uma orientadora exemplar em todos os sentidos, que me incentivou a ingressar no mestrado e que até em seu período de férias não deixou de me orientar na pesquisa. Muito obrigado!

A meus irmãos Alan, Leo e Aline, a minhas cunhadas Mércia, Marciele e Kívia, a Ilton e a meus segundos pais (vulgo, sogros) Célia e Ancelmo, meu muito obrigado por acreditarem em mim e a todo o incentivo.

Por fim, agradeço à SINFO por toda infraestrutura disponibilizada para que este trabalho pudesse ser realizado.

(6)

partir da Mineração e Análise de Crash Reports

Autor: Leandro Dias Beserra Orientador: Profa. Dra. Roberta de Sousa Coelho.

Resumo

Os mecanismos de tratamento de exceções são uma característica comum de linguagens de programação convencionais. Exceções não capturadas (do inglês, uncaught exceptions) são a principal causa de crashes em sistemas de software. Um crash é um comportamento anormal de um sistema que leva à interrupção de sua execução. Esta é uma das razões que têm motivado a utilização de ferramentas de crash reports, que podem armazenar informações de falhas que ocorreram no sistema para facilitar a localização, priorização e depuração de falhas. Contudo, podemos pensar em uma outra utilidade para tais ferramentas. Estas informações poderiam ser utilizadas para apoiar o desenvolvedor durante a codificação do sistema. Neste trabalho foi realizado um estudo em um relatório de falhas de um sistema Web real, onde foram documentadas causas de crashes em um determinado período e identificados alguns padrões de erros. Além disso, foram observadas informações do relatório que poderiam apoiar programadores em suas atividades do dia a dia. A partir desta análise, foi implementada uma ferramenta, chamada CrashAwareDev, integrada ao ambiente de desenvolvimento Eclipse, com objetivo de aproximar o ambiente de desenvolvimento ao crash report. Um estudo de caso foi realizado e mostrou que a ferramenta pode apoiar o desenvolvimento de software através (i) da exibição de alertas de bug patterns diretamente no código-fonte, (ii) do rastreamento de classes envolvidas em falhas recentes e (iii) da agilidade em localizar crashes dentro do próprio ambiente de desenvolvimento.

Palavras-chave: Crash Report, Tratamento de Exceções, Eclipse, Java, Padrão de Erro,

(7)

Crash Report Mining and Analysis

Author: Leandro Dias Beserra Supervisor: Profa. Dra. Roberta de Sousa Coelho.

Abstract

Exception handling mechanisms are a common feature of mainstream programming languages. Uncaught exceptions are the main cause of crashes in software systems. A crash is an abnormal behavior of a system that leads to the interruption of its execution. This is one of the reasons that have led to the use of crash reporting tools, which can store fault information that has occurred in the system for easier location, prioritization, and debugging. However, we can think of another utility for such tools. This information could be used to support the developer during system coding. In this work a study was performed on a crash report of a real Web system, where the causes of crashes in a given period were documented and some error patterns were identified. In addition, reporting information was observed that could support programmers in their day-to-day activities. A tool integrated with the Eclipse development environment, named CrashAwareDev, was implemented in order to bring the development environment closer to the crash report. A case study was conducted and showed that the tool can support software development by (i) displaying bug pattern alerts directly in the source code, (ii) tracing the classes involved in recent faults, and (iii) speeding up the crashes’s location within the development environment itself.

(8)

1 Fluxo de registro de falhas no crash report da UFRN. . . p. 24

2 Visão geral do sistema de Crash Report da UFRN. . . p. 25

3 Visão geral da análise de falhas. . . p. 25

4 Visão geral do estudo. . . p. 26

5 Stacktrace de uma falha do tipo NullPointerException. . . p. 31

6 Principais componentes da CrashAwareDev. . . p. 48

7 Alerta de potencial null pointer após uma consulta ao banco. . . p. 49

8 Validação do objeto na linha 9, após uma consulta ao banco. . . p. 50

9 Alerta sobre a necessidade em validar a variável inteiro passada como

argumento do método. . . p. 51

10 Após a validação correta da variável, a mensagem não é mais exibida. . . p. 51

11 Parâmetro recuperado da request sem validação. . . p. 52

12 O alerta não é mais exibido após a validação da variável. . . p. 52

13 Alerta que informa que o atributo privado não possui métodos get e/ou

set associados. . . p. 53

14 Alerta não é mais exibido após a implementação dos métodos acessores. . p. 53

15 Alerta de uma classe que esteve presente em stack trace de falhas recentes. p. 54

16 Consulta de falhas integrada ao Eclipse. . . . p. 55

17 Visualização de algumas informações da exceção NullPointerException. p. 56

18 Informações da exceção podem ser editadas por qualquer programador. . p. 56

19 Principais tipos de exceções não capturadas em um período recente. . . . p. 57

(9)

elasticsearch. . . p. 59

22 Diagrama com as classes responsáveis pela renderização de interfaces de

visualização no Eclipse. . . p. 60

23 Diagrama com as classes responsáveis pela análise estática de código

(10)

1 Top 10 tipos de exceções não capturadas que geraram falha no SIPAC,

no período de Junho/2018. . . p. 28

2 Número de falhas analisadas no estudo. . . p. 29

3 Causas de falhas relacionadas a null pointers. . . . p. 31

4 Mensagens de causas das exceções do tipo JspException. . . p. 35

5 Possíveis bug patterns detectados durante a análise. . . p. 39

6 Perguntas do questionário aplicado entre os programadores. . . p. 43

7 Métricas coletadas para responder a questão Q1. . . p. 65

(11)

1 Introdução p. 13 1.1 Contexto e Motivação . . . p. 13 1.2 Objetivos da Pesquisa . . . p. 15 1.3 Metodologia . . . p. 15 1.4 Contribuições . . . p. 16 1.5 Organização do Documento . . . p. 16 2 Fundamentação Teórica p. 18 2.1 Terminologia . . . p. 18

2.2 Mecanismos de tratamento de exceções . . . p. 19

2.2.1 Tratamento de exceções em Java . . . p. 20

2.3 Identificando Más Práticas Estaticamente . . . p. 21

3 Analisando Causas de Crash: Um Estudo Exploratório p. 23

3.1 Contexto dos Sistemas-alvo . . . p. 23

3.2 Visão Geral . . . p. 24

3.3 Levantamento de falhas em um período . . . p. 27

3.4 Identificação dos Principais Tipos de Exceções . . . p. 27

3.5 Análise Detalhada dos Crashes . . . p. 28

3.5.1 Análise de NullPointerException . . . p. 30

3.5.2 Análise de LazyInitializationException . . . p. 33

(12)

3.5.5 Análise de PSQLException . . . p. 37

3.6 Padrões de Bugs Identificados . . . p. 38

3.7 Utilizando Ferramentas de Análise Estática Existentes . . . p. 40

3.7.1 Utilizando a SonarLint . . . p. 40 3.7.2 Utilizando a SpotBugs . . . p. 42 3.7.3 Discussão . . . p. 42 3.8 Survey de Avaliação . . . p. 43 3.9 Discussão . . . p. 44 4 CrashAwareDev p. 46 4.1 Visão Geral . . . p. 47 4.2 Desenvolvimento da CrashAwareDev . . . p. 47

4.2.1 Analisador de Bug Patterns . . . p. 48

4.2.1.1 Validar Retorno de Dados do Banco . . . p. 49

4.2.1.2 Validar Argumentos de Métodos de Classes Utilitárias . p. 50

4.2.1.3 Validar Valores Passados em Request . . . p. 51

4.2.1.4 Obrigatoriedade de Métodos GET e SET . . . p. 52

4.2.2 Analisador de Classes . . . p. 54

4.2.3 Consulta de Crashes Integrada . . . p. 55

4.2.4 Consulta de Informações de Tipos de Exceções . . . p. 55

4.2.5 Consulta de Principais Tipos de Exceções . . . p. 57

4.2.6 Projeto Detalhado da Ferramenta . . . p. 58

4.2.6.1 Principais Classes da CrashAwareDev . . . p. 59

4.3 Discussão . . . p. 61

(13)

5.1.1 Respondendo as Questões do Estudo . . . p. 64

5.1.2 Opinião dos Participantes . . . p. 68

5.2 Discussão . . . p. 69

6 Trabalhos Relacionados p. 70

6.1 Mineração de informações em relatório de falhas e/ou stack traces . . . . p. 70

6.2 Ferramentas de detecção de padrões de bugs . . . p. 72

7 Considerações finais p. 74

7.1 Limitações da Pesquisa . . . p. 75

7.2 Trabalhos Futuros . . . p. 76

(14)

1

Introdução

1.1

Contexto e Motivação

Os mecanismos de tratamento de exceções (GOODENOUGH, 1975) são uma característica comum de linguagens de programação convencionais. Estruturas de tratamento de exceções são utilizadas para lidar com eventos inesperados que ocorrem durante a execução de um programa (SAWADPONG; ALLEN, 2016), permitindo que exceções possam ser lançadas, capturadas e tratadas, em diferentes pontos do sistema. Exceções não capturadas (do inglês, uncaught exceptions) são a principal causa de crashes em sistemas de software (JO et al., 2004). Um crash é um comportamento anormal de um sistema que leva à interrupção de sua execução. Uma falha de sistema ocorre quando um sistema desvia do fluxo normal de execução, ou seja, ele não se comporta conforme foi especificado (GARCIA et al., 2001). Nem toda falha de sistema pode levar à interrupção da execução. Porém, ao longo deste trabalho utilizamos o termo “falha” para representar falhas que geraram crash por questão de simplicidade. Esta é uma das razões que têm motivado a utilização de ferramentas de crash

reports que armazenam informações deste tipo de falha, frequentemente utilizadas para a

tarefa de depuração. Alguns exemplos de sistemas de crash reports têm sido utilizados em grandes organizações, como o Windows Error Reporting (GLERUM et al., 2009), o Apple

Crash Reporter (APPLE, 2010) e o Mozilla Crash Reporter (MOZILLA, 2012).

Após a ocorrência de um crash, geralmente os sistemas armazenam a stack trace do erro, que é uma representação da pilha de chamada de métodos e contém informações de classes e métodos pelos quais uma exceção foi propagada. A stack trace é uma fonte de informação amplamente utilizada por programadores na tarefa de depuração de falhas e, por isto, é de suma importância adicioná-la ao relatório de falhas (SCHROTER et al., 2010). Contudo, estes relatórios podem conter outras informações como sistema operacional utilizado pelo usuário no momento do crash, login do usuário, navegador utilizado, endereço de IP do usuário, parâmetros de request, dentre outras. Além de agilizar a localização do defeito no código, o relatório pode ajudar na priorização das correções (dependendo do número de

(15)

usuários afetados, por exemplo) ou na compreensão do impacto das falhas no sistema (AN; KHOMH, 2015).

Podemos pensar nos sistemas de crash report como algo além de apenas sistemas que registram dados de crash. Podemos vê-lo como uma grande fonte de informação “bruta” que poderia ser minerada, de forma que possamos extrair dados que sirvam de apoio aos programadores durante a codificação do sistema. Alguns estudos foram realizados com objetivo de extrair dados a partir de relatórios de falhas e utilizá-los para diversos propósitos, como criar formas de classificação de falhas (KIM; ZIMMERMANN; NAGAPPAN, 2011) (DHALIWAL; KHOMH; ZOU, 2011) ou auxiliar na localização de defeitos (SINHA et al., 2009) (WANG; KHOMH; ZOU, 2013) (WU et al., 2014). Porém, nenhum dos trabalhos propôs utilizar as informações dos relatórios de falhas para promover este apoio aos desenvolvedores, então propomos uma forma de aproveitar melhor esta fonte de dados.

Bugs (ou defeitos) são introduzidos no código ao longo do ciclo de desenvolvimento

de software. Um bug pattern é uma abstração para a recorrência de bugs (Farchi; Nir; Ur, 2003). Isto significa os que erros que os desenvolvedores cometem durante a codificação podem seguir um padrão, de acordo com as características do defeito. Ferramentas de análise estática implementam validadores de acordo com padrões de bugs mais comuns. Várias destas ferramentas foram propostas como a PMD1, SonarLint2 e a FindBugs

(que posteriormente foi sucedida pela SpotBugs3). Elas são utilizadas para encontrar

automaticamente bugs no código fonte do sistema através de técnicas como busca de padrões de código, análise de fluxo de dados, checagem de modelos, etc (RUTAR; ALMAZAN; FOSTER, 2004). Uma das desvantagens destas ferramentas, citadas por alguns autores, como veremos mais adiante, é a presença de falsos positivos e/ou falsos negativos reportados. Além disso, as ferramentas não levam em consideração informações sobre as falhas que comumente ocorrem nos sistemas analisados. Iremos propor, também, uma abordagem para extrair padrões de relatório de falhas específicos de um sistema e utilizá-las como entrada (ou regras) de uma ferramenta de análise estática de código, de forma que os resultados da análise sejam mais relevantes.

1https://pmd.github.io/ 2https://www.sonarlint.org/ 3https://spotbugs.github.io

(16)

1.2

Objetivos da Pesquisa

Neste trabalho, objetivamos investigar como podemos utilizar informações sobre as falhas mais frequentes (mais especificamente falhas do tipo uncaught exception) para apoiar o programador durante o desenvolvimento, de forma a prevenir ou reduzir estes tipos de falhas no código sendo implementado. A fonte de informação de falhas utilizada será um sistema de crash rerport e será implementada uma ferramenta integrada ao ambiente de desenvolvimento do programador para apoiá-lo. Após a aplicação da ferramenta em um ambiente real, tentaremos responder a questão de pesquisa principal:

RQ: Podemos usar informações de crash reports para apoiar o desenvolvimento no

momento da codificação e prevenir crashes?

Na próxima seção detalharemos a metodologia utilizada no trabalho.

1.3

Metodologia

A ideia principal do trabalho é que informações sejam extraídas dos relatórios, de forma manual ou automática, e que possam apoiá-los durante a atividade de codificação, diminuindo a distância entre o programador e as informações contidas no crash reporter. A abordagem foi dividida em duas partes: a primeira consistiu em um levantamento de dados de crashes de um determinado período, seguida de uma análise manual, onde foram documentadas informações de tipos de exceções e causas de crashes. Na segunda parte, foram implementadas consultas automatizadas sob um crash reporter que retornam dados relevantes ao desenvolvedor durante a codificação.

Para auxiliar no estudo, foi implementada uma ferramenta, chamada CrashAwareDev, em formato de plug-in da IDE Eclipse4, com objetivo de apresentar as informações dos

relatórios diretamente na IDE do programador. A CrashAwareDev exibe as informações por meio de alertas diretamente no código ou através de diferentes visões características da IDE Eclipse. Adicionalmente, foi implementada na ferramenta uma funcionalidade colaborativa, onde os programadores podem compartilhar experiências relacionadas à correção de defeitos ou informações sobre exceções.

Este estudo foi conduzido seguindo os passos abaixo:

1. Utilizar um relatório de falhas de um sistema Web para consultar os crashes de um

(17)

período específico e criar um ranqueamento dos principais tipos de exceções lançadas.

2. Para os principais tipos de exceções, coletar um subconjunto de crashes de cada tipo e documentar as causas do defeito e informações sobre o tipo de exceção (o que causam, como corrigir, etc).

3. Investigar algumas aplicações de análise estática existentes e verificar se elas seriam capazes de prevenir algum dos crashes do item anterior.

4. Aplicar um questionário entre um conjunto de desenvolvedores com objetivo de avaliar como utilizam e como poderiam utilizar o relatório de falhas no dia a dia.

5. Implementar uma ferramenta integrada ao Eclipse capaz levar informações extraídas no passo 2 diretamente à IDE dos programadores. Além disso, deve haver uma comu-nicação entre a ferramenta e o crash reporter para que possamos coletar informações de forma automática e exibi-las aos programadores em tempo de codificação.

6. Aplicar a ferramenta em um ambiente de desenvolvimento real e avaliá-la atra-vés de coleta de logs e de um questionário aplicado entre os participantes após a experimentação.

1.4

Contribuições

Podemos listar como contribuições deste trabalho:

∙ Um estudo de caso que realizou uma análise de crashes de um determinado período de um sistema Web, onde foram documentadas informações sobre tipos de exceções e causas de falhas associadas à tipos de exceções.

∙ Identificação de padrões de erros específicos de um sistema Web.

∙ Construção de uma ferramenta, integrada ao ambiente de desenvolvimento, com objetivo de apoiar desenvolvedores utilizando informações de crash reports durante a tarefa de codificação.

1.5

Organização do Documento

O restante deste trabalho está organizado como segue. No capítulo 2, apresentamos a fundamentação teórica com os conceitos principais necessários para o entendimento do

(18)

trabalho. O capítulo 3, mostra os detalhes do levantamento dos crashes, a execução das ferramentas de análise estática existente e os resultados do questionário aplicado durante a análise das falhas. Mostraremos todas as funcionalidades e detalhes de implementação da CrashAwareDev no capítulo 4. No capítulo 5 apresentaremos os resultados desta pesquisa. E, por fim, os trabalhos relacionados serão discutidos no capítulo 6.

(19)

2

Fundamentação Teórica

2.1

Terminologia

Um sistema de software pode ser visto como um conjunto de componentes que in-teragem entre si, em um ambiente computacional. O comportamento dinâmico de um sistema é caracterizado por uma série de estados internos que são assumidos durante seu processamento. Considera-se que o sistema se comporta corretamente quando as transições entre os estados são realizadas entre dois estados válidos. Uma transição errônea (i.e, que levou o sistema a um estado inválido) pode ser causada por um ou mais componentes ou por uma falha de projeto (GARCIA et al., 2001).

Alguns conceitos podem causar uma certa confusão devido à semelhança no significado como os conceitos de falha, defeito e erro. Os significados destes termos são definidos como segue (GARCIA et al., 2001):

∙ Falha: Ocorre quando um sistema desvia do fluxo normal de execução, ou seja, ele não se comporta conforme foi especificado.

∙ Erro: É a parte do estado do sistema interno que pode levar a uma falha. Podemos ver como um estágio intermediário entre um defeito e uma falha.

∙ Defeito: É a possível causa do erro. Pode ser um defeito físico ou uma imperfeição tanto no hardware como num componente do software.

Em resumo, se um trecho de código foi implementado errado, causa um defeito, que quando for ativado vai produzir um erro. Se esse erro for propagado até a saída do software constituirá uma falha (um comportamento inesperado).

Neste trabalho utilizamos bastante os termos falha e defeito. Além destes, o termo crash também será encontrado ao longo do texto. Um crash nada mais é que uma interrupção abrupta de um sistema de software. Uma falha de sistema pode ou não ocasionar um crash

(20)

no sistema. Por exemplo, se um sistema acadêmico oferece uma funcionalidade de consultar boletim de notas do aluno e, devido a um defeito, as notas são calculadas de forma errada. Ao exibir o boletim, a falha será manifestada (isto é, os dados serão apresentados de forma incorreta), porém o sistema não entrará em um estado de crash. Por outro lado, se durante a consulta do boletim alguma exceção for lançada sem receber o devido tratamento, a execução poderá ser interrompida e teremosr uma situação de crash de sistema. Por outro lado, um crash pode ocorrer sem que haja um defeito no código, como no caso de esgotar a memória disponível na máquina em que o sistema é executado.

Utilizamos neste trabalho um software Web como sistema alvo de nosso estudo. Na maioria dos sistemas Web as exceções lançadas não tratadas, são capturadas em um filtro específico para que uma tela de erro padrão seja exibida, por exemplo, e o sistema não pare de funcionar. Porém, a execução de uma determinada funcionalidade é interrompida após a falha e, por este motivo, iremos considerar estes casos como um crash de sistema. Além disso, em nosso estudo analisaremos um conjunto de crashes que foram originados a partir de falhas do software. Portanto, utilizamos ao longo do texto o termo falha como sinônimo de crash por questões de simplificação.

2.2

Mecanismos de tratamento de exceções

Uma exceção é uma indicação de um problema que ocorre durante a execução de um programa. O nome “exceção” deixa claro que o problema ocorre raramente – se a “a regra” é que uma instrução execute geralmente de modo correto, então “exceção à regra” é que um problema ocorra. O tratamento de exceções permite aos programadores criar aplicativos que podem resolver (ou tratar) exceções. Em muitos casos, o tratamento de uma exceção permite que um programa continue executando como se nenhum problema tivesse sido encontrado. Um problema mais grave poderia impedir um programa de continuar executando normalmente, em vez de requerer que o problema seja informado antes de encerrar de uma maneira controlada (DEITEL HARVEY M.; DEITEL, 2007).

O tratamento de exceções deve impedir que resultados indesejados ocorram, por exemplo:

∙ a aplicação pare de funcionar;

∙ o sistema operacional que executa a aplicação trave;

(21)

A seguir são listadas algumas vantagens no uso de mecanismos de tratamento de exceções (ORACLE, 2017).

∙ Permitir o programador separar o código do fluxo principal do código do fluxo excepcional. Em linguagens de programação tradicionais, a detecção de erros, o lançamento e o tratamento muitas vezes levam a um “código espaguete”.

∙ Propagar a exceção pela pilha de chamadas de rotinas, para que o seu tratamento possa ser feito em um outro momento da execução.

∙ Agrupar e diferenciar tipos de erros criando uma hierarquia de exceções, permitindo, por exemplo, que o programador possa tratar a exceção de acordo com o tipo de falha que ocorreu.

A seguir será apresentada uma breve introdução de como funciona o tratamento de exceções na linguagem Java.

2.2.1

Tratamento de exceções em Java

Em Java, todo código que pode lançar uma exceção “checked” (será explicado mais adiante) deverá ser envolvido por um bloco try-catch-finally. A nomenclatura deste bloco já diz seu propósito: o programa vai tentar (try) executar aquele trecho de código e, caso alguma exceção seja lançada, ela será capturada e tratada dentro do bloco catch. Ao final da execução do try será executado tudo que tiver dentro do bloco finally. O bloco

try-catch-finally tem o seguinte formato:

1 try {

2 // Trecho de c o d i g o que pode l a n c a r e x c e c a o

3 } catch ( NomeDaException e ) { 4 // E x e c u t a e s t e b l o c o , c a s o a e x c e c a o acima s e j a l a n c a d a 5 } f i n a l l y { 6 // E x e c u t a e s t e b l o c o sempre que o t r y t e r m i n a 7 // o f i n a l l y e ’ o p c i o n a l 8 }

Listing 2.1: Estrutura do bloco de tratamento de exceções em Java.

(22)

∙ Checked Exceptions: estas são codições excepcionais em que uma aplicação bem codificada deve estar preparada para capturar e tratar adequadamente. Por exem-plo, supõe-se que uma aplicação solicita que o usuário digite o nome de um ar-quivo que a aplicação irá ler, passando o nome do arar-quivo para o construtor de java.io.FileReader. O usuário tanto pode digitar o nome de um arquivo que existe como também pode informar um inexistente. Neste segundo caso, o construtor irá lançar uma exceção do tipo java.io.FileNotFoundException. Portanto, é impor-tante que a aplicação esteja preparada para dar um tratamento adequado quando esta exceção for capturada. Exceções deste tipo herdam de java.lang.Exception.

∙ Error: estas são condições excepcionais que ocorrem externamente à aplicação e, portanto, não podem ser previstas pela aplicação. Por exemplo, o programa quando vai abrir um arquivo para leitura pode gerar um erro no hardware ou no sistema operacional que irá ocasionar uma exceção do tipo java.io.IOError. Exceções deste tipo são herdadas de java.lang.Error.

∙ Runtime Exceptions: este terceiro tipo de exceção são lançadas internamente na aplicação, mas também não podem ser previstas. Usando a mesma ideia dos exemplos anteriores, o programa irá fazer a leitura de um arquivo e recebe o nome como argumento para o construtor. Por algum motivo, o construtor poderá receber um valor null como argumento e, consequentemente, lançará um NullPointerException. A aplicação poderá até capturar e tratar a exceção, porém faz mais sentido corrigir o código para que nunca chegue um valor null como argumento para este construtor. Exceções deste tipo são herdadas de java.lang.RuntimeException.

O conjunto de exceções do tipo Error e Runtime são chamadas de Unchecked Exception, pois não podem ser previstas pela aplicação em tempo de compilação. Todas exceções em Java são herdadas da classe java.lang.Throwable.

2.3

Identificando Más Práticas Estaticamente

Nos últimos anos, ferramentas têm sido desenvolvidas para encontrar defeitos em

softwares de forma automatizada. Elas utilizam de diferentes técnicas de análise como

prova de teoremas, checagem de modelos, execução simbólica, análise de fluxo de dados e correspondência de padrões sintáticos (MENG et al., 2008). Em resumo, tais ferramentas ampliam e sofisticam as mensagens de warning emitidas por compiladores. Adicionalmente,

(23)

podem contribuir para verificar estilos e boas práticas de programação, como convenções de nomes e de identação (ARAUJO; SOUZA; VALENTE, 2011). Utilizamos duas ferramentas existentes nesta pesquisa, a SpotBugs e a SonarLint.

A SpotBugs é a ferramenta sucessora da FindBugs (HOVEMEYER; PUGH, 2004) e ambas implementam detectores para uma série de bug patterns de programas Java. Ela contém mais de 400 regras implementadas1 divididas em 9 categorias. Na página da documentação, as regras possuem alguns prefixos para categorizá-las.

Assim como a SpotBugs, a SonarLint também implementa detectores de padrões, porém dá suporte para um conjunto de 27 linguagens de programação atualmente. No período em que esta pesquisa foi realizada, SonarLint possuía um total de 530 regras implementadas (para a linguagem Java), divididas em quatro categorias: Vulnerability,

Bug, Security Hotspot e Code Smell.

(24)

3

Analisando Causas de Crash:

Um Estudo Exploratório

Neste capítulo descreveremos o estudo exploratório realizado baseado nos dados de

crashes que foram registrados em um determinado período no crash report de um sistema Web real, o SIPAC, que será apresentado na próxima seção. Para reforçar nossa proposta

de implementação da CrashAwareDev, iremos antes investigar algumas ferramentas de análise estática existentes e aplicamos um questionário com o intuito de entender como o

crash report de nosso sistema-alvo vem sendo utilizado pelos desenvolvedores no dia a dia.

Na próxima seção, contextualizaremos o ambiente em que esta pesquisa foi realizada e apresentaremos os sistemas e seus crash reports utilizados como base para nosso estudo. Na seção 3.2 apresentaremos uma visão geral de um estudo cujo objetivo foi analisar um conjunto de dados de falhas extraídas em um determinado período. As informações obtidas nesta análise foram utilizadas como base para a implementação de uma ferramenta que será detalhada no próximo capítulo. Em seguida, na seção 3.3, apresentaremos os critérios utilizados na busca das falhas. Na seção 3.4 mostraremos os principais tipos de exceções lançadas e logo após a seção 3.5 detalhará a análise mais detalhada feita em um subconjunto das falhas totais. Na seção 3.6 apresentamos uma síntese dos padrões de bugs encontrados durante a análise. Os resultados de uma análise estática de código – utilizando ferramentas existentes – serão reportados na seção 3.7. Um survey foi aplicado e discutido na seção 3.8. Por fim, faremos as considerações finais na seção 3.9.

3.1

Contexto dos Sistemas-alvo

Como base para este estudo, utilizaremos o contexto dos sistemas SIG-UFRN desenvol-vidos na UFRN. A UFRN possui um conjunto de sistemas, implementados na linguagem Java, com propósitos de gestão acadêmica, administrativa e de recursos humanos. Desen-volvidos pela Superintendência de Informática da UFRN (SINFO), os principais sistemas

(25)

são chamados SIGAA, SIPAC e SIGRH (SINFO, 2016) que tratam, respectivamente, os assuntos acadêmicos, administrativos e de RH da UFRN.

Em 2016 foi criado um relatório de falhas para os sistemas SIG-UFRN. A Figura 1 representa o fluxo desde a ocorrência do crash do sistema até seu registro no relatório. Uma observação a ser feita, é que as uncaught exceptions são tratadas em um handler genérico que realiza um tratamento das exceções que propagaram até o último nível da pilha de chamada de métodos. Este tratamento genérico é feito para evitar a interrupção da execução dos sistemas, abortando apenas a execução de uma operação específica e exibindo uma página ou mensagem de erro padrão.

Figura 1: Fluxo de registro de falhas no crash report da UFRN.

Quando um erro ocorre no sistema, é gerado um arquivo de log no servidor de aplicação. Logo em seguida, utilizando a plataforma Beats (ELASTIC, 2018a), os logs são coletados e enviados a um servidor para processamento dos dados. Este servidor utiliza a ferramenta

Logstash (ELASTIC, 2018d) para extrair as informações do arquivo de logs e convertê-lo em formato JSON. Os dados ficarão armazenados em um servidor distribuído de busca, o

Elasticsearch (ELASTIC, 2018b). Por ser um motor de pesquisa textual altamente escalável, o Elasticsearch permite armazenar e analisar grandes volumes de informações praticamente em tempo real. Por fim, precisamos de uma forma eficiente de consulta e exibição dos dados retornados pelo Elasticsearch e, para esta tarefa, é utilizado a ferramenta Kibana (ELASTIC, 2018c). A Figura 2 mostra a visualização de uma consulta realizada no crash

reporter da UFRN, utilizando o Kibana.

Na próxima seção apresentaremos uma visão geral com os passos de uma análise feita com base neste relatório de falhas.

3.2

Visão Geral

Com objetivo de investigarmos as principais causas de crash reportadas para o sistema SIPAC, realizamos o estudo que será detalhado neste capítulo. A Figura 3 representa a sequência de passos executados na análise de falhas, com base em relatórios de falhas do SIPAC. Primeiramente, realizamos uma consulta no crash reporter considerando todo o

(26)

Figura 2: Visão geral do sistema de Crash Report da UFRN.

mês de Junho de 2018 (1) e, no passo seguinte, classificamos os principais tipos de uncaught

exceptions que mais ocasionaram falhas no sistema (2). No terceiro passo, fizemos uma

análise mais detalhada em cima de um subconjunto de falhas (com apoio do código fonte do SIPAC), selecionando-as com base nos principais tipos de exceções. O objetivo desta análise foi gerar um relatório detalhado (3) com informações da causa do erro, linha do código que contém o defeito e, se possível, uma solução para resolvê-lo (em alguns casos documentamos a solução implementada pelos desenvolvedores). Tentamos, ainda durante esta análise, definir alguns bug patterns a partir do relatório detalhado (4). Cada um destes passos serão discutidos nas próximas seções.

Figura 3: Visão geral da análise de falhas.

Além da análise de falhas, ainda apresentaremos neste capítulo outros dois passos do estudo. A Figura 4 apresenta uma visão geral de nossa metodologia de análise que levaram

(27)

ao desenvolvimento da ferramenta CrashAwareDev.

(28)

Iniciamos por uma análise manual, que será explicada a partir da seção 3.3. Em seguida, estudamos se algumas ferramentas existentes poderiam evitar algum dos crashes documentados na análise anterior. Explicaremos os detalhes deste estudo na seção 3.7. Por fim, aplicamos um survey a todos os desenvolvedores dos sistemas SIG-UFRN a fim de determinar como os crash reports os auxiliam durante seu trabalho. A metodologia e os resultados deste survey serão explicados na seção 3.8.

3.3

Levantamento de falhas em um período

Realizamos uma consulta no crash reporter do SIPAC utilizando a interface do Kibana, que nos permite consultar informações na base de dados do Elasticsearch. Os filtros utilizados foram (i) apenas os erros do sistema SIPAC e (2) apenas aqueles que ocorreram no período de 01/06/2018 a 30/06/2018. Foi necessário ainda excluir alguns tipos de exceções, pois estavam relacionadas à aplicações desktop ou mobile, e nosso trabalho se restringe apenas ao sistema Web do SIPAC.

Neste período, foram realizados aproximadamente 1933617 logons no sistema, onde em 680 destes algum erro foi registrado (que representa em torno de 0,0035% dos acessos). Observamos que aproximadamente 1966 falhas foram registradas no período, afetando 347 usuários distintos.

É importante destacar que este número (680) não representa a quantidade de defeitos no sistema, pois um defeito pode se manifestar como falha diversas vezes. Portanto, este número representa o total de ocorrências de falhas. A ferramenta realiza um agrupamento de ocorrências semelhantes, porém em alguns casos ela não é eficiente em determinar a igualdade entre dois erros e isto foi um dificultador em nossa análise, pois não tivemos como mensurar com precisão a quantidade de defeitos (distintos) do período.

Na próxima seção iremos apresentar os principais tipos de exceções que estão relacio-nadas à estas falhas.

3.4

Identificação dos Principais Tipos de Exceções

Extraímos da consulta realizada os principais tipos de exceções lançadas no momento dos crashes. Quando uma exceção é lançada, ela pode ser propagada por vários métodos e podendo também ser encapsulada em outro tipo. Por exemplo, um NullPointerException poderia ser encapsulado em uma ArqException (exceção específica dos sistemas

(29)

SIG-UFRN). O próprio crash reporter é responsável por identificar a exceção que originou a falha, chamada de root cause. Ao longo deste trabalho, iremos considerar apenas as root

causes como exceções lançadas durante um crash. A Tabela 1 lista o levantamento das 10

principais.

Root Cause Ocorrências Percentual (%)

java.lang.NullPointerException 749 38,09 org.hibernate.LazyInitializationException 171 8,69 javax.servlet.jsp.JspException 166 8,44 java.lang.IllegalArgumentException 102 5,18 org.postgresql.util.PSQLException 53 2,69 java.util.ConcurrentModificationException 53 2,69 java.lang.OutOfMemoryError 42 2,13 br.urfn.arq.erros.ArqException 40 2,03 com.sun.star.task.ErrorCodeIOException 38 1,93 org.hibernate.StaleStateException 36 1,83

Tabela 1: Top 10 tipos de exceções não capturadas que geraram falha no SIPAC, no período de Junho/2018.

Podemos observar da Tabela 1, que aproximadamente 73,7% (1450 erros, do total de 1966) das falhas do período se concentraram em apenas 10 tipos de exceções e 55,22% nos três primeiros da tabela. Conforme já constatado em um estudo realizado anteriormente (BESERRA, 2012), o NullPointerException – decorrente da tentativa de referenciar objetos nulos – continua sendo a principal causa de falhas nos sistemas SIG-UFRN, com aproximadamente 38% de todas as falhas levantadas.

Um subconjunto destes dados coletados foi analisado de forma mais detalhada e apresentaremos os resultados na próxima seção.

3.5

Análise Detalhada dos Crashes

Nesta seção, explicaremos os detalhes da análise manual realizada em cima dos dados obtidos a partir do crash report do sistema alvo (SIPAC). O objetivo principal desta análise foi, a partir dos principais tipos de exceções não capturadas (apresentadas na seção anterior), levando em conta um subconjunto das falhas, identificar:

1. Quais foram as causas de cada falha. Isto é, localizar o defeito no código-fonte e documentá-lo, descrevendo o erro de programação cometido e uma possível solução para corrigí-lo.

(30)

2. A partir da análise do item anterior, definir possíveis padrões de erros (bug patterns) específicos (ou não) do sistema alvo. Definiremos como padrão as causas de erros identificadas mais de uma vez dentre as falhas estudadas.

3. Estudar sobre as características dos principais tipos de exceções levantados. Por exemplo, no caso da PSQLException poderíamos tentar responder algumas questões como: que erros de programação podem lançar esta exceção? Existem técnicas para evitá-la? Quais soluções mais comuns para corrigir estes defeitos?

Conforme já mencionado, os 1450 erros apontados na Tabela 1 não são defeitos distintos, mas o geral de ocorrências de falhas. O crash reporter que utilizamos não nos fornece explicitamente a quantidade de erros distintos e, para ter este número preciso, teríamos que contabilizar manualmente analisando a stack trace de todos os erros, mas não foi necessário fazê-lo. Dos dez tipos de exceções mostrados na Tabela 1 analisamos um subconjunto das cinco primeiras, conforme resumido na Tabela 2.

Root Cause Falhas Distintas Analisadas

NullPointerException 749 ?? 10

LazyInitializationException 171 ?? 2

JspException 166 21 21

IllegalArgumentException 102 1 1

PSQLException 53 5 5

Tabela 2: Número de falhas analisadas no estudo.

As abreviações da Tabela 2 D.A e O.A significam Defeitos Analisados e Ocorrên-cias Analisadas, respectivamente. A listagem abaixo descreve os subconjuntos de falhas analisados:

∙ NullPointerException: Analisamos 10 defeitos que geraram 95 falhas no sistema, que representam 12% do total de 749 levantadas. Não mensuramos o total de defeitos distintos, pois teríamos que analisar as 749 stack traces manualmente para distingui-las.

∙ LazyInitializationException: Investigamos a causa de 2 erros deste tipo, que foram responsáveis por 81 ocorrências de falhas, que representam 47% do total de 171. Também não mensuramos o total de defeitos distintos pelo mesmo motivo citado anteriormente.

(31)

∙ JspException: Analisamos todos os 21 defeitos deste tipo de exceção, que geraram 166 ocorrências de falhas. Neste caso foi viável analisar todas as stack traces, pois nela continha informações suficientes para identificar a causa do erro.

∙ IllegalArgumentException: Só houve um defeito que gerou 102 falhas. Também analisamos sua causa.

∙ PSQLException: Analisamos todos os 5 defeitos deste tipo de exceção, que geraram 53 ocorrências de falhas.

Uma observação a ser feita sobre a análise de NullPointerException, é que um único defeito deste tipo foi responsável por 333 ocorrências. Porém, analisamos uma delas e observamos que o defeito estava em uma página JSP. Nosso trabalho se limitou a análise de defeitos originados em classes Java e, por este motivo, desconsideramos este defeito em específico.

Então, podemos concluir das Tabelas 1 e 2 que analisamos 39 defeitos, dos cinco principais tipos de exceções lançadas, que abrangeu 25% ocorrências (497 de um total de 1450). Nas próximas subseções serão explicados os resultados desta análise, agrupando-os por tipo de exceção.

3.5.1

Análise de NullPointerException

A maior causa de falhas observadas no relatório de falhas – em outros períodos consultados, além de junho – são originadas de null pointers. Em Java, uma exceção do tipo NullPointerException é lançada quando se tenta referenciar um objeto com valor nulo (null).

Um defeito relacionado a este tipo de exceção é, frequentemente, fácil detectar e corrigir. Em Java, a stacktrace de uma NullPointerException mostra de forma bem clara o método e linha onde houve a tentativa de referenciar um objeto nulo, como pode ser visto na Figura 5. A primeira linha destacada se refere ao local onde a exceção foi lançada (arquivo OcorrenciaDTO, linha 63) e a segunda contém o local onde a falha se originou (arquivo RelHistoricoCompraAction, linha 146)

Porém, nem sempre a correção de um defeito deste tipo é trivial. Pode ser fácil identificar a linha do código em que o objeto nulo foi acessado (e, consequentemente, lançou a exceção), porém não necessariamente a correção deverá ser realizada neste mesmo trecho de código. A causa real do defeito pode estar em um outro artefato que deveria

(32)

Figura 5: Stacktrace de uma falha do tipo NullPointerException.

instanciar o valor do objeto. Ou ainda, a causa real do null pointer pode se tratar apenas de uma inconsistência na base de dados. Por exemplo, suponhamos que o sistema realiza uma consulta à base de dados e espera como retorno um valor válido, mas é retornado

null.

Analisamos no código-fonte do SIPAC, juntamente com as informações obtidas a partir do relatório de falhas, 10 erros distintos que lançaram uma NullPointerException. Categorizamos as causas das falhas cometidas pelos desenvolvedores e mostramos na Tabela 3.

Cat. Causa do Defeito Quantidade

NPE1 Referência a objeto nulo após consulta ao banco 4 NPE2 Referência a objeto nulo passado como argumento de método 4

NPE3 Referência a objeto nulo (causa indefinida) 2

Tabela 3: Causas de falhas relacionadas a null pointers.

Primeiramente, estudamos os defeitos relacionados à tentativa de acessar um ob-jeto recém consultado da base de dados. Dentre os 4 defeitos levantados, 3 deles fo-ram após uma consulta pela chave primária da entidade. Existe um método chamado findByPrimaryKey(int pk) presente na arquitetura do SIPAC que realiza esta consulta pela chave primária (PK, do inglês primary key). Porém, caso não haja nenhum elemento no banco com a PK passada como parâmetro, o valor null será retornado. Uma validação simples para verificar se o elemento retornado é nulo poderia evitar o crash nestes casos. Para exemplificar, o trecho de código abaixo gerou um crash no sistema (linha 5) por não verificar se o objeto reqPagamento foi retornado conforme esperado.

(33)

1 R e q u i s i c a o P a g a m e n t o reqPagamento = getGenericDAO ( ) 2 . findByPrimaryKey ( r e q V i s u a l i z a r . g e t I d ( ) ,

3 R e q u i s i c a o P a g a m e n t o . c l a s s ) ) ; 4

5 r e q V i s u a l i z a r . s e t U n i d a d e C u s t o ( reqPagamento . g e t U n i d a d e C u s t o ( ) ) ;

Listing 3.1: Potencial null pointer, caso reqPagamento não seja encontrado.

Outra causa de falha analisada diz respeito à validação de parâmetros passados como argumentos de métodos. Não há, no contexto do SIPAC, uma política definida para determinar que todos os argumentos devam ser validados. Além disso, o sistema foi desenvolvido em Java 7 e apenas na versão 8 da linguagem que foi inclusa a anotação @NonNull, que serve para indicar que argumentos de métodos não podem ser nulos. Os desenvolvedores podem realizar esta verificação no ponto em que o método é invocado ou dentro do próprio método executado.

No SIPAC existem um conjunto de classes chamadas “classes utilitárias” (identificadas pelo sufixo “Helper” ou “Utils”) que são classes auxiliares e possuem métodos estáticos utilizados em vários pontos do código-fonte. Pelo fato de poderem ser utilizadas por vários desenvolvedores, não temos a certeza de que os argumentos do método serão checados se são válidos (i.e., não nulos) na chamada ao método. Por este motivo, poderíamos definir como uma regra, no contexto do SIPAC, de que todos os argumentos de classes utilitárias devem ser verificadas no corpo do método estático.

1 public Date g e t D a t a L i m i t e E n v i o ( ) { 2 Date dataChegada = n u l l ; 3 i f ( r e q u i s i c a o D i a r i a s != n u l l ) 4 dataChegada = r e q u i s i c a o D i a r i a s . getDataChegada ( ) ; 5 e l s e i f ( r e q u i s i c a o P a s s a g e m != n u l l ) 6 dataChegada = r e q u i s i c a o P a s s a g e m . getFimAfastamento ( ) ; 7 8 return C a l e n d a r U t i l s . a d i c i o n a D i a s ( dataChegada , 5 ) ; 9 }

Listing 3.2: Atributo dataChegada não poderia ter valor null na linha 8.

O algoritmo 3.2 também contém um defeito que causou uma falha real no sistema. O método adicionaDias não verifica se o primeiro atributo é nulo, deixando a

(34)

respon-sabilidade para o método que o invoca. No caso abaixo, pode-se observar que a variável dataChegada pode ser nula na linha 8, o que causou o erro de null pointer.

3.5.2

Análise de LazyInitializationException

Esta é uma exceção do framework Hibernate e é um dos tipos que mais causam erros no nosso sistema alvo. Quando tentamos obter um relacionamento de uma entidade (mapeado como Lazy1), mas não existe uma transação do Hibernate ativa (ou seja, a sessão com

a base de dados foi fechada) iremos receber uma LazyInitializationException. Por exemplo, no trecho de código abaixo:

1 // C r i a o E n t i t y Manager

2 EntityManager em = emf . c r e a t e E n t i t y M a n a g e r ( ) ; 3 // I n i c i o da t r a n s a c a o

4 em . g e t T r a n s a c t i o n ( ) . b e g i n ( ) ;

5 // Recupera uma e n t i d a d e do banco

6 Author a = em . f i n d ( Author . c l a s s , 1L ) ; 7 // E f e t u a o commit da t r a n s a c a o 8 em . g e t T r a n s a c t i o n ( ) . commit ( ) ; 9 // Fecha a s e s s a o 10 em . c l o s e ( ) ; 11 // Tenta a c e s s a r a c o l e c a o de l i v r o s do a u t o r 12 System . o u t . p r i n t l n ( " L i v r o s : " + a . g e t B o o k s ( ) . s i z e ( ) ) ;

Listing 3.3: Exemplo de LazyInitializationException.

Caso a coleção de Book do Author esteja mapeada como sendo um atributo Lazy, no código acima uma LazyInitializationException será lançada na linha 12. Como os livros não foram carregados na linha 6, na linha 12 o método getBooks() deverá gerar um novo select ao banco. Como a sessão já foi fechada na linha 10 a exceção será lançada, causando o crash.

Observando o exemplo acima, o problema parece ser de simples solução. Bastaria fechar a sessão apenas após a chamada do getBooks(). Porém, em um sistema Web este erro pode acontecer de várias maneiras. Por exemplo, suponhamos que temos uma

1O Lazy Loading (carregamento preguiçoso) faz com que possamos ordenar que um determinado atributo de um objeto seja consultado no banco de dados somente quando for necessário utilizá-lo, ou seja, é uma forma de consulta sob demanda. (JOSHI, 2016)

(35)

funcionalidade de consultar alunos onde uma página jsp contém um formulário de busca que redirecionará para outra jsp com o resultado da busca. Ao clicar no botão Consultar, da primeira jsp, a sessão do Hibernate será aberta, a consulta será realizada e depois a sessão fechará. Porém, após a consulta, a jsp com o resultado exibirá alguns dados dos alunos consultados utilizando métodos get da entidade Aluno. Caso um destes atributos mostrados na resposta seja mapeado com Lazy, uma falha será mostrada no momento que a jsp resposta estiver sendo renderizada.

Um padrão que se tornou muito popular a partir de 2010 em aplicações ORM (do inglês,

Object-Relational Mapping) foi o chamado Open Transaction in View. Na comunidade

do Hibernate, este padrão se ficou conhecido por Open Session in View (OSIV) (WADIA, 2008). Sua proposta consiste em manter a sessão do Hibernate ativa até o final da renderização da página web evitando, desta forma, que ocorresse alguma falha originada por LazyInitializationException. Porém, posteriormente este padrão foi considerado um

anti-pattern por conter algumas desvantagens graves. Podemos citar alguns, como:

∙ Facilitar a ocorrência do problema N+1 select. Quando uma lista está sendo rende-rizada, caso uma das linhas da lista possuir uma referência a um objeto lazy, será feita uma consulta para cada linha da listagem.

∙ O desempenho e a escalabilidade podem ser afetados pelo fato de os recursos ficarem alocados por mais tempo (até a renderização da página).

∙ Ele “quebra” o encapsulamento das camadas da arquitetura, de forma que será necessário implementação na camada de controle ou de view para gerenciar transações com a base de dados.

O padrão OSIV foi implementado no SIPAC, porém erros originados de

LazyInitializationException ainda ocorrem em grande número. O motivo principal disto é que existem várias sessões do Hibernate ativas simultaneamente e isto dificulta em saber qual sessão manter aberta ou não.

O Hibernate fornece recursos mais adequados para lidar com atributos lazy. Uma delas é a utilização de consultas com FETCH JOIN, em que estes atributos já podem ser carregados de imediato. Outra alternativa é o uso dos chamados Entity Graphs. Um Entity Graph é utilizado para definir subconjuntos de relacionamentos entre entidades e quais atributos do relacionamento serão retornados nas consultas à base de dados.

(36)

Com base nos dados analisados, não foi possível determinar um padrão de erro específico que leva a esta exceção. Uma das razões que dificulta determinar, através de uma análise estática, se há um potencial defeito no código, é a sessão do Hibernate. Não temos como saber estaticamente se haverá ou não uma sessão válida quando um método get (de um atributo lazy) é invocado. Além disso, nossa análise foi feita estritamente em classes Java e, muito frequentemente, estas falhas se mostram após uma referência ao atributo na camada de visualização (i.e., nas páginas JSP).

3.5.3

Análise de JspException

Segundo a documentação da JspException (APACHE, 2012), ela é uma exceção genérica conhecida pelo motor JSP (do inglês, engine JSP). Este tipo de exceção, porém, não pode ser definido como root cause da falha, pois geralmente encapsula outro tipo de exceção (IOException, NullPointerException, etc).

Analisamos o stacktrace de todas as 21 falhas que lançaram uma JspException, naquele período, e montamos a Tabela 4 com as mensagens exibidas em cada caso e a quantidade de falhas de cada tipo. Estas mensagens são atribuídas pelo próprio framework

MVC (neste caso, o Struts (APACHE, 2018)) e são úteis para direcionar o programador à real causa da falha – visto que esta exceção é genérica e pode ser originada por diferentes motivos.

# Mensagem da Exceção Quantidade

M1 Cannot find bean under name <bean name> 6

M2 No getter method for property <property name> of bean <bean name>

4

M3 Invalid argument looking up property <property name> of bean <bean name>

4

M4 No bean found under attribute key <attribute name> 4

M5 Cannot create iterator for this collection 3

Tabela 4: Mensagens de causas das exceções do tipo JspException.

Analisamos cada item da Tabela 4 e listamos as principais causas de erros cometidos pelos desenvolvedores. A lista abaixo está numerada de acordo com a numeração nas linhas da tabela:

∙ M1 - Quando um bean da aplicação não foi instanciado ou seu nome foi escrito errado na página JSP.

(37)

∙ M2 - Quando não é definido algum método acessor (get ou set) de um atributo do

bean ou seu nome foi escrito errado na página JSP.

∙ M3 - Quando um atributo no bean não foi inicializado (i.e., possui valor null).

∙ M4 - Quando parâmetros da requisição são perdidos entre as transições das páginas.

∙ M5 - Quando a JSP não consegue iterar sob uma lista de objetos. Este erro foi observado quando o objeto passado à view não era do tipo lista2 ou o nome do objeto foi passado entre aspas erroneamente.

É importante destacar que os motivos listados acima foram extraídos apenas das falhas que foram analisadas. É possível que outros defeitos possam lançar alguma JspException com diferentes mensagens não mencionadas.

Assim como a LazyInitializationException, as exceções do tipo JspException são originadas a partir de referências na camada de visualização e, por isto, limitaria uma ferramenta de análise estática de código que não fosse capaz de verificar páginas JSP. Porém, pudemos observar uma verificação simples que poderia ser realizadas em classes Java que poderia prevenir alguns dos defeitos analisados.

No Struts as classes chamadas Action Forms são Java Beans que possuem atributos que são inicializados com os valores de parâmetros de request. Em alguns casos os crashes foram gerados por simplesmente não existir um método get na classe referenciada. Além disso, existe uma convenção de nomes para estes métodos que são utilizados pelo Struts que devem ser respeitados. Podemos definir como regra específica que todas as Action

Forms devem possuir os métodos acessores dos atributos privados e com a nomenclatura

correta.

Outra causa deste tipo de falha observado foi com relação à leitura de parâmetros da

request no controlador da view. O código abaixo, apresenta um trecho de código que causou

uma falha no SIPAC. O método getParameterInt() (linha 5) é utilizado para recuperar um valor da request no controlador e, caso não tenha sido encontrado, o valor será 0 por padrão. Logo em seguida (linha 6), é feita uma consulta no banco utilizando o método findByPrimaryKey() que consulta um dado pela chave primária (conforme explicado na seção 3.5.1) utilizando o valor do parâmetro recuperado da request. Porém, em nenhum momento foi verificado se o valor inteiro foi retornado corretamente, pois, caso contrário, o método findByPrimaryKey() retornará um valor null e uma JspException será lançada

2

(38)

ao tentar referenciá-lo na JSP.

1 /* *

2 * Metodo que r e c u p e r a um c o n t r a t o com i d v i n d o na r e q u e s t . 3 */

4 public C o n t r a t o g e t C o n t r a t o ( ) throws ArqException {

5 i n t i d C o n t r a t o = g e t P a r a m e t e r I n t ( " i d C o n t r a t o " , 0 ) ;

6 return dao . findByPrimaryKey ( i d C o n t r a t o , C o n t r a t o . c l a s s ) ;

7 }

Listing 3.4: Trecho de código que causou uma falha do tipo JspException.

3.5.4

Análise de IllegalArgumentException

O quarto tipo de exceção que mais ocorreu no período de junho de 2018 foi o IllegalArgumentException. Segundo sua documentação (ORACLE, 2018a), esta exceção é lançada quando um argumento ilegal ou inapropriado é passado para um método. O contexto do método é quem vai determinar se o argumento é válido ou não. Podemos citar como argumento ilegal, por exemplo, um método que recebe um arquivo em formato de imagem JPEG e realiza algum processamento na imagem, mas quando invocado é passado um arquivo de texto em formato TXT.

Analisamos as falhas apontadas no crash report e concluímos que todas estavam relacionada à um mesmo defeito. Existe no SIPAC um serviço de conversão de formatos de documentos para PDF. Uma API externa é utilizada para auxiliar nesta conversão e, por algum motivo que não conseguimos descobrir, até o momento da escrita deste trabalho a conversão não está funcionando. Ao passar uma URL com o arquivo a ser convertido, o método da API está lançando uma IllegalArgumentException.

Após uma consulta rápida em outros períodos, observamos que este tipo de exceção não está entre os mais frequentes do SIPAC e, por este motivo, não aprofundamos nossa análise neste caso isolado.

3.5.5

Análise de PSQLException

O PostgreSQL (POSTGRESQL, 2019) é um sistema gerenciador de banco de dados (SGBD) e é sua API quem fornece as operações necessárias para o SIPAC realizar operações relacionadas à base de dados (por exemplo, consultas, inserções, atualizações e remoções

(39)

de dados). A exceção do tipo PSQLException faz parte da API do PostgreSQL e tem a mesma semântica da conhecida java.sql.SQLException (ORACLE, 2018b), isto é, provê informações de erros relacionados às operações sobre a base de dados.

Naquele período, levantamos um total de 53 falhas que lançaram esta exceção e analisamos a causa de todas elas. Basicamente, todas são referentes à violações de restrições (constraints) do banco de dados. Pudemos detectar facilmente a causa de cada falha apenas com as informações das stack traces, pois uma mensagem com os detalhes da exceção esteve sempre presente. Das 53 ocorrências, vimos que no total haviam 5 defeitos distintos, que foram categorizados em:

∙ 1 defeito que violou a restrição na tentativa de inserção de uma informação nula em uma coluna que não permite valores nulos (NOT NULL). Este defeito ocorreu 7 vezes.

∙ 1 defeito que violou a restrição ao tentar remover um registro do banco que possuía referência em outra tabela através de uma chave estrangeira. Este defeito foi registrado 29 vezes.

∙ 3 defeitos associados à inserções de informações que também violaram a restrição de chave estrangeira. Neste caso, o valor da chave estrangeira não existia na tabela referenciada. Tivemos um total de 17 ocorrências deste tipo de erro.

Como pudemos observar, todas as 5 falhas estiveram relacionadas à violação de restrição de banco de dados. Isto significa que o erro se manifesta dependendo das entradas do programa, o que dificulta a detecção do problema a partir de uma análise estática. Algumas IDEs atuais (assim como o Eclipse) fornecem mecanismos para conexão do ambiente com o banco de dados, permitindo que os scripts SQL possam ser avaliados estaticamente com relação à sintaxe, mas também não são capazes de capturar violações de restrições. Em nossa análise não foi possível determinar padrões de bugs responsáveis por gerar falhas do tipo PSQLException, pois existe a necessidade de um prévio conhecimento da modelagem da base de dados.

3.6

Padrões de Bugs Identificados

Conforme apresentado nas seções anteriores, levantamos um total de 39 falhas distintas do SIPAC em um determinado período e documentamos a causa de cada uma. Dentre estas

(40)

falhas, identificamos que algumas causas poderiam representar padrões de bugs específicos do SIPAC. Conseguimos extrair 4 possíveis padrões que estão sintetizamos na Tabela 5.

Tag Descrição Ocorr./Analisados

BP01 Retorno de findByPrimaryKey() não verificado 3/10 BP02 Argumentos de métodos em classes utilitárias não

verifi-cados

2/10

BP03 Retorno de getParameter() não verificado 2/21

BP04 Métodos get() e/ou set() não implementados em con-troladores

4/21

Tabela 5: Possíveis bug patterns detectados durante a análise.

Os padrões BP01 e BP02 foram identificados durante a análise de NullPoiterException enquanto que os BP03 e BP04 na análise de JspException.

O BP01 e BP03 são semelhantes e alertam à verificação de objetos retornados do banco de dados ou recuperados da sessão da aplicação Web. A diferença entre eles foi a origem da falha, onde o primeiro ocorreu em uma referência a objeto nulo dentro de uma classe enquanto que a terceira se manifestou durante a renderização da página JSP.

Ainda relacionado a null pointers, vimos que 2 dos 10 defeitos de NullPointerException analisados ocorreram após a utilização de métodos de classes auxiliares (ou utilitárias). Definimos como padrão (BP02) que todos os argumentos dos métodos destas classes devem ser verificados no corpo do método se são válidos (isto é, não nulos).

Por fim, durante a análise de JspException, vimos que este tipo de exceção pode ser de várias origens e se manifestam durante a renderização da JSP. Porém, identificamos 4 defeitos (dos 21 analisados) que a causa foi a ausência (ou escrita incorreta) de métodos acessores (get()) no controlador da camada de view. Por isso, documentamos como padrão de que métodos get() ou set() não implementados podem lançar uma JspException.

Estes padrões definidos estão relacionados à 11 defeitos dos 39 levantados. Os demais não pudemos identificar padrões, mas foram categorizados conforme apresentado nas seções anteriores.

Na próxima seção iremos apresentar uma análise, utilizando ferramentas de análise de código existentes e investigaremos se estes 39 defeitos poderiam ser prevenidos por alguma delas.

(41)

3.7

Utilizando Ferramentas de Análise Estática

Exis-tentes

Estudamos e executamos algumas das ferramentas de análise estática (de detecção de

bugs) existentes atualmente, a fim de afirmar se elas alertariam ou não sobre potenciais

problemas no código que levariam a alguma destas falhas apresentadas na seção anterior.

As ferramentas executadas foram a SonarLint (SONARSOURCE, 2019) e a SpotBugs (SPOTBUGS, 2019), ambas possuem versão de plug-in para o Eclipse. Elas podem ser configuradas para buscar categorias de bugs específicas como corretude, code smells, más práticas de programação, segurança, desempenho, etc.

O primeiro passo desta análise foi levantar as regras de cada ferramenta que estão relacionadas àquelas 39 falhas. Criamos categorias para cada tipo de exceção e, com base na documentação de cada ferramenta, selecionamos as regras que potencialmente poderiam detectar a falha. Na maioria dos casos descartamos a possibilidade de a ferramenta reportar o defeito apenas consultando a documentação, mas em alguns casos foi necessário executá-las sob determinadas classes.

Nas seções 3.7.1 e 3.7.2 descreveremos a metodologia de análise utilizando as duas ferramentas.

3.7.1

Utilizando a SonarLint

No período em que esta pesquisa foi realizada, SonarLint possuía um total de 530 regras implementadas, divididas em quatro categorias:

∙ Vulnerability

∙ Bug

∙ Security Hotspot

∙ Code Smell

Selecionamos apenas a categoria relacionada à bugs (que possui 120 regras) nesta análise, pois as de segurança e vulnerabilidade não estão relacionadas com os crashes levantados. Já as regras da categoria de Code Smells também não possui efeito significativo com relação à falhas, como demonstrou um estudo feito por Hall (HALL et al., 2014), e

(42)

também foi desconsiderada. Descreveremos agora o resultado de nossa análise da SonarLint de acordo com cada tipo de exceção.

Estudamos 10 falhas do tipo NullPointerException conforme mostrado anterior-mente. Das 120 regras de bug, apenas 5 estão relacionadas à null pointers. Com base nas categorias dos erros mostradas na Tabela 3 e na documentação das regras da ferramenta, verificamos que apenas 1 falha da categoria NPE3 poderia ser encontrada. O algoritmo 3.5 foi extraído do código do SIPAC e é onde está localizado a causa do null pointer. A variável total não foi inicializada (linha 2) e, na linha 11, ainda estava nula quando foi referenciada, gerando o erro. Porém, ao executar a ferramenta no arquivo que contém o defeito, ela não nos alertou sobre o potencial null pointer.

1 // . . . 2 Long t o t a l , i n i c i o ; 3 // . . . 4 i f ( V a l i d a t o r U t i l . isEmpty ( a n t e r i o r ) ) { 5 t o t a l = a . getOdometro ( ) ; 6 } e l s e { 7 // . . . 8 t o t a l = a b a s t e c i m e n t o . getOdometro ( ) ; 9 } 10 11 totalOdometroAcum += ( t o t a l ) ; 12 // . . .

Listing 3.5: Potencial null pointer, caso reqPagamento não seja encontrado.

Com relação aos erros de JspException, conforme já dito, eles se manifestam durante a renderização das páginas JSP. Nenhuma das regras da documentação faz referência a estas páginas. As falhas de LazyInitializationException que estudamos também ocorreram durante a renderização da página, além disso existe apenas uma regra apenas relacionada ao framework hibernate e não está relacionada ao lazy loading. Portanto, concluímos que os 23 defeitos (somando os dois tipos) também não seriam detectados.

A única falha do tipo IllegalArgummentException (que ocorreu 102 vezes) diz respeito à um método de uma API externa. Algum argumento não aceito foi passado como argumento deste método e a exceção foi lançada. Executamos a SonarLint (com as 120 regras habilitadas) em uma classe que utilizou deste método, mas nenhum alerta foi

(43)

exibido.

Por fim, analisamos a documentação da ferramenta para verificar se algum dos 5 defeitos relacionados à persistência (isto é, que lançaram uma PSQLException) poderiam ser identificados. A SonarLint analisa 6 regras relacionadas à script SQL, porém nenhum verifica violações de restrições de banco (que foram a causa todas as falhas).

3.7.2

Utilizando a SpotBugs

A SpotBugs contém mais de 400 regras implementadas divididas em 9 categorias. Na página da documentação, as regras possuem alguns prefixos para categorizá-las. O prefixo “NP” diz respeito às regras relacionadas a null pointers e observamos 50 regras com este prefixo na documentação. Diferentemente da abordagem utilizada com a ferramenta anterior (que possuía apenas 6 regras deste tipo), executamos a SpotBugs em cada uma das 10 classes que originaram os defeitos de NullPointerException com todas as regras habilitadas. Após a execução, a ferramenta também não sinalizou nenhuma das linhas em que havia potencial null pointer.

Para o caso das falhas do tipo JspException, foi observado que na documentação há apenas duas regras relacionadas à páginas JSP. Uma relacionada a vulnerabilidade e outra analisava um caso em que poderia ocorrer condição de corrida (do inglês, race

conditions). Porém, a análise se restringe apenas a classes Java e, assim como a SonarLint,

os 21 defeitos que lançaram esta exceção também não seriam alertados.

Para os outros 3 tipos de exceções, nenhum dos defeitos analisados seriam reportados pela SpotBugs pelos mesmos motivos citados na seção 3.7.1.

3.7.3

Discussão

A partir da análise apresentada nesta seção, chegamos à conclusão que as duas ferramentas de análise estática de código avaliadas não identificaram nenhum dos defeitos levantados neste estudo. Observamos que estas ferramentas podem ter melhores resultados quando programadas para detectar bug patterns específicos do sistema sob teste. Por isso, construímos a ferramenta proposta neste estudo com checagem de bug patterns extraído de apenas crashes reais de um sistema.

Referências

Documentos relacionados

Neste tipo de situações, os valores da propriedade cuisine da classe Restaurant deixam de ser apenas “valores” sem semântica a apresentar (possivelmente) numa caixa

nesta nossa modesta obra O sonho e os sonhos analisa- mos o sono e sua importância para o corpo e sobretudo para a alma que, nas horas de repouso da matéria, liberta-se parcialmente

A placa EXPRECIUM-II possui duas entradas de linhas telefônicas, uma entrada para uma bateria externa de 12 Volt DC e uma saída paralela para uma impressora escrava da placa, para

Este trabalho buscou, através de pesquisa de campo, estudar o efeito de diferentes alternativas de adubações de cobertura, quanto ao tipo de adubo e época de

No entanto, maiores lucros com publicidade e um crescimento no uso da plataforma em smartphones e tablets não serão suficientes para o mercado se a maior rede social do mundo

O objetivo do curso foi oportunizar aos participantes, um contato direto com as plantas nativas do Cerrado para identificação de espécies com potencial

Neste estudo foram estipulados os seguintes objec- tivos: (a) identifi car as dimensões do desenvolvimento vocacional (convicção vocacional, cooperação vocacio- nal,

Este estudo apresenta como tema central a análise sobre os processos de inclusão social de jovens e adultos com deficiência, alunos da APAE , assim, percorrendo