Revista MundoJ

37

description

Mundo J revista para desenvolvedores de software

Transcript of Revista MundoJ

editorial_

Arquitetura: a Peça que Faltava nos Métodos Ágeis

O movimento desencadeado pela ascensão dos métodos ágeis na engenha-ria de software trouxe várias práticas extremamente importantes para a

qualidade de código. Essas práticas visam tornar o desenvolvimento de sof-tware sustentável, de forma que seja possível adicionar novas funcionalidades continuamente sem incorrer em altos custos para sua modificação e manu-tenção. Dentre essas práticas, se destaca a criação de testes automatizados, que contribuem para uma detecção precoce de problemas e dá segurança para realização de modificações. A cultura do código limpo, mantido com práticas de refatoração constante, é outra contribuição importante desse movimento. Porém alguma coisa estava faltando: a arquitetura do software!

O Desenvolvimento Orientado a Testes, vulgo TDD, ajuda muito no de-sign individual de cada classe, já criando-as de forma desacoplada de suas dependências. Porém, o TDD possui um foco local e não ajuda muito quando estamos buscando soluções de design mais amplas. Apenas com TDD, seria criada uma grande rede de classes desacopladas, porém ele não garante uma consistência global entre as soluções. Nesse momento é que a refatoração en-tra em cena!

As classes criadas com TDD podem ir sendo refatoradas e uma reestru-turação para manter uma consistência geral no software pode ser alcançada. A partir de pequenos passos, as transformações podem ir sendo realizadas de forma a tornar a estrutura homogênea e promover o reúso de código. Esse é o chamado design emergente! Porém, será mesmo que essa é a melhor solução? Será que vale a pena deixar para trás anos de conhecimento e experiência no trabalho com arquiteturas de software? Em metodologias tradicionais, perdia--se muito tempo no projeto, especificação e documentação da arquitetura e, talvez por esse motivo, os métodos ágeis romperam com esse paradigma de pensar em arquitetura antes de começar a implementação do software. Mas, parafraseando o título de uma palestra da Rebecca Wirfs-Brock, será que pre-cisa correr ou apostar tudo? Será que não é possível trabalhar com o projeto da arquitetura de uma forma ágil?

Os métodos ágeis em geral defendem que as atividades feitas em um projeto possuam o propósito de agregar valor ao produto final, diminuindo ao máximo a quantidade de desperdício. Nesses desperdícios inclui-se, por exemplo, a criação de documentos write-only e longas fases de análise para evitar mudanças que muitas vezes são inevitáveis. Porém, um projeto inicial de arquitetura não pode ser incluído nessa categoria! Muito pelo contrário, uma boa arquitetura promove o reúso de código e, consequentemente, dimi-nui o tempo gasto em cada funcionalidade. Isso não é agregar valor?

O projeto da arquitetura e a implementação de questões não-funcionais podem ir acontecendo de forma iterativa, porém é importante ter um ponto de partida que aponte a direção a ser seguida para os requisitos não-funcionais mais importantes. Isso complementa práticas como o TDD e a refatoração. A pouco tempo atrás começou-se a falar mais sobre práticas relacionadas a ar-quitetura para serem aplicadas em métodos ágeis, reconhecendo a importân-cia delas existirem. Cada um tem exposto sua solução, e diferentes abordagens para se lidar com isso estão surgindo...

Fique atento ao que está sendo dito e filtre o que se aplica aos projetos em que participa. Tome apenas cuidado ao descartar soluções que pareçam não ser adequadas a princípio, pois pode estar jogando fora conhecimento valioso! Lembre-se que o mesmo aconteceu com diversas outras práticas ágeis quando elas surgiram.

“Com Arquitetura na ponta Ágil dos dedos!”

EDITOR EXECUTIVO | Marco Antonio Guapo

EDITOR ADMINISTRATIVO | Osmar Zózimo de Souza Jr.

EDITOR-CHEFE | Eduardo GuerraEQUIPE TÉCNICA | Alexandre Gazola, Breno Barros, Givanildo Santana do Nascimento, Guilherme de Azevedo Silveira, Paulo Silveira, Rafael Santos, Roberto Perillo, Rodrigo Cunha

COLABORADORES DESTA EDIÇÃO | Paulo Silveira, Breno Barros, Eduardo Guerra, Ricardo Linden, Rafael Santos, André Grégio, Cézar Taurion, Sylvio Barbon Junior, Marcos Pedro Gomes da Silva, Fernanda Boaglio e Nicolas Gentille.

PROJETO E DESENVOLVIMENTO GRÁFICO |Editora MundoAdoro Design

JORNALISTA RESPONSÁVEL | Débora A. R. Dias – DRT 3793

ARTIGOS

Se você deseja escrever para a MundoJ, envie sua sugestão para [email protected]

CONTATO

Revista MundoJCaixa Postal 18.830 - CEP 80410-990Curitiba - PR - Tel.: 41 3029.9353PUBLICIDADE [email protected] [email protected]

DISTRIBuIçãO ExCLuSIvA NO BRASILFernando Chinaglia Distribuidora S.A.Rua Teodoro da Silva, 907CEP 20563-900 - Rio de Janeiro – RJA revista MundoJ é uma publicação bimestral da Editora MundoISSN 1679-3978O conteúdo dos artigos é de responsabilidade dos autores. Os softwares distribuídos via CD-ROM e encartes com a revista são de propriedade e responsabilidade de seus fabricantes, assim como o suporte e os direitos autorais.

Eduardo Guerraeditor-chefe

@emguerraSiga o editor-chefe da MundoJ no Twitter e acompanhe em tem-po real as novidades sobre a revista e sobre desenvolvimento de softwares em geral.

08 Adotando Arquitetura Ágil em seu Pro-cesso de Desenvolvimento de Softwareveja como adotar, modelar e documentar a arquitetura, de maneira ágil, em seu processo de desenvolvimento de software.

14 Práticas para Lidar com Arquitetura em Ambientes ÁgeisAprenda as principais práticas que são utiliza-das para lidar com arquitetura em empresas que adotaram com sucesso o uso de métodos ágeis.

28 Gerenciando a Cobertura de Testes em um SistemaComo avaliar se todo o seu código está sendo verificado pelos seus testes? A resposta é: maximizando a cobertura de seu código pelos seus testes.

NÚMERO 50 | ANO IX | NOVEMBRO & DEZEMBRO DE 2011

_índice

artigos >36 Visualização Gráfica de Grafos com

a API JUNG Aprenda como usar a API JuNG para visualizar conjuntos de dados representados como grafos.

54 Comparando Persistência de Dados com JPA TopLink e Hibernate testados em diferentes bancos de dados e sistemas operacionais.

58 Profiles no Spring 3.1 Melhore o controle de suas aplicações usando os profiles do Spring 3.1.

62 Qualidade sob Medida com o PMD e o Eclipse De uma forma simples e sem muito esforço, crie suas próprias regras para validação de código Java.

colunas >06 Tópicos Mais Quentes do GUJ.com.

br. veja o que apareceu, foi notícia e gerou discussão no fórum do GuJ durante setembro e outubro de 2011.

52 Tendências em Foco: Conhecendo o Hadoop uma das maiores invenções de data management desde o modelo relacional.

www.mundoj.com.br

/ 6

tópicos mais quentes doguj.com.br

notíc

ias m

ais lidas e comentadas guj.com.br

Duas notícias ruins nesses dois meses: a saída de Steve Jobs como

CEO e seu falecimento.

Saída e morte de Steve Jobshttp://www.guj.com.br/250853http://www.guj.com.br/254508

A idéia de que as empresas não devem

procurar especialistas em tecnolo-gias e linguagens específicas traz a discussão sobre boas faculdades,

teoria X prática, evolução do desenvolvedor na empresa

etc.

Google: “não queremos especialistas“

www.guj.com.br/ 251396

Um dos principais

lançamentos no JavaOne 2011, a notícia de uma nova versão do Java FX surpreen-deu. Novos componentes, melhor

documentação, plugins, outras linguagens e mais. Será que ainda

dá tempo da Oracle abocanhar esse mercado?

Java FX 2.0 www.guj.com.br/254294

Com os novos movimentos da Ora-cle, apenas o OpenJDK fica como alternativa para já vir instalado nas distribuições do Linux. Com

isso, novas profecias sobre problemas com a Oracle

surgem.

Oracle retira licença para distribuir Java com Linux, só resta usar o OpenJDK

http://www.guj.com.br/251394

7 \

Paulo Silveira | [email protected] dos fundadores do GUJ.com.br, o maior fórum em língua portuguesa sobre a plataforma Java, nascido em 2002.

É desenvolvedor e instrutor pela Caelum e editor-técnico da revista MundoJ.

Re-cursividade é

um dos recursos que logo aprendemos, seja na

faculdade, seja no trabalho. Apesar de ser um dos princí-

pios básicos para facilitar a im-plementação de diversos algo-ritmos, muitas vezes aparecem

dúvidas, e outras estouros de pilha. O Fibonacci é o

exercício clássico:

Aprendendo Recursividadehttp://www.guj.com.br/251258

twitter.com/guj_noticias

facebook.com.br/guj.com.br

ACOMPANHE O GUJguj.com.br/mundoj

FÓRUM EXCLUSIVO

Nesse fórum você pode conversar com os autores de cada edição da MundoJ, tirando dúvidas, colaborando, adicionando e criticando todos os artigos.

tópi

cos m

ais lidos e comentados

Em um post polê-

mico, Sergio Oliveira discute alguns pontos que

considera problemáticos do Hibernate. Alguns deles são

considerados vantagens por ou-tras pessoas.

Você não gosta de hiberna-te? Eu também não.

www.guj.com.br/252013

Onde devemos, afinal,

colocar o try/catch? Muita gente vai simples-mente relançando ou dei-

xando passar a exceção para a chamada anterior, mas quando fazer o tratamento de um pro-blema? No managed bean? No

DAO? Varia?

Tratamento de exceções em camadas

www.guj.com.br/253741

Muitos desenvolve-dores Java acabam

caindo em um projeto .NET: seja por mudan-ças na empresa, de pro-jeto, cargo etc. Quais

são os desafios?

Migrando para .NETwww.guj.com.br/252097

Hiding/Shadowing de atributos pode con-

fundir o programador, em especial quando há compara-ções com reescrita de métodos (não há de atributos). É uma

dúvida frequente

Acessando atributo da superclassewww.guj.com.br/255342

/ 8

capa_

Sabendo que os aspectos arquiteturais são fato-res que podem afetar todo um software em desen-volvimento, e não somente uma funcionalidade. Somando-se ao fato que a adoção de métodos de de-senvolvimento de software incrementais e ágeis está cada vez mais em alta. Tem resultado em uma preo-cupação, ou interesse, cada vez maior em como rea-lizar decisões arquiteturais de maneira incremental sem que elas afetem todo o software produzido até o momento.

De fato, se não encontrarmos maneiras de in-crementar e evoluir a arquitetura de forma fl exível, segura e controlada, nada adiantará entregar por-ções de softwares rápidos, pois em algum momento, necessidades funcionais ou não-funcionais poderão surgir e a arquitetura atual não suportará ser altera-da sem por em risco tudo que já foi desenvolvido. Ou seja, uma falha na arquitetura tornou-se agora uma falha de planejamento e entrega do projeto.

Além disso, durante muito tempo viemos recla-mando do problema em métodos tradicionais de se especifi car funcionalmente todo o software antes de desenvolvê-lo, gerando uma paralisia da análise, e resultando muitas vezes em atrasos dos projetos de desenvolvimento de software.

Entretanto, depois de vários esforços e técni-cas para sanar essa paralisia da análise, o problema agora é outro: a Paralisia Arquitetural. O problema

Veja como adotar, modelar e documentar a arquitetura, de maneira ágil, em seu processo de

desenvolvimento de software.

lArquitetura ági

Adotando

em seu processo de desenvolvimento de software

não é mais apenas as variações de escopo de um projeto, mas sim se a arquitetura para atender a primeira entrega será sufi cientemente fl exível para atender eventuais necessidades na décima entrega, por exemplo, sem gerar grandes retrabalhos ou até mesmo refazer todo o software. Com isto, as pessoas responsáveis pela arquitetura do projeto fi cam cada vez mais inseguras se o que elas projetaram já está apto para o desenvolvimento da primeira entrega.

Diante disto, se o método de desenvolvimento de software é ágil, por que a atividade de defi nição da arquitetura deverá ser feita toda no início do projeto, e não de forma incremental?

Este artigo faz uma breve introdução sobre a Arquitetura Ágil e suas infl uências. Logo após serão apresentadas práticas de como aplicar a arquitetura ágil em processo de desenvolvimento de software, qual é o papel do arquiteto ágil, modelagem e docu-mentação ágil.

As Infl uências da Arquitetura ÁgilEm métodos tradicionais, as atividades relacio-

nadas à arquitetura gastam um tempo signifi cativo no início do projeto para a defi nição da visão arqui-tetural, apenas do ponto de vista técnico. E, uma vez que esta visão tenha sido estabelecida, as equipes tendem a resistir às mudanças arquiteturais durante todo o ciclo de vida do projeto, muitas vezes, por não

9 \

As práticas de arquiteturas ágeis visam permitir a evolução do sof-tware de forma objetiva, rápida, controlada e colaborativa para ga-rantir a entrega de softwares que atendam as variações de negócios cada vez mais frequentes. Este artigo visa apresentar os conceitos, infl uências, atividades e estratégia para adoção, modelagem e do-cumentação de arquiteturas ágeis.

Breno Barros | [email protected] | @brenoobarrosLíder do Escritório de Arquitetura e Métodos Ágeis e do Centro de Excelência SOA da Stefanini IT Solutions. Além disso, atua na evangelização e desenvolvimento de práticas ágeis e Lean nos processos de desenvolvimento de novos produtos

para start-ups de tecnologia.

que diz: “várias cabeças pensam melhor que uma”). Ou seja, a arquitetura ágil foca em dar Autonomia para a Equipe participar das decisões arquiteturais, como forma de melhorar não só as soluções técnicas, mas também a comunicação e Amplifi car o Aprendi-zado, visando com isto Entregas mais Rápidas e uma Construção com maior Integridade, para gerar valor aos clientes.

Sendo assim, a arquitetura ágil é uma técnica, ou conjunto de práticas, que uma equipe de desen-volvimento usa para criar um sistema de software. Ela envolve a tomada de decisões sólidas e oportu-nas em todo o ciclo de desenvolvimento de software. Em alguns casos, isso signifi ca admitir que um erro arquitetural ocorreu e que a equipe precisa modifi car a arquitetura.

Adotando uma Arquitetura Ágil de um Processo de Desenvolvimento

Até aqui, vimos o quão importante é a arquite-tura de software independentemente se o método de desenvolvimento adotado é ágil ou não. Assim, sem radicalismos, a decisão se a arquitetura de sof-tware será realizada totalmente antes da codifi cação do projeto ou se ela vai evoluir incrementalmente, é uma decisão particular de cada equipe.

Entretanto, para uma adoção real e cautelo-sa das práticas de arquitetura ágil, é recomendado uma adaptação entre os dois mundos: investir um pouco de tempo no início do projeto (comumente denominado de fase de Inception) para pensar nas “grandes necessidades”, e abordar os detalhes e to-mar as decisões mais assertivas no momento certo (just-in-time) e responsável, ao longo das iterações (ou sprints) do projeto.

A fi gura 1 ilustra bem a estratégia recomenda-da. No início do projeto, ou fase Inception, após o Product Owner já ter levantado um conjunto de re-quisitos do sistema em um nível abstrato (mas que

entenderem que o cenário de negócio que aquele software é destinado mudou de um mês para o outro. E acreditem, em um mundo cada vez mais dinâmico e globalizado, isso já é comum e esperado.

Não precisamos ir muito longe, imaginando modelos de negócios tão complexos, para acreditar nesse fato. Basta olharmos para as diversas start-ups tecnológicas que surgem a todo momento. Muitas das vezes, as mudanças de negócios neste segmen-to não mudam de um mês para o outro, mas sim de um dia para outro. E neste e em outros cenários, a arquitetura de software deve ser capaz de evoluir ou ser modifi cada a qualquer momento sem grandes impactos.

É neste contexto dinâmico que a arquitetura ágil nasceu e vem ganhando cada vez mais força. Pois não devemos olhar a arquitetura como um aspecto técnico de um projeto de software, mas olhá-la como um componente crucial para a entrega de valor aos envolvidos no projeto.

E por falarmos em desenvolvimento de software, valor de negócio, fl exibilidade e entregas frequentes, nos vêm em mente um pensamento que vem cada vez mais infl uenciando a engenharia de softwa-re de ponta-a-ponta: o pensamento Lean, ou Lean Thinking. E não é à toa que o conceito de arquitetura ágil tem em seus pilares os princípios Lean.

Assim, a arquitetura ágil visa Eliminar o Des-perdício de tempo para a defi nição da arquitetura de software, buscando Tomar a Decisão o mais Tarde Possível, pois quanto mais tempo você adiar suas de-cisões, mais contextualizadas e assertivas elas serão. Porém isso não implica em não visualizar o Todo, mas sim que devemos estar diariamente antenados para tudo que está ocorrendo no projeto, inclusive o cenário de negócio que se destina, considerando fu-turas mudanças (e certamente elas vão ocorrer), para que quando elas surgirem, não surpreendam.

Além disso, a arquitetura ágil foca no poder de decisões em conjunto (recordando o velho provérbio

/ 14

capa_

Arquitetura de software é um conceito difícil de ser defi nido, porém existe consenso a respeito de algumas características. A arquitetura de software envolve uma representação abstrata dos tipos de componentes de um software, de forma a defi nir a responsabilidade de cada um e como eles devem interagir. As decisões arquiteturais são aquelas que afetam todo o software e não somente uma funcio-nalidade ou pedaço.

uma das difi culdades de se lidar com arquitetu-ra de software em ambientes ágeis é que ela envol-ve decisões que afetam todo software. Sendo assim, pode ser muito arriscado ignorar certas questões ar-quiteturais em iterações iniciais, pois isso pode ge-rar um grande retrabalho depois. Diz-se que se deve lidar com essas decisões arquiteturais no “último momento responsável”, porém esse momento não é algo trivial de se descobrir. Daí surgem questões como: será que implementando certa funcionalidade da arquitetura antes das outras, não estou criando um design complexo demais antes da hora? Será que deixando uma funcionalidade para depois não vou gerar muito retrabalho? Será que trabalhando na ar-quitetura antes do código não estou deixando de ser ágil?

Este artigo não tem o objetivo de apresentar uma solução defi nitiva para se lidar com arquitetura em ambientes ágeis, mas apresentar várias práticas que podem ser utilizadas para se lidar com decisões arquiteturais. Elas podem ser utilizadas todas jun-tas ou de forma individual. O fato de existirem essas práticas não signifi ca também que elas representam a única forma de se lidar com arquitetura ágil. Essas

práticas foram tiradas de experiências pessoais, de relatos de experiências e de conversas informais com amigos que já passaram por essas experiências. Sen-do assim, representam práticas do mundo real e não apenas teorias que ninguém nunca experimentou.

As seções seguintes apresentam seis práticas para arquitetura ágil e na última seção é mostrado como elas se relacionam e podem ser combinadas. Curioso para saber quais são as práticas? Então siga para a próxima seção!

Arquitetura Cartoonuma das questões que envolvem a defi nição

da arquitetura em um projeto ágil é o medo de se empregar muito esforço no desenho da arquitetura antes do projeto começar. Por outro lado, também é arriscado pular direto para o código sem pensar nas principais questões relacionadas com os requi-sitos não-funcionais, pois isso pode gerar um risco de retrabalho grande no futuro. Outro problema é os desenvolvedores começarem cada um a desen-volver sua própria solução para o mesmo problema na aplicação. Mesmo que o código em si não esteja duplicado, isso diminui o reúso de código dentro da arquitetura e a torna heterogênea.

Mas qual é o limite? Será que é possível traba-lhar em cima da arquitetura sem gastar muito tem-po gerando documentações write-only (ou seja, que ninguém vai ler)? Como defi nir soluções arquitetu-rais para lidar com os principais requisitos não-fun-cionais e compartilhar esse entendimento com toda a equipe?

uma prática que lida com esse problema é a cha-

Arquitetura em Práticas para Lidar com

Aprenda as principais práticas que são utilizadas para lidar com arquitetura em empresas que adotaram com sucesso o uso de métodos ágeis.

ambientes ágeis

15 \

Um dos principais focos do desenvolvimento ágil é a criação de um código de qualidade. Práticas comuns como TDD e refatoração pos-suem um impacto grande em porções locais do código, porém mui-tas vezes não atingem o objetivo de manter uma consistência na arquitetura de toda a aplicação. Desse fato, surgem questões a res-peito do quanto se deve dedicar ao projeto da arquitetura antes da implementação ou quando se deve trabalhar em refatorações para adequar requisitos não-funcionais. O objetivo deste artigo é apre-sentar algumas práticas para lidar com arquiteturas ágeis.

Eduardo Guerra | [email protected] | @emguerraDesenvolvedor de frameworks, pesquisador em design de software, editor-chefe da revista MundoJ e professor do ITA, onde

concluiu sua graduação, mestrado e doutorado. Possui diversas certificações da plataforma Java e experiência como arquiteto de software nas plataformas Java SE, Java EE e Java ME. Participou de projetos open-source, como SwingBean e JColtrane, e acredita

que um bom software se faz mais com criatividade do que com código.

mada Arquitetura Cartoon. A Arquitetura Cartoon é uma prática na qual o arquiteto faz um desenho livre para a representação dos principais elementos da arquitetura. Além dos tradicionais retângulos, bolinhas e setinhas, também é recomendável inserir figuras ou ícones que representam de maneira mais visual os elementos e componentes definidos na ar-quitetura.

» Para que fique mais claro como esse tipo de re-presentação pode ser feito, vamos a um exem-plo. Imagine uma aplicação em que o usuário precise solicitar relatórios que são gerados a partir de uma base de dados. Devido à comple-xidade dos relatórios e ao tamanho da base de dados, cada relatório pode demorar um tempo para ser processado. Nesse cenário, a partir de conversas com os clientes, foram identificados os seguintes requisitos:

» O usuário não deve ficar aguardando para sa-ber se sua requisição de relatório está sendo processada.

» uma quantidade grande de relatórios comple-xos rodando ao mesmo tempo pode derrubar o servidor, então a arquitetura deve ter uma forma de controlar sua carga máxima de pro-cessamento simultâneo.

» Relatórios gerados para um usuário devem po-der ser recuperados novamente sem a necessi-dade de um novo processamento.

Depois de pensar a respeito de como resolveria o problema, foi feito o desenho apresentado na figura 1. Acho que nesse ponto vale a pena ressaltar que demorei menos de 30 minutos para fazer esse dese-

nho. O investimento em tempo para a criação dessa arquitetura cartoon não deve ser muito grande. uma alternativa seria fazer esse desenho em um quadro branco, por exemplo. O ideal é tentar representar tudo que for mais importante em um único desenho, porém nada impede que sejam feitos dois ou três de-senhos com focos em diferentes questões. Ressalto aqui que quanto maior for a quantidade de desenhos e detalhes relativos a questões marginais, menos atenção será dada ao que realmente é importante.

A arquitetura cartoon deve focar nas soluções para os requisitos não-funcionais mais importantes para a arquitetura em questão. No exemplo apresen-tado, o foco foi em como os relatórios serão proces-sados, armazenados e recuperados. O desenho não representa, por exemplo, como irá funcionar a se-gurança dentro da aplicação. O motivo é que isso não foi considerado um requisi-to crítico no mo-mento e que não oferece muito risco de ser imple-mentado depois. Em um sistema em que a assinatura digital de documentos fosse um requi-sito importante, por exemplo, certamente a represen-tação da arquitetura incluiria algo a res-peito.

Note também que

/ 22

tar o que realmente é necessário para ela, evitando a inserção de coisas que nunca serão utilizadas. Deve--se trabalhar nas funcionalidades da arquitetura fo-cando sempre em necessidades concretas e que são necessárias agora, como para user Stories da iteração atual. Isso evita especulações do tipo: ”Pode ser que um dia iremos precisar disso!”.

Por outro lado, é importante criar esses serviços arquiteturais de forma fácil de serem reutilizados em outras funcionalidades. vamos ilustrar usando como exemplo uma funcionalidade de controle de acesso. Imagine que na terceira iteração de um projeto deci-de-se implementar o controle de acesso em uma fun-cionalidade onde isso é muito importante. Na ideia de fazer tudo da forma mais simples, isso poderia ser incluído como uma verificação “hard-coded” no meio do corpo de um método. Porém todos sabem que o

Como embutir novos requisitos não-funcionais em uma arquiteturaMuitos dos requisitos não-funcionais representam características transversais do sistema, ou seja,

que cortam toda a estrutura de funcionalidades. Isso significa que essas questões, como segurança e logging, estarão presentes em diversas funcionalidades do sistema. Para permitir que a implementação dos interesses transversais possa acontecer em paralelo e de forma independente das funcionalidades, eles devem estar de alguma forma modularizados.

um padrão de projeto que nos permite adicionar novas funcionalidades em uma classe existente é o Decorator. Essa classe funciona como um proxy que implementa a mesma interface da própria classe. O Decorator delega a funcionalidade para a classe original, executando a funcionalidade adicional antes ou depois. Se as classes que quer encapsular possuem todas a mesma interface, a simples aplicação do padrão deve ser suficiente, porém quando existem várias interfaces é necessário apelar à API de refle-xão e utilizar proxys dinâmicos. De qualquer forma, para essa estratégia poder ser utilizada, é preciso ter controle do momento da criação do objeto, como com o uso de uma fábrica, por exemplo. Assim é possível retornar o objeto encapsulado com o Decorator, ao invés do objeto original!

Outra alternativa nesses casos é o uso de componentes que podem interceptar a execução de fun-cionalidades. A API de Servlets, por exemplo, possui os Servlet Filters que podem ser utilizados pata executar funcionalidades antes ou depois de uma requisição HTTP ao servidor. O EJB a partir da versão 3 e a nova API CDI possuem os interceptores, que podem ser utilizados pata interceptar a chamada de métodos nas classes controladas pelo container. Até mesmo a API de persistência como o JPA possui os listeners, que são chamados quando determinados eventos ocorrem no momento de persistir ou recuperar uma entidade. Todos esses componentes podem ser utilizados para adicionar novos compor-tamentos em funcionalidades já existentes de forma transparente.

Finalmente, não poderia me esquecer dos aspectos que foram tema dos meus dois últimos artigos para a revista! Eles são módulos cujo objetivo é justamente a separação desses interesses transversais do resto da aplicação. Se sua aplicação utiliza o Spring, ele já vem preparado para o uso de aspectos em tempo de execução a partir da sintaxe de anotações do AspectJ. Se você não o utiliza, o AspectJ ainda permite que os aspectos sejam combinados com a aplicação no momento da compilação ou do carrega-mento das classes pela máquina virtual.

Muitas vezes essas funcionalidades precisam ser configuradas e são ligeiramente diferentes para cada método invocado. No caso do controle de acesso, é preciso ter a informação de quem tem autori-zação para aquela funcionalidade. Nesse caso, uma saída é utilizar metadados adicionais na forma de anotações, armazenados em documentos xML ou no banco de dados. A partir dessas configurações, esses módulos têm informação suficiente para saber como a funcionalidade precisará ser executada.

Independentemente da estratégia utilizada, a separação desses interesses deixa o código funcional mais limpo, mais fácil de ser reutilizado e mais fácil de ser testado. Além disso, isso permite a evolução dos serviços da arquitetura de forma independente do resto da aplicação.

Figura 2. Representação da Landing Zone.

LANDING ZONE

mínimo alvo excelente

23 \

controle de acesso precisará ser feito para todas as funcionalidades e essa abordagem geraria retrabalho. Sendo assim, apesar do foco ser nos requisitos atuais de controle de acesso, deve-se tentar isolar a funcio-nalidade de forma a ela poder ser reutilizada. Nesse caso, poderia ser utilizado algo como um filtro, um interceptor ou um proxy (ver quadro).

um serviço da arquitetura não precisa em sua primeira versão já atender a todos os requisitos pos-síveis. Ele pode começar simples, atendendo a neces-sidade do momento, e depois ir evoluindo aos poucos. O isolamento dessa funcionalidade do resto da apli-cação permite que ela possa ser alterada facilmente, sem impactar em diversos pontos do código, caracte-rizando o bad smell Shotgun Surgery. O uso de testes de unidade ajuda a garantir que o caso mais simples continua funcionando com adição de comportamen-tos mais sofisticados.

É preciso deixar bem claro que deixar espaço para que os requisitos não-funcionais possam ser incluí-dos e evoluídos aos poucos é diferente de querer im-plementar todos de uma vez. Quando a arquitetura é elaborada, é importante que já exista um plano de como essas novas funcionalidades que afetam toda aplicação poderão ser incluídas de forma isolada da parte funcional do sistema. É diferente criar um de-sign especulativo, no qual se inclui coisas que não sabe se eram necessárias, de um design responsável, na qual se inclui pontos de extensão nos locais certos para que a arquitetura tenha a capacidade de evoluir. Saber a diferença entre os dois não é algo trivial e que vem da experiência dos membros do time. Felizmen-te errar é permitido e a refatoração existe exatamente para eliminar soluções desnecessárias e evoluir para novas estruturas.

Landing Zones (ou Zonas de Pouso)Existem algumas características não-funcionais

de uma arquitetura que não são serviços que cada uma das funcionalidades irá utilizar. um bom exem-plo é desempenho! O tempo que um software demora para ser executado ou a memória que ele consome são consequência de sua implementação como um todo. Outras características não-funcionais como carga, disponibilidade e, em alguns casos, portabi-lidade também entram nessa categoria. Quando se deve trabalhar na arquitetura da aplicação para me-lhorar uma dessas características? Quando ela está boa o suficiente?

Tive a oportunidade de assistir a palestra da Re-becca Wirfs-Brock no Agile Portugal deste ano, onde ela introduziu um conceito muito interessante cha-mado Landing Zone (ou Zona de Pouso). Em um sof-tware complexo, é complicado definir que um valor ótimo para uma determinada característica, princi-palmente porque muitas vezes é preciso balancear

essas características para se chegar a um resultado adequado. A Landing Zone é um intervalo de uma característica mensurável do sistema apresentando quais são os valores aceitáveis para o mesmo. A figura 2 apresenta graficamente a ideia do conceito.

uma forma simples de se representar uma Lan-ding Zone é através de uma tabela. Essa tabela possui uma coluna contendo o valor alvo para o valor da me-dição, um valor que seria excepcional e outra que se-ria o limite mínimo aceitável. Cada linha representa um atributo diferente que deve ser medido. No caso de valores que precisam ser medidos manualmente, o ideal é que a medição seja feita pelo menos uma vez ao final de cada iteração, para saber se será necessá-rio incluir alguma atividade de ajuste da arquitetura para a iteração seguinte. Algumas medições podem ser obtidas de forma automatizada, de forma a pode-rem ser executadas em períodos mais curtos, como a cada dia ou a cada build.

A tabela 1 apresenta um exemplo de uma tabela com a definição de Landing Zones para o desempe-nho e a carga do sistema de relatórios que está sen-do usado como exemplo neste artigo. Note que nem sempre o valor alvo fica exatamente na metade entre o limite e o excelente. No caso do tempo de proces-samento de um relatório o valor alvo é 30 minutos, sendo que o valor limite é o dobro, uma hora. O valor excelente já é de um minuto, 30 vezes menos do que o alvo. Em alguns casos, pode ser que o valor limite e o alvo sejam iguais, caracterizando uma situação em que o valor desejado já é o limite mínimo para aquele atributo.

Landing Zones para Qualidade de Código

Requisitos para qualidade de código são atributos que podem ser incluídos dentro de uma Landing Zone do sistema. A partir de-les pode-se monitorar continuamente como anda a qualidade de cada módulo do sistema e realizar as devidas refatorações quando ne-cessário. Ferramentas para inspeção e con-trole contínuo de código que podem ser uti-lizadas para o controle desse tipo de Landing Zone. um bom exemplo de uma ferramenta desse tipo é o Sonar, que foi apresentado com maiores detalhes no artigo “Evoluindo Design e Arquitetura Através das Métricas do Sonar” da edição 43 da revista.

/ 26

necessários testes funcionais ou testes de integração. uma refatoração que adicionar uma nova camada precisará que os testes funcionais validem se o com-portamento geral da aplicação permaneceu o mesmo. Em outra refatoração que alterar a relação entre dois subsistemas, um teste de integração entre eles se-ria suficiente. O tipo de teste utilizado vai depender muito do escopo da refatoração.

Da mesma forma que a refatoração de código, a

refatoração arquitetural deve ser feita em pequenos incrementos. Por esse motivo, ela acaba muitas vezes sendo composta por diversas refatorações de código mais granulares até se chegar a um resultado com impacto em toda a aplicação. A cada pequeno passo da refatoração é importante rodar os testes para se certificar que até aquele ponto o comportamento se manteve.

As Landing Zones podem ser utilizadas para de-tectar a necessidade de refatorações arquiteturais. Quando um atributo do sistema começa a sair dos li-mites do Landing Zone, é o momento de planejar uma atividade para fazer com que o valor daquele atributo volte para um valor desejável. Quando se utiliza mé-todos ágeis, deve-se procurar focar em questões que são importantes para o cliente e que agregam valor para o negócio. Teoricamente, ficar trabalhando na melhoria do desempenho da aplicação é algo que a princípio não agrega valor ao negócio e por isso essas atividades arquiteturais muitas vezes são deixadas de lado. Com as Landing Zones é possível detectar o mo-mento em que aquele esforço para melhorar uma ca-racterística não-funcional do sistema realmente tem um impacto significativo para o negócio.

As refatorações arquiteturais também podem for-necer um feedback em relação as Landing Zones de-finidas. Ao se refatorar a arquitetura, muitas vezes se faz trocas que melhoram um atributo e pioram outro. A criação de um cache, por exemplo, pode melhorar o tempo de resposta, mas irá aumentar a quantida-de de memória consumida. A partir das refatorações, pode-se ir repensando os limites dos Landing Zones definidos, negociando com o cliente de acordo com a importância de cada requisito para o negócio.

Nem sempre a alteração de uma característica geral da arquitetura demanda alterações em várias funcionalidades. Se aquela característica estiver de-sacoplada e isolada, muitas vezes a mudança pode alterar apenas uma parte específica, apesar dela ser executada a cada funcionalidade. Imagine que no exemplo dos relatórios a fila de processamento seja a princípio implementada com acesso ao banco de da-dos, mas os Landing Zones mostram que essa solução não atende o processamento mínimo de relatórios. uma refatoração arquitetural nesse caso seria utilizar JMS e um servidor de mensagens para implementar a fila e distribuir o processamento dos relatórios. Se o acesso a fila estiver isolado do código funcional, isso poderá ser feito sem precisar mexer em vários locais da aplicação.

Juntando as práticasAs práticas apresentadas neste artigo podem ser

utilizadas de forma independente uma das outras. Podem também existir outras práticas que comple-mentem ou possam substituir as práticas apresen-Figura 3. Relacionamentos entre as práticas apresentadas.

para saber mais/

ARQUITETURA CARTOON

ARQUITETURA DE TESTES

USE STORY EXPERIMENTAL

REQUISITOS NÃO-FUNCIONAIS

INCREMENTAIS

LANDING ZONES

REAFATORAÇÕES ARQUITETURAIS

para criar referência de como a arquitetura será testada

para validar decisões e definir arquitetura concreta

para verificar testabilidade da arquitetura

para inserir questões não-funcionais

incrementalmente

para ajustar atributos

para monitorar atributos não-funcionais

para ajustar limites da Landing Zone

para definir uma arquitetura abstrata

27 \

tadas. Cabe a cada equipe decidir o que utilizar ou não! Porém, as práticas apresentadas neste artigo apresentam certa sinergia quando utilizadas juntas, de forma que cada uma deve ser empregada em uma fase diferente e para um propósito diferente. Dessa forma, elas se complementam criando um método de lidar com arquitetura dentro de um projeto ágil de software.

A figura 3 apresenta um mapa que mostra como as práticas se relacionam. A Arquitetura Cartoon é o primeiro passo, na qual se cria um esboço da ar-quitetura focando nos principais requisitos não-fun-cionais e de uma forma que facilite a comunicação com a equipe e com os clientes. Depois dessa fase, entra a definição da arquitetura mais concreta com a implementação de uma user Story Experimental, em paralelo com a Arquitetura de Testes. A criação dos serviços da arquitetura vai então ocorrendo de for-ma incremental com os Requisitos Não-Funcionais Incrementais. Durante o projeto é feito um acompa-nhamento dos atributos mais importantes do siste-ma através das Landing Zones. Quando um limite da Landing Zone é ultrapassado, isso motiva uma Refa-toração Arquitetural para a correção. Através das re-fatorações pode-se ter uma noção mais realista dos requisitos do sistema e as Landing Zones também podem ser ajustadas.

Considerações finaisEste artigo apresentou seis práticas para lidar

com a definição, projeto e evolução da arquitetura de um software em um ambiente ágil. Apesar das prá-ticas poderem ser utilizadas de forma independente, foi também apresentado como elas possuem papéis complementares desde a definição até a evolução de uma arquitetura. Em cada prática, foram apresenta-dos quadros com algumas dicas a respeito de como elas podem ser implementadas ou com casos reais que podem servir como referência.

É importante ressaltar que, assim como o xP e o Scrum, essas práticas servem como um ponto de par-tida e que, depois de implementadas, a equipe deve evoluir de acordo com as necessidades e seu modo de trabalho. Lembre-se que “se você tem um processo ágil hoje igual ao de um ano atrás, então você não deve ser mais tão ágil”, então não se prenda a nomes e práticas, assumindo a responsabilidade do seu pro-cesso de desenvolvimento.

> Paul Clements, Felix Bachmann, Len Bass, David Garlan,

James Ivers, Paulo Merson, Reed Little, Robert Nord, Judith

Stafford. Documenting Software Architectures: Views and

Beyond, Segunda Edição.

> The Responsible Designer — “Don’t you want to take

responsibility for your designs?” — Rebecca Wirfs-Brock —

http://wirfs-brock.com/blog/

> Agilcast — “O podcast da AgilCoop!” — Episódio 12

— Arquitetura Ágil (parte 1) — http://ccsl.ime.usp.br/

agilcoop/files/Agilcast12-Arquitetura%20Agil%20-%20

parte%201.mp3 e Episódio 13 — Arquitetura Ágil (parte

2) — http://ccsl.ime.usp.br/agilcoop/files/Agilcast13-

Arquitetura%20Agil-parte2.mp3

> Gerard Meszaros. xUnit Test Patterns: Refactoring Test

Code

> Framework MakeATest – https://github.com/

marcusfloriano/makeatest-core

> Kerievsky, Joshua. Refactoring to Patterns.

> Muller, Gerrit. From Legacy to State-of-the-art;

Architectural Refactoring.

> Stal, Michael. Software Architecture Refactoring. http://

www.sigs.de/download/oop_08/Stal%20Mi3-4.pdf

> Michael Stal on Architecture Refactoring — http://

www.infoq.com/interviews/michael-stal-on-architecture-

refactoring

/referências

Na edição 19, o artigo “Reflexão + Anotações — Uma

Combinação Explosiva” apresenta situações em que a

reflexão e anotações podem ser utilizadas para criar

soluções mais inteligentes.

A edição 32, o artigo “Proxys Estáticos e Dinâmicos”

mostrou as técnicas que podem ser utilizadas para

adicionar funcionalidades nas classes através de proxys.

A sequência de artigos nas edições 23, 24 e 26, chamados

respectivamente “Testes Unitários para Camadas de

Negócios no Mundo Real”, “Testes de Unidade para

Camadas de Persistência no Mundo Real” e “Testes de

Unidade para Camadas de Apresentação no Mundo

Real” apresentam técnicas para criar testes de unidade

para diversas camadas de uma aplicação. Eles podem ser

utilizados como referência para a criação da arquitetura

de testes.

Nas edições 46 e 48, os artigos “Programação Orientada

a Aspectos para Leigos” e “Explorando Funcionalidades

de Static Crosscutting do AspectJ” apresentaram o uso

de aspectos que podem ser utilizados para uma evolução

transparente da arquitetura.

para saber mais/

/ 28

capa_

Hoje em dia os bons desenvolvedores já se convenceram de que têm que desenvolver testes unitários para as classes que desenvolvem. Entretanto, a pergunta é: estes testes são efi cazes? Eles realmente verifi cam todas as partes do seu código? Neste artigo vamos discutir uma ferramenta que analisa esta cobertura e ideias para fazer com que seus testes realmente analisem todo o seu código.

O conceito de testar seu código através de testes unitários já está bem disseminado entre os bons

desenvolvedores. Todos concordam que os testes unitários fornecem uma garantia de que todo código desenvolvido funciona como esperado, permitindo a refatoração de seus sistemas sem a perda do com-portamento correto.

Os conceitos colocados no parágrafo anterior in-dependem do fato de você usar desenvolvimento ba-seado em testes (veja o quadro). Eles simplesmente dizem que se você é um bom profi ssional, então você se preocupa com o produto que você entrega e deseja que ele satisfaça os requisitos de seu cliente. A única maneira de ter certeza de que seu sistema efetiva-mente vai fazer o que se espera dele é testando – e os testes unitários são uma parte muito importante do seu código.

Como todos os textos que falam de metodologia ágil nesta edição vão deixar claro, refatorar código é uma necessidade real. Nós vamos falar um pouco mais sobre isto na seção intitulada “Cobertura e Re-fatoração”, neste artigo. Entretanto, neste momento é importante entender que não importa a sua quali-dade como programador, uma destas situações será realidade em sua vida:

» você vai precisar atender um novo requisito que sua estrutura de classes não previa;

» você precisará melhorar a clareza do código

gerado para facilitar a manutenção; » vai precisar aumentar a velocidade de seu có-

digo; » vai encontrar um usuário que faz uma sequên-

cia de ações absurda; » vai ver seu módulo ser ligado a outros que não

fazem testes nos parâmetros que lhe passam; » etc.uma vez que vai ter que enfrentar uma refato-

ração, temos que garantir que nosso sistema faz o que desejamos. Para isto, nós fazemos os testes. En-tretanto, uma pergunta muito importante se impõe: como você sabe que seus testes estão efetivamente testando todo o seu código? Como saber se todos os casos de uso estão efetivamente sendo verifi cados? Será que aquele seu “else” no fi nal do método está funcionando direito? Seus testes passam por ele?

As dúvidas colocadas acima são resolvidas pelo conceito de cobertura de testes. Cobertura é uma medida de abrangência dos testes, que pode ser me-dida através dos casos de uso ou das linhas de código desenvolvidas, isto é, podemos verifi car se estamos efetivamente testando todos os casos de uso ou se estamos testando todas as linhas de código de nosso sistema.

O objetivo deste texto é ajudá-lo a analisar o se-gundo tipo de cobertura, isto é, a cobertura de linhas de código desenvolvidas. Existem ferramentas auto-

Coberturade testes em um sistema

Gerenciando a

Como verifi car se todo o seu código está sendo verifi cado pelos seus testes? A resposta é: maximizando a cobertura de seu código pelos seus testes.

29 \

Ricardo Linden | [email protected]é formado em Engenharia de Computação pela PUC-RJ e tem doutorado em Engenharia Elétrica pela COPPE-UFRJ. Trabalha com Java há cerca de 8 anos, desenvolvendo aplicativos para o setor elétrico no CEPEL. Atualmente é professor da disciplina de progra-mação com qualidade na Faculdade Salesiana Maria Auxiliadora (FSMA), de Macaé-RJ e mantém um twitter sobre os conceitos de

conduta profissional (@BomProfissional).

máticas para realizar esta análise, que pode ser de grande valia para que você possa entregar para seus clientes sistemas mais confiáveis e com menos bugs.

Neste texto, teremos vários fragmentos de lista-gens. Por questões de espaço, não podemos colocar as classes completas. Entretanto, todos os códigos descritos neste artigo estão disponíveis no formato de projeto do Netbeans 6.9 no diretório http://www.algoritmosgeneticos.com.br/cobertura /index.html.

Instalando a ferramenta de análise de cobertura

uma das primeiras ferramentas gratuitas para verificação de cobertura de testes foi a Emma (http://emma.sourceforge.net/). Quando do seu desenvolvi-mento, as ferramentas existentes eram usualmente caríssimas, o que fazia com que a maioria dos usuá-rios não as utilizassem. Esta ferramenta foi incorpo-rada nas duas principais IDEs do mercado: o Netbe-ans e o Eclipse, podendo ser facilmente instalada e utilizada, de forma gratuita.

Para instalar esta ferramenta no Netbeans, vá ao Menu Ferramentas, opção Plugins e selecione a tab Plugins disponíveis. Neste momento, você deve ver uma tela similar à figura 1. Selecione então a opção Coverage e clique no botão Instalar. Responda a to-das as opções tradicionais de licença e você terá ins-talado o software. Simples e direto.

Podemos agora usar esta ferramenta para ana-lisar nossos testes. Para tanto, vamos começar com um pequeno exemplo para que você efetivamente entenda o problema que estamos enfrentando.

Figura 1. Tela vista quando selecionamos a opção de instalação de novos plugins no Netbeans.

Um exemplo simplesPara que nós possamos compreender o concei-

to efetivo de cobertura, vamos analisar o código da Listagem 1. Eu sei que o código é bastante “tosco” e merece uma refatoração extensiva1, mas ele serve para nossos propósitos.

Listagem 1. Um simples método que retorna uma string dizendo qual dos elementos passados como parâ-metro é o maior.

public class Utilidades { public String maior(int a, int b) { String retorno=””; if (a>b){ retorno=”O maior dos elementos é o primeiro (“+a+”)”; } else { retorno=”O maior dos elementos é o segundo (“+b+”)”; } return(retorno); }}

Como somos pessoas sérias, vamos criar um tes-te unitário para verificar este código. Podemos fazê--lo manualmente ou usando as ferramentas auto-máticas de nossa IDE. No caso do Netbeans, isto é conseguido clicando com o botão direito em cima do pacote de testes, selecionando a opção Novo e a sub--opção Testes para Classes Existentes. Depois de es-colhermos uma classe e clicarmos em finalizar, rece-beremos. Ao recebermos uma grande classe com os métodos para testar cada um dos métodos existentes representados por stubs. Assim, para criar um teste todo o código que o Netbeans colocou (afinal, é ape-nas um stub, código colocado para ocupar espaço) e substituir pelo código da Listagem 2.

Listagem 2. Um método de teste para o código desenvolvido na Listagem 1.

@Testpublic void testMaior() { System.out.println(“maior”); Utilidades instance = new Utilidades(); String result = instance.maior(3, 2); assertEquals(“O maior dos elementos é o primeiro (“+3+”)”, result);}

1 Fica como exercício para nossos leitores descobrir maneiras mais eficientes de implementá-lo. Aceito sugestões por e-mail!

/ 30

Clicamos então com o botão direito sobre a classe utilidadesTest e selecionamos a opção Executar Ar-quivo. Ao final, recebemos a maravilhosa tela vista na figura 2, e nos sentimos maravilhosamente bem por termos feito testes unitários para nosso software e sermos boas pessoas que entregarão um programa eficaz para nossos clientes.

Figura 2. Tela vista quando executamos nosso método de teste. Como podemos ver, nosso software passou no teste que fizemos. Isto quer dizer que ele sempre funciona?

Entretanto, a pergunta é: como saber se efetiva-mente testamos tudo que deveríamos?

A resposta a esta pergunta é muito simples: use a ferramenta de cobertura. Ela vai analisar se seu pro-grama foi testado de forma conveniente ou não (ao menos sobre termos testado todas as possibilidades existentes em nosso código).

vamos então verificar se nosso código está efe-tivamente testado. Para tanto, nós primeiramente clicamos no nome do projeto, selecionamos a opção Coverage e a subopção Activate Coverage Collection (vemos mais um exemplo de como a internaciona-lização de software pode ser difícil – apesar de meu Netbeans ser em português, as opções de análise de cobertura de código são todas em inglês).

O próximo passo é muito simples: rode todos seus testes. Para tanto, clique com o botão direito so-bre o seu projeto e clique na opção Teste. você verá que as mensagens de saída incluem um monte de linhas começando com “EMMA”, indicando que seu projeto está tendo sua cobertura analisada.

Ao final do processo, você verá um relatório de testes habitual. Entretanto, uma coisa terá mudado: se você abrir qualquer um dos seus códigos-fonte, você verá que partes dele estão pintadas de verde, como vemos na figura 3. Estas partes são aquelas que são executadas durante os testes. Aquelas que não estão pintadas não foram testadas e a pergunta que você deve fazer agora é: o que eu faço com elas?

Figura 3. Aparência de nosso código depois de executarmos nosso teste com a opção de análise de cobertura habilitada. Note que a opção do else não está com fundo verde, o que indica que ela não foi testada por nossos testes unitários.

Além da análise linha a linha, você pode obter um relatório sobre a cobertura de suas classes. Para tanto, selecione o seu projeto com o botão direito do mouse, escolha a opção coverage e a subopção Show Project Coverage Statistics. você verá um relatório si-milar ao mostrado na figura 4.

Neste relatório você verá o número de linhas co-bertas e não cobertas de cada classe do sistema, assim como uma percentagem de cobertura de cada classe. Na figura 4 podemos ver que a nossa classe utilidades tem apenas cinco de suas seis linhas cobertas.

Neste momento você deve estar com uma “gran-de coceira” para ampliar a cobertura de seus testes e fazer com que seus sistemas tenham 100% de cober-tura. O primeiro objetivo é nobre e deve ser realizado o mais rapidamente possível. O segundo é provavel-mente desnecessário, como discutiremos na seção “Nem sempre seja 100%”.

Antes de discutir estas questões, vamos avançar um pouco mais no conceito de ampliação da cober-tura de forma a melhorar a segurança que temos em relação à correção de nosso software.

Figura 4. Aparência do relatório de estatísticas de cobertura for-necido pela ferramenta do Netbeans. Note que nossa classe Main tem 0% de cobertura (o que é muito razoável, posto que não de-senvolvemos nenhum teste para ela) e a classe Utilidades tem uma linha descoberta, equivalente ao que vimos na figura 4, de forma mais detalhada.

Ampliando a coberturaA nossa reação imediata aos resultados relatados

na seção anterior é propor uma solução para o pro-blema que consiste em descobrir quais linhas não es-tão cobertas pelos testes e escrever novos testes que façam com que as testemos também. No caso do nos-so pequeno exemplo, temos o acréscimo mostrado na Listagem 3 para fazer os testes.

Listagem 3. Nossos testes com um acréscimo (marcado em negrito), de forma a testar as linhas que foram deixadas de fora na tentativa anterior.

@Testpublic void testMaior() { System.out.println(“maior”); Utilidades instance = new Utilidades(); String result = instance.maior(3, 2); assertEquals(“O maior dos elementos é o primeiro (“+3+”)”, result);

31 \

result = instance.maior(2, 3); assertEquals(“O maior dos elementos é o segundo (“+3+”)”, result);}

Rodando o teste da mesma maneira que fizemos na seção anterior, temos a felicidade de encontrar 100% de cobertura em nossa classe utilidades, como podemos ver na figura 5.

uma pergunta razoável é: será que obter 100% de cobertura resolve todos os nossos problemas? Será que isto é uma panaceia para seus problemas com-putacionais?

A resposta a esta pergunta é que apesar do ob-jetivo de obter 100% de cobertura das linhas de có-digo ser louvável e algo que efetivamente deve estar em nossos corações, ele não é o único objetivo que teremos quando escrevermos testes. Nós precisamos também procurar ter 100% de cobertura nos casos de uso de nosso sistema. Infelizmente, para isto, não existe uma ferramenta automática na nossa IDE para nos ajudar – nós temos que conversar com nossos clientes e ouvir o que eles têm a dizer para poder en-tender suas necessidades e desenvolver os casos de uso competentes. O box intitulado Desenvolvimento Iterativo discute um pouco como isto pode ser feito e como isto pode nos ajudar a ter softwares que efeti-vamente fazem o que o cliente quer (e ganharmos a fama merecida de bons profissionais).

No caso de nosso pequeno exemplo, nós es-quecemos de fazer um teste para o caso em que os dois números são iguais. Esta é uma situação legíti-

ma e relativamente comum em testes de elementos. Acrescentando o teste, nós veremos que chegaremos ao else de nosso método e obteremos o resultado de que o maior é o segundo elemento. Entretanto, é isto mesmo que nós queremos? Ou será que queremos uma mensagem indicando que os dois elementos são iguais?

Figura 5. Aparência do código e do relatório de estatísticas de co-bertura fornecido pela ferramenta do Netbeans depois de acres-centarmos o novo teste. A classe Main continua com 0% de cober-tura, mas a nossa classe Utilidades passou a ser 100% coberta.

Esta questão não é de correção de código, mas sim de verificação dos requisitos do usuário. Assim, você deve conversar com seu usuário antes, durante e depois da codificação, de forma a criar testes que

Desenvolvimento interativo O desenvolvimento iterativo consiste em criar nosso software através de um processo evolucionário

em que ele cresce através do incremento gradual. Cada iteração é determinada através do diálogo entre clientes e desenvolvedores (que muitas vezes trabalham juntos em um mesmo ambiente, em uma equipe multifuncional).

O Manifesto Ágil (Beck et al, 2001) promove este conceito através da preferência de interações entre os indivíduos envolvidos no projeto e a interação com o cliente de forma a transformar o desenvolvi-mento de software em algo participativo que gere um resultado mais satisfatório para o cliente final (o que deve ser garantido pela sua participação mais intensa no processo inteiro).

Entenda que o código que você desenvolve é apenas uma ferramenta para que as pessoas atinjam seus objetivos, tanto de negócio quanto pessoais. Assim, se você conversar com o destinatário final do seu aplicativo você terá mais chances de satisfazê-lo.

O desenvolvimento iterativo preconiza a maximização da conversa com os usuários, através da cria-ção de uma série de casos de uso a cada conversa. Estes casos serão entregues aos poucos, de forma a se-rem avaliados pelo cliente, de forma que ele perceba o desenvolvimento do sistema, avalie sua qualidade e participe bastante do processo de seu desenvolvimento.

Sistemas que são desenvolvidos de forma iterativa tendem a satisfazer mais os clientes e a diminuir a necessidade de alteração e manutenção dos produtos entregues. Não é uma panaceia para o processo de desenvolvimento, mas sim uma constatação do princípio de escutar as pessoas para entender quem elas são e o que elas precisam. Siga este princípio tanto na sua vida profissional quanto na privada e suas relações com as pessoas que o cercam serão mais satisfatórias.

/ 32

reflitam aquilo que o usuário efetivamente deseja. Lembre-se: não basta seu código compilar e exe-

cutar sem bugs – ele não estará correto senão satisfi-zer os requisitos estabelecidos pelo usuário, fazendo aquilo que ele deseja/necessita.

Bons testes A primeira questão importante é saber o que tes-

tar. Como estamos colocando neste artigo, seu pri-meiro objetivo é ampliar a cobertura tanto das linhas de código quanto dos casos de uso. Existem vários livros que podem lhe ajudar a definir seus testes de forma a verificar seu código de forma completa. um dos mais interessantes é (HuNT & THOMAS, 2004).

Os princípios fundamentais de um teste é basi-camente ele ser completo em relação aos casos de uso e as condições que seu código deve enfrentar. Lembre-se de que as ferramentas que colocamos aqui são voltadas para a análise da cobertura das linhas de código, mas que isto não é o objetivo único de seu código.

Para entender esta questão, vamos colocar uma situação hipotética em que temos um método que manipula um vetor de inteiros, como aquele que ve-mos na Listagem 4.

Listagem 4. Código que determina a diferença entre o maior e menor elemento de um vetor.

public static intdeterminaDiferencaEntreLimites(int[] v) { Arrays.sort(v); return v[v.length-1]-v[0];}

Se você criar uma série de testes que sejam ba-seados em um vetor grande (com 50 elementos, por exemplo), como aquele que mostramos na Listagem 5, você vai conseguir chegar a 100% de cobertura des-te método (verifique!). Entretanto, a pergunta é: você testou o seu método de forma adequada?

Listagem 5. Um teste que gera 100% de cobertura no método e que passa com sucesso. Será que ele testa nosso método completamente?

@Testpublic void testDiferenca() { System.out.println(“Diferenca”); int[] v=new int[50]; for(int i=0;i<50;i++) { v[i]=i; } int diferenca=Utilidades. determinaDiferencaEntreLimites(v); assertEquals(diferenca,49);}

A resposta é não. Para testar um vetor, existem

várias outras situações relevantes que devem ser tes-tadas. Por exemplo:

» O comportamento do método quando é passa-do um vetor igual a null ou vazio é o esperado? você pode ter definido que seu método lança uma exceção, retorna false ou não faz nada – isto é uma decisão que depende de sua equipe e seu processo de programação. Entretanto, seu código deve responder como esperado. No caso de nosso método simples teremos uma exceção em tempo de execução em ambos os casos. Será que era isto que desejávamos?

» O que acontece quando seu vetor tem apenas um elemento? No caso de nosso método, o re-torno é zero. Será que que queríamos zero mes-mo ou queríamos o valor do único elemento?

» Se passamos um vetor com dois elementos e tentamos pegar o último ou o primeiro, ele en-tende que não “tem nada entre eles”? Isto não se aplica ao nosso método, mas pode ser apli-cável a outras situações que você enfrente.

» um vetor verdadeiramente enorme (com um milhão de elementos, por exemplo) gera algum tipo de exceção em tempo de execução? Lem-bre-se apenas de que este último teste deve ser rodado de forma controlada, pois ele pode fazer o seu teste demorar demais e desmotivar sua equipe a rodar os testes frequentemente. En-tretanto, sua demora não minimiza sua impor-tância.

Esta lista não é exaustiva – é apenas uma mostra de que você deve analisar com cuidado todas as situ-ações que cada um dos seus métodos vai enfrentar. Lembre-se de que a ferramenta de cobertura é muito importante, mas não é o seu objetivo único e/ou úl-timo.

Entretanto, existe outra questão importante a ser considerada. uma vez que tenhamos tudo testado, ainda assim não necessariamente nós temos testes que podem ser efetivamente considerados como sen-do de alta qualidade. Existe uma noção equivocada de que os testes devem simplesmente rodar, não tendo qualquer requisito maior de legibilidade ou qualidade – o importante é o teste funcionar.

Os testes são uma importante ferramenta de do-cumentação do seu código. Tendo em vista que seu código deve ser testado para todos os comportamen-tos que vai enfrentar e pressupondo que você seja um desenvolvedor disciplinado, então todas as pessoas que encararem seu código poderão olhar para seu conjunto de testes e ver o que seu código efetivamen-te faz.

Este comportamento deveria ser esperado da do-cumentação, como os javadocs e outros documentos escritos. Entretanto, todos aqueles que trabalham com código há algum tempo sabem que existe uma

33 \

grande tendência a deixar a documentação desatua-lizada. Infelizmente, esta é uma realidade inexorável que permeia todos os projetos – as pessoas tendem a considerar que os processos de documentação são

menos relevantes. Assim, você pode contribuir

para a melhor compreensão do seu código fazendo

testes que sejam fa-cilmente compreen-síveis pelos leito-res. Martin (2008) oferece uma série de normas simples para guiar seu de-senvolvimento de testes que, se se-guidos, farão com que os testes (que você já desenvol-verá para garantir o funcionamen-to do seu código) sejam uma ferra-menta de autodo-cumentação.

Basicamente o conceito é tratar os seus testes da mes-ma maneira que você trata seu có-

digo – procure ma-ximizar a legibilidade

seguindo os mesmos princípios que guiam

seu desenvolvimento. Da mesma maneira que seu

código, os seus testes tam-bém deverão ser lidos e alte-

rados no futuro.Entenda, é claro, que estes

princípios não são algo “inque-brantável” – se você os segui-los existe a tendência de que seus testes sejam mais legíveis, mas é possível fazer testes legíveis sem segui-los. Por exemplo, um dos princípios que Martin aponta é o princípio da responsabilidade única (SRP). Entretanto, eu acho muito chato desenvolver 20 ou 30 métodos diferentes para testar um único método. Assim, tenho a tendência a colocar todos os tes-

tes em um único método e co-locar uma linha de comentá-

rio explicitando a função de cada pedaço. Não segue os princípios consagrados pela comunidade mundial, mas funciona adequadamente para mim e para as equipes com as quais trabalho.

Não digo que eu sou um exemplo de conduta para todos os desenvolvedores, mas sim que você deve ler todos os livros e artigos sobre metodologia e adaptá--los à realidade em que você está inserido. Lembre-se apenas de seguir dois conceitos fundamentais: teste tudo de forma completa e faça testes bastante com-preensíveis e manuteníveis.

Nem sempre seja 100%A primeira ideia de qualquer desenvolvedor é que

temos que desenvolver nossos testes com o objetivo de ter 100% de cobertura do nosso código. Entretan-to, este número raramente será atingido (eu, pessoal-mente, nunca o atingi, apesar de buscar desenvolver o máximo de testes para garantir a qualidade dos sis-temas que desenvolvo). Existem vários motivos para que isto aconteça.

Primeiramente, você não vai testar código que seja gerado automaticamente pela sua IDE. Por exemplo, ao desenhar uma tela no Netbeans, uma centena ou mais de linhas podem ser geradas. Apesar de teoricamente precisarmos testar isto, na prática é raro que uma IDE que está sendo usada por milhões de pessoas no mundo inteiro gere um erro em ques-tões tão simples. Assim, se você não as testar, você não estará cometendo nenhum pecado capital.

Em segundo lugar, certos métodos triviais, como métodos get que não têm código nenhum além de um return não necessitam ser testados em um primeiro momento. É claro que as melhores práticas sugerem que você os teste também, pois amanhã eles podem deixar de ser algo trivial e aí você já estará coberto. Entretanto, na prática, muitos desenvolvedores os deixam de lado.

Certos trechos de códigos, como os de interface gráfica ou de comunicação com a rede, só podem ser testados com ferramentas específicas. Neste caso, não necessariamente sua ferramenta de cobertura se integrará com o objeto mock ou qualquer outro arte-fato que usar. Assim, mesmo que você efetue o teste sua ferramenta de cobertura pode não aumentar o número de linhas cobertas.

O ponto fundamental é: seu objetivo deve ser uma aplicação que funciona e é confiável. Não se con-centre na metodologia ou na ferramenta – elas são apenas instrumentos para que você entregue algo que vai efetivamente ajudar o usuário final a atingir seus objetivos (pessoais ou de negócio).

Cobertura e refatoraçãoRefatoração é definida no site de Martin Fowler

(http://refactoring.com/) como uma técnica para rees-

/ 34

truturar um corpo de código existente alterando sua estrutura interna sem alterar seu comportamento ex-terno. Apesar desta frase ser complicada, ela basica-mente quer dizer que nós vamos melhorar o código existente sem causar qualquer mudança no seu fun-cionamento.

Até mesmo os principais evangelizadores do có-digo limpo, como Robert Martin, preconizam que sua primeira preocupação é fazer o seu código funcionar. Depois, você deve preocupar-se com conceitos como legibilidade e manutenibilidade. Para isto é que pre-cisamos dos conceitos de refatoração.

você deve estar pensando neste momento: “eu sou um bom programador – meu código já sai exce-lente de primeira”. Infelizmente, por melhor que você seja – esta frase não é verdade. Praticamente todo có-digo existente pode ser refatorado para melhorar a sua legibilidade e, consequentemente, aumentar sua manutenibilidade.

Existe toda uma lista de “code smells” (literal-mente, fedores de código) que usualmente estão escondidos dentro de nossos códigos (uma peque-na lista pode ser encontrada em Martin (2008)). Ao remover cada um deles fazemos uma pequena mu-dança na estrutura de nosso código, com o intuito de melhorá-lo. A pergunta que resta é: como podemos garantir que nosso código ainda funciona?

Neste momento, a ampla cobertura de código (garantida por testes que satisfazem os critérios que colocamos anteriormente) é a principal garantia que você tem. Ao fazer uma refatoração, seu passo se-guinte deve ser rodar todos os testes que você tem (em uma IDE como o Netbeans ou o Eclipse, você pode fazer isto com um único comando). A refatora-ção só estará terminada quando você voltar a ter a barrinha verde de sucesso como resultado.

Assim, ao criar seus testes é recomendável utili-zar uma ferramenta de análise de cobertura para ter certeza que está verificando todos os casos possíveis de seu programa. Entenda que a refatoração é a pre-condição para que seu código efetivamente seja de boa qualidade e facilmente manutenível. Assim, sa-bendo que vai ter que mexer no seu código no futuro, você só poderá fazê-lo com segurança se seus tes-tes efetivamente verificarem todas as possibilidades existentes no seu código (e também todos os casos de uso necessários para seu cliente).

Nós já passamos da época em que frases como “compilou, está validado” ou “funciona aqui no meu ambiente” são aceitáveis. Lembre-se que você está entregando ferramentas de importância fundamental para os outros e deve ser respeitoso. Realizar as ativi-dades de testes e de refatoração é uma demonstração deste respeito e dos conceitos de profissionalismo que você sempre deve desejar transmitir.

Saber refatorar código é a diferença entre o pro-

gramador que faz código que funciona e código que é de qualidade e altamente manutenível. Quem quiser conhecer mais sobre as principais técnicas de refato-ração deve ler o principal livro sobre o assunto (FO-WLER, 1999) e terá em suas mãos o poder de trans-formar nosso código “macarrônico” em algo que terá uma sobrevida muito maior.

Cobertura e desenvolvimento orientado a testes

O desenvolvimento orientado a testes (Test Dri-ven Development, ou TDD) é uma técnica de desen-volvimento que procura primeiro criar os testes e de-pois desenvolver o código necessário para atendê-los.

O desenvolvimento torna-se então um ciclo que tem os seguintes passos:

1. Desenvolva um teste para um determinado caso de uso que faça o código existente falhar. Lembre-se de que a primeira lei do TDD pre-coniza que não se deve desenvolver nenhum código antes de termos desenvolvido testes unitários e a segunda lei diz que desenvolve-remos apenas o necessário para fazer o seu có-digo falhar.

2. Escreva somente o código necessário para fa-zer com que este teste não falhe mais. Note que escrever mais do que o código necessário, você estará violando a terceira lei do TDD.

3. Se necessário, refatore o código para que o design permaneça claro e eficiente. Isto vale tanto para seu código quanto para os testes, pois estes, como vamos discutir na seção “Bons Testes”, devem seguir os mesmos princípios de qualidade que o seu código.

4. Se todos os casos de uso já foram devidamente testados e efetivamente desenvolvidos, encerre o desenvolvimento.

5. Caso contrário, volte ao passo 1.Ao fim do ciclo, teremos uma suíte de testes que

verifica todos os casos de uso especificados e o códi-go está pronto para ser refatorado ou devidamente modificado, se necessário (veja a seção Cobertura e refatoração para ver uma discussão sobre esta neces-sidade).

TDD permite a criação de código de forma incre-mental e simples, devidamente testado e com concei-tos que tendem a ser bastante simples, pois cobrem os conceitos pontualmente. Como os programadores fazem o código de forma a fazer funcionar cada um dos testes existentes, então a depuração tende a ser bastante simples.

Outra vantagem importante é que o desenvolvi-mento dos testes obriga o programador a entender os casos de uso, de forma a gerar testes que sejam realis-tas e reflitam as necessidades expressas nas histórias de usuário.

35 \

Ivan Sanchez (2006) coloca em seu site que TDD não consiste só em desenvolver os testes antes do código, mas também pensar no design do código de forma incremental (como o passo 3 descrito acima procura deixar claro). Não procure refatorar de forma extensa neste momento, mas sim eliminar alguns dos erros mais comuns como duplicação, números mági-cos e outros. Como colocaremos mais adiante neste artigo: a refatoração será uma necessidade imperati-va ao fim do processo (e isto não quer dizer que você é um mau programador).

Quando usamos o desenvolvimento orientado a testes, o uso de ferramentas de cobertura de testes tem importância diminuída, pois se o código é es-tritamente escrito com TDD, neste caso não haverá linhas de código de produção que não tenham sido cobertas por um teste (já que o código é feito para que o teste que falha passe a ser bem-sucedido). Isto não quer dizer que você deve ignorá-las, mas sim que pro-vavelmente elas vão gerar o 100% que você espera.

No caso de usar desenvolvimento orientado a testes, o mais importante é o outro aspecto dos testes – a cobertura de todos os casos de uso que seu usuá-rio pode precisar. Assim, a análise e a conversa com o usuário torna-se um aspecto ainda mais fundamental do código bem-sucedido.

Considerações finais Testes são fundamentais para garantir que um

sistema faz exatamente o que é esperado. Em um am-biente profissional não se pode esperar que um clien-te aceite que você se comporte como naquele comer-cial em que um paraguaio falava “la garantía soy yo”.

Ao desenvolver testes, você deve procurar fazê-lo de forma que todas as possibilidades de caminho por seu código sejam devidamente testadas. Para isto, as ferramentas de análise de cobertura são de grande valia, podendo apontar de forma gráfica e integrada à sua IDE quais partes não foram devidamente tes-tadas.

Lembre-se, entretanto, que a cobertura não é exaustiva em termos de testes a realizar. É possível, como mostramos aqui, que você tenha 100% de co-bertura e alguns casos de uso não tenham sido de-vidamente testados. Assim, você deve fazer com que seus testes, além de maximizar a cobertura, busquem cobrir todas as situações que podem ser encontradas na operação real do sistema.

Outro ponto fundamental é que a cobertura não é um objetivo por si só. Não é obrigatório ter 100% de cobertura – existem várias situações que não re-querem testes ou cujos testes não agregam valor ao seu sistema. Não fique “deprimido” se não conseguir chegar a 100% de cobertura – a maioria dos desenvol-vedores raramente chega a este valor.

Nosso objetivo final sempre será desenvolver

aplicações sem bugs que agradam imensamente nos-sos usuários. Neste sentido, as ferramentas de análise de cobertura serão de grande valia, ajudando você a ser visto por todos como um ótimo profissional.

Entenda que desenvolver, usar testes, ferramen-tas de cobertura e preocupar-se com a qualidade e manutenibidade de seus testes não é perda de tempo. Todo o tempo gasto com este tipo de preocupação é devidamente recuperado com a diminuição das ne-cessidades de manutenção e com o aumento da qua-lidade do seu produto final, diminuindo o down time dos aplicativos que você coloca no mercado e am-pliando a sua fama de bom profissional.

> BECK, K.; BEEDLE, M., 2001, “Manifesto for Agile

Software Management”, site da Internet de endereço http://

agilemanifesto.org/, última visita em setembro/2011

> FOWLER, M.; BECK, K. et al, 1999, “Refactoring:

Improving the desing of existing code”, Addison-Wesley

Professional, EUA

> HUNT, A.; THOMAS, D., 2004, “Pragmatic Unit Testing

with Junit”, The Pragmatic Programmers, EUA

> MARTIN, R., 2008, “Clean Code – A Handbook of Agile

Software Craftsmanship”, Prentice Hall, Nova Iorque, EUA

> SANCHEZ, I., 2006, “Confusões sobre TDD”, site

da internet de endereço http://dojofloripa.wordpress.

com/2006/11/28/confusoes-sobre-tdd/, última visita em

setembro/2011

/referências

Na MundoJava 41 tivemos um ótimo artigo sobre testes

e desenvolvimento baseado em testes (TDD), que cobre

alguns dos principais aspectos destes conceitos que podem

lhe ajudar bastante a desenvolver aplicativos com menos

erros.

Na MundoJ 44 eu escrevi um artigo sobre os princípios

da programação sem bugs que buscava explicar os

princípios básicos que devem nortear a boa programação

e o desenvolvimento de testes unitários para garantir o

funcionamento correto de nossos programas.

Na MundoJ 46 existe um artigo muito interessante sobre

a qualidade através de testes funcionais usando três

ferramentas específicas. Mesmo que você não use as

ferramentas em questão, os conceitos passados pelo artigo

poderão ser de grande valia.

/para saber mais

/ 36

grafos_

Grafos podem ser usados para representar relacionamentos em diversos tipos de redes: sociais, de computadores, de tráfego e outros tipos de dados relacio-nais. Frequentemente é necessário representar grafos de forma visual para ilus-trações ou análises. Neste artigo, veremos como usar a API JUNG para visualizar conjuntos de dados representados como grafos.Este artigo complementa o artigo “Introdução à Representação e Análise de Grafos com a API JUNG”, publicado na Edição 49 da revista MundoJ.

Grafos são estruturas que servem para representar muitos objetos reais de forma natural: redes sociais e relações entre objetos em geral, redes de computadores e estradas, hierarquias de dados em vários tipos de sistema e muitos outros conceitos podem ser repre-sentados como grafos. Em um artigo na última edição vimos como representar grafos e executar análises simples em Java usando a API JUNG (Java Universal Network/Graph Framework). Neste ar-tigo, veremos como usar a mesma API para visualizar estes grafos.

Grafos são estruturas de dados que representam objetos e relações entre eles. Grafos podem ser

usados para representar vários tipos de conceitos e objetos do mundo real, como, por exemplo, relações em redes sociais reais ou virtuais, links em docu-mentos na Web, rotas de tráfego de veículos ou de redes de computadores e muitos outros. Grafos são conjuntos de vértices (que correspondem aos obje-tos que queremos representar) e arestas, que ligam pares de vértices e correspondem às relações entre os objetos.

No artigo “Introdução à Representação e Análise de Grafos com a API JuNG”, publicado na Edição 49 da revista MundoJ, apresentamos a API JuNG (Java universal Network/Graph Framework), que permite a representação de grafos em Java como coleções de vértices e arestas. Além de classes para representar grafos de diversos tipos, a API contém implemen-tações de vários algoritmos para análise dos grafos e operações como criação de subconjuntos dos gra-fos. Neste artigo veremos outras classes da API que permitem a visualização dos grafos usando também diversos algoritmos para determinação das posições

e aparência de seus vértices e arestas.visualização de grafos pode ser usada para ilus-

tração simples: é mais fácil entender os objetos e suas relações em um grafo quando este é represen-tado graficamente do que pela lista de vértices e arestas. visualização também pode ser usada para análises visuais mais complexas: alguns fenômenos podem ser mais bem compreendidos ou identifica-dos através da visualização dos grafos, em especial quando associamos outras informações sobre vérti-ces e arestas à representação visual dos grafos.

visualização de grafos é uma tarefa mais com-plexa do que aparenta ser: grafos com grande núme-ro de arestas e vértices podem ter uma representação visual muito densa e até incompreensível. Adicio-nalmente não existe uma única forma de visualizar um grafo – os vértices podem ser posicionados de muitas formas diferentes, o que afeta também a apa-rência das arestas que conectam estes vértices. Em-bora alguns tipos específicos de grafos possam ser visualizados de forma natural (árvores, por exem-plo), muitos tipos de grafos podem ser representa-dos graficamente de forma diferente, mesmo tendo a

Visualização Gráfica de Grafos com a API JUNG

37 \

Rafael Santos | [email protected]é doutor em Inteligência Artifi cial pelo Instituto Tecnológico de Kyushu, Japão. É tecnologista do Instituto Nacional de Pesquisas Espaciais, atuando em pesquisa e desenvolvimento de aplicações e técnicas de mineração de dados, processamento de imagens e

inteligência artifi cial aplicada, e presentemente é pesquisador visitante da Universidade Johns Hopkins, nos Estados Unidos. É autor do livro “Introdução à Programação Orientada a Objetos usando Java” e de várias palestras e tutoriais sobre Java.

mesma representação como conjuntos de vértices e arestas. Isto é exemplifi cado na fi gura 1, que mostra quatro layouts ou representações gráfi cas diferentes para o mesmo grafo (mesmo conjunto de vértices com o mesmo conjunto de arestas conectando estes vértices).

Este artigo apresenta seções mostrando como criar aplicações para visualização básica de grafos com a API JuNG, comenta sobre os layouts disponí-veis na API (que determinam como os vértices de um grafo serão posicionados na visualização), e apre-senta diversos exemplos de como a aparência gráfi ca de vértices e arestas podem ser modifi cados para vi-sualização do grafo.

Visualização básica de grafos com a API JUNG

A API JuNG contém vários métodos que facili-tam a visualização de grafos de diversos tipos, per-mitindo também a defi nição da aparência gráfi ca de vértices e arestas de forma bastante fl exível, o layout (distribuição) dos vértices e arestas usando vários algoritmos diferentes ou mesmo de forma manual e algumas formas de interação do usuário com a re-presentação visual do grafo.

A API JuNG foi criada de forma a facilitar bas-tante a criação de aplicações para visualização de grafos, usando algumas classes básicas para as ta-refas mais comuns e permitindo a customização de vários aspectos através da criação de instâncias de classes específi cas com tarefas bem defi nidas.

Para visualizar um grafo representado na API JuNG precisamos primeiro de uma instância de clas-se que implementa a interface Graph. Os passos para a criação de uma representação visual do grafo são:

1. usando a instância que representa o grafo, criamos um layout para a representação grá-fi ca deste. Layouts em JuNG são implementa-dos por instâncias de classes que herdam de AbstractLayout (que por sua vez implementa a interface Layout) e que devem ser declara-das com os mesmos parâmetros de tipo usa-

André Grégio | [email protected]é mestre em Computação Aplicada pelo Instituto Nacional de Pesquisas Espaciais. É tecnologista do Centro de Tecnologia da Infor-

mação Renato Archer (CTI), atuando em pesquisa e desenvolvimento na área de segurança de sistemas de informação.

Figura 1. Quatro representações gráfi cas do mesmo grafo.

Instalando a API JUNGA API JuNG permite a representação e pro-

cessamento de grafos em Java de forma efi ciente e fl exível. Os arquivos da API podem ser copiados de http://sourceforge.net/projects/jung/fi les/. A ver-são mais recente em setembro de 2011 é a 2.0.1. A API é distribuída como um arquivo .zip, que con-tém 17 arquivos .jar, que devem ser adicionados ao classpath da máquina virtual Java ou do projeto que usa a API. Para adicionar estes arquivos .jar em um projeto desenvolvido com a IDE Eclipse, clique no nome do projeto com o botão direito do mouse, selecionando o menu Build Path / Add Ex-ternal Archives e escolhendo os nomes dos arqui-vo .jar no diálogo de seleção. Estas instruções são as mesmas apresentadas no artigo complementar na Edição 49.

/ 38

dos para declarar a instância que representa o grafo.

2. usando a instância que representa o layout, criamos uma instância de visualizationviewer, classe que herda indiretamente de JComponent e que será usada como componente gráfico na criação da interface gráfica para visualização.

3. Opcionalmente recuperamos o método ge-tRenderContext() da instância de visualizatio-nviewer para acessar o objeto correspondente ao contexto de renderização. Este objeto possui métodos para definir, através de classes trans-formadoras, a aparência dos vértices e arestas no grafo.

4. Opcionalmente associamos comportamentos ao componente de visualização para responder a gestos do mouse – isto é usado para permitir transformações como pan e zooming no grafo e seleção e modificação das arestas, entre outras funções.

5. usamos a instância de visualizationviewer para compor a interface gráfica da aplicação.

Para ilustrar estes passos vejamos um exemplo simples baseado em um grafo apresentado no arti-go “Introdução à Representação e Análise de Grafos com a API JuNG”, publicado na Edição 49. Para faci-litar a leitura, o trecho de código que cria este grafo é mostrado na Listagem 1. O grafo criado tem vértices e arestas do tipo String, e é o mesmo grafo usado para criar as visualizações mostradas na figura 1.

Listagem 1. Trecho de código que cria um grafo no qual vértices e arestas são Strings.

// Este método cria um grafo simples para uso em alguns exemplos deste artigo.public static Graph<String,String> criaGrafo() { //Criamos um grafo onde vértices e arestas são Strings Graph<String,String> grafo = new UndirectedSparseGraph<String,String>(); // Criamos as arestas e vértices simultaneamente. grafo.addEdge(“A-B”,”A”,”B”); grafo.addEdge(“A-C”,”A”,”C”); grafo.addEdge(“A-M”,”A”,”M”); grafo.addEdge( “B-C”,”B”,”C”); grafo.addEdge(“B-D”,”B”,”D”); grafo.addEdge( “B-J”,”B”,”J”); grafo.addEdge(“B-K”,”B”,”K”); grafo.addEdge( “B-L”,”B”,”L”); grafo.addEdge(“C-D”,”C”,”D”); grafo.addEdge( “C-E”,”C”,”E”); grafo.addEdge(“D-F”,”D”,”F”); grafo.addEdge( “D-G”,”D”,”G”); grafo.addEdge(“D-H”,”D”,”H”); grafo.addEdge( “D-I”,”D”,”I”); grafo.addEdge(“D-J”,”D”,”J”); grafo.addEdge( “E-N”,”E”,”N”); grafo.addEdge(“F-G”,”F”,”G”); grafo.addEdge(“J-K”,”J”,”K”); grafo.addEdge(“L-M”,”L”,”M”); return grafo;}

Os passos para a criação da visualização do grafo são mostrados na Listagem 2 (para manter as listagens concisas, os passos opcionais, usados para definir di-versos aspectos visuais do grafo, não são implemen-tados nesta versão do código. A visualização criada pela Listagem 2 pode ser vista na figura 2).

Listagem 2. Primeira versão da classe para visualiza-ção do grafo criado na Listagem 1.

// Classe para criar uma visualização básica de um grafo.public class VisBasica { public VisBasica() { // O grafo é criado em outra classe, para organizar // melhor o código. Graph<String,String> grafo = CriaGrafo1.criaGrafo(); // Passo 1: criamos uma instância de classe para // controlar o layout. AbstractLayout<String,String> layout = new KKLayout<String,String>(grafo); // Passo 2: criamos o componente para visualização. VisualizationViewer<String,String> componente = new VisualizationViewer<String,String>(layout); //Modificamos características do componente componente.setPreferredSize( new Dimension(750,750)); componente.setBackground(Color.WHITE); //Passo 5: usamos o componente para montar a GUI JFrame f = new JFrame(“Visualização de Grafos”); f.getContentPane().add(componente); f.pack(); f.setVisible(true); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); } // Executa a classe como uma aplicação. public static void main(String[] args) { new VisBasica(); // Monta a GUI. } }

Figura 2. Visualização criada pelo código na Listagem 2.

A figura 2 mostra uma visualização bastante bá-sica do grafo criado na Listagem 1 – alguns passos

39 \

que permitem a customização da aparência dos vér-tices e arestas não foram executados, para manter a primeira versão do código simples.

Podemos observar que as arestas e vértices da fi-gura 2 estão em posições diferentes das arestas e vér-tices mostradas nas diversas visualizações da figura 1, embora o grafo usado nas figuras seja o mesmo. Existem duas razões para estas diferenças: a primeira é porque escolhemos classes diferentes para repre-sentar e definir o layout que será usado para visua-lização do grafo; a segunda é que alguns dos layouts implementados na API JuNG dependem de iniciali-zação das posições das arestas, que pode ser aleatória em alguns casos. Em outras palavras, mesmo usando a mesma classe para implementar o layout podemos ter diferenças na aparência final da visualização (bas-ta executar a classe mostrada na Listagem 2 algumas vezes para ver a variação dos resultados). Existem soluções na própria API para resolver este problema, que serão vistas em outras seções neste artigo.

Alguns layouts para visualização de grafos

Ao criar visualizações de grafos é importante considerar que tipo de layout deve ser usado, e que alterações nas aparências das arestas e vértices de-vem ser feitas para que o resultado seja o mais pró-ximo do esperado. A definição do tipo de layout é simples para alguns tipos de grafos, como árvores e florestas ou organogramas e fluxogramas, mas não é trivial para outros tipos de grafos, em especial os genéricos. Nestes casos, sugere-se tentar diferentes layouts, verificando que implementação da API pro-duz os resultados mais agradáveis ou interpretáveis.

Alguns layouts implementados na API JuNG e suas respectivas classes são listados a seguir. Deta-lhes específicos das classes não serão apresentados: o leitor pode obter mais informações (inclusive refe-rências bibliográficas sobre os algoritmos de layout correspondentes às classes) na documentação da API.

» CircleLayout: faz com que os vértices sejam distribuídos em um círculo, com distância igual entre eles. A quarta visualização mostrada na figura 1 foi criada usando uma instância des-ta classe. Não realiza cálculos interativos para determinar a posição final do vértice, e não usa posicionamento aleatório inicial. O raio do círculo é determinado automaticamente, mas pode ser definido pelo usuário através do mé-todo setRadius.

» FRLayout: usa o algo-ritmo iterativo Fru-chterman-Reingold para determinar as posições finais dos vértices através de

cálculos de atração e repulsão entre vértices. O usuário pode mudar o comportamento do al-goritmo modificando as constantes de atração e repulsão e o número máximo de iterações. A API JuNG possui também a classe FRLayout2, que contém uma versão melhorada, mas ainda experimental do algoritmo.

» KKLayout: usa o algoritmo iterativo Kamada--Kawai para determinar as posições finais dos vértices através de uma heurística que conside-ra os comprimentos dos vértices que conectam as arestas do grafo.

» SpringLayout: usa um método iterativo para posicionar os vértices, que tenta aproximar e afastar grupos de arestas considerando os vér-tices que as unem. A execução do algoritmo que calcula o layout das arestas é considera-velmente lenta, sendo possível ver a sua mo-dificação durante a exibição do componente de visualização.

» ISOMLayout: inicializa as coordenadas dos vértices de forma aleatória e usa uma varian-te do Mapa Auto-Organizável de Kohonen (um tipo de rede neural) para calcular as posições finais dos vértices. Embora sua execução seja iterativa e lenta este layout pode ser adequado para visualização de grafos esparsos.

» TreeLayout: usado para visualizar florestas ou conjuntos de árvores (não pode ser usado dire-tamente para visualizar grafos como os usados nos exemplos deste artigo). Modifica o posi-cionamento inicial dos vértices para espalhar as árvores da floresta na área do componente gráfico.

» RadialTreeLayout: similar ao TreeLayout, tenta organizar as árvores da floresta em círcu-los concêntricos, com as raízes das árvores no centro do componente.

» StaticLayout: permite ao programador deter-minar posições específicas para cada vértice. Este layout garante consistência entre exe-cuções de uma aplicação: os vértices estarão sempre na posição definida pelo programador. É possível usar uma função específica para de-terminar a posição de cada vértice, ou expli-citar as coordenadas de cada vértice, mas isto pode ser impraticável para grandes grafos. um exemplo de uso desta classe será apresentado neste artigo.

49 \

public Stroke transform(Trecho t) { float largura = 1f; if (caminho.contains(t)) largura = 3f; if (t.isPassaÔnibus()) return new BasicStroke(largura, BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL, 0,new float[]{5},0); else return new BasicStroke(largura); }}

A classe vMv_EdgeDrawT, mostrada na Listagem 24, determina a cor que será usada para desenhar as arestas. A mesma classe será usada para determinar a cor das arestas, e a cor e preenchimento das setas usadas nas arestas (veja a classe visMv na Listagem 13).

Listagem 24. Classe VMV_EdgeDrawT, que determina a aparência do traço usado para desenhar os vértices.

// Esta classe transforma uma instância de Trecho em // uma instância de Paint que será usada para desenhar // a aresta. Arestas no caminho mínimo serão// desenhadas em azul, as outras em preto.public class VMV_EdgeDrawT extends VMV_TransBase implements Transformer<Trecho,Paint>{ public VMV_EdgeDrawT(Graph<Ponto,Trecho> grafo, List<Trecho> caminho) { super(grafo,caminho); } public Paint transform(Trecho t) { if (caminho.contains(t)) return Color.BLUE; else return Color.GRAY; }}

As figuras 5, 6 e 7 mostram a interface gráfica criada pela classe visMv (Listagem 13). A figura 5 mostra a aparência inicial, a figura 6 mostra o grafo reduzi-do para poder ser visualizado por inteiro e a figura 7 mostra um trecho do grafo ampliado – é possível observar que os desenhos têm características veto-riais: a ampliação não degrada a qualidade das linhas e elementos gráficos.

A classe GraphZoomScrollPane, parte da API JuNG, facilita bastante a visualização de grandes gra-fos: ela funciona como uma versão de JScrollPane, e contém métodos para processar a interação do mouse com o componente.

Para usar esta classe é preciso primeiro definir o comportamento do mouse para o componente de visualização (neste exemplo, instância de visualiza-tionviewer) através da chamada ao método setGra-phMouse, passando como argumento a classe que determina o comportamento, neste caso, DefaultMo-

dalGraphMouse (com parâmetros de tipo iguais ao do grafo). Depois basta criar uma instância de Gra-phZoomScrollPane recebendo a instância de visuali-zationviewer como parâmetro para seu construtor. O componente automaticamente processará os seguin-tes gestos do mouse:

» Clicar e arrastar o mouse: modifica a área de visualização do grafo no componente (pan).

» usar o mouse wheel: amplia ou reduz a área vi-sualizada (zoom).

» Clicar e arrastar o mouse pressionando shift: gira o grafo no componente.

» Clicar e arrastar o mouse pressionando control ou command: deforma o grafo no componente na vertical ou horizontal, dependendo da dire-ção de arrasto (skew).

»

Figura 5. Interface gráfica criada pela classe VisVM (Listagem 12).

Figura 6. Interface gráfica criada pela classe VisVM (mostrando todo o grafo).

/ 50

Figura 7. Interface gráfica criada pela classe VisVM (mostrando região ampliada).

A instância da classe DefaultModalGraphMou-se que é usada para controlar o comportamento do mouse para o componente de visualização também permite operações de seleção de vértices: basta criar a instância desta classe e executar o método setMode(ModalGraphMouse.Mode.PICKING) antes de associar o comportamento com a classe de visuali-zação. vértices podem ser selecionados com o mouse e arrastados para novas posições. O trecho de código apresentado na Listagem 25 mostra como isto pode ser feito.

Listagem 25. Trecho de código que demonstra como configurar o componente de visualização para permitir seleção de vértices no grafo.

> O artigo “Introdução à Representação e Análise de

Grafos com a API JUNG”, publicado na Edição 49 da

revista MundoJ, mostra como representar e processar

grafos de diversos tipos, inclusive como executar algoritmos

de análise sobre os grafos.

> O site da API JUNG contém vários exemplos e acesso à

documentação da própria API gerada pela ferramenta

javadoc, e pode ser acessado em http://jung.sourceforge.

net/doc/index.html. Um tutorial curto, mas simples da

API JUNG pode ser encontrado em http://www.grotto-

networking.com/JUNG/JUNG2-Tutorial.pdf (ambos em

inglês).

> O documento http://jung.sourceforge.net/doc/

JUNGVisualizationGuide.html (também em inglês) explica

os conceitos usados no design das classes da API para

visualização, e foi usado como base para parte deste artigo.

> Conceitos básicos de grafos podem ser encontrados

em diversas páginas na Wikipédia, em especial em

http://pt.wikipedia.org/wiki/Teoria_dos_grafos e http://

pt.wikipedia.org/wiki/Grafo. Alguns conceitos e referências

sobre visualização de grafos, particularmente sobre os

layouts, pode ser vista em http://en.wikipedia.org/wiki/

Graph_drawing (em inglês).

/para saber mais

VisualizationViewer<Ponto,Trecho> componente = new VisualizationViewer<Ponto,Trecho>(layout);DefaultModalGraphMouse<Ponto,Trecho> graphMouse = new DefaultModalGraphMouse<Ponto,Trecho>();graphMouse.setMode(ModalGraphMouse.Mode.PICKING);componente.setGraphMouse(graphMouse);

Considerações finaisA API JuNG contém classes para facilitar a visua-

lização de grafos de diversos tipos, com várias formas de alterar a aparência visual dos elementos do grafo. A API contém também algumas classes que imple-mentam algoritmos de layout dos vértices e arestas de um grafo, mas também permite a determinação manual do layout.

A criação de exemplos de visualização requer a criação de várias classes que determinam a aparência de vários elementos gráficos do grafo, o que é com-plexo e trabalhoso. Se o objetivo é criar uma ilustra-ção simples de um grafo pode ser mais simples usar de um editor gráfico; por outro lado a abordagem programática permite a automação da tarefa de de-senho com grande flexibilidade, como demonstrado – no exemplo do grafo de malha viária, para visuali-zar o caminho mais curto entre outros pontos basta mudar uma linha de código.

acesse www.revistamundoj.com.brou ligue para (41) 3029-9353

para ler artigos na revista virtual

a melhor revista de desenvolvimento de software

Assine

51 \

/para saber mais

acesse www.revistamundoj.com.brou ligue para (41) 3029-9353

para ler artigos na revista virtual

a melhor revista de desenvolvimento de software

Assine

/ 52

tendências em foco

O Hadoop foi criado pelo Yahoo em 2005 e pode ser conside-

rado uma das maiores invenções de data management desde o modelo relacional. Hoje é um dos projetos da comunidade Apache e vem sendo adotado por empresas que precisam tratar volumes mas-sivos de dados não estruturados. Já existe inclusive um ecossiste-ma ao seu redor, mas ainda vai demandar algum tempo para se disseminar de forma mais ampla pelo mercado. Neste post vamos debater um pouco mais o que é e o que não é o Hadoop, seu mer-cado e tentar visualizar algumas tendências. Quem sabe acertamos algumas?

Mas, o que é o Hadoop? É, na prática, uma combinação de dois projetos separados, que são o Hadoop MapReduce (HMR), que é um framework para processa-mento paralelo e o Hadoop Dis-tributed File System (HDFS). O HMR é um spinoff do MapReduce,

Conhecendo o Hadoop

coluna_

Volta e meia em eventos sobre Cloud Computing surge o termo Hadoop. Já ouvi em uma palestra que o grande desafio de desenvolver aplicações para Cloud era exatamente a complexidade no uso do Hadoop. O que gera a confusão é que o Hadoop e o MapReduce, do qual ele se originou, vêm sendo usados pelas empresas de Internet, que inspiraram o modelo de Cloud Computing, e que precisam de escala massiva para suas aplicações, como Yahoo, Google e Facebook. Mas dizer que o Hadoop é a base para todo e qualquer projeto de Cloud absolutamente não é correto. O Hadoop é usado para aplicações analíticas de dados massivos, que não é o dia-a-dia das empresas que usam ou pretendem usar Cloud Computing.

software que o Google usa para acelerar as pesquisas endereça-das ao seu buscador. O HDFS é um sistema de arquivos distribuídos otimizados para atuar em dados não estruturados e é tambem ba-seado na tecnologia do Google, neste caso, o Google File System. Existe também o Hadoop Com-mon, conjunto de bibliotecas e utilitários que suportam os proje-tos Hadoop. Na prática, para que o HMR processe os dados, eles de-vem estar armazenados no HDFS.

O Hadoop é um projeto Open Source, com licenciamento Apa-che e, portanto, permite a criação de um ecossistema de negócios baseados em distribuições espe-cíficas. E o surgimento de servi-ços em nuvem, como o Amazon Elastic MapReduce, permite às empresas tratarem dados massi-vos sem demandar aquisição de servidores físicos. Neste modelo, o usuário escreve a aplicação Ha-doop e a roda em cima da nuvem

da Amazon.A base das distribuições Ha-

doop é a comunidade Apache. Diversas empresas vêm contri-buindo com código para seu de-senvolvimento como a Yahoo, Facebook, Cloudera, IBM e outras. Em torno do código-base, surgem as distribuições, como Cloudera (www.cloudera.com) e DataStax (http://www.datastax.com/brisk), que agregam valor com utilitários e serviços de suporte e educação, no mesmo modelo das distribui-ções Linux. Interessante que a distribuição da DataStax, chama-do de Brisk, substituiu o HDFS por um sistema de arquivos distribu-ídos baseados no software NoS-QL Cassandra, chamado agora de CassandraFS.

Mas, em torno do Hadoop (http://hadoop.apache.org/), a co-munidade Apache mantém diver-sos projetos relacionados, como o Hbase, que é um banco de da-dos NoSQL que trabalha em cima

53 \

Cezar Taurion | [email protected] |www.ibm.com/developerworks/blogs/page/ctaurion

Formado em Economia e Ciências da Computação, atualmente gerente de Novas Tecnologias Aplicadas da IBM Brasil. Também pode ser encontrado no Twitter, em @ctaurion.

do HDFS. Este banco de dados é usado pelo Facebook para supor-tar seu sistema de mensagens e os seus serviços de informações analíticas em tempo real. Existe também o Hive, criado pelo Face-book, que é uma camada de data warehouse que roda em cima do Hadoop. utiliza uma linguagem chamada Hive SQL, similar à SQL, o que facilita sua utilização, pois desenvolvedores acostumados com SQL não encontram maiores dificuldades em trabalhar com o Hive SQL.

Outro e também muito inte-ressante projeto é o Pig, criado pelo Yahoo. É uma plataforma que permite análises de arquivos muito grandes usando uma lin-guagem de alto nível chamada de Pig Latin. Olhando-se o stack de softwares do Hadoop, o Pig se situa entre o Hive e o HMR e é uma tentativa de fornecer uma linguagem de alto nivel para se trabalhar com o Hadoop. Outros projetos menos conhecidos são o Avro (sistema de serialização de dados), o Chukwa (monitoramen-to de sistemas distribuídos) e o Hama (para computações cientí-ficas massivas).

A IBM usa intensamente o Hadoop em diversos projetos, o integrando com outros de seus softwares como o Cognos, crian-do soluções para tratamento ana-lítico de dados massivos e não estruturados, como o InfoSphere

BigInsights, que agrega um con-junto de tecnologias open source como o próprio Hadoop, Nutch e Pig, com as tecnologias pró-prias da IBM, como InfoSphere e ManyEyes. vejam em (http://www-01.ibm.com/software/data/bigdata/). A IBM também desen-volveu uma variante do HDFS chamado de IBM General Parallel File System (GPFS), que pode ser visto em http://www-03.ibm.com/systems/software/gpfs/.

Ok, e quem usa Hadoop? Existem os casos emblemáticos como Facebook, Yahoo, Twitter e Netflix (na nuvem da Amazon), mas também já começamos a ver seu uso em ambientes corporati-vos brick-and-mortar. Recente-mente uma pesquisa mostrou que pelo menos umas 20 empresas da lista da Fortune mil assumiram publicamente que usam Hadoop de alguma forma. A adoção do Hadoop em aplicações analíticas corporativas como as ofertadas pela IBM vão ajudar na sua disse-minação. Só para lembrar, quan-do a IBM anunciou seu apoio ao Linux, em 2001, o Linux passou a ser visto sob outra ótica pelo am-biente corporativo.

Nem todo usuário de Hado-op demanda uma escala massiva de dados ao nível do Facebook ou Yahoo. Mas empresas com ra-zoável volume de informações não estruturadas, como bancos, varejo, empresas aéreas e outras

vão encontrar no Hadoop uma boa alternativa para o tratamento analítico dos seus dados. vejam alguns exemplos em ftp://public.dhe.ibm.com/common/ssi/ecm/en/imb14103usen/IMB14103USEN.PDF.

O Hadoop ainda está nos pri-meiros passos de sua evolução e disseminação pelo mercado. Mas tenho certeza de que em poucos anos ele será bem mais conhe-cido e utilizado. uma pesquisa pelo termo Hadoop no Google Trends já aponta um cresci-mento significativo no interesse pela tecnologia, como podemos ver em http://www.google.com/trends?q=hadoop. Na minha opi-nião vale a pena investir tempo estudando a tecnologia. um dos problemas com o Hadoop é real-mente a sua complexidade de uso, demandando desenvolvedores al-tamente qualificados. A sua curva de aprendizado é bastante íngre-me e praticamente inexiste gen-te habilitada para usar o Hadoop adequadamente. Bem, temos aí uma boa oportunidade para as universidades inserirem seu es-tudo e prática em seus cursos de Ciência da Computação. uma su-gestão para quem quer estudar Hadoop mais a fundo: acessem www.ibm.com/developerworks e pesquisem pela keyword Hadoop. vocês vão encontrar milhares de artigos muito interessantes.

/ 54

JPA_

Comparando Persistência de Dados com JPA

TOPLINK E HIBERNATE TESTADOS EM DIFERENTES BANCOS DE DADOS E SISTEMAS OPERACIONAIS.

Este artigo descreve os testes com diversas combinações de fra-meworks, banco de dados e sistemas operacionais analisando qual o melhor na avaliação de desempenho no que diz respeito à per-sistência de dados em suas versões padrão, sem qualquer tunning. Abordaremos MySQL, PostgreSql, Oracle, SQL Server, Windows, Ubuntu, Toplink e Hibernate.

A persistência é uma das áreas de maior importân-cia em uma aplicação JEE (Java Enterprise Edi-

tion), sendo especificada pela JPA (Java Persistence API), que originalmente fazia parte da plataforma EJB (Enterprise Java Beans). A partir da versão 5 do Java a JPA passou a ter a sua própria especificação, JPA 1.0.

Com o uso de frameworks para persistência o de-senvolvedor obtém um nível mais alto de abstração sobre o tradicional JDBC, permitindo utilizar o me-canismo de ORM (Object to Relational Management) em sua aplicação, diminuindo o acoplamento da tec-nologia do SGBD (Sistema Gerenciador de Banco de Dados) ao código orientado a objetos.

O acoplamento reduzido amplia a possibilida-de de seleção de tecnologia na camada chamada de EIS (Enterprise Information System) que, segundo a documentação JEE 6, permite a integração entre diferentes plataformas e mecanismos de armazena-mento de dados. Para este trabalho, as tecnologias selecionadas foram MySQL, PostgreSql, Oracle e SQLServer.

Neste artigo, serão demonstrados os resultados relacionados ao desempenho entre dois frameworks baseados em JPA: Hibernate e TopLink. Foram ana-

lisadas diferentes combinações dos frameworks com os sistemas operacionais Windows 2003 Server e Linux ubuntu 11.04, junto aos bancos de dados ci-tados. Os sistemas operacionais foram selecionados buscando abranger o mercado brasileiro de softwa-res licenciados e opções de softwares livres mais po-pulares.

Persistência de dadosA persistência de dados nos possibilita trabalhar

com o ORM, que permite a criação de classes Java como beans de entidade, mapeando-as para tabelas em um banco de dados relacional. uma das vanta-gens do uso da JPA é a não obrigatoriedade do uso de instruções SQL, o que aumenta a velocidade de desenvolvimento do software e não restringe as ins-truções de persistência a um padrão ou versão espe-cífica de SQL.

A criação das instruções SQL é de responsabili-dade do framework provedor de JPA sendo utilizado na camada de persistência. Desta forma, um softwa-re que utiliza JPA torna-se não dependente das par-ticularidades da linguagem SQL própria do banco de dados, o que é muito útil, uma vez que o desenvolve-dor não precisa reescrever todo o código adaptando-

55 \

Sylvio Barbon Junior | [email protected]é formado em Engenharia e Ciência da Computação, mestre e doutor em Física Computacional pelo IFSC/USP. Realiza trabalhos como consultor Java no interior do Estado de São Paulo. Possui certificação SCJP, é docente na Universidade do Estado de Minas

Gerais (UEMG) e na FATEC de São José do Rio Preto. Junto com outros colaboradores mantém o site http://www.patternizando.com.br para a discussão de desenvolvimento de software.

-o para outro banco de dados, aumentando a compa-tibilidade e portabilidade do software, desfrutando da manipulação baseada em objetos.

O HibernateMantido pela JBoss, foi criado em 2001 por Ga-

vin King e é um framework livre e de código aberto licenciado sob a LGPL v2.1, segue o padrão JPA e dis-ponibiliza uma versão para a tecnologia .Net.

Atualmente está em sua versão 3.6.6 (estável) e contém uma vasta documentação disponível no site na mantenedora.

O TopLinkCriado pela Oracle, teve sua primeira distribui-

ção no início da década de 90 e é um framework pro-prietário, também segue o padrão JPA e atualmente está na versão 11g.

Ambiente de simulaçãoTodos os testes foram executados em notebook

Sony vaio com processador Core 2 Duo 2.0GHz com 4GB de memória RAM.

O software utilizado para os testes controlava o tempo de cada tarefa para posterior comparação, considerando ainda a possibilidade de testar em di-ferentes Sistemas Operacionais. Além disso, em to-dos os casos, a mesma máquina virtual foi utilizada, neste caso a JvM 1.6.

O intuito deste artigo não é definir qual é a me-lhor das tecnologias, mas sim, analisar apenas o seu desempenho sem nenhuma personalização em sua configuração, preservando-a como foi lançada ao mercado. Sabe-se que com o uso de personalizações e configurações aprimoradas (tunning), assim como o uso de pools de conexão, o resultado pode ser dife-rente do encontrado neste artigo.

Foram utilizadas as seguintes tecnologias:Produtos e versões » Frameworks persistência: Hibernate versão 3.2.5, compatível com JPA 1.0TopLink versão 2.1 build 3, compatível com JPA 1.0 » Bancos de dados:MySql versão 5.1.11PostgreSql versão 8.4.4Oracle versão 10g Express Edition – 10.2.0SqlServer 2005 versão 9.00.4053.00 Express » Sistemas operacionais:Windows 2003 Service Pack 2Linux ubuntu 11.04, kernel 2.6.35-24 » Java:Máquina virtual Java versão 1.6.0_22Para a simulação de um cenário de software real,

foi criada uma estrutura com 10 entidades relacio-nadas. Cada entidade apresentava atributos de tipos variados, porém presentes em todos os sistemas ge-renciadores de bancos de dados testados.

Os tipos de dados foram INT, DATE, TExT e FLOAT, como mostra a figura 1. Outra característica utilizada para simular situações reais foi o relaciona-mento entre as entidades, em que foram utilizados relacionamentos diversos, com cenários de persis-tência em cascata e exportação de chaves estrangei-ras que exigem a geração de sintaxes mais comple-xas pelo framework JPA.

Os dados foram gerados aleatoriamente pelo sis-tema, respeitando o tipo de dado de cada atributo e utilizando o limite máximo de informação de cada tipo de dado.

A Listagem 1 apresenta um exemplo de enti-dade mapeada para posterior manipulação pelos frameworks. Considerando que os frameworks são selecionados pela configuração no persistence.xml,

Marcos Pedro Gomes da Silva | [email protected] é graduando em Sistemas de Informação pela Universidade do Estado de Minas Gerais (UEMG) entusiasta da linguagem

Java e empresário.

Figura 1. Diagrama do Banco de Dados dos Testes.

/ 56

para cada opção é criada uma nova unidade de per-sistência (Persistence unit), o que permite o mesmo mapeamento para qualquer framework que segue o padrão JPA, com mínimas modificações apenas no persistence.xml.

Listagem 1. Implementação da entidade Table3.

@Entity@Table(name = “table3”)public class Table3 im plements Serializable { @Id @SequenceGenerator(name = “seq_table3”, sequenceName = “seq_table3”, allocationSize = 1) @GeneratedValue(generator = “seq_table3”, strategy=GenerationType.AUTO) @Column(name = “idtable3”) private int idTable3; @Column(name = “valor1”) private int valor1; @Column(name = “valor2”) private float valor2; @Column(name = “texto”, length=1000) private String texto; @ManyToOne @ForeignKey(name=”fk_table3_table2”) @JoinColumn(name = “idtable2”, referencedColumnName = “idtable2”, nullable = false) private Table2 table2; // Gets e Sets}

Desempenho entre Hibernate e TopLinkForam executados 112 testes em cada framework

nos dois sistemas operacionais, quatro bancos de da-dos variando entre 1, 2, 3 e 4 tabelas (mostradas na figura 1) em cada banco de dados (com exceção do SQL Server no ubuntu), e todo o tempo gasto foi so-mado e o resultado está exibido na figura 2. A tabela 1 mostra o resultado de tempo total em horas, que é a

média independentemente de banco de dados e siste-ma operacional. Ao total foram manipulados 781.200 registros para cada teste.

Tabela 1. Tempo total da comparação entre TopLink e Hibernate.

Framework Tempo Total MédioHibernate 0h48m46sTopLink 1h29m18s

Comparando os dois frameworks, o Hibernate obteve o melhor desempenho, mostrando-se ser mais rápido, realizando todas as operações em menos de uma hora como demonstrado pelo gráfico da figura 2.

Desempenho entre sistemas operacionais

Nestas simulações foram executados 96 testes em cada sistema operacional, desprezando o SQL Server no ambiente Windows, para que a quantidade de si-mulações fosse equivalente, já que ele não é compatí-vel em ambiente Linux. Na tabela 2 são apresentados os resultados obtidos, que corresponde à média entre os dois frameworks no respectivo sistema operacio-nal utilizando todos os bancos testados, com exceção do SQL Server. A quantidade de registros manipula-dos foi de 669.900.

Tabela 2. Tempo total da comparação entre sistemas operacionais.

Sistema Operacional Tempo Total MédioLinux 0h49m41s

Windows 1h08m45sNesta comparação, como exibido na figura 3, o

sistema operacional Linux obteve tempo menor, em que ao final de todas as operações o tempo foi inferior à uma hora.

Desempenho entre banco de dadosNestas simulações, foram executadas 32 opera-

ções para cada banco de dados, sendo elas: inserção, seleção, atualização e exclusão de dados, variando entre 1, 2, 3, 4 tabelas. Os resultados dos testes em

referências/

Figura 2. Gráfico de comparação entre frameworks. Figura 3. Gráfico de comparação entre sistemas operacionais.

Tempo gasto entre frameworks Tempo gasto entre sistemas operacionais

HibernateTopLink

Frameworks

6543210

Tempo (em milhões de milissegundos)

4,54

3,53

2,52

1,51

0,50

Tempo (em milhões de milissegundos)

LinuxWindows

57 \

ambiente Windows estão disponíveis na tabela 3. A quantidade de registros manipulados foi a mes-ma dos testes anteriores com a quantidade total de 781.200 registros por operação.

Tabela 3. Comparação de resultados entre bancos de dados no Windows.

Banco de dados Tempo total médioSQL Server 0h19m37sOracle 0h20m16sPostgreSQL 0h21m52sMySql 0h26m36s

Em ambiente Linux, obviamente, não foi consi-derado o banco de dados da Microsoft. Os resultados mostraram-se próximos, expressando uma pequena vantagem do PostgreSql.

Tabela 4. Resultados em ambiente Linux.

Banco de dados Tempo total médioPostgreSQL 0h13m35s

Oracle 0h13m45sMySql 0h22m21s

Em ambiente Windows, o SQL Server mostrou--se ser mais rápido perante os outros. Neste caso, os dois bancos de dados proprietários levaram menos tempo em relação às opções gratuitas. No ambiente Linux, coincidentemente, quem levou a melhor foi o PostgreSQL, que é gratuito e de código-fonte aberto. Nos dois sistemas operacionais, o MySql foi a opção mais lenta, com resultados superiores a 20 minutos em ambos os testes.

Conclusão – Comparativo framework, banco de dados e sistema operacional

Após os testes, podemos concluir que existiram combinações otimizadas, sendo que o Hibernate ob-teve as cinco primeiras colocações em desempenho, sendo seguido pelo TopLink em ambiente Linux.

Dessa forma, conclui-se que a combinação de tecnologias com configurações padronizadas, sem qualquer customização, em um cenário que apresen-ta diversas entidades e diferentes tipos de relaciona-mento para persistência com JPA é o Hibernate como provedor de JPA em ambiente Linux, com um banco de dados PostgreSql, seguido muito próximo pelo banco de dados Oracle. Já a combinação menos veloz foi TopLink em ambiente Windows, utilizando a base gratuita MySQL.

Considerações finaisResultados como os descritos neste artigo mos-

tram que, além das possibilidades de softwares p r o p r i e t á r i o s poderosos e con-fiáveis, as opções gratuitas tam-bém devem ser c o n s i d e r a d a s . Outra afirmação que pode ser fei-ta é que soluções gratuitas em am-bientes proprie-tários obtiveram os piores resulta-dos. No entanto, o SQL Server, em ambiente Windo-ws, mostrou-se competitivo às soluções gratui-tas.

> Keith, Mike; Shincariol, Merrick. EJB 3 Profissional: Java

Persistence API. Rio de Janeiro: Moderna, 2008

> Burke, Bill; Monson-Haefel, Richard, Enterprise

JavaBeans 3.0, São Paulo, 2007

> Bauer, Christian; King, Gaving, Hibernate in Action,

Manning, 2004

> Documentação Oficial Hibernate, disponível em <www.

hibernate.org> Acessado em 12 de julho de 2011

> Documentação oficial TopLink, disponível em <http://

www.oracle.com/technetwork/middleware/toplink /

overview/index.html> Acessado em 15 de julho de 2011

referências/

Tabela 5. Tempo médio, considerando framework de persistência, OS e banco de dados.

Ranking Framework Sistema operacional Banco de dados Tempo médio por

operação1 Hibernate Linux PostgreSQL 0m20s632ms2 Hibernate Linux Oracle 0m20s646ms3 Hibernate Windows SQL Server 0m22s119ms4 Hibernate Windows Oracle 0m22s636ms5 Hibernate Windows PostgreSQL 0m23s390ms6 TopLink Linux PostgreSQL 0m30s324ms7 TopLink Linux Oracle 0m30s926ms8 Hibernate Windows MySql 0m35s619ms9 Hibernate Linux MySql 0m37s840ms10 TopLink Linux MySql 0m45s991ms11 TopLink Windows SQL Server 0m51s501ms12 TopLink Windows Oracle 0m53s411ms13 TopLink Windows PostgreSQL 0m58s624ms14 TopLink Windows MySql 1m04s155ms

/ 58

spring_

Profiles no Spring 3.1 Melhore o controle de suas aplicações usando os profiles do Spring 3.1 .

Conheça o uso dos profiles presentes na próxima versão do framework Spring (3.1), que permitirá que você defina em tempo de execução os recur-

sos que serão utilizados pelo sistema. Aqui será mostrado

um exemplo prático de uma das possíveis aplicações dessa nova característica que é muito bem--vinda.

Uma aplicação desenvolvida utilizando Spring e alguns de seus módulos é uma ótima opção,

mas dependendo do seu uso pode ser trabalhoso e cansativo de usar em diferentes ambientes. Normal-mente quando construímos um sistema, temos um ambiente local de desenvolvimento e um ambiente de produção, mas em algumas empresas temos mais ambientes, como, por exemplo, o ambiente de testes, para a equipe de qualidade testar, ou o ambiente de homologação, para o usuário validar o que foi feito. Para a mesma aplicação funcionar em diversos am-bientes, faz-se necessária uma configuração extra, normalmente manual, que obriga a gerar sempre um pacote para cada ambiente. Imagine que acidental-mente um pacote que testes foi para o ambiente de produção; “cabeças podem rolar” por um descuido desses.

Neste artigo, depois de uma breve introdução ao Spring, será explicado com detalhes um exemplo prático de como colocar o mesmo sistema em dife-rentes ambientes e tirar proveito dessa novidade da versão 3.1. Faremos uma aplicação Web extrema-mente simples que terá acesso a diferentes bancos de dados conforme o ambiente que estiver rodando: desenvolvimento ou produção.

Conhecendo o SpringSe você já conhece o framework e entende o fun-

cionamento dos beans, pode pular para o próximo tópico. Os beans são o coração do Spring, tudo gira em torno deles para qualquer coisa que você queira fazer.

O framework é dividido em diversos módulos que vão adicionando funcionalidades que você pode usar sempre que precisar. No nosso exemplo, usamos o módulo de injeção de dependências para atribuir os valores aos nossos objetos automaticamente, o módulo JDBC que auxilia no trabalho com o banco de dados e também usamos o módulo Model-view--Controller para gerenciar a navegação dos objetos do banco de dados até a página Web.

Toda a configuração no Spring era centralizada em arquivos xML, mas depois da popularização das anotações, surgiu o Spring Annotations e ele foi in-corporado como padrão na versão 3, o que fez com que parte da configuração necessária do xML ficasse diluída nas anotações das classes Java.

A configuração (Listagem 1) é feita declarando um filtro no arquivo web.xml e mapeando para ex-tensão que desejar. Opcionalmente, mapeia-se para o arquivo de configuração do Spring, onde serão li-das todas as configurações de todos os módulos que sua aplicação usará.

Listagem 1. Arquivo web.xml com a configuração do Spring.

59 \

Fernando Boaglio | [email protected] em Ciências da Computação pela Unesp, foi instrutor oficial da Sun Microsystems e da Oracle Education. Atualmente

contribui para alguns projetos open source, como KDE e Mentawai, e é da equipe de arquitetura da CodeIT Solutions, uma empresa especializada na prestação de serviços de desenvolvimento de software para as indústrias de seguros.

<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springapp-prod.xml</param-value></context-param><listener> <listener-class> org.springframework.web.context. ContextLoaderListener</listener-class></listener><servlet> <servlet-name>appServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springapp-prod.xml </param-value> </init-param> <load-on-startup>1</load-on-startup></servlet><servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>*.html</url-pattern></servlet-mapping>

No exemplo deste artigo, temos um bean homeCon-troller que declara o mapeamento da classe Home-Controller feito pelo módulo MvC e logo abaixo o mapeamento dataSource que declara como será feito o acesso ao banco de dados, usado pelo módulo JDBC (Listagem 2).

Listagem 2. Beans do Spring.

<bean id=”homeController” name=”/index.html” class=”br.com.mundoj.controller.HomeController”/>

<bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource”> <property name=”driverClassName” value=”com.mysql.jdbc.Driver”/> <property name=”url” value=”jdbc:mysql://localhost/spring” /> <property name=”username” value=”root”/> <property name=”password” value=””/></bean>

Com o uso de anotações, precisamos apenas confi-gurar as classes como esse DAO (Listagem 3) para utilizá-lo no nosso controlador (Listagem 4), a ano-

tação Autowired criará uma nova instância de Pesso-aDAO sempre que a classe for chamada. Essa é uma das diversas facilidades que esse framework propor-ciona.

Listagem 3. Classe PessoaDAO responsável pela persistência de dados.

@Repository(“pessoaDAO”)public class PessoaDAOImpl implements PessoaDAO { private HibernateTemplate hibernateTemplate; @Autowired public PessoaDAOImpl(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } public List<Pessoa> list() { List<Pessoa> l = this.hibernateTemplate.find( “from Pessoa”); return l; }}

Listagem 4. Classe controlador (do MVC) que manda os dados para a página Web.

@Controllerpublic class HomeController extends AbstractController { private PessoaDAO pessoaDAO; @Autowired public HomeController(PessoaDAO pessoaDAO) { this.pessoaDAO = pessoaDAO; } @Override protected ModelAndView handleRequestInternal(HttpServletRequest req,HttpServletResponse res) throws Exception { List<Pessoa> lista = pessoaDAO.list(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject(“pessoas”,lista); return modelAndView; } }