97
Universidade do Minho Escola de Engenharia Amadeu José Freitas Barroso Andrade outubro de 2015 Análise de complexidade de programas em ferramentas de apoio à decisão Amadeu José Freitas Barroso Andrade Análise de complexidade de programas em ferramentas de apoio à decisão UMinho|2015

Amadeu José Freitas Barroso Andrade · testes, mas o foco deste projeto foram os testes de caixa-branca e os testes de caixa-preta, pois são os mais usados na SISCOG. Os testes

  • Upload
    dangnhi

  • View
    214

  • Download
    0

Embed Size (px)

Citation preview

Universidade do MinhoEscola de Engenharia

Amadeu José Freitas Barroso Andrade

outubro de 2015

Análise de complexidade de programas emferramentas de apoio à decisão

Amad

eu J

osé

Frei

tas

Bar

roso

And

rade

An

ális

e d

e c

om

ple

xid

ad

e d

e p

rog

ram

as

em

fe

rra

me

nta

s d

e a

po

io à

de

cisã

oU

Min

ho|2

015

Amadeu José Freitas Barroso Andrade

outubro de 2015

Análise de complexidade de programas emferramentas de apoio à decisão

Universidade do MinhoEscola de Engenharia

Trabalho efetuado sob orientação do Professor Doutor Cláudio Manuel Martins Alves

Dissertação de MestradoMestrado em Engenharia de Sistemas

iii

AGRADECIMENTOS

Deixo o meu agradecimento a todas pessoas que tornaram este projeto possível, em especial:

À minha família, que sempre me apoiou.

Ao meu orientador, o professor Cláudio Alves, pela oportunidade de trabalhar sob a sua orientação e

por todo o tempo que dedicou a aconselhar-me.

À SISCOG, pela oportunidade de realizar este projeto com o seu apoio.

Ao Ricardo Nogueira, por ter sido o impulsionador deste projeto e ter-se mostrado sempre disponível

para ajudar.

A todos os meus amigos que estiveram comigo durante este trajeto.

iv

v

RESUMO

Na área do desenvolvimento de software, o processo de análise de código é um processo

extremamente delicado, pois é suscetível a erros e que varia consoante a experiência do programador,

tornando-se por vezes um processo complexo e demorado se for feito sem a ajuda de ferramentas

informáticas.

O trabalho desenvolvido nesta dissertação usou a linguagem LISP como base e visa proporcionar uma

nova forma de apoio aos analistas-programadores durante a análise do código produzido e também

para servir de apoio à equipa de testes na contabilização do número de casos de testes a desenhar.

Aqui é também apresentado um estudo sobre todos os conceitos teóricos relativos à complexidade

ciclomática e tudo que esta envolve, fazendo a ligação desta métrica de análise com os testes de

software e os grafos de controlo de fluxo.

Palavras-Chave: Complexidade ciclomática, testes de software, grafos de controlo de fluxo, análise de

software.

vi

vii

ABSTRACT

In the field of software development, the process of code analysis is a very delicate process because it

is error-prone and varies with the experience of the developer, making it a complex and time consuming

process if done without the support of the appropriate software tools.

The work described in this thesis was done using LISP and aims to provide a new form of support for

the anlyst-programmer during the analysis of the code produced and also to provide support to the tests

team making it possible to know the number of test cases that need to be designed.

It is also presented a study of all the theoretical concepts related to cyclomatic complexity and all the

related issues, linking this analysis metric with the software testing process and with the control flow

graphs.

KEYWORDS: Cyclomatic complexity, software testing, control flow graphs, software analysis.

viii

ix

ÍNDICE

1. Introdução ................................................................................................................................... 1

1.1 Contextualização e Enquadramento ........................................................................................ 1

1.2 Motivação e objetivos ............................................................................................................. 3

1.3 Apresentação da empresa ...................................................................................................... 3

1.4 Estrutura da dissertação ........................................................................................................ 5

2. Complexidade ciclomática de programas ..................................................................................... 7

2.1 Complexidade em geral ......................................................................................................... 7

2.2 Definição e caracterização da complexidade ciclomática ......................................................... 8

2.3 Aspetos positivos da complexidade ciclomática ..................................................................... 15

2.4 Aspetos negativos da complexidade ciclomática ................................................................... 16

3. Testes de software ..................................................................................................................... 19

3.1 Introdução aos testes de software ........................................................................................ 19

3.2 Testes de caixa branca e testes de caixa preta ..................................................................... 19

3.3 Testes e complexidade ciclomática ....................................................................................... 21

4. Grafos de controlo de fluxos ....................................................................................................... 23

4.1 Introdução à teoria de grafos e grafos de controlo de fluxo .................................................... 23

4.1.1 Conceitos da teoria de grafos ........................................................................................ 24

4.2 Complexidade ciclomática e os grafos .................................................................................. 25

5. Implementação .......................................................................................................................... 29

5.1 Abordagens ponderadas ...................................................................................................... 29

5.2 Linguagem de programação e ferramentas externas de apoio ............................................... 29

5.2.1 LISP ............................................................................................................................. 30

5.2.2 GNU Emacs e SLIME .................................................................................................... 31

5.2.3 Allegro CL IDE .............................................................................................................. 31

5.2.4 Analisador de código: code walker ................................................................................. 31

5.2.5 Gestor de bibliotecas LISP: Quicklisp ............................................................................. 33

5.2.6 Biblioteca de grafos: CL-Graph ...................................................................................... 34

5.2.7 Graphviz ....................................................................................................................... 34

x

5.3 Estrutura do código .............................................................................................................. 35

5.4 Decisões de implementação ................................................................................................ 36

5.4.1 Definição genérica ........................................................................................................ 36

5.4.2 Operador lógico: and e or .............................................................................................. 36

5.4.3 Condicional simples: if .................................................................................................. 38

5.4.4 Condicional simples: cond ............................................................................................. 39

5.4.5 Condicional simples: when e unless .............................................................................. 40

5.4.6 Condicionais compostos ............................................................................................... 41

5.4.7 Iterador: dolist .............................................................................................................. 43

5.4.8 Iterador: do/do* ............................................................................................................ 45

5.4.9 Operadores especiais: let e let* ..................................................................................... 47

5.4.10 Operadores especiais: labels e flet .............................................................................. 48

5.4.11 Operadores especiais: multiple-value-bind ................................................................... 49

5.4.12 Outros ........................................................................................................................ 49

5.5 Relatório .............................................................................................................................. 49

6. Estudo de casos ........................................................................................................................ 51

6.1 Caso I – Definição de cálculo de compensações ................................................................... 51

6.2 Caso II – Definição de expansão de partilha de equipamento ................................................ 53

6.3 Caso III – Definição de processamento de licitações ............................................................. 56

7. Conclusões ................................................................................................................................ 61

7.1 Síntese dos resultados alcançados ....................................................................................... 61

7.2 Trabalho futuro .................................................................................................................... 63

Bibliografia ....................................................................................................................................... 65

Anexo I – Relátorio gerado para o caso I ............................................................................................ 73

Anexo II – Grafo do caso de estudo II ................................................................................................ 74

Anexo III – Grafo do caso de estudo III (original) ................................................................................ 77

Anexo IV – Grafo do caso de estudo III (refatorizado) ......................................................................... 79

xi

LISTA DE FIGURAS

Figura 1 - Logótipo da SISCOG. ........................................................................................................... 4

Figura 2 - Exemplo de um if. ............................................................................................................. 10

Figura 3 - Exemplo de grafo de controlo de fluxo. ............................................................................... 11

Figura 4 - Grafo de controlo de fluxo da métrica original. .................................................................... 11

Figura 5 - Grafo de controlo de fluxo com a alternativa 1. ................................................................... 12

Figura 6 - Grafo de controlo de fluxo com a alternativa 2. ................................................................... 13

Figura 7 - Linguagens de programação usadas na SISCOG. (SISCOG, 2013). ..................................... 30

Figura 8 - Exemplo de um if e um cond em LISP. .............................................................................. 32

Figura 9 - Exemplo de um macroexpand. ........................................................................................... 32

Figura 10 - Resultado do macroexpand. ............................................................................................. 32

Figura 11 - Exemplo de um grafo em linguagem DOT. ....................................................................... 34

Figura 12 - Estrutura geral do código implementado. ......................................................................... 35

Figura 13 - Grafo de um and/or. ....................................................................................................... 37

Figura 14 - Grafo de um and/or com nodos intermédios. ................................................................... 38

Figura 15 - Grafo de um if simples. ................................................................................................... 39

Figura 16 - Grafos da macro cond. .................................................................................................... 40

Figura 17 - Grafo de um when/unless. .............................................................................................. 41

Figura 18 - Pseudo-código de um if com recurso aos operadores and/or. .......................................... 41

Figura 19 - Grafos de um if com recurso aos operadores and/or. ...................................................... 42

Figura 20 - Grafos do dolist. .............................................................................................................. 44

Figura 21 - Grafo de um do/do*. ....................................................................................................... 45

Figura 22 - Grafo 2 de um do/do*. .................................................................................................... 46

Figura 23 - Grafo de um let/let*. ....................................................................................................... 47

Figura 24 – Definição do cálculo de compensações. .......................................................................... 52

Figura 25 - Grafo do caso de estudo I. ............................................................................................... 52

Figura 26 - Exemplo de parte do grafo do caso de estudo II. .............................................................. 55

Figura 27 - Condições do when no caso de estudo II. ........................................................................ 56

Figura 28 - Exemplo de parte do grafo da definição original do caso III. .............................................. 58

Figura 29 - Grafos das definições criadas. ......................................................................................... 59

Figura 30 – Anexo I: Exemplo de um dos relatórios HTML gerados. ................................................... 73

xii

Figura 31 - Anexo II - Grafo do caso de estudo II (Parte 1). ................................................................. 74

Figura 32 - Anexo II - Grafo do caso de estudo II (Parte 2). ................................................................. 75

Figura 33 - Anexo II - Grafo do caso de estudo II (Parte 3). ................................................................. 76

Figura 34 - Anexo III - Grafo do caso de estudo III, original (Parte 1). .................................................. 77

Figura 35 - Anexo III: Grafo do caso de estudo III, original (Parte 2). ................................................... 78

Figura 36 - Anexo IV - Grafo do caso de estudo III, refatorizada (Parte 1). ........................................... 79

Figura 37 - Anexo IV - Grafo do caso de estudo III, refatorizada (Parte 2). ........................................... 80

xiii

LISTA DE TABELAS

Tabela 1 - Fórmulas da complexidade ciclomática. ............................................................................ 10

Tabela 2 - Análise do valor da complexidade ciclomática (Enescu, Mancas, Manole, & Udristoiu, 2008).

........................................................................................................................................................ 13

Tabela 3 - Ferramentas utilizadas no projeto. ..................................................................................... 30

Tabela 4 - Formas retornadas pelo code walker. ................................................................................ 33

Tabela 5 - Grupos de palavras reservadas. ........................................................................................ 36

Tabela 6 - Resumo sobre o cálculo da complexidade das novas definições. ........................................ 59

xiv

xv

LISTA DE ABREVIATURAS, SIGLAS E ACRÓNIMOS

AAAI - American Association for Artificial Intelligence

AST - Abstract Syntax Tree

IDE – Integrated Development Environment

IEEE – Institute of Electrical and Electronics Engineers

xvi

1

1. INTRODUÇÃO

1.1 Contextualização e Enquadramento

O trabalho realizado e que irá ser descrito nesta dissertação, foi um projeto realizado numa empresa

portuguesa, SISCOG, na área de desenvolvimento de sistemas de apoio à decisão. Um dos objetivos

centrais deste projeto foi a construção de uma ferramenta que permitisse a análise automática da

complexidade de programas de código LISP, sendo um passo no processo de melhoria contínua pelo

qual a empresa se rege.

O trabalho desenvolvido nesta dissertação visa proporcionar uma nova forma de apoio aos

analistas-programadores durante a análise do código produzido e também para servir de apoio à

equipa de testes na contabilização do número de casos de testes a desenhar, pois é uma área onde é

gasta uma grande quantidade de esforço dos recursos da empresa.

Partindo dos testes de software em ferramentas de apoio à decisão, na engenharia de

software, os testes são, tradicionalmente, uma das principais técnicas a contribuir para uma maior

fiabilidade e qualidade do software (Del Grosso, Antoniol, Merlo, & Galinier, 2008), sendo por isso

usadas em várias empresas de desenvolvimento de software. Estes testes podem incidir numa vertente

de análise de eficiência ou numa vertente de resolução e/ou prevenção de erros. Neste projecto, o foco

principal foi a segunda vertente pois é aquela em que existe um maior número de recursos da empresa

envolvidos. Na procura do erro são desenhados casos de teste na tentativa de cobrir o máximo possível

de funcionalidades de um programa. Um caso de teste é considerado bom quando tem uma alta

probabilidade de mostrar um erro não descoberto até ao momento. Erro esse que pode ser

categorizado como erro fatal, que bloqueia por completo o funcionamento do programa, ou ser apenas

uma falha subtil, que origina uma alteração do output, sem causar a falha do programa (Kanewala &

Bieman, 2014). Se o teste não encontra erros serve para demonstrar o âmbito no qual o programa

funciona e serve portanto para validar que o programa cumpre os requisitos. Existem vários tipos de

testes, mas o foco deste projeto foram os testes de caixa-branca e os testes de caixa-preta, pois são os

mais usados na SISCOG.

Os testes de caixa-preta são tipicamente utilizados para isolar possíveis comportamentos

erróneos do sistema, tais como resultados, interfaces e rendimento. Por seu lado, os testes de caixa-

branca, são utilizados para verificar o software antes de integrá-lo no sistema, sendo baseados em

2

estruturas de controlo e correcção algorítmica. Nos testes de caixa-branca surge o método dos

caminhos básicos que engloba um grafo de fluxo e a sua complexidade ciclomática.

No que toca à complexidade de programas e sua medição, pode-se começar por referir que a

complexidade do software é sempre indesejada, já que é uma das razões fundamentais para o

diminuição da qualidade do software (Madi, Zein, & Kadry, 2013) e está directamente ligada à

produtividade dos recursos que trabalham na manutenção do software (Gill & Kemerer, 1991), pois

quanto mais complexo é um sistema, mais difícil é de manter, o que por sua vez vai acarretar custos

maiores (Gill & Kemerer, 1991; Coleman, Ash, Lowther, & Oman, 1994) para as empresas.

Neste contexto, surgem então as métricas de complexidade de software. Desde 1976 têm sido

usadas diferentes métricas para avaliar a complexidade de um sistema, acompanhar o progresso e

avaliar a efetividade do software (Suresh, Pati, & Rath, 2012; Fenton & Neil, 1999). Estas fornecem

uma forma de descrever quantitativamente os projetos de software, assim como uma forma de avaliar

os métodos e ferramentas usadas nesses projetos, com o intuito de aumentar a produtividade e

qualidade dos mesmos (Gill & Kemerer, 1991). Neste conjunto de métricas destaca-se a complexidade

ciclomática de McCabe. Esta métrica é considerada como um dos melhores indicadores da fiabilidade

do sistema (Suresh et al., 2012), sendo por isso escolhida como o principal foco desta dissertação.

A complexidade ciclomática surgiu em 1976 (McCabe, 1976), como uma abordagem para a

medição da complexidade de um sistema, através do cálculo do seu valor ciclomático. Para isso foram

necessárias algumas definições e teoremas da teoria de grafos, sendo o valor da complexidade

ciclomática calculado através do número de arestas e nós de um grafo de controlo de fluxo (Suresh et

al., 2012). O seu principal propósito, segundo Gill and Kemerer (1991), é identificar módulos de

software que vão ser difíceis de testar ou manter. Além desta avaliação da complexidade dos módulos

de um sistema, a complexidade ciclomática pode também ser usada para estimar o número de casos

de teste necessários para atingir uma máxima cobertura do código (Suresh et al., 2012), estudando

para isso o número de caminhos possíveis de um programa. No estudo de McCabe (1976), este refere

que apesar de ser possível definir um conjunto de expressões algébricas que fornecessem o número

total de caminhos possíveis através de um programa estruturado, usar este número não se tornaria

prático, optando-se então por seguir o método dos caminhos básicos, que quando combinados

gerariam todos os caminhos possíveis.

Estas informações foram o ponto de partida deste projecto, que teve como resultado final a

criação de uma ferramenta automática de análise de complexidade.

3

1.2 Motivação e objetivos

No desenvolvimento de software, a análise do código produzido ou existente é um processo suscetível

a erros e que varia com a experiência do programador, tornando-se por vezes num processo complexo

se for feito sem a ajuda de ferramentas informáticas.

O objetivo deste projeto centrou-se na análise automática da complexidade de ferramentas

informáticas dedicadas especificamente ao apoio à decisão e teve início com uma pesquisa

bibliográfica, em que se realizou um levantamento dos conceitos chave para a resolução do problema e

das principais técnicas e ferramentas que poderiam servir de suporte para a realização do mesmo.

Como este projeto pretendeu-se em particular avaliar a estrutura de ferramentas informáticas

através da medida da sua complexidade ciclomática. Usando como base programas reais em uso

numa empresa do sector, foi desenvolvida uma ferramenta de avaliação automática. A análise dos

resultados obtidos com esta ferramenta permite avaliar o número de casos necessários para testar

efetivamente estes programas, assim como avaliar quantitativamente a qualidade de partes do

software produzidas pela empresa.

A motivação para este estudo assentou na sofisticação de algumas das rotinas (nomeadamente

as de otimização) que compõem estas ferramentas, na dificuldade em delinear processos de teste

efetivos, no reduzido número de estudos dedicados a este assunto na literatura, e na criticidade dos

erros que podem ocorrer neste tipo de ferramentas.

1.3 Apresentação da empresa

A SISCOG é uma empresa criada em 1986, dedicada ao desenvolvimento de software, que fornece

sistemas de apoio à decisão com o intuito de otimizar o planeamento de recursos e gestão de

companhias de transporte, com um maior ênfase na ferrovia e metropolitanos.

Utilizando uma combinação de técnicas de inteligência artificial e investigação operacional

(Morgado & Martins, 1998; Morgado, Martins, & Haugen, 2003), a empresa foi criando e

desenvolvendo os seus produtos, que neste momento são o CREWS, o ONTIME e o FLEET.

Estes produtos surgiram na tentativa de cobrir grande parte do processo de planeamento e

gestão dos clientes, fornecendo ferramentas para que estes consigam otimizar de forma precisa,

efetiva e rápida os seus recursos operacionais e trabalho diário.

4

O CREWS é o produto usado no planeamento e gestão de pessoal, oferecendo aos utilizadores

diferentes níveis de suporte à decisão, como o modo manual, que apenas valida todas as restrições do

problema e faz os cálculos necessários enquanto os utilizadores constroem os planos dos recursos, o

modo semi-automático, que aponta uma direção para atingir uma boa solução e por fim, o modo

automático, que fornece uma solução otimizada. Este produto, foi premiado em 1997 e 2003 com o

"Innovative Application Award" dado pela AAAI e laureados pelo "The Computerworld Honors Program"

em 2006.

O ONTIME é usado para a criação de horários e cobre todo este processo, que vai desde a

criação de horários anuais até aos ajustes feitos no dia-a-dia. Neste produto é possível planear e gerir a

alocação de dois importantes recursos para as viagens das companhias: espaço (rotas, linhas, etc) e

tempo (tempo de saída e chegada).

O FLEET, por sua vez, é usado para o planeamento e gestão de material motor. Este cria o

escalonamento otimizado para veículos, considerando o número de passageiros expectável,

especificações da frota e restrições operacionais. Produz também planos cíclicos de longo-prazo,

planos de calendário de curto-prazo, lida com a manutenção planeada de veículos e fornece suporte à

decisão para as operações do dia-a-dia.

Relativamente aos produtos FLEET e ONTIME, a empresa encontra-se neste momento a fazer

grandes desenvolvimentos.

Figura 1 - Logótipo da SISCOG.

5

1.4 Estrutura da dissertação

Esta dissertação está dividida em 7 capítulos, que serão descritos em seguida.

No primeiro capítulo é feita uma introdução onde é apresentada a contextualização e

enquadramento do tema, é feita a apresentação da empresa, assim como a motivação e os objetivos

do projeto. Este capítulo acaba com a descrição da estrutura do relatório.

No segundo capítulo é introduzida a métrica da complexidade ciclomática, sendo exposto o

estudo geral feito sobre o tema. Este estudo é o resultado da revisão de literatura existente e cobre

aspectos como a sua definição, utilização, cálculo e análise dos seus resultados. São também

apresentadas algumas críticas a esta métrica.

No terceiro capítulo é introduzido o tema dos testes de software, onde são abordados os testes

de caixa branca e caixa preta, assim como a sua ligação à complexidade ciclomática.

No quarto capítulo é apresentada uma revisão sobre os grafos de controlo de fluxo, começando

por uma breve introdução à teoria de grafos direcionada para o número ciclomático e acabando na

ligação entre os grafos de controlo de fluxo e a complexidade ciclomática.

No quinto capítulo é apresentado o processo de desenvolvimento da ferramenta informática,

desde a linguagem de programação utilizada até aos elementos principais e cálculos efetuados pela

ferramenta.

No sexto capítulo mostram-se exemplos práticos de utilização da ferramenta, apresentando e

analisando os casos de estudos selecionados.

No último capítulo são retiradas conclusões do trabalho realizado e apresentam-se propostas

para trabalho futuro.

6

7

2. COMPLEXIDADE CICLOMÁTICA DE PROGRAMAS

Neste capítulo será exposto o estudo geral feito sobre a métrica da complexidade ciclomática. Este

estudo é o resultado da revisão de literatura existente e cobre aspectos como a sua definição, críticas,

utilização, cálculo e análise dos resultados.

2.1 Complexidade em geral

Antes de abordar a parte da complexidade ciclomática, vou começar por introduzir o tema da

complexidade em geral, passando uma visão geral sobre o que é a complexidade de software e como é

que esta tem vindo a ser definida na engenharia de software.

A complexidade de software é tradicionalmente um indicador direto da qualidade e do custo do

software (Banker, Srikant, Kemerer, & Zweig, 1993; Basili & Perricone, 1983; Curtis, Sheppard, &

Milliman, 1979; Gill & Kemerer, 1991; Munson & Khoshgoftaar, 1992; Wilkie & Hylands, 1998; Jay et

all., 2009), pois quanto maior a complexidade de um programa, maior é a probabilidade de este ter

falhas, o que consequentemente se traduz numa menor eficiência do mesmo (Gaur, 2013; Khalid, ul

Haq, & Khan, 2013; Yadav & Khan, 2012; Bandara, Wikramanayake, & Goonethillake, 2009).

Na engenharia de software, o conceito genérico de complexidade foi investigado por vários

autores, resultando daí uma grande variedade de definições que correspondem às diferentes visões de

complexidade dos mesmos. De seguida irei expor alguns conceitos, compilados no artigo de Abram,

Lopez, and Habra (2004):

Em IEEE (1990), pode-se encontrar o conceito de complexidade como sendo o grau de

dificuldade em entender e verificar o desenho ou implementação de um sistema ou componente,

sendo aqui definido que a complexidade é uma propriedade da implementação do desenho, mas que

está também relacionada com o esforço necessário para perceber e verificar a implementação do

desenho.

Em Evans and Marciniak (1987), a complexidade é baseada na estrututura do sistema e na

definição de algumas das suas caraterísticas, sendo então determinada por fatores intrínsecos ao

sistema, como o número e complexidade das interfaces, o número e complexidade dos ramos

condicionais, pelo grau de encadeamento e pelos tipos de estruturas de dados. Quanto maior o

número de fatores e relações de um sistema, maior é a interação entre os seus elementos, o que torna

o sistema mais complexo.

8

Em Whitmire (1997), o conceito de complexidade tem um âmbito mais abrangente, pois

considera alguns tipos de complexidade, como a complexidade computacional e psicológica. Estes

conceitos de complexidade computacional e complexidade psicológica, são baseadas nas noções

propostas por Whitmire (1997) e Henderson-Sellers (1996), sendo a primeira definida em termos de

recursos de hardware necessários para executar o software e, a segunda, baseada em fatores como o

problema complexo que foi resolvido, as caraterísticas do software utilizadas e o conhecimento e

experiência do programador sobre o problema e domínio da solução.

Analisando estas definições, pode-se concluir que a complexidade é algo que a indústria de

software deve evitar, sendo por isso a questão da avaliação da complexidade uma área crítica no

desenvolvimento do software. Nesta área é gasto muito esforço na tentativa de identificar técnicas e

métricas para avaliar a complexidade do software e dos seus módulos (Munson & Khoshgoftaar,

1989), mas que também permitam o acompanhamento do progresso e avaliação da efetividade do

software (Fenton & Neil, 1999). Estas métricas são então consideradas como fatores decisivos na

medição da qualidade de um produto de software.

Segundo Gill and Kemerer (1991), sem o recurso às métricas, as tarefas de planeamento e

controlo do desenvolvimento de software iriam permanecer estagnadas, já que estas competências são

adquiridas apenas através do ganho de experiência, e esta, não é facilmente transferível para um

próximo sistema ou para futuros melhoramentos. Por seu lado, com o uso das métricas, os projetos de

software podem ser quantitativamente descritos e os métodos e ferramentas usadas nesses projetos

para melhorar a produtividade e qualidade podem ser avaliados.

Sendo a métrica da complexidade ciclomática um dos melhores indicadores da fiabilidade de

um sistema (Suresh et al., 2012), irei aborda-la de seguida.

2.2 Definição e caracterização da complexidade ciclomática

Sabendo que os problemas de testabilidade e manutenção nasceram do fato de as empresas

passarem metade do tempo de desenvolvimento em testes (Boehm, 1973) e do custo excessivo em

manter os sistemas (Cammack & Rogers, 1973), a métrica da complexidade ciclomática surgiu como

uma técnica matemática que fornece uma base quantitativa para a modularização de um sistema de

software, permitindo identificar quais os módulos desse software é que vão ser difíceis de testar ou

manter (McCabe, 1976).

9

Em McCabe, Wallace and Watson (1996), esta métrica é descrita como tendo dois propósitos.

Um deles é ser usada durante todas as fases do ciclo de vida do software, na tentativa de manter o

software fiável, testável e gerível; o outro é dar o número recomendado de casos de teste para o

software. Pelo mesmo caminho, em Shuman (1990) e Suresh et al., (2012), a métrica da

complexidade ciclomática é descrita como fornecedora de um meio para quantificar a complexidade do

software e a sua utilidade tem vindo a ser sugerida no processo de desenvolvimento e de testes de

software.

Para o cálculo desta, é necessário recorrer à teoria de grafos (McCabe, 1976; McCabe et al.,

1996), pois esta métrica calcula a complexidade de um módulo baseada no seu grafo de controlo de

fluxo, isto é, todas as instruções de um módulo são transformadas em nodos do grafo, com os

caminhos do grafo a representarem a sua ligação/sequência, sendo depois representada por um único

número (Madi et al., 2013).

Sendo os grafos tão importantes para a medição da complexidade ciclomática, todos os

pormenores relativos a estes vão ser abordados num capítulo em separado (4. Grafos de controlo de

fluxos).

Originalmente, a fórmula de cálculo desta métrica, foi definida por McCabe (1976) como sendo:

V(G) = E – N + 2

Onde:

V(G), é o valor da complexidade do grafo G;

E, é o número de caminhos do grafo;

N, é o número de nodos.

A complexidade ciclomática pode também ser definida por meios alternativos, sendo as formas mais

usuais (McCabe, 1976; Sarwar, Ahmad, & Shahzad, 2012; Pressman, 2010; Jovanović, 2006) as

seguintes:

10

Fórmula Explicação

Original V(G) = E – N + 2 V(G), é o valor da complexidade

ciclomática do grafo G;

E, é o número de caminhos

existentes no grafo.

N é o número de nodos

existentes no grafo.

Alternativa 1 V(G) = P + 1 V(G), é o valor da complexidade

ciclomática do grafo G;

P é o números de pontos de

decisão do grafo.

Alternativa 2 V(G) = R V(G), é o valor da complexidade

ciclomática do grafo G;

R, é o número de regiões do

grafo (região exterior e regiões

interiores fechadas).

Tabela 1 - Fórmulas da complexidade ciclomática.

Segue um exemplo para ajudar na compreensão da Tabela 1, ilustrando assim todas as alternativas

possíveis para definir a complexidade ciclomática.

Usando um pequeno exemplo de um “if” simples:

Figura 2 - Exemplo de um if.

11

Começamos por desenhar o grafo de controlo de fluxo:

Figura 3 - Exemplo de grafo de controlo de fluxo.

Com a fórmula original, V(G) = E- N + 2, em que contamos o número de caminhos, representados na

Figura 4 por nodo origem à nodo destino, e o número de nodos, representados por um único número,

temos:

Figura 4 - Grafo de controlo de fluxo da métrica original.

12

V(G) = 5 – 5 + 2

V(G) = 2 à O valor da complexidade ciclomática é 2.

Com a alternativa 1, V(G) = P + 1, em que P é o número de pontos de decisão, ou seja, é a contagem

dos nodos onde existe uma condição, sendo estes caracterizados por terem 2 ou mais caminhos a sair

de si. Temos então:

Figura 5 - Grafo de controlo de fluxo com a alternativa 1.

V(G) = 1 + 1

V(G) = 2 à O valor da complexidade ciclomática é 2.

Por fim, com a alternativa 2, V(G) = R, em que R é o número de regiões. Quando se está a calcular

regiões, estas regiões são as áreas cercadas por caminhos e nodos (na Figura 6 denominada por

região fechada), e a área exterior ao grafo (na Figura 6 considerada região exterior):

13

Figura 6 - Grafo de controlo de fluxo com a alternativa 2.

V(G) = 1 + 1, em que somamos o número de regiões (a exterior e a fechada)

V(G) = 2 à O valor da complexidade ciclomática é 2.

Como podemos observar pelos exemplos em cima, independentemente da fórmula escolhida, o valor

da complexidade ciclomática é sempre o mesmo.

No que toca à análise dos resultados, a seguinte tabela mostra como é avaliado o risco de não se

conseguir testar todo o código, com base na complexidade ciclomática.

Complexidade ciclomática Avaliação do risco

1 – 10 Risco baixo; Código testável

11 – 20 Risco moderado

21 – 50 Risco alto

> 50 Risco muito alto; Código não testável

Tabela 2 - Análise do valor da complexidade ciclomática (Enescu, Mancas, Manole, & Udristoiu, 2008).

14

Segundo Enescu, Mancas, Manole, and Udristoiu (2009), analisando a Tabela 2, concluiu-se

que o número da complexidade ciclomática calculada de um programa deve estar no intervalo de 1 a

10 para o programa ser considerado simples e apenas aí o software é considerado como estando

quase livre de risco. Se o valor se encontra entre 11 e 20, então o risco é considerado moderado e o

programa começa a ser considerado como algo complexo. Se o número ciclomático cair no intervalo de

21 a 50, o módulo é considerado de alto risco, sendo o programa considerado muito complexo. Por

fim, se o valor ciclomático for maior que 50, o programa/módulo é considerado não testável e de

muito alto risco.

Em McCabe et al. (1996), foram analisadas as razões sobre o porquê de se dever limitar o

valor da complexidade ciclomática a 10, mas tal como em várias outras métricas da indústria de

software, não há um consenso quanto a um número fixo de complexidade ciclomática que sirva a

todas as empresas, no entanto, o valor de 10 é considerado como bom ponto de partida (McCabe,

1976).

Algumas das principais razões apontadas a favor do limite do valor da complexidade ciclomática

são que módulos excessivamente complexos são mais propensos a erros, são difíceis de perceber, são

mais difíceis de testar e também mais difíceis de modificar (Gaur, 2013; Khalid, ul Haq, & Khan, 2013;

Yadav & Khan, 2012; Bandara, Wikramanayake, & Goonethillake, 2009). Então, ao limitar a

complexidade em todas as fases de desenvolvimento de software, está-se a evitar os grande parte dos

problemas associados à alta complexidade de software.

Segundo McCabe et al. (1996), várias organizações conseguiram implementar limites à

complexidade como parte dos seus programas de software. No entanto, o número exacto a ser usado,

permanece ainda sem consenso. O limite original de 10 tem evidências de suporte significativas

(McCabe, 1976), mas anos mais tarde, na publicação de McCabe et al. (1996), foi reportado que

limitar esse número a 15 foi igualmente usado com sucesso. Segundo essa publicação, limitar o valor

acima de 10 deve ser reservado a projetos com, por exemplo, desenvolvedores experientes, onde

existe um desenho formal, uma linguagem de programação moderna, programação estruturada,

orientações de código e um plano de testes compreensivo.

A Hewlett Packard, pode ser usada como exemplo de uma empresa que usou e limitou o

número da complexidade ciclomática acima de 10, pois segundo Grady (1992), na Hewlett Packard,

qualquer módulo com complexidade acima de 16, deve ser redesenhado, mostrando que até esse

valor a complexidade é aceitável.

15

2.3 Aspetos positivos da complexidade ciclomática

A métrica da complexidade ciclomática sempre foi relacionada com qualidades de software (Nystedt,

1999) como a reutilização, manutenção (Geoffrey, 1991), segurança (Chowdhury, 2009), previsão de

falhas de software (Shimeall, 1990) e como um bom guia para o desenvolvimento de casos de teste

(McCabe, 1976).

Em Butler (1983), esta métrica é descrita como o melhor método para analisar e limitar a

complexidade de programas de software, sendo que ao contrário da maioria das métricas de

complexidade que são mais discriminatórias, esta é facilmente calculada por uma análise estática

(Fenton & Neil, 1999).

Existem vários estudos que reforçam os aspectos positivos da complexidade ciclomática.

Muitos deles focam-se na análise entre a correlação da complexidade ciclomática com a incidência de

erros no software, como são os casos de Walsh (1979), Schneidewind (1979), Ward (1989), Meals

(1981), Henry, Kafura and Harris (1981), Gollhofer, Shimeall and Leveson (1983) e Shuman (1990).

Nestes estudos podem ser encontrados vários aspectos positivos para a utilização da complexidade

ciclomática.

Em Walsh (1979), foram analisados oito módulos de um programa de larga escala com

funcionalidades relacionadas. Neste estudo, foi medida a relação entre a ocorrência de erros e o valor

da complexidade ciclomática. Para a realização do estudo, os módulos a serem testados foram

divididos em dois grupos, um com o valor de complexidade ciclomática menor que 10 e outro onde era

maior. O objetivo era comparar a incidência de erros entre os dois grupos. No fim, o autor conclui que

havia uma incidência de erros 21% maior no grupo com maior complexidade ciclomática.

Em Schneidewind (1979), foram analisados quatro programas, todos com um número

diferente de instruções, com o intuito de examinar a correlação entre a incidência de erros e a

complexidade ciclomática. Da análise da estrutura destes programas, o autor concluiu que quanto

maior a complexidade ciclomática, maior a ocorrência de erros.

Em Ward (1989), foram analisados dois programas de larga escala, com milhares de linhas de

código, para estudar a correlação entre erros e complexidade ciclomática. Neste estudo foi encontrada

uma correlação estatística de 0.8 (numa escala entre -1 e 1) entre a complexidade e a densidade dos

erros.

Em Meals (1981), foram usadas ferramentas automáticas de medição de complexidade, para

analisar três programas independentes e de larga escala. O objetivo era determinar se existia uma

correlação entre complexidade ciclomática e a história de erros conhecida. Dois desses três programas

16

mostraram uma correlação entre a incidência de erros e a complexidade ciclomática, apesar de não ter

sido dado nenhum coeficiente. A conclusão foi que a complexidade ciclomática é um indicador útil de

secções do código propensas a erros.

Em Henry, Kafura and Harris (1981), foi levado a cabo o estudo de um programa de larga

escala. Foi encontrada uma forte correlação, 0.95 (numa escala entre -1 e 1) entre procedimentos que

continham erros e a complexidade ciclomática.

Em Gollhofer, Shimeall and Leveson (1983), foi analisada a complexidade de 27 programas

independentes. Nove porcento dos módulos tinham complexidade superior a 10, sendo que estes

mesmos módulos tinham 47% do total de erros. Os autores concluiram que existe uma forte correlação

entre a complexidade ciclomática (quando maior que 10) e a incidência de erros.

Por fim, em Shuman (1990), foram analisadas múltiplas versões de programas relativamente

largos e complexos, sendo encontrada uma correlação entre erros e a medida de complexidade

ciclomática.

Como podemos observar por estes estudos, várias autores consideram existir uma forte

correlação entre a complexidade ciclomática e a incidência de erros.

Mais recentemente, em McCabe, McCabe Jr., and Fiondella (2012), os autores concluíram que

a complexidade ciclomática permite um escrutínio mais compreensivo da estrutura e do fluxo de

controlo de código, fornecendo capacidades de deteção significativamente melhores.

Mesmo sendo esta uma das métricas mais populares (Fenton & Neil, 1999; Sarwar, Shahzad,

& Ahmad, 2013) no que se refere à medição da complexidade de software, tem também algumas

críticas associadas que passarei a expor de seguida.

2.4 Aspetos negativos da complexidade ciclomática

A complexidade ciclomática tem vindo a sofrer alguns ajustes de modo a tornar-se uma métrica mais

completa e consensual (Curtis, 1983; Hansen, 1978; Harrison & Magel, 1981; Iyengar,

Parameswaran, & Fuller, 1982; Magel, 1981; Myers, 1977; Oviedo, 1980; Stetter, 1984; Woodward,

Hennell, & Hedley, 1979) mas, mesmo sendo esta bastante usada e amplamente citada tanto em

estudos, como artigos ou mesmo livros (Arthur, 1985; Cobb, 1978; De Marco, 1982; Dunsmore,

1984; Harrison, Magel, Kluczny, & De Kock, 1982; Schneidewind, 1979; Tanik, 1980, Pressman,

1987; Wiener & Sincovec, 1984), encontra-se também sujeita a várias críticas.

17

Em Jay et all. (2009), os autores concluíram que a complexidade ciclomática, por si só, não

tem poder explanatório suficiente, e que esta métrica mede a mesma propriedade que a métrica das

linhas de código. Vários estudos (Curtis, Sheppard, Milliman, Borst, & Love, 1979; Kitchenham, 1981;

Paige, 1980; Wang & Dunsmore, 1984; Basili & Hutchens, 1983) indicam mesmo que a métrica das

linhas de código tem melhor performance que a complexidade ciclomática.

Em Shepperd (1988), a complexidade ciclomática é colocada em causa tanto no campo

teórico como no campo empírico, sendo concluído que há uma grande dificuldade em avaliar a métrica

de McCabe e o trabalho empírico associado devido à inexistência de um modelo explícito onde a

complexidade ciclomática é baseada.

Em Baker & Zweben (1980), Oulsnam (1979) e Prather (1984), é argumentado que a

complexidade ciclomática de um programa pode aumentar quando são usadas técnicas na melhoria da

estrutura interna de um programa, sendo que essas técnicas são bastante consensuais na indústria de

software.

Em Garg (2014), o autor concluiu que apesar das grandes vantagens obtidas através da

análise do número da complexidade ciclomática, este não calcula o número exacto da complexidade do

software porque não tem em conta a interação entre duas classes de objetos.

Em Vinju and Godfrey (2012), foram analisados oito sistemas Java de código aberto e

recolhidas evidências empíricas, de que a complexidade ciclomática não avalia corretamente a

compreensibilidade dos métodos, pois tanto é possível que esta seja subestimada como sobrestimada.

Em Sarwar et al. (2013), é citado que a métrica de McCabe tem várias limitações, sendo um

dos principais problemas o encadeamento de condições. Neste estudo, é exemplificado que a

complexidade ciclomática calcula o mesmo valor de complexidade para uma construção simples e para

uma construção encadeada, sendo que no segundo caso o valor deveria ser maior já que o programa

se torna mais complexo.

Numa outra análise sobre o encadeamento de condições, em Solichah, Hamilton, Mursanto,

Ryan, and Perepletchikov (2013), os autores comentam que a complexidade ciclomática tem

incompletudes no que toca à medição da complexidade de um grafo de controlo de fluxo, já que esta

métrica, quando existem dois grafos de programas, sendo que um deles tem um controlo de fluxo

sequencial e o outro tem um controlo de fluxo encadeado, calcula o mesmo valor de complexidade

para os dois e o segundo caso deveria trazer um maior valor de complexidade.

Como se pode ver pelos estudos em cima, apesar da complexidade ciclomática de McCabe ser

bastante aceite na indústria de software, ainda existem vários estudos que lhe apontam falhas.

18

19

3. TESTES DE SOFTWARE

Neste capítulo será feita uma introdução aos testes de software, mostrando algumas abordagens de

testes e fazendo a sua ligação com a complexidade ciclomática.

3.1 Introdução aos testes de software

Os testes de software podem ser definidos como o resultado da execução de um software e

comparação entre o comportamento observado e o comportamento esperado, sendo o seu principal

objetivo a detecção de erros (Myers, 1989). A medição objetiva da qualidade de teste é uma das

questões-chave nos testes de software (Zhu et al., 1997), já que um importante problema na gestão

dos testes de software é assegurar que antes de qualquer teste, os objetivos desses testes são

conhecidos e aceites, e que esses objetivos são definidos em termos que possam ser medidos. Esses

objetivos devem ser quantificáveis, razoáveis e atingíveis (Ould & Unwin, 1986).

Os testes podem ser considerados como uma das principais técnicas a contribuir para uma

maior fiabilidade e qualidade do software (Del Grosso et al,. 2008), mas tal como as outras fases do

desenvolvimento de software, são atividades bastante exigentes em termos de recursos humanos,

fazendo com que seja necessário e quase obrigatório um processo continuo de procura de técnicas

com o objetivo de tornar o processo de testes o mais eficiente possível. Embora a procura dessas

técnicas consuma grande parte do tempo da investigação dos testes, é também importante fazer

software que possa efetivamente ser testado, visto que quando não são detetados erros na execução

dos testes, isto pode significar duas coisas, ou que a qualidade do software é alta ou que o processo de

testes é de baixa qualidade (McCabe et al., 1996).

No que toca aos processos de teste de software, existem várias abordagens no que toca à

tentativa de controlar a qualidade do software a ser testado. De seguida falarei de duas das mais

usadas, os testes de caixa branca e os testes de caixa preta.

3.2 Testes de caixa branca e testes de caixa preta

Na procura do erro são desenhados casos de teste na tentativa de cobrir o máximo possível de

funcionalidades de um programa. Um caso de teste é considerado bom quando tem uma alta

probabilidade de mostrar um erro não descoberto até ao momento. Erro esse que pode ser

categorizado como erro fatal, que bloqueia por completo o funcionamento do programa, ou ser apenas

20

uma falha subtil, que origina uma alteração do output, sem causar a falha do programa (Kanewala &

Bieman 2014). Se o teste não encontra erros, serve para demonstrar o âmbito no qual o programa

funciona e portanto serve para validar que o programa cumpre os requisitos.

Na literatura de testes de software, é normal encontrar os conceitos de testes de caixa branca

e testes de caixa preta. Estes são os testes mais usados na SISCOG.

Os testes de caixa preta pressupõem que o programa que está a ser testado seja uma “caixa

preta”, ou seja, é assumido que não há conhecimento sobre a forma como o programa está

implementado (Zhu et al., 1997). Este tipo de teste é tipicamente utilizado para isolar possíveis

comportamentos erróneos do sistema, tais como resultados, interfaces e rendimento. Uma das

abordagens usadas no desenvolvimento de testes de caixa preta é o teste baseado na especificação.

Esta abordagem utiliza os requisitos como forma de testar as funcionalidades, já que a especificação

dos requisitos é convertida em casos de teste, sendo que cada requisito resulta em pelo menos um

caso de teste. Segundo McCabe et al. (1996), apesar desta abordagem ser bastante usada, ela não

pode ser considerada uma solução completa, pois para além dos documentos de requisitos serem

propensos a erros, o código contempla muitos mais detalhes do que os requisitos, pois este são

normalmente escritos de uma forma muito mais abstrata. Devido a este motivo, foi argumentado que

um caso de teste que seja desenvolvido a partir de um requisito pode originar várias falhas no que toca

à cobertura total do programa.

Por seu lado, nos testes de caixa branca, existe o acesso a detalhes sobre o programa a ser

testado, sendo que os testes são feitos de acordo com esses detalhes (Zhu et al., 1997). Estes, são

utilizados para verificar o software antes da sua integração no sistema, sendo baseados em estruturas

de controlo e correção algorítmica. Neste tipo de testes, de caixa-branca, surge então o método dos

caminhos básicos criado por McCabe (1976). Com o uso deste tipo de testes, o erros são mais

facilmente detetados mesmo quando existem falhas na especificação do software, pois sabe-se que

toda a implementação do software é tida em conta. Um dos problemas destes testes, segundo McCabe

et al. (1996), é que quando um ou mais requisitos do software não são implementados, os testes de

caixa branca podem não detetar os erros que resultam dessa omissão. Portanto, tanto os testes de

caixa branca como os de caixa preta são importantes para um processo de testes efetivo.

21

3.3 Testes e complexidade ciclomática

Sabendo que os testes na SISCOG baseiam-se essencialmente na prevenção de erros, com a

complexidade ciclomática podemos estimar o número de casos de testes necessários para cobrir todos

os caminhos possíveis de uma definição. Assim sendo, a métrica da complexidade ciclomática não

pode ser só vista como um meio para quantificar a complexidade de software, já que também pode e

tem vindo a ser utilizada no apoio ao processo de testes de software (McCabe et al., 1996; Zhu, Hall, &

May, 1997; Gold, 2013).

O valor da complexidade ciclomática indica o número de caminhos possíveis de execução de

um programa, sendo este conjunto de caminhos conhecido como caminhos básicos. A sua lógica

consiste essencialmente em gerar os casos de teste de forma a que estes passem por um número

mínimo de caminhos entre a entrada e a saída do programa, sem o risco de ocorrerem redundâncias.

Baseados no número de caminhos encontrados, os casos de testes são gerados manualmente ou

através de um processo automático, de modo a cobrir todos os caminhos executáveis, o que vai

assegurar que é atingida a máxima cobertura do código (Suresh et al., 2012).

De uma forma simples, pode-se explicar a forma de cálculo do número de casos de teste

através da complexidade ciclomática da seguinte forma:

• Desenhar o grafo de controlo de fluxo;

• Determinar o número de caminhos independentes (complexidade ciclomática);

• Identificar esses caminhos;

• Para cada caminho, gerar os casos de teste (manualmente ou com uma ferramenta);

• Por fim,

o Executar cada caso de teste;

o Comparar os resultados obtidos com os resultados esperados.

Para assegurar então a máxima cobertura, todos os caminhos do programa devem ser

testados. Isto implica que, um programa com um número de complexidade alto, vai necessitar de um

maior esforço ao nível dos testes, já que quanto maior o número de complexidade, maior o número de

caminhos existentes no código.

Ao identificarmos o número de casos de testes e as zonas do código onde o esforço dos testes

deve ser concentrado, além da poupança de tempo e dinheiro, talvez se consiga ter um sistema mais

fiável (Shuman, 1990), já que os testes se podem centrar em áreas que são mais suscetíveis a erros.

22

23

4. GRAFOS DE CONTROLO DE FLUXOS

Neste capítulo é feita uma breve introdução à teoria de grafos e aos grafos de controlo de fluxo,

expondo alguns conceitos essenciais para posteriormente facilitar a compreensão da ligação entre a

complexidade ciclomática e os grafos.

4.1 Introdução à teoria de grafos e grafos de controlo de fluxo

A teoria de grafos é o estudo dos grafos, que são considerados como estruturas matemáticas usadas

para modelar pares de relações entre objetos. Um grafo é um conjunto de nodos ligados por caminhos,

podendo ser direcionado, em que o caminho indica a direção de ligação dos nodos, ou não-

direcionado, significando que não há qualquer distinção entre os dois nodos associados pelo caminho

(Berge, 1973).

Usando a notação de grafo pode-se usar grafos de controlo de fluxo para descrever todos os

caminhos que podem ser executados por um programa. Estes grafos têm sido objeto de vários estudos

ao longo dos anos (Jalote, 2005; Kosaraju, 1973; McCabe, 1976; Paige, 1977; Rapps and Weyuker,

1982; Tan, 2006; White, 1981; Zhu et al., 1997) e são largamente usados na análise de software

(Fenton, Whitty, & Kaposi, 1985; Kosaraju, 1973; McCabe, 1976; Paige, 1975).

São também conhecidos por grafos de programas que representam o controlo de fluxo do

programa, isto é, são grafos que descrevem a estrutura lógica de módulos software (McCabe et al.,

1996; Zhu et al., 1997). Esses módulos de software correspondem a uma única função ou sub-rotina,

que têm um único ponto de entrada e outro de saída, e podem ser usados como componente de

desenho através de um mecanismo de chamada/retorno (McCabe et al., 1996).

Os grafos de controlo de fluxo, consistem num conjunto de nodos e caminhos, sendo que os

nodos representam as instruções do programa e os caminhos representam o controlo de fluxo entre

essas instruções (Gold, 2010).

De seguida abordarei alguns conceitos fundamentais que são necessários para o estudo da

complexidade ciclomática.

24

4.1.1 Conceitos da teoria de grafos

Para o estudo elaborado neste projeto são necessários alguns conceitos da teoria de grafos. Estes

conceitos foram retirados de Berge (2001), Cardoso (2009) e Abran (2010):

Grafo

“Um grafo G, é um par (V (G), A(G)), onde V (G) é um conjunto não vazio de elementos

chamados vértices, e A(G) é uma família finíta de pares não ordenados de pares de elementos,

não necessariamente distintos, chamados arestas.”

Grafo simples

“Um grafo simples G, é um par (V (G), A(G)), onde V (G) é um conjunto não vazio de elementos

chamados vértices, nodos ou pontos, e A(G) é um conjunto finito de pares não ordenados de

elementos distintos de V (G), chamados arestas ou linhas; V (G) é o conjunto dos vértices e

A(G) é o conjunto das arestas.”

Grafo direcionado

“Um grafo direcionado (ou digrafo) D, é um par (V (D), A(D)), onde V (D) é um conjunto não

vazio de elementos chamados vértices, e A(D) é uma família finita de pares ordenados de

elementos de V (D), chamados arcos. Se D não tem laços e se os arcos de D são todos

distintos, então D é um grafo direcionado simples.”

Caminho

“Um caminho de comprimento l em G, de vi a vj, é a sequência finita de vértices em G,

vi = u0, u1, . . . , ul = vj, tais que ut−1 e ut são adjacentes para 1 ≤ t ≤ l, sendo que os vértices

são distintos (excepto, possivelmente, u0 = ul).”

Grafo ligado

“Um grafo G é ligado se, para todo o x e para todo o y (vértices), existe um percurso que liga x

e y. Um grafo que não é ligado pode ser dividido em componentes ligados.”

Grafo cíclico

“Um grafo G, em que o seu caminho que começa e acaba no mesmo vértice.”

25

Ciclo simples

“Um ciclo que tem o comprimento mínimo de 3 e no qual apenas o vértice inicial e final

podem aparecer repetidos, todos os outros aparecem apenas uma vez.”

Grafo fortemente ligado

“É um grafo direcionado que tem um caminho de cada vértice para todos os outros vértices.”

Número ciclomático

“É o menor número de arestas que devem ser removidas de um grafo para que o mesmo não

apresente ciclos.”

Tendo então presentes estes conceitos, mostrarei de seguida como McCabe se apoiou na teoria

de grafos e no número ciclomático para realizar o seu estudo da complexidade ciclomática.

4.2 Complexidade ciclomática e os grafos

A medida de McCabe é baseada na teoria de grafos (McCabe, 1976; McCabe et al., 1996), pois o seu

trabalho apoiou-se em alguns conceitos de medida presentes na teoria de grafos e na transposição

desses conceitos para o domínio da medida de software (Abran, 2010).

Segundo McCabe (1976), a abordagem seguida no estudo da medida da complexidade

centrou-se na medição e controlo do número de caminhos existentes num programa. Um dos

problemas imediatamente detetado, foi durante a medição do número total de caminhos possíveis, no

caso de existir um caminho no sentido inverso, isto é, para trás, estavamos perante a possibilidade de

obter um número infinito de caminhos. Por este motivo, o uso do número total de caminhos foi

descartado, pois não era considerado uma abordagem realista. Então, a abordagem escolhida para a

definição da complexidade ciclomática passou a focar-se no número de caminhos básicos através de

um programa, que quando combinados geram todos os caminhos possíveis.

Por outras palavras, a complexidade ciclomática expõe que para qualquer grafo de fluxo existe

um conjunto de caminhos de execução, de tal modo que cada caminho de execução pode ser expresso

como uma combinação linear dos mesmos. Um conjunto de caminhos é considerado independente se

nenhum deles for uma combinação linear dos outros. De acordo com McCabe (1976), um caminho

deve ser testado, se ele for independente dos caminhos que foram testados. Por outro lado, se um

26

caminho é uma combinação linear dos caminhos testados, ele pode ser considerado redundante (Zhu

et al., 1997).

De acordo com a teoria de grafos e segundo Berge (1973), o número ciclomático é definido

como o número de ciclos fundamentais (ou básicos) num grafo ligado e não-direcionado ao que

McCabe et al. (1996) acrescentou que era também o número de caminhos independentes através de

um grafo direcionado fortemente ligado, podendo ser calculado por:

v(G)= E - N + P

onde:

E, é o número de caminhos;

N, é o número de nodos;

P, é o número de componentes separados.

Este foi o princípio base usado por McCabe no seu estudo (McCabe, 1976), no qual este fez a

transposição desta definição para o domínio da medida de software.

Segundo Abran (2010), na engenharia de software, o programa é modelado como um grafo de

controlo de fluxo, que é uma estrutura abstrata usada pelos compiladores, em que os nodos

representam os blocos básicos, e os caminhos direcionados representam os saltos de controlo de

fluxo.

Sabendo que os grafos de controlo de fluxo de programas não são fortemente ligados, McCabe

percebeu que os poderia tornar fortemente ligados adicionando um caminho virtual que liga-se o nodo

de saída com o nodo de entrada (McCabe et al., 1996). Assim sendo, transformando o grafo de

controlo de fluxo num grafo fortemente ligado, o número ciclomático do grafo pode ser aplicado na

representação de programas (Abran, 2010). Nesta transposição, o número ciclomático, quando

aplicado ao software calcula-se da seguinte maneira:

v(G) = E - N + P + 1

Mas sabendo que esta transposição de McCabe só se aplica a módulos individuais (McCabe,

1976), o número de componentes ligados, P, é sempre igual a 1, ficando então definida a fórmula de

cálculo da complexidade ciclomática por:

v(G) = E - N + 2

27

McCabe (1976), sugeriu que, a fórmula, v(G) = E – N + 2, por associação, é a medida da

complexidade de um programa, à qual ele chamou de complexidade ciclomática e interpretou como a

quantidade de decisão lógica num módulo único de software (McCabe et al., 1996).

Por fim, no seu trabalho original, McCabe (1976) listou algumas propriedades da complexidade

ciclomática:

• v(G) ≥ l.

• v(G) é o número máximo de caminhos linearmente independentes em G; é o tamanhos de um

conjunto básico.

• Inserir ou apagar instruções funcionais em G não afeta v(G).

• G tem apenas um caminho se, e apenas se, v(G) = 1.

• A inserção de um novo caminho em G, aumenta o v(G) uma unidade.

• v(G) depende apenas da estrutura de decisão de G.

28

29

5. IMPLEMENTAÇÃO

Neste capítulo será explicado todo o processo de desenvolvimento, mostrando qual a linguagem de

programação usada, quais as ferramentas que serviram de suporte e explicando as decisões tomadas

durante a implementação.

5.1 Abordagens ponderadas

Na discussão sobre que caminho seguir para a elaboração deste projeto, foram discutidas duas

alternativas para a implementação da ferramenta.

A primeira passava pela criação de um parser, que é uma metodologia muito utilizada em

ferramentas do género, tendo esta abordagem sido posteriormente abandonada muito por causa das

particularidades do LISP. Entre algumas dessas particularidades que levaram a esta decisão, surgiu o

fato de esta linguagem permitir a criação macros que, em LISP, podem ser vistas como extensões da

linguagem e que podem, entre outras utilizações, permitir a definição de novos condicionais,

iteradores, etc. Isto faria com que fosse impossível cobrir todos estes casos com a utilização de

parsers.

A segunda, e que acabou por ser a escolhida, passava pela utilização de um code walker. Um

code walker permite que todas as macros sejam totalmente expandidas, isto é, sejam transformadas

em instruções básicas e nativas, removendo assim a limitação encontrada na primeira abordagem que

tinha sido ponderada. Esta ferramenta retorna como resultado uma Abstract Syntax Tree, que é uma

representação abstrata e simplificada, em forma de árvore, da estrutura semântica do código. Esta

Abstract Syntax Tree, foi então o ponto de partida para a análise proposta para este projeto, que passa

pela criação do grafo e cálculo da sua complexidade ciclomática.

5.2 Linguagem de programação e ferramentas externas de apoio

Aqui serão demonstradas todas as ferramentas utilizadas no desenvolvimento do projeto, sendo que a

tabela seguinte serve como uma pequena introdução ao que vai ser falado nesta secção.

30

Linguagem/Ferramentas Breve descrição

LISP Linguagem de programação

GNU Emacs + SLIME Editor de texto com o modo para

desenvolvimento em Common LISP.

Allegro CL IDE IDE; Ambiente de desenvolvimento integrado

para o Common LISP.

Quicklisp Gestor de bibliotecas LISP.

CL-Graph Biblioteca de grafos.

Graphviz Software para visualização de grafos.

hu.dwim.walker Code walker.

Tabela 3 - Ferramentas utilizadas no projeto.

5.2.1 LISP

Começando pela linguagem de programação usada, o LISP (McCarthy, 1960), abreviatura para List

Processing, é uma linguagem criada por John McCarthy, no final dos anos 50 e que, como podemos

ver pelo nome, tem como ideia principal usar listas como a estrutura para os dados e para o código.

Esta, é a linguagem de programação mais utilizada na SISCOG, sendo uma linguagem que

está fortemente ligada ao mundo da inteligência artificial. Existem diversos dialetos LISP, sendo que o

usado neste projeto foi o Allegro Common LISP.

Figura 7 - Linguagens de programação usadas na SISCOG. (SISCOG, 2013).

31

5.2.2 GNU Emacs e SLIME

O GNU Emacs, criado por Richard Stallman, é um dos editores de texto mais conhecido, enquanto que

o SLIME, criado por Luke Gorrie e Helmut Eller, é um modo do Emacs para o desenvolvimento em

Common LISP, que permite dotar o Emacs de um conjunto de funcionalidades adicionais que

melhoram a interacção entre o Emacs e o LISP.

Na SISCOG o Emacs é muito utilizado pois, para além de ser usado como editor de texto para

os desenvolvimentos em LISP, é também através dele que os analistas-programadores lançam as

aplicações da empresa em modo de desenvolvimento, criando uma ligação com o IDE Allegro CL que

será abordado de seguida.

5.2.3 Allegro CL IDE

O Allegro CL é um ambiente de desenvolvimento criado pela Franz Inc. e que corresponde à

implementação do LISP que é usada na SISCOG para desenvolver, compilar e distribuir as aplicações

da empresa. Algumas das vantagens apontadas (SISCOG, 2013) pelos analistas-programadores são:

• IDE com trace dialog, profiler, class browser;

• Compilador rápido;

• Multiprocessamento simétrico (SMP).

5.2.4 Analisador de código: code walker

O code walker hu.dwim.walker, foi umas das ferramentas mais importantes para o desenvolvimento do

projeto, já que através dele foi possível aceder às Abstract Syntax Tree que contêm toda a informação

necessária para a construção do grafo.

Para que se perceba melhor as vantagens de se utilizar um code walker para LISP é necessário

compreendermos quais os principais passos executados pelo compilador:

1) O texto é convertido em s-expressions (árvores de células cons, com símbolos, números, etc),

sendo que isso é feito pelo reader do LISP.

2) As macros são expandidas.

3) O resultado é compilado.

32

O code walker baseado no resultado da fase 2, fornece uma Abstract Syntax Tree com o

resultado de todas as macros totalmente expandidas. Isto foi também um dos pontos chaves para a

utilização do code walker neste projeto, pois era importante ter as macros totalmente expandidas, já

que em LISP as macros permitem extender a sintaxe da linguagem o que poderia criar problemas na

análise do código.

Um exemplo da utilização de macros no LISP pode ser observado pelo facto de existirem na

própria especificação da linguagem algumas macros para representar algumas instruções nativas,

como por exemplo a instrução cond. Essa instrução pode ser representada na sua forma mais primitiva

como um conjunto de instruções if encadeadas:

Figura 8 - Exemplo de um if e um cond em LISP.

Se se utilizar o macroexpand do LISP para ver a expansão da macro cond da Figura 8:

Figura 9 - Exemplo de um macroexpand.

Obtem-se o seguinte resultado:

Figura 10 - Resultado do macroexpand.

Como se pode observar pela Figura 10, o cond não é totalmente expandido para as suas

formas mais primitivas, pois, nem todas as suas as condições foram retornadas como if.

Sabendo que a instrução cond faz nativamente parte da especificação do LISP e por esse

motivo, se a abordagem de utilização de um parser fosse seguida, esta instrução iria certamente ser

considerada, não seria possível precaver todas as outras possibilidades que um programador de LISP

tem ao seu alcance quando lhe é facultada esta facilidade de extender a linguagem. Na SISCOG, e

tirando partido desta vantagem de extensão da linguagem, existem muitos iteradores criados

33

internamente e que fazem parte da API dos produtos da empresa, sendo que não seria viável conseguir

uma cobertura total de todos estes casos através da utilização do parser.

Com a utilização do code walker, este problema não existe pois todas as macros são

totalmente expandidas, ou seja, todas elas estão nas suas formas primitivas, o que tomando os casos

dos condicionais como exemplo, faria com que todos eles, na Abstract Syntax Tree do resultado,

tivessem a mesma forma, ou seja, uma if-form. Na tabela seguinte podemos ver algumas das formas

retornadas pelo code walker, assim como alguns exemplos do que estas abrangem:

Formas: O que abrange:

If-form if; cond; when; unless.

Block-form dolist; do; do*.

Free-application-form Chamada a definições não nativas, dentro da

definição analisada.

Progn-form Instrução agrupadas propositadamente.

Constant-form Variáveis constantes.

Let-form let; let*, labels; flet; multiple-value-bind.

Setq-form setf; setq.

Tabela 4 - Formas retornadas pelo code walker.

5.2.5 Gestor de bibliotecas LISP: Quicklisp

O Quicklisp é um gestor de bibliotecas para Common Lisp, que permite descarregar, instalar e carregar

bibliotecas usando apenas comandos simples. Este gestor foi utilizado para descarregar a biblioteca

hu.dwim.walker, que foi o code walker necessário para este projeto.

Para instalar o quicklisp usa-se o seguinte comando:

(quicklisp-quickstart:install)

E para descarregar/carregar a biblioteca do code walker:

(ql:quickload "hu.dwim.walker ")

34

5.2.6 Biblioteca de grafos: CL-Graph

O CL-Graph é uma biblioteca de Common LISP usada para a manipulação de grafos, e que contém,

entres outros, uma série de algoritmos orientados a grafos.

No âmbito deste projeto, a utilização desta biblioteca foi feita de modo muito simplista, de acordo

com as necessidades que foram identificadas, tendo sido usadas as seguintes definições desta

biblioteca:

• add.vertex, para adicionar um vértice ao grafo;

• delete.vertex, para apagar um vértice do grafo;

• iterate.vertexes, para percorrer os vértices existentes no grafo;

• add.edge, para adicionar um caminho entre dois vértices;

• delete.edge, para apagar um caminho entre dois vértices.

5.2.7 Graphviz

O Graphviz, abreviatura para Graph Visualization Software, é um software de código livre usado para o

desenho de grafos que sejam especificados, entre outros, em scripts de linguagens DOT. Esta

linguagem é usada para descrever grafos através de um formato simples de texto, sendo que esse

texto é posteriormente usado pelo Graphviz para a criação de um ficheiro de imagem com o grafo

correspondente.

Figura 11 - Exemplo de um grafo em linguagem DOT.

Neste projeto, esta ferramenta foi utilizada durante o processo de construção do relatório de

complexidade e toda a implementação foi pensada para que esta operação fosse realizada de forma

completamente automática, não sendo para isso necessária qualquer intervenção do programador.

Mais pormenores sobre esta implementação são dados no Capítulo 5.5 - Relatório.

35

5.3 Estrutura do código

A estrutura do código foi pensada da seguinte forma:

Figura 12 - Estrutura geral do código implementado.

Este processo tem início com a construção do grafo. Numa primeira fase é feita uma verificação

à definição que está a ser analisada, com o intuito de identificar se esta definição tem ou não variáveis

e, em caso afirmativo, é chamada outra definição para lidar com essa variáveis, criando os nodos

respetivos e ligando-os entre si, devolvendo como resultado a parte inicial do grafo com esta

informação. Este passo é importante, porque em LISP podem existir pontos de decisão na definição

das variáveis, o que faz com que a própria definição de variáveis possa influenciar a complexidade

ciclomática de um programa.

Tendo a parte introdutória do grafo definida, começa-se a avaliar o corpo da definição, que

corresponde a um ciclo onde se avaliam todas as instruções do corpo, transformando cada instrução

num nodo do grafo e criando os respetivos caminhos entre instruções, representando assim a ordem

de execução das mesmas. Quando este processo acaba, é calculada a complexidade ciclomática,

36

usando o número de nodos e caminhos do grafo calculado anteriormente. Findo este processo é

gerado o relatório com todas estas informações.

De seguida irão ser abordadas com mais detalhe as decisões tomadas durante todo o processo

de implementação.

5.4 Decisões de implementação

Depois de definida a forma sobre como o projeto se devia desenrolar e qual a estrutura do código,

começou-se então com a construção da ferramenta com base nos casos mais simples possíveis e que

serão descritos de seguida. Esses casos mais simples foram dando origem a casos cada vez mais

complexos, podendo a ferramenta, no fim da implementação, lidar com definições de diferentes

complexidades, isto é, desde as mais simples às mais complexas.

Os casos analisados de seguida encontram-se agrupados como mostra a seguinte tabela:

Grupo Principais palavras reservadas

Condicionais if; cond; when; unless.

Iteradores dolist; do; do*.

Operadores lógicos and; or.

Operadores especiais let; let*; labels; flet; multiple-value-bind.

Outros setf; list; cons; push; format; print.

Tabela 5 - Grupos de palavras reservadas.

5.4.1 Definição genérica

Foi criada uma definição genérica, para controlar o tipo da forma devolvida pelo code walker,

identificando assim qual a definição que deve ser usada para determinada forma, ou seja, de cada vez

que se vai construir um nodo do grafo, esta definição é chamada para verificar qual o seu tipo de

forma, fazendo posteriormente a chamada da definição responsável por tratar esse tipo.

5.4.2 Operador lógico: and e or

Os operadores lógicos and e or permitem agrupar condições, podendo ser usados de forma direta, na

definição de variáveis, ou em condições de ciclos. Neste sub-capítulo é abordada a utilização destes

37

operadores de forma direta, sendo que a sua utilização nas instruções condicionais irá ser abordada

mais à frente no Capítulo 5.4.6 - Condicionais compostos.

A representação num grafo destes operadores é igual em ambos os casos, mudando apenas

os caminhos no caso das suas condições serem verdadeiras ou falsas, ou seja, num and se a condição

é verdadeira avança-se para a condição seguinte, enquanto que no or se avança para o fim; no caso da

condição ser falsa, no and avança-se para o fim e no or para a condição seguinte. Esta troca de

caminhos em termos de valor final de complexidade, não tem qualquer relevância mas, faz com que os

dois operadores sejam interpretados de maneira distinta pelo code walker, o que devido à

complexidade de manutenção do código que isso poderia acarretar devido à validação dos vários

caminhos existentes, fez com que se optasse pela criação de duas definições distintas para lidar com

os operadores and e or.

Figura 13 - Grafo de um and/or.

Na implementação destas definições, foi necessária a introdução de um nodo fictício pois,

como podemos observar pela Figura 13, na última condição, “condicao2”, se não fosse introduzido um

nodo fictício, o caminho entre o nodo dessa condição e o nodo final, tanto no caso de ser verdadeiro

como no caso de ser falso seria o mesmo, o que introduziria um erro no cálculo da complexidade, pois

a biblioteca de grafos não permite duas ligações do tipo A-B.

Foi também contemplada a possibilidade destes operadores terem nodos intermédios, como

mostra a seguinte figura:

38

Figura 14 - Grafo de um and/or com nodos intermédios.

Para lidar com esta situação da existência de nodos intermédios, foram criadas mais duas

definições, uma para cada operador, sendo estas responsáveis pela construção dos nodos do grafo e

dos seus caminhos. Devido à possibilidade de existência de mais do que um nodo intermédio, estas

definições foram implementadas usando recursividade, ou seja, estas definições são chamadas

recursivamente enquanto existirem nodos.

Quando o processo dos nodos intermédios acaba, este sub-grafo é ligado ao grafo produzido

pelas definições principais do and ou or, ficando apenas a existir um grafo completo.

5.4.3 Condicional simples: if

O primeiro condicional a ser implementado foi o if, pois é a forma básica de todos os condicionais

existentes no LISP, sendo que o seu grafo, no caso mais simples pode ser definido como mostra a

seguinte figura:

39

Figura 15 - Grafo de um if simples.

Este é o caso mais simples do if, onde existe apenas uma condição e uma instrução em cada

ramo. Neste caso específico, tendo apenas uma condição e uma instrução em cada ramo, o grafo

construído contempla estes três nodos, assim como os nodos “início” e “fim”.

Na implementação foi também considerado o caso de existirem várias condições no if, com o

recurso aos operadores lógicos and e or. Estes casos especiais, assim como os restantes condicionais

que vão ser abordados de seguida, resultaram numa única definição. Esta definição lida com todos os

condicionais e, por isso mesmo, é a mais complexa de todo o projeto, necessitando ainda de um

trabalho de modularização. Todas as decisões tomadas relativas a esta implementação e o processo de

construção do grafo serão explicadas mais à frente no Capítulo 5.4.6 - Condicionais compostos.

Convém destacar que, em LISP, o then e o else do if apenas podem conter uma instrução

cada, caso contrário torna-se necessário o uso de outros operadores especiais como o progn ou o let

para agrupar instruções, ou então, em vez do if usar a macro cond.

5.4.4 Condicional simples: cond

A macro cond é bastante utilizada em LISP e pode ser definida como um conjunto de if’s encadeados.

Esta, ao contrário do if, permite definir várias instruções para a mesma condição sem ser necessário

recorrer a operadores especiais.

Na figura seguinte pode-se visualizar o grafo resultante da utilização da macro cond com uma

ou várias condições.

40

Figura 16 - Grafos da macro cond.

O cond vai verificando as suas condições sequencialmente, sendo que quando uma se verifica,

as suas intruções são executadas e mais nenhuma condição é verificada. No caso de nenhuma das

condições se verificar, são executadas as instruções da condição representada no grafo da Figura 16

pelo nodo otherwise.

Como a macro cond, é analisada pela mesma definição do if, os pormenores sobre a mesma

serão abordados no Capítulo 5.4.6 - Condicionais compostos.

5.4.5 Condicional simples: when e unless

O when e o unless pertecem também aos condicionais do LISP. O grafo destes, da mesma forma que

o do and e do or, é igual entre si, ou seja, o grafo produzido para ambas as situações só difere nos

caminhos de verdadeiro ou falso, o que não tem qualquer relevância para o cálculo da complexidade.

Mais uma vez, o code walker, interpreta-os de maneira diferente, pelo que na definição dos

condicionais exista uma verificação para cada um destes, de forma a ser possível fazer uma validação

correta dos caminhos entre nodos. O grafo destes condicionais pode ser consultado de seguida:

41

Figura 17 - Grafo de um when/unless.

5.4.6 Condicionais compostos

A complexidade dos condicionais pode crescer consideravelmente com a introdução dos operadores

lógicos and e or na definição das condições pois, com o recurso a estes operadores podem ser

definidas várias condições nos ciclos, o que faz com que a complexidade ciclomática seja maior, pois

vão existir mais pontos de decisão no código.

Usando um pequeno exemplo em pseudo-código de um if:

Figura 18 - Pseudo-código de um if com recurso aos operadores and/or.

42

Isto resultaria nos seguintes grafos:

Figura 19 - Grafos de um if com recurso aos operadores and/or.

O processo de implementação da definição dos condicionais, baseou-se na informação

fornecida pelo code walker, que no caso dos condicionais é uma Abstract Syntax Tree com a condição,

o ramo do else e o ramo do then. Qualquer um destes (condição e ramos), no caso mais simples,

resulta em três nodos, mas no caso dos compostos, qualquer um desses nodos pode gerar um novo

grafo. Então o primeiro passo da definição é a identificação do tipo de condição, que pode ser um dos

seguintes:

• condição com recurso a um and;

• condição com recurso a um or;

• condição simples.

Se o resultado desta verificação for uma condição simples, é criado um nodo para essa

condição e o processo de construção do grafo continua para a análise dos ramos. Caso contrário,

consoante o resultado da verificação é chamada a definição do and ou do or e, se necessário, são

também usadas as definições que lidam com os nodos intermédios destes operadores lógicos. Foi

também considerado na implementação destes casos, o caso de existirem condições que contenham

tanto o and como o or na mesma condição, sendo para isso feitas validações extra aos caminhos.

No fim deste processo, tendo o grafo das condições completo, são analisados os ramos then e

else. Através da definição genérica, começa-se mais uma vez por identificar qual o seu tipo, o que tanto

pode resultar num único nodo, no caso de uma instrução simples, como num conjunto de nodos, no

43

caso de a instrução ser um condicional, um iterador ou um operador especial. Durante a análise

destes, são criados os nodos das suas instruções e seus respetivos caminhos. No fim deste processo

de análise, faz-se a ligação entre os nodos da condição e os nodos dos ramos, obtendo-se então o

grafo completo.

Uma dificuldade encontrada foi o comportamento do code walker na análise dos diferentes

condicionais, já que este, apesar de muitos casos resultarem num grafo idêntico, os interpreta de

maneira diferente, o que fez com que fosse necessário validar todos os caminhos possíveis entre os

objetos em estudo.

De um modo simplista, o fluxo da definição que trata dos condicionais pode ser definida da seguinte

maneira:

• Verificar o tipo dos nodos (condição, then, else);

• Chamar a definição genérica para cada um desses nodos, direcionando assim a execução do

código para as definições necessárias (outros condicionais, iteradores, constantes, etc);

• Tendo os três nodos (condição, then, else), que podem ser grafos, ligá-los entre si, validando

todos os caminhos. Esta foi a parte mais complexa deste projeto, pois existia uma imensidão

de caminhos para validar, o que levou a que fosse necessária a introdução de várias

condições para contemplar todos os casos estudados.

5.4.7 Iterador: dolist

No estudo dos iteradores decidiu-se começar pelo dolist, já que é um dos iteradores de listas mais

usados em LISP.

No seu processo de implementação foi necessário contemplar dois casos, um deles onde o

iterador contém várias instruções no seu corpo e outro, onde só tem uma instrução. Esta separação foi

necessária, pois no caso de só ter uma instrução no corpo, sendo essa instrução uma constante, é

necessária a criação de um nodo fictício, pois a biblioteca de grafos usada não permite ligações do tipo

A-B B-A. O seu resultado pode ser visto nos seguintes grafos:

44

Figura 20 - Grafos do dolist.

Foi também definido que o fluxo do processo de criação do grafo do dolist devia seguir a

ordem de execução do mesmo, iniciando-se com a criação do nodo da condição e depois partindo para

a análise do seu corpo.

Na análise do corpo foi então necessário verificar o tamanho do mesmo. Se este valor for 1,

antes de ser criado o nodo fictício é verificado se a instrução é uma constante, o que em caso

afirmativo resulta num único nodo, o da instrução, e na criação de um nodo fictício como mostra a

figura. Esta verificação do tipo de instrução é necessária, porque se a instrução não for uma constante,

este nodo vai ter que ser expandido com o resultado da análise da instrução, fazendo com que o nodo

seja na verdade um grafo. Neste caso, já não é precisa a criação do nodo fictício, porque

transformando-se a instrução do corpo em vários nodos, ou seja, num sub-grafo, o último nodo desse

sub-grafo ligar-se-á à condição do dolist, não havendo então o problema de se ter uma ligação A-B B-A,

o que posteriormente resultaria num erro na ferramenta devido à limitação da biblioteca de grafos.

No caso do tamanho ser maior que 1, é feito o mesmo processo descrito em cima para

quando o tamanho é 1 e a instrução não é uma constante, isto é, é chamada a definição genérica para

verificar o tipo da instrução e posteriormente chamar a definição adequada para analisar o tipo em

causa.

Por fim, o último nodo resultante dessa análise é ligado ao nodo da condição do dolist,

resultando outra vez num único grafo.

45

5.4.8 Iterador: do/do*

Os iteradores do e do* foram os outros iteradores estudados. Foi necessário diferenciá-los do dolist,

pois apesar de o code walker os ler como sendo do mesmo tipo, estes funcionam de maneira diferente,

permitindo definir variáveis e os seus incrementos antes da condição, sendo as variáveis usadas no

corpo do iterador e sendo os incrementos feitos após a execução do corpo e antes de se voltar a

analisar a condição.

Uma dificuldade encontrada foi a análise destes dois iteradores por parte do code walker, pois

este lê as variáveis do do e do do* de maneira diferente. Para resolver este problema foi necessário

acrescentar uma verificação para distinguir qual das situações está a ser analisada através de

informação encontrada na Abstract Syntax Tree. O do e do* partilham o mesmo grafo, que pode ser

consultado de seguida:

Figura 21 - Grafo de um do/do*.

O nível de complexidade destes iteradores pode aumentar consideravelmente, quando se

definem várias condições de paragem, como se pode ver pelo grafo que se segue:

46

Figura 22 - Grafo 2 de um do/do*.

A complexidade pode aumentar ainda mais, pois para além do número de condições, é

possível que o nodo do corpo seja passível de expansão. Para resolver este problema foi decidido

tornar o código um pouco mais modular, sendo que cada um dos módulos é responsável por

determinada tarefa, e no seu conjunto resolvem a problemática associada a estes iteradores.

O fluxo da definição principal pode ser definido da seguinte forma:

• Construção da parte do grafo relativa às variáveis do iterador;

• Construção da parte do grafo relativa às condições;

• Ligação entre variáveis e condições;

• Construção dos seus nodos/grafos e respectivas ligações do corpo;

• Ligação entre as condições e o corpo;

• Construção parte do grafo relativa aos incrementos, steps, das variáveis;

• Ligação entre o corpo e os incrementos;

• Ligação entre os incrementos e as condições.

47

5.4.9 Operadores especiais: let e let*

Estes operadores especiais, let e let*, são usados com bastante frequência, pois permitem definir

variáveis que podem depois ser usadas no seu corpo. A diferença entres os dois operadores é apenas

na forma de execução das variáveis, não havendo diferenças no valor da complexidade usando

qualquer um deles.

Na implementação foram considerados dois casos de uso do let. O primeiro, com o let a ser

usado para definir um ambiente léxico e as suas variáveis, no qual está incluída também a criação de

definições, e o segundo, a ser usado em situações em que é necessária a criação de variáveis locais

dentro do corpo de uma determinada definição. Esta diferenciação deveu-se ao modo como o code

walker analisa estes dois casos.

No processo inicial da implementação, quando se verifica qual o tipo da definição a ser

estudada, o que se está realmente a fazer é a verificar se a definição contém ou não um let, isto é, se a

definição tem variáveis definidas, pois em caso afirmativo, é necessário criar os nodos para essas

variáveis e ligá-los entre si, para posteriormente serem ligadas com o corpo da definição. O grafo do let,

como podemos ver de seguida, não apresenta grande complexidade:

Figura 23 - Grafo de um let/let*.

Neste projeto, a única diferença entre o let e o let*, é mais uma vez, a forma como o code

walker os analisa pois, apesar de na implementação representarem o mesmo, este não os lê da

mesma maneira. Para resolver este problema, foi necessário introduzir uma verificação para os

distinguir, pois caso contrário, isto originaria um grafo com os caminhos trocados.

48

Outra decisão de implementação, foi a criação de uma definição específica para lidar com a

definição de variáveis no let, pois quando estamos a lidar com a definição de variáveis em LISP, temos

que ter algum cuidado, pois é possível introduzir complexidade nas mesmas através de condicionais ou

iteradores, causando isto um aumento do valor de complexidade ciclomática. Nesta definição é então

necessário verificar sempre o tipo do que está a ser atribuído à variável, pois no caso de não ser uma

constante, em vez de existir um único nodo, passa-se à expanção desse nodo, tornando-o num

pequeno grafo que é posteriormente ligado ao resto das variáveis.

O processo de análise de definições que contenham o let, começou então a ser definido pela

chamada da definição que analisa o tipo das variáveis e constrói o seu grafo. Isto faz com que o grafo

das variáveis vá sendo criado à medida que todas as instruções vão sendo analisadas. Quando este

processo acaba e todas as variáveis estão analisadas, o grafo destas é ligado ao primeiro nodo do

corpo. Na análise do corpo é, mais uma vez necessário proceder à expansão dos nodos das várias

instruções, fazendo uma análise sequencial dessas mesmas instruções, garantindo assim que a

criação de nodos e as suas ligações se encontram bem feitas.

5.4.10 Operadores especiais: labels e flet

No LISP, este tipo de operadores são bastante úteis, pois permitem criar um ambiente léxico onde é

possível definir funções localmente, isto é, permitem definir funções que poderão ser usadas dentro do

contexto da definição onde estão definidas.

O code walker mostrou-se muito útil na análise de definições que contenham funções

localmente definidas através do labels ou do flet, pois durante a análise das instruções do corpo destas

definições, consegue-se aceder à Abstract Syntax Tree dessas funções locais. Isto fez com que se

decidisse criar o grafo destas funções locais apenas quando estas são chamadas em alguma instrução

da definição principal, tornando assim os caminhos do grafo mais fiáveis e completos.

Em termos de funcionamento, o labels e o flet funcionam de maneira semelhante, sendo que a

diferença entre os dois para este projeto é, à semelhança do que acontece com o let e o let*, a forma

como o code walker os analisa.

Quanto ao processo de construção do grafo destes operadores, foi então decidido começar a

análise pelas instruções do corpo do operador, analisando as funções locais apenas quando estas são

invocadas. Aqui verifica-se normalmente o tipo de instrução, cria-se o nodo da mesma e os caminhos

entre nodos devidamente validados. No fim deste processo obtém-se então o grafo final.

49

5.4.11 Operadores especiais: multiple-value-bind

O multiple-value-bind é um operador especial que cria um ambiente léxico e permite executar uma

definição, guardando em variáveis todos ou parte dos resultados desse processo, sendo depois possível

usar essas variáveis nas instruções do seu corpo.

O code walker analisa o multiple-value-bind como sendo um let, o que levou a que fosse

necessário adicionar uma verificação na definição de análise do let para detetar quando se estava na

presença de um multiple-value-bind e aí chamar a definição criada para fazer a análise do mesmo.

O fluxo da definição de análise deste operador inicia-se com a criação de um nodo para a

definição que está definida no multiple-value-bind, passando de seguida para a análise de todas as

instruções do corpo do operador, criando os nodos respetivos e expandindo-os caso necessário.

Simultaneamente são também criadas as ligações entre nodos, validando sempre todas essas ligações.

5.4.12 Outros

Existem várias definições implementadas, mas que não vão ser abrangidas aqui, pois o seu nível de

complexidade é bastante baixo e pouco de relevante há a dizer sobre elas. Estas definições são todas

aquelas em que uma instrução é transformada num único nodo, como por exemplo as constantes, o

format, o push, etc.

5.5 Relatório

Um dos objetivos deste projeto era a disponibilização de um relatório, exportado em formato HTML,

que reunisse toda a informação da análise de complexidade ciclomática efetuada. Foi definido que os

constituintes do relatório seriam:

• Logótipo da empresa;

• Nome da definição analisada;

• Valor da complexidade ciclomática;

• Grafo gerado;

• Código analisado.

Relativamente ao processo de implementação do mesmo, foi necessária a implementação de

várias definições para se conseguir que o relatório cumprisse os objetivos, sendo que toda a

50

implementação inerente a este foi codificada em LISP. As definições implementadas cobrem os

seguintes pontos:

• Recolha da informação do grafo;

• Criação das etiqueta dos nodos e caminhos; sendo isto opcional;

• Transformação dos nodos e caminhos no formato DOT;

• Transformação do grafo para o formato DOT;

• Transformação do ficheiro DOT em imagem;

• Geração do relatório em HTML.

Dos pontos referidos em cima, destaca-se a transformação do grafo resultante da análise de

complexidade num ficheiro de formato DOT e a sua consequente transformação em imagem.

Para a transformação do grafo num ficheiro foram criadas várias definições em LISP, que

tratam de todo processo de manipulação do grafo e criação e gravação do ficheiro.

Para a transformação do ficheiro em imagem, foi usada uma definição do LISP que permite

chamar automaticamente a linha de comandos, na qual foi passado o comando que corre o Graphviz e

transforma o ficheiro DOT em imagem. Esse comando foi introduzido no código de maneira a que não

seja necessário realizar qualquer operação manual por parte do programador, fazendo com que este

processo seja totalmente automático e impercetível ao utilizador.

O relatório é construído depois de todo o processo de análise de complexidade ter acabado

pois, como pode ser visto em cima, necessita de informação dessa análise para poder ser construído,

o que implica que antes do relatório ser gerado, é obrigatória a existência da imagem do grafo e o

cálculo do valor da complexidade ciclomática.

Um exemplo do relatório pode ser consultado no Anexo I – Relátorio gerado para o caso I.

51

6. ESTUDO DE CASOS

Neste capítulo serão analisados os três casos de estudo escolhidos para este projeto, dando um

pequeno enquadramento das definições no código global, falando no seu objetivo e características.

Todos os casos de estudo aqui apresentados pertencem a especializações do produto CREWS, usado

para o planeamento e gestão de pessoal, sendo essas especializações feitas para clientes concretos da

SISCOG.

6.1 Caso I – Definição de cálculo de compensações

A definição escolhida para servir como introdução do projeto tem como objetivo realizar o cálculo das

compensações dos trabalhadores da DSB S-tog, que é uma companhia dinamarquesa que opera, entre

outros, os comboios urbanos de Copenhaga.

A definição em estudo surge no meio do processo de cálculo oficial de compensações, sendo

que este processo pode ser definido da seguinte forma:

• Definição dos recursos a calcular;

• Cálculo das compensações;

• Geração de relatório;

• Gravação dos dados.

Este processo de cálculo de compensações inicia-se então com a escolha dos recursos para os

quais vão ser calculadas as compensações, sendo de seguida chamada a definição em estudo para

cada um dos recursos selecionados anteriormente. Esta definição tem como objetivo fazer o cálculo de

compensações de cada recurso num dado período de tempo. Após este cálculo estar concluído para

todos os recursos, é produzido um relatório e são gravados os dados.

52

Figura 24 – Definição do cálculo de compensações.

Da análise de complexidade ciclomática efetuada, obteve-se um valor ciclomático de 9 e o

grafo mostrado de seguida:

Figura 25 - Grafo do caso de estudo I.

53

Como referido anteriormente, a ferramenta calcula a complexidade ciclomática aplicando a

fórmula original de McCabe, usando para isso o número de caminhos e nodos, que como se pode

constatar pela Figura 25, são 25 e 18, respetivamente, ou seja:

V(G) = E – N + 2

V(G) = 25 – 18 + 2

V(G) = 9

Este valor pode ser confirmado manualmente pela fórmula alternativa, V(G) = P + 1, onde P é o

número de pontos de decisão.

Os oito pontos de decisão identificados na Figura 25 são os nodos representados por todos os

condicionais when e if, e pelos iteradores dolist e “it_datas”, que é uma macro que corresponde a um

iterador criado internamente na SISCOG para iterar datas. Cada um destes pontos de decisão realiza

uma verificação específica tal que o when1 verifica se existem compensações, o if é uma verificação

pelo tipo de compensação, os “when2_1” e “when2_2” representam um when com duas condições,

ou seja, a condição do when é constituída por um and com duas condições. O mesmo se passa no

“when3_1” e “when3_2”. O dolist e o “it_datas” são iteradores em que o dolist é utilizado para

percorrer todas as compensações do recurso (que podem ser várias) e o “it_datas” é utilizado para

percorrer um conjunto de datas.

Fora dos pontos de decisão surgem os nodos “varX” que correspondem a definições de

variáveis, os setf que são atribuições de valores e o “resultado” que é valor do cálculo das

compensações de um determinado recurso.

De acordo com a Tabela 2, um valor de complexidade ciclomática de 9, representa um baixo

risco de erro, sendo o código facilmente testável. Não há portanto muitos comentários a acrescentar.

No Anexo I – Relátorio gerado para o caso I pode ser consultado o relatório gerado para esta definição.

6.2 Caso II – Definição de expansão de partilha de equipamento

O segundo caso de estudo escolhido foi uma definição que é usada pelo sistema CREWS_NS, que é

um sistema desenvolvido para os caminhos de ferro holandeses. Esse definição é utilizada quando

estão a ser calculados quais os comboios que podem ou não partilhar equipamento.

54

Esta definição foi escolhida por ser bastante rica sintaticamente, possibilitando o teste da

ferramenta para diferentes condicionais e iteradores.

O processo onde a definição é utilizada inicia-se com a definição de intervalos de séries de

comboios, num parâmetro gráfico, onde se define quais as séries de comboios que podem ou não

partilhar equipamento. A definição é então chamada com esse intervalo definido de séries de comboio

e expande-o, isto é, torna todos os comboios dessa série como comboios possíveis de partilhar (ou

não) equipamento, gravando o resultado numa hash-table. No fim de todo o processo, o resultado é

mostrado na interface gráfica com uma marca visual sobre os comboios afetados.

Como se pode ver pelo grafo do Anexo II – Grafo do caso de estudo II, foram calculados 54 caminhos e

38 nodos, o que resulta numa complexidade ciclomática de 18.

V(G) = E – N + 2

V(G) = 54 – 38 + 2

V(G) = 18

De acordo com a Tabela 2, o valor ciclomático desta definição encontra-se num nível

intermédio (entre 11 e 20), apresentado por isso um risco moderado para a manutenção e para os

testes.

Apesar de não ser uma definição muito grande (36 linhas de código), apresenta um número

ciclomático que é metade do número de linhas de código, devido à presença de várias condições na

definição. A figura que se segue, tem uma parte do grafo que contém algumas dessas condições.

55

Figura 26 - Exemplo de parte do grafo do caso de estudo II.

Como se pode constatar pela Figura 26, neste pequeno pedaço do grafo, a definição contém

um when com oito condições, sendo que estas condições representam quase metade da complexidade

total do grafo. A condição do nodo representado na Figura 26 como “when3_X”, onde X é um número

e representa uma condição, é expressa em LISP da seguinte forma:

56

Figura 27 - Condições do when no caso de estudo II.

Este pequeno pedaço de código criou alguns desafios ao nível da implementação, no que toca

à validação de caminhos, pois a abordagem de desenho inicial da ferramente não tinha sido

suficientemente flexível para se conseguir lidar com situações desta complexidade. Os desafios que

daqui surgiram permitiram posteriormente ter uma ferramenta mais completa.

O resto das condições do grafo baseiam-se nas condições de paragem dos iteradores do e

dolist, assim como nas condições do when e unless espalhados pelo corpo da definição.

Esta é uma definição bastante rica em termos de conteúdo, tendo servido como principal forma

de apoio para a elaboração do projeto, pois nela podemos encontrar uma variedade de casos para

testar a implementação com o let, dolist, do, do*, multiple-value-bind, labels, when e unless.

6.3 Caso III – Definição de processamento de licitações

Como último caso de teste para este projeto, foi escolhida a definição que trata do processamento de

licitações dos trabalhadores. Tal como no caso I, esta definição é usada pelo sistema de gestão de

recursos da DSB S-tog.

O motivo da escolha desta definição foi um trabalho recente de refatorização do código que

esta sofreu, o que fez com que a sua estrutura interna fosse modificada com o intuito de tornar o

código menos complexo e mais fácil de manter.

No contexto do sistema indicado acima, esta definição surge numa funcionalidade relacionada

com o processamento das licitações feitas pelos trabalhadores, onde estes definem a sua preferência

no que toca às linhas da escala de um plano de trabalho. Ao realizar este processamento, a definição

57

tem em conta as preferências dos trabalhadores assim como um conjunto de regras definidas pela

empresa.

Este processo decorre da seguinte maneira:

• Criação das linhas de escala;

• Abertura do processo de licitação;

• Escolha das linhas de escala, por parte dos trabalhadores, de acordo com a sua preferência;

• Processamento das licitações dos recursos;

• Atribuição das linhas da escala.

No processo de análise da complexidade ciclomática, a definição original, que foi a primeira

implementação do algoritmo de licitações, continha 120 linhas de código e resultou numa

complexidade de 35 (133 caminhos e 100 nodos).

V(G) = E – N + 2

V(G) = 133 – 100 + 2

V(G) = 35

Apesar de ser uma definição complexa e apresentar um alto risco no que diz respeito à

probabilidade da ocorrência de falhas no processo de testes, o seu conteúdo não era muito rico, pois

consistia muito na utilização de condicionais relativamente simples e atribuição de valores a variáveis.

De uma forma simples, o processo descrito neste algoritmo tratava de verificar a validade de

determinada condição, sendo que em caso afirmativo realizavam-se algumas atribuições, caso

contrário, testava-se outra condição.

Da análise do grafo do Anexo III – Grafo do caso de estudo III (original) e da Figura 28, pode

ver-se que este era um algoritmo bastante confuso, pois existiam várias condições encadeadas e

muitos caminhos possíveis, o que tornava a tarefa de testes bastante complicada, assim como a

própria manutenção do código devido a sua complexidade.

58

Figura 28 - Exemplo de parte do grafo da definição original do caso III.

Este algoritmo, tendo sido implementado por um analista-programador com menor

experiência, continha alguma complexidade que não era necessária e até mesmo indesejável, tendo

surgido então a necessidade de refatorizar o código.

O grafo desta nova definição, após refatorização, pode ser consultado no Anexo IV – Grafo do

caso de estudo III (refatorizado). Com esta refatorização, o novo algoritmo obteve um valor de

complexidade ciclomática de 14.

Parte da estratégia de refatorização consistiu em remover algumas das responsabilidades do

corpo da função principal criando três novas definições, representadas na figura em baixo por

“Função”, “Função_X” e “Função_Y”, com complexidades de 2, 2 e 3, respetivamente.

59

Figura 29 - Grafos das definições criadas.

Estas novas definições, são definições relativamente simples e que fazem cálculos muito

específicos. A primeira apenas verifica se deve usar a “funcao_X” ou a “funcao_Y” para fazer o

cálculo, e essas funções fazem os cálculos específicos sobre que linha de escala e que semana devem

ser atribuidas aos recursos que licitaram, dependendo da sua antiguidade na empresa.

A tabela seguinte é um resumo dos valores de complexidade da definição refatorada e das

novas definições criadas.

V(G) = E – N + 2: V(G) calculada:

Definição refatorada V(G) = 42 – 30 + 2 V(G) = 14

Definição 1 V(G) = 8 – 8 + 2 V(G) = 2

Definição 2 V(G) = 8 – 8 + 2 V(G) = 2

Definição 3 V(G) = 12 – 11 + 2 V(G) = 3

Tabela 6 - Resumo sobre o cálculo da complexidade das novas definições.

60

Como se pode observar pela Tabela 6, houve uma diminuição drástica na complexidade

ciclomática da definição analisada, pois esta passou de 35 para 14, e no que toca a linhas de código

de 120 para 31 linhas.

Mesmo juntando a complexidade da definição de processamento de licitações refatorizada com

todas as pequenas definições criadas, obtém-se uma complexidade total de 21, resultante de

14+(2+2+3)*n, sendo n o número de vezes que as novas funções são chamadas (neste caso é igual a

1), o que continua a ser menor que a inicial.

Além desta refatorização ter trazido vantagens no que toca à manutenção do código, foi também

uma preciosa ajuda para os testes, pois estando estes pequenos módulos separados, tornou-se mais

fácil testar cada um deles.

61

7. CONCLUSÕES

7.1 Síntese dos resultados alcançados

Nos dias de hoje, existe uma grande preocupação com a qualidade do software, sendo a alta

complexidade do mesmo considerada um fator indesejável. Sabendo que esta está diretamente ligada

à produtividade dos recursos que trabalham na manutenção de software, onde eu me incluo, foi um

fator de interesse e uma motivação extra ter realizado o projeto nesta área.

Esta área tem sido alvo de estudos constantes, tendo sido feito um investimento considerável

no campo das métricas de avaliação, com o intuito de fornecer ferramentas que auxiliem a análise de

complexidade de software, assim como a diminuição de complexidade resultante desse processo.

Estas métricas de avaliação visam proporcionar uma forma de medir a complexidade e

também a efetividade de um sistema.

Neste contexto, foi escolhida para este projeto a métrica da complexidade ciclomática, que veio

trazer uma abordagem baseada na teoria de grafos e que se adequava bastante bem ao propósito

deste projeto. Não sendo este um processo fácil de realizar manualmente quando se está a lidar com

software bastante complexo, como é o caso dos produtos da SISCOG, foi necessário implementar uma

ferramenta que fizesse esta análise automaticamente e sem grande esforço por parte dos analistas-

programadores. Esta ferramenta é apenas para ser usada internamente na SISCOG, não sendo por

isso divulgada em código aberto.

Para este projeto foi então necessário realizar um estudo das temáticas inerentes à

complexidade ciclomática, nomeadamente os testes de software, no qual foi abordado o papel dos

testes na indústria de software e alguns dos seus tipos, como os testes de caixa branca e os testes de

caixa preta, assim como uma parte da teoria de grafos que engloba todos os conceitos necessários

para a complexidade ciclomática e o papel dos grafos de controlo de fluxo na sua medição.

No que toca à implementação da ferramenta, houve um certo cuidado em tentar modularizar

os vários componentes do código, tornando o mesmo mais fácil de manter e compreender. Existem,

contudo, ainda alguns componentes que precisam de ser revistos pois apresentam alguma

complexidade.

O processo de utilização da ferramente é bastante simples, já que o utilizador apenas tem de

invocar uma definição, que automaticamente analisa o código e constrói um relatório com os

resultados. Neste relatório, é possível ver pelo grafo onde se concentram as partes mais complexas do

62

código, podendo depois o utilizador focar aí a sua atenção para proceder à análise na tentativa de

reduzir a complexidade.

Nos desafios encontrados durante este projeto, o primeiro a destacar foi a escolha da

linguagem LISP, pois era uma linguagem relativamente nova para mim e sendo esta bastante rica e

flexível, acabou por colocar vários desafios durante o desenho e implementação da ferramenta.

Outro dos principais desafios deste projeto foi a utilização do code walker, pois a

documentação do mesmo é praticamente inexistente, o que levou a que em alguns pontos, fosse

necessário recorrer a uma abordagem de tentativa-erro para perceber como este funcionava e o que se

podia extrair do mesmo. O code walker trouxe também outros desafios por causa da forma como este

lê os vários operadores, macros e todas as definições especificadas no LISP, pois por vezes, este tem

um comportamento diferente no caso destas estarem isoladas ou dentro de outras definições.

Na parte de implementação propriamente dita, o maior desafio foi validar a imensidão de

caminhos possíveis capazes de ser gerados dentro das definições. Para contornar este problema, mais

uma vez, foi necessário recorrer à uma abordagem de tentativa-erro, usando casos cada vez mais

complexos para assim tentar cobrir o máximo de caminhos possíveis. Neste âmbito foi também

necessário recorrer por vezes à especificaçãoo da linguagem para conseguir compreender a linguagem

de uma forma mais completa.

Para avaliar os resultados desta ferramenta analisaram-se três casos de estudos, começando

por uma definição pouco complexa e evoluindo posteriormente para casos mais complexos.

Sendo que o primeiro caso de estudo serviu apenas como introdução, pois a sua complexidade era

baixa e o segundo caso de estudo foi usado por ser bastante rico sintaticamente e ter já alguma

complexidade, o caso que se pode considerar verdadeiramente como o caso de estudo deste projeto é

o terceiro.

Este caso foi escolhido por ser uma definição que já sofreu uma reformulação, podendo aí ser

comparada a complexidade ciclomática antes e depois dessa intervenção. Se antes, a definição era

bastante complexa e como se pode observar pelo seu grafo, existiam diversas condições encadeadas, o

depois, reflete uma forte redução da complexidade pois, pelo mais baixo valor da complexidade

ciclomática e pelo grafo mais simples, nota-se que já é uma definição mais fácil de compreender e de

manter. Sendo que esta definição não sofreu alterações de comportamento durante a reformulação,

pode-se concluir que existia uma maior complexidade que era desnecessária e que estava intimamente

ligada a decisões tomadas pelo analista-programador.

63

Com esta análise e analisando todos os outros resultados deste projeto, pode-se concluir que

então que software com complexidade ciclomática alta é difícil de compreender, difícil de manter e é

geralmente sinal de modularização inadequada ou então da existência de muita lógica inserida na

mesma definição.

Acabado este projeto, espera-se que com o uso da ferramenta, os analistas-programadores

tenham uma nova ferramenta de suporte durante a análise do código produzido e que a equipa de

testes possa utilizar a mesma como apoio na contabilização do número de casos de testes a desenhar.

7.2 Trabalho futuro

Acabada a primeira fase deste projeto, podem ser considerados vários aspetos a ser trabalhados no

futuro.

Relativamente à ferramenta em si, vai ser necessário continuar o trabalho de modularização de

algumas partes da ferramenta com o intuito de torná-las mais fáceis de compreender e manter. Vai

também ser necessário continuar a extender a ferramenta para outros casos ligados a especificidades

da linguagem LISP que possam não ter sido contemplados e sejam entretanto detetados.

Uma ideia interessante que surgiu durante o desenvolvimento deste projeto foi introduzir a

ferramenta no mecanismo de compilação do código, tornando o processo totalmente automático, isto

é, o valor ca complexidade ciclomática passaria a ser obtido junto com a mensagem de compilação.

Por outro lado, seria também interessante, analisar estratégias que permitissem fazer uma

análise da complexidade mais completa, nevegando até um determinando nível pelas definições que

são utilizadas ao longo do código que está a ser analisado, para que se consiga obter a complexidade

total de uma dada funcionalidade, sendo essa complexidade representada pela complexidade da

definição principal e de todas as sub-definições analisadas. Apesar da métrica original não contemplar

estes casos, pois só considera módulos individuais, existem estudos que sugerem outras abordagens

de adaptação da complexidade ciclomática a estes casos.

Por fim, surgiram também outras ideias que passavam pelo cálculo da complexidade por

“mouse-over”, isto é, calcular automaticamente a complexidade ao passar o rato sobre determinada

definição; e pela navegação dos nodos do grafo para o código, podendo o utilizador carregar num nodo

e ir direto para o código relativo a essa nodo.

64

65

BIBLIOGRAFIA

Abran, A. (2010). Software Metrics and Software Metrology, Wiley-IEEE Computer Society Press, July.

Abran, A., Lopez, M., & Habra, N., (2004). An Analysis of the McCabe Cyclomatic Complexity Number,

14th International Workshop on Software Measurement (IWSM2004).

Arthur, L.J., (1985). Measuring programmer productivity and software quality. Wiley-lnterscience.

Baker, A.L., & Zweben, S., (1980). A comparison of measures of control flow complexity. IEEE

Transactions on Software Engineering, SE-6,(6),pp. 506 – 511.

Bandara, P.L.M.K., Wikramanayake, G.N., & Goonethillake, J.S., (2009). Software Reliability Estimation

Based on Cubic Splines. Proceedings of the World Congress on Engineering.

Banker, R.D., Srikant, M.D., Kemerer, C.F., & Zweig, D., (1993). Software complexity and maintenance

cost. Communications of the ACM, Vol. 36, No. 11, pp. 81–94.

Basili, V.R., & Hutchens, D.H., (1983). An empirical study of a syntactic complexity family. IEEE

Transactions on Software Engineering, SE-9, (6),pp. 664-672

Basili, V.R., & Perricone, B.T., (1983). Software errors and complexity: An empirical investigation.

Communications of the ACM, Vol. 27, No. 1, pp. 42–52.

Berge, C., (1973). Graphs and Hypergraphs. North-Holland Publishing Company.

Berge, C., (2001). The Theory of Graphs. Dover Publications, September 4.

Boehm, B.W., (1973). Software and its impact: A quantitative assessment. Datamation, vol.19, pp. 48-

59, May.

Butler, L.P., (1983). Software Quality Assurance Cyclomatic Complexity of a Computer Program.

Proceedings of the IEEE 1983 National Aerospace and Electronics Conference, v. 2, pp. 867-73.

Cammack, W.B., & Rogers, H.J., (1973). Improving the programming process. IBM Tech. Rep. TR

00.2483, Oct.

Cardoso, A., (2009). Teoria de Grafos: uma reflexão sobre a sua abordagem no ensino não

universitário. Tese de Mestrado, Universidade Portucalense.

Chowdhury, I., (2009). Using Complexity, Coupling, And Cohesion Metrics as Early Indicators of

Vulnerabilities. Queen’s University Canada, September.

Cobb, G.W., (1978). A measurement of structure for unstructured languages. Proceedings of ACM

SIGMETRICSISIGSOFT, Software Quality Assurance Workshop.

66

Coleman, D., Ash, D., Lowther, B., & Oman, P., (1994). Using metrics to evaluate software system

maintainability. IEEE Computer, vol. 27, no. 8, August.

Curtis, B., (1983). Software metrics: guest editor’s introduction. IEEE Transactions on Software

Engineering, 9, (6), pp.637-638.

Curtis, B., Sheppard, S.B., & Milliman, P., (1979). Third time charm: Stronger prediction of

programmer performance by software complexity metrics. Proceedings of the 4th International

Conference on Software Engineering, pp.356–360.

Curtis, B., Sheppard, S.B. ; Milliman, P. ; Borst, M.A. ; Love, T., (1979). Measuring the psychological

complexity of software maintenance tasks with the Halstead and McCabe metrics. IEEE

Transactions on Software Engineering, SE-5,(2),pp.96 – 104.

De Marco, T., (1982). Controlling software projects: management, measurement and estimation.

Yourdon Press.

Del Grosso, C., Antoniol, G., Merlo, E., & Galinier, P. (2008). Detecting buffer overflow via automatic

test input data generation. Computers & Operations Research, 35(10), 3125–3143.

doi:10.1016/j.cor.2007.01.013.

Dunsmore, H.E., (1984). Software metrics: an overview of an evolving methodology. Information

Processing & Management, 20, (1-2), pp.183-192.

Enescu, N.I., Mancas, D., Manole, E.I., & Udristoiu, S., (2008). Evaluating the correlation between the

increasing of the correctness level and McCabe complexity. Proceedings of the 8th WSEAS

International Conference on Applied Computer Science (ACS'08).

Enescu, N.I., Mancas, D., Manole, E.I., & Udristoiu, S., (2009). Increasing Level of Correctness in

Correlation with McCabe Complexity. International Journal of Computers, Vol. 3, pp. 63-74.

Evans, M.W., & Marciniak, J., (1987). Software Quality Assurance and Management. John Wiley and

Sons, Inc.

Fenton, N.E., & Neil, M., (1999). Software metrics: success, failures and new directions. Journal of

Systems and Software - Special issue on invited articles on top systems and software engineering

scholars, Volume 47 Issue 2-3, July 1, 1999, 149 – 157

Fenton, N.E., Whitty, R. W., & Kaposi, A.A., (1985). A generalised mathematical theory of structured

programming. Theor. Comput. Sci., 36, 145–171.

Garg, A., (2014). An approach for improving the concept of Cyclomatic Complexity for Object-Oriented

Programming. CoRR - Computing Research Repository.

67

Gaur, M. (2013). Software Security-Static Buffer Overflow Analysis in Object Oriented Programming

Environment-A Comparative Study. IJCAIT 2(1): 1-8.

Geoffrey, K., (1991). Cyclomatic Complexity Density and Software Maintenance Productivity. IEEE

Transactions on Software Engineering, vol. 17, no. 12, December.

Gill, G.K., & Kemerer, C.F., (1991). Cyclomatic complexity density and software maintenance

productivity. IEEE Transactions on Software Engineering, vol.17, no.12, pp.1284-1288, Dec

1991. doi: 10.1109/32.106988.

Gold, R., (2010). Control flow graphs and code coverage. Int. J. Appl. Math. Comput. Sci., 2010, Vol.

20, No. 4, 739–749. DOI: 10.2478/v10006-010-0056-9.

Gold, R., (2013). Decision Graphs and Their Application to Software Testing. ISRN Software

Engineering, vol. 2013, Article ID 432021, 12 pages, 2013. doi:10.1155/2013/432021

Gollhofer, M., (1983). Predicting Errors using McCabe's Metric, Master's Thesis, University of California,

Davis.

Grady, R.B, (1992). Practical Software Metrics for Project Management and Process Improvement.

Prentice-Hall.

Hansen, W.J., (1978). Measurement of program complexity by the pair (cyclomatic number, operator

count). SIGPLAN Notices, 13, (3), pp. 29 - 33

Harrison, W., & Magel, K., (1981). A complexity measure based on nesting level. SIGPLAN Notices, 16,

(3), pp. 63 – 74

Harrison, W., Magel, K., Kluczny, R., & De Kock, A., (1982). Applying software complexity metrics to

program maintenance. Computer, 15, Sept., pp. 65 - 79

Henderson-Sellers, B. (1996), Object-Oriented Metrics. Measures of Complexity , Prentice Hall.

Henry, S., Kafura, D., & Harris K., (1981). On the relationship among three software metrics.

Performance Evaluation Review, v. 10,

IEEE (1990). An Empirical Evaluation (and Specification) of the all-du-paths. IEEE Standard Computer

Dictionary: A Compilation of IEEE Standard Computer Glossaries.

Iyengar, S.S., Parameswaran, N., & Fuller, J., (1982). A measure of logical complexity of programs.

Computer Languages, 1982, 7, pp. 147 – 160

Jalote, P. (2005). An Integrated Approach to Software Engineering. Springer, New York, NY.

Jay, G., Hale, J.E., Smith, R.K., Hale, David, Kraft, N.A., & Ward, C. (2009). Cyclomatic Complexity and

Lines of Code: Empirical Evidence of a Stable Linear Relationship. J. Software Engineering &

Applications, 2: 137-143. doi:10.4236/jsea.2009.23020

68

Jovanović, I., (2006). Software Testing Methods and Techniques. The IPSI BgD Transactions on Internet

Research: 30.

Kanewala, U., & Bieman, J. M. (2014). Testing scientific software: A systematic literature review.

Information and Software Technology, 56(10), 1219–1232. doi:10.1016/j.infsof.2014.05.006

Khalid, M., ul Haq, S., Khan, M.N.A., (2013). An Assessment of Extreme Programming Based

Requirement Engineering Process. International Journal of Modern Education and Computer

Science (IJMECS) 5(2): 41.

Kitchenham, B.A., (1981). Measures of programming complexity. ICL Technical Journal, 2, (3),pp. 298-

316

Kosaraju, S. (1973). Analysis of structured programs. Proceedings of the 5th Annual ACM Symposium

on Theory of Computing, Austin, TX, USA, pp. 240–252.

Krusko, A. (2002). Complexity Analysis of Real Time Software. Royal Institute of Technology Sweden.

Madi, A., Zein, O., & kadry, S., (2013). On the important of cyclomatic complexity metric. International

journal of software engineering and its application, Vol.7, No.2, 2013, pp.67-82.

Magel, K., (1981). Regular expressions in a program complexity metric. SIGPLAN Notices, 16, (7). pp.

61 - 65

McCabe, T. J., (1976). A complexity measure. IEEE Transactions on Software Engineering, 2(4), 308-

320.

McCabe, T., McCabe Jr., T., & Fiondella, L., (2012). Uncovering Weaknesses in Code With Cyclomatic

Path Analysis. CrossTalk - The Journal of Defense Software Engineering, Volume: 25, Issue: 4, Pages:

9-14

McCabe, T.J., Wallace, D., Watson, A.H., (1996). Structured Testing: A Software Testing Methodology

Using the Cyclomatic Complexity Metric. NBS Special Publication, 500-99.

McCarthy, J., (1960). Recursive functions of symbolic expressions and their computation by machine

(Part I). Communications of the ACM, in April.

Meals, R.R., (1981). An Experiment in the Implementation and Application of Halstead's and McCabe's

Measures of Complexity. Software.

Morgado, E.M., & Martins, J.P. (1998). CREWS_NS: Scheduling Train Crew in the Netherlands. AI

Magazine 19 (1): 25-38.

Morgado, E.M., Martins, J.P., & Haugen, R. (2003). TPO: A System for Scheduling and Managing Train

Crew in Norway. Paper published by AAAI.

69

Munson, J.C., & Khoshgoftaar, T.M., (1992). The detection of fault-prone programs. IEEE Transactions

on Software Engineering, Vol. 18, No. 5, pp. 423–433.

Munson, J.C., & Khoshgoftaar T.M., (1989). The dimensionality of program complexity. Proceedings of

the 11th InternationalConference on Software Engineering, pp.245–253.

Myers, G.J., (1977). An extension to the cyclomatic measure of program complexity. SIGPLAN Notices,

12, (lo), pp. 61-64

Myers, G., (1989). The Art of Software Testing. Wiley.

Nystedt, S., (1999). Software Complexity and Project Performance, University of Gothenburg.

Oulsnam, G., (1979). Cyclomatic numbers do not measure complexity of unstructured programs.

Information Processing Letters,8,pp. 207 – 211

Ould, M.A., & Unwin, C., (1986). Testing in Software Development. Cambridge University Press, New

York.

Oviedo, E., (1980). Control flow, data flow and program complexity. Proceedings of COMPSAC 80

Conference, Buffalo, NY, USA, Oct., pp. 146 – 152

Paige, M., (1980). A metric for software test planning. Proceedings of COMPSAC 80 Conference,

Buffalo, NY, USA, Oct., pp. 499-504

Paige, M. (1977). On partitioning program graphs. IEEE Transactions on Software Engineering, SE-3(6): 386–393.

Paige, M.R., (1975). Program graphs, an algebra, and their implication for programming. IEEE Trans. Softw. Eng., SE-1, 3, Sept., 286–291.

Patelia, R. M, & Vyas, S., (2014). A review and analysis on Cyclomatic Complexity. Orient.J. Comp. Sci.

and Technol;7(3)

Prather, R.E., (1984). An axiomatic theory of software complexity metrics. ComputerJournal,27,(4),pp.

340 - 347

Pressman, R.S., (2010). Software engineering: a practitioner's approach. McGraw-Hill Higher

Education.

70

Rapps, S., & Weyuker, E. (1982). Data flow analysis techniques for test data selection. Proceedings of the 6th International Conference on Software Engineering, Tokyo, Japan, pp. 272–278.

Sarwar, M.M.S., Ahmad, I., & Shahzad, S., (2012). Cyclomatic Complexity for WCF: A Service Oriented

Architecture. IEEE on Frontiers of Information Technology (FIT), 10th International Conference.

Sarwar, M.M.S., Shahzad, S., & Ahmad, I., (2013). Cyclomatic complexity: The nesting problem. Eighth

International Conference on Digital Information Management (ICDIM), pp.274-279, 10-12 Sept.

doi: 10.1109/ICDIM.2013.6693981

Schneidewind, N.F., (1979). An Experiment in Software Error Data Collection and Analysis. IEEE

Transactions on Software Enginerring,

Schneidewind, N.F., (1979). Software metrics for aiding program development and debugging. National

Computer Conference, New York, NY, USA, Jun., AFIPS Conference Proceedings Vol. 48, pp.

989 - 994

Shepperd, M., (1988). A critique of cyclomatic complexity as a software metric. Software Engineering

Journal, Volume 3 Issue 2, March 1988, 30-36.

Shimeall, T., (1990). Cyclomatic Complexity as a Utility for Predicting Software Faults. March.

Shuman, E. A., (1990). Cyclomatic complexity as utility for predicting software faults. Postgraduation’s

thesis, Naval Postgraduate School: California, 96 pgs.

SISCOG, (2013). A story written in Lisp. ECLM'13 conference.

SISCOG - Sistemas Cognitivos, SA. Retrieved September 24, 2015, from http://www.siscog.pt/

Solichah, I., Hamilton, M., Mursanto, P., Ryan, C., & Perepletchikov, M., (2013). Exploration on

software complexity metrics for business process model and notation. 2013 International

Conference on Advanced Computer Science and Information Systems (ICACSIS), pp.31-37, 28-

29 Sept. doi: 10.1109/ICACSIS.2013.6761549

Stetter, F., (1984). A measure of program complexity. Computer Languages, 9, (3), pp. 203 - 210

Suresh, Y., Pati, J., & Rath, S. K. (2012). Effectiveness of Software Metrics for Object-oriented System.

Procedia Technology, 6, 420–427. doi:10.1016/j.protcy.2012.10.050

Tan, L. (2006). The Worst Case Execution Time Tool Challenge 2006: The External Test. Technical

report.

Tanik, M.M. (1980). A comparison of program complexity prediction models. SIGSOFT Software

Engineering Notes, 5, (4), pp. 10 - 16

71

Vinju, J.J., & Godfrey, M.W., (2012). What Does Control Flow Really Look Like? Eyeballing the

Cyclomatic Complexity Metric. 2012 IEEE 12th International Working Conference on Source

Code Analysis and Manipulation (SCAM), pp.154-163, 23-24 Sept. 2012. doi:

10.1109/SCAM.2012.17

Walsh, T.J., (1979). A software reliability study using a complexity measure. AFIPS Conference

Proceedings, v. 48, pp. 761-768, 1979.

Wang, A.S., & Dunsmore, H.E., (1984). Back-to-front programming effort prediction. Information

Processing & Management, 29,(1-2), pp. 139-149

Ward, W.T., (1989). Software Defect Prevention Using McCabe's Complexity Metric. Hewlett-Packard

Journal, v. 40, p. 64, April 1989.

White, L. (1981). Basic mathematical definitions and results in testing. Computer Program Testing,

North-Holland, New York, NY.

Whitmire, S. A., (1997). Object-Oriented Design Measurement. Wiley Computer Publishing, 1 edition.

Wiener, R., & Sincovec, R., (1984). Software engineering with Modula-2 and Ada. Wiley.

Wilkie, F.G., & Hylands, B., (1998). Measuring complexity in C++ application software. Software:

Practice and Experience, Vol. 28, No. 5, pp. 513–546

Woodward, M.R., Hennell, M.A., & Hedley, D.A., (1979). A measure of control flow complexity in

program text. IEEE Transactions on Software Engineering, 5, (1). pp. 45 - 50

Yadav, A., & Khan, R., (2012). Development of Encapsulated Class Complexity Metric. Procedia

Technology 4: 754-760.

Zhu, H., Hall, P.A.V., & May, J.H.R, (1997). Software unit test coverage and adequacy. Computing

Surveys, Vol. 29, No. 4 December 1997.

72

73

ANEXO I – RELÁTORIO GERADO PARA O CASO I

Figura 30 – Anexo I: Exemplo de um dos relatórios HTML gerados.

74

ANEXO II – GRAFO DO CASO DE ESTUDO II

Figura 31 - Anexo II - Grafo do caso de estudo II (Parte 1).

75

Figura 32 - Anexo II - Grafo do caso de estudo II (Parte 2).

76

Figura 33 - Anexo II - Grafo do caso de estudo II (Parte 3).

77

ANEXO III – GRAFO DO CASO DE ESTUDO III (ORIGINAL)

Figura 34 - Anexo III - Grafo do caso de estudo III, original (Parte 1).

78

Figura 35 - Anexo III: Grafo do caso de estudo III, original (Parte 2).

79

ANEXO IV – GRAFO DO CASO DE ESTUDO III (REFATORIZADO)

Figura 36 - Anexo IV - Grafo do caso de estudo III, refatorizada (Parte 1).

80

Figura 37 - Anexo IV - Grafo do caso de estudo III, refatorizada (Parte 2).