• Nenhum resultado encontrado

Capítulo 3 – Compreensão de Programas 3.1 Importância da Compreensão de Programas

3.4. Suporte a Compreensão de Programas

3.4.1. Engenharia Reversa

As técnicas e ferramentas de engenharia reversa se confundem com o próprio conceito de compreensão de programas. Como descreveu Brooks (1982), “a tarefa de entender um programa para um programador se torna a de construir ou reconstruir informação suficiente a respeito dos domínios de modelagem que o programador original usou para ligar o problema a um programa em execução”. Reconstruir o mapeamento entre os domínios tem como resultado recuperar as abstrações adotadas pelo programador do software enquanto o construía. A rastreabilidade entre os artefatos do software – código fonte, modelos, documentação, descrições de regras de negócio, etc – é um suporte essencial para a reconstrução deste mapeamento (MÜLLER et al, 2000).

A engenharia reversa é definida como o processo de analisar um sistema e identificar seus componentes e inter-relações, resultando na criação de representações do sistema em níveis mais altos de abstração (CHIKOFSKY e CROSS, 1990). A forte ligação entre a engenharia reversa e a compreensão de programas pode ser observada nos seis objetivos que Chikofsky e Cross (1990) atribuem à engenharia reversa:

(i) lidar com a complexidade, criar visões alternativas – possivelmente gráficas; (ii) recuperar informações perdidas sobre o projeto dos sistemas,

(iii) detectar efeitos colaterais;

(iv) sintetizar níveis mais altos de abstração; e (v) facilitar o reuso.

Diversas abordagens podem ser adotadas para suportar a compreensão de programas através da engenharia reversa (TILLEY et al, 1996). Por exemplo, o trabalho em engenharia reversa de casos de testes de Sneed (2004) é particularmente interessante, pois dá uma visão diferente daquela que intuitivamente se associa ao termo engenharia reversa. Seu objetivo é recuperar as relações entre os casos de testes e o resto dos documentos de especificação do sistema, como processos, telas, relatórios, componentes, etc. Chikofsky e Cross (1990) apresentaram uma taxonomia da terminologia adotada nesta área, como por exemplo:

• Redocumentação é a forma mais simples e antiga de engenharia reversa. Compreende a criação ou a revisão de representações em um mesmo nível de

abstração e normalmente se refere à criação de visões alternativas, como modelos de dados e fluxos de dados.

• Recuperação de Projeto – Design Recovery – é um tipo específico de engenharia reversa que procura criar ou recuperar representações do sistema em níveis mais altos de abstração e também suas ligações com o código fonte. É interessante notar a semelhança desta definição com a forma como Brooks (1982) define a própria compreensão de programas como a reconstrução do mapeamento do domínio do problema no domínio de programação através de níveis intermediários. A diferença é que o modelo cognitivo de Brooks se refere às representações mentais e a engenharia reversa se propõe a criar representações documentais destes diferentes níveis de abstração.

• Reestruturação corresponde a transformar uma representação em determinado nível de abstração em outra no mesmo nível, sem mudar o comportamento externo do sistema. Normalmente está associada a melhorias arquiteturais, realizadas em nível de código fonte.

• Reengenharia trata-se de uma renovação ou re-desenvolvimento de um sistema. Normalmente envolve algum tipo de engenharia reversa, reestruturação e também a adição de novos requisitos na criação de um novo sistema.

Ferramentas CASE, tais como IBM® Rational Rose™, IBM® Rational Software Architect™, Borland® Together™, dotadas da capacidade de realizar engenharia reversa são comuns na indústria de software. Elas geralmente são capazes de analisar um código fonte e gerar modelos UML, com diagramas de classe, máquinas de estado, grafos de chamadas ou ainda gerar modelos lógicos a partir de esquemas de bancos de dados. Em todos esses casos o produto gerado tem um nível de abstração mais alto e, portanto, menos detalhes o que facilita a sua assimilação pela proximidade com conceitos de domínio humano. O trabalho de Bojic e Velasevic (2000a, 2000b) ilustra como é possível recuperar arquiteturas de sistemas a partir de ferramentas como estas.

Observando a forma empírica como as soluções de engenharia reversa eram propostas, Tilley et al (1996) propuseram um framework para classificar as ferramentas, técnicas e propostas de solução na área de compreensão de programas. A estrutura de classificação foi criada em 3 passos. Primeiro ocorreu uma investigação dos aspectos cognitivos da

compreensão de programas. Depois foram identificadas as atividades canônicas que compõem as atividades de engenharia reversa, são elas: coleta de dados, organização do conhecimento e exploração de informação. E por fim, foram definidas as dimensões para classificação de ferramentas e técnicas de engenharia reversa: domínios de aplicação, domínio de implementação e escalabilidade, suporte a tarefas específicas e extensibilidade.

É importante mencionar que os trabalhos ligados à recuperação de arquitetura e projetos, como o de Mendonça (1999), apesar de sua importância para a compreensão, não conseguem superar a barreira de aspectos técnicos do produto de software. Os níveis mais altos de abstração criados se referem, por exemplo, ao uso de algoritmos e estruturas de programação para implementar determinadas tarefas, como a comunicação e a organização entre processos em sistemas distribuídos. Contudo, é muito difícil atingir questões relacionadas ao domínio da aplicação do software em seu ambiente operacional, com os processos de negócio mencionados por Chapin (2003).

3.4.2. Slicing

O processo de slicing – fatiamento, em português – consiste na criação de subconjuntos do código fonte do programa original – as fatias – que sejam equivalentes ao programa original do ponto de vista de um determinado critério, normalmente um conjunto de variáveis um ponto do código fonte.

Weiser (1981) foi quem primeiro introduziu o conceito de slicing ao observar programadores durante a tarefa de depuração. Para ele, reduzir o programa a partes menores e consistentes segundo algum critério é a forma pela qual programadores entendem programas muito grandes. O trabalho de Weiser surgiu inicialmente quase como um modelo cognitivo, procurando não apenas descrever uma técnica, mas sim com o objetivo de explicar o processo de compreensão.

Apesar de ter surgido com o propósito de auxiliar a compreensão, as técnicas de slicing acabaram sendo usadas outros fins, como dividir linhas de processamento de um programa a serem alocadas para processadores paralelos, avaliação de conformidade, testes, suporte ao reuso entre outros (HARMAN et al, 2003) (LUCIA, 2001) (FRANCEL e RUGABER, 2001).

Existem diversas formas de realizar slicing. Lucia (2001) classificou-as em estáticas, dinâmicas e quasi dinâmicas. O slicing estático tem como ponto de partida um conjunto de variáveis dado, deve-se então gerar um programa mínimo que seja equivalente ao programa

original. De certo ponto de vista, é o mesmo que remover todas as linhas de código que de nenhuma forma afetam os valores armazenados nessas variáveis em qualquer situação. No slicing dinâmico o programa original é substituído por rastros de execução específicos. Com isso, potencialmente o tamanho do programa gerado reduz-se, o uso de ponteiros de memória para dados e funções é tratado de forma mais precisa. Por fim, o slicing quasi dinâmico procura fixar determinados valores de variáveis enquanto outras variáveis são livres para assumir qualquer valor. Se todas as variáveis são livres o slicing quasi dinâmico coincide com o slicing estático. Se, ao contrário, todas as variáveis forem fixadas teremos o mesmo resultado do slicing dinâmico.

Harman et al (2003), aplicaram um slicing com resultados menos formais em termos sintáticos com o objetivo de evitar a indecibilidade e dar respostas aproximadas a perguntas feitas na forma de critérios. Por exemplo, para determinar se o acesso a um vetor é seguro – no sentido de não ler ou escrever dados fora da área de memória alocada para ele. Villavicencio (2001) procurou aplicar slicing para realizar o reconhecimento automático de planos (ver Modelos Cognitivos). Com base em técnicas de slicing dinâmicos e quasi dinâmicos, Lucia et al (2001) criaram o Slicing Condicionado como um framework para suportar a compreensão de programas.

3.4.3. Ontologias

Ontologias estão tornando-se mais populares como ferramentas de apoio a diversas atividades relacionadas ao conhecimento. Uma ontologia é a especificação explícita de conceitos (GRUBBER, 1993). Na prática isso significa que as ontologias ajudam a reduzir a ambigüidade dos conceitos ao explicitar suas definições e suas relações. Trabalhos que utilizam ontologias como suportes à compreensão de programas chamam a atenção para a inconsistência entre os conceitos representados na documentação dos sistemas, a realidade de seu código fonte e o modelo mental do mantenedor envolvido na tarefa de compreensão (DERIDDER, 2002) (ZHANG et al, 2006).

Em seu trabalho, Deridder (2002) propõe o uso de uma ontologia leve – lightweight – para ajudar a capturar e organizar o conhecimento dos construtores originais do sistema. A ontologia é leve, pois não se pretende que ela seja adotada por comunidades fora do contexto do projeto de manutenção, como normalmente ocorre com ontologias.

O que se pretende é que a ontologia seja usada como referência nos artefatos produzidos, criando um vocabulário comum e sem ambigüidade dentro da equipe envolvida no sistema. As principais motivações de Deridder (2002) para propor o uso de ontologias para suportar a compreensão de programas são:

• A natureza explícita da representação de conceitos evita que grande parte do conhecimento fica apenas na mente dos projetistas do sistema, a que se pode creditar grande parte da inconsistência da documentação do sistema citada anteriormente.

• A organização intrínseca que a ontologia provê aos conceitos representados, descrevendo suas propriedades e relacionando-os uns aos outros.

Preocupados com a evolução da arquitetura de websites, Zhang et al (2006) desenvolveram uma abordagem baseada em duas ontologias para reduzir a inconsistência entre os modelos mentais dos desenvolvedores e as representações tradicionais. A primeira ontologia é a ontologia de Código fonte, onde estão representados os conceitos de linguagens de programação orientada a objetos, como classes, métodos e pacotes. A segunda ontologia é a de documentação onde estão representados os conceitos encontrados na documentação do sistema com relação à arquitetura, como camadas e componentes.

Um terceiro componente da abordagem de Zhang é a mineração de texto que ajuda a identificar automaticamente os conceitos dentro dos documentos e criar a ontologia de documentação. A mineração de textos busca não apenas encontrar termos isolados, mas também dar a eles semântica o que os faz representáveis na forma de ontologia.