Ouça seus imports

Gerenciar dependências é um dos aspectos mais importantes de um software tecnicamente bom. Baixo acoplamento é um dos principais objetivos de quem deseja um código mais fácil de ler, manter e reutilizar.

Com o passar do tempo, descobri que verificar os imports de uma classe Java é uma boa maneira de avaliar suas dependências.

Exemplo

Observe abaixo a seção de imports de uma classe Java, que pertence a um software comercial. Você consegue identificar algum problema nas dependências dessa classe?

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.io.FileTransfer;

Acima, podemos ver 5 grupos de dependências:

  • Com as bibliotecas do Java:  java.io.*, java.text.* e java.util.*
  • Com o Google Guice, um framework de injeção de dependências: com.google.inject.*
  • Com o JasperReports, um framework de relatórios:  net.sf.jasperreports.*
  • Com a API de Servlets: javax.servlet.*
  • Com o DWR, um framework de Ajax: org.directwebremoting.*

Classificando os imports, podemos descobrir mal-cheiros nas dependências e decidir se são problemas de fato.

Podemos, então, classificar as dependências em:

  • Infra-estrutura: bibliotecas do Java e Google Guice
  • Relatórios: JasperReports
  • Web: API de Servlets e DWR

Mesmo sem saber detalhadamente o contexto em que essa classe está sendo usada, o fato de uma classe depender do JasperReports e do DWR ao mesmo tempo levanta suspeitas de problemas nas dependências!

Na minha opinião, a classe acima tem um problema de muitas responsabilidades (ou seja, falta de coesão): é responsável por gerar um relatório mas, além disso, por buscar informações da Web.

Porém, sem saber mais detalhes sobre o código e onde a classe é utilizada, não podemos decidir se os imports suspeitos são de fato culpados.

Um pouco mais de contexto

Agora, olhe o pacote dessa classe:

package com.blablabla.reports;

Através do nome do pacote (reports), confirmamos que a classe é responsável por gerar relatórios.

Considerando o Princípio da Responsabilidade Única (SRP), essa classe só deveria depender de classes envolvidas na geração de relatórios.

Buscar informações da Web não deveria pertencer às atividades dessa classe. Essa classe deveria ser ignorante da existência da Web.

Ao invés disso, essas informações deveriam ser oferecidas por quem a utiliza através de parâmetros e/ou configurações.

Considerando modularização

Pode ser que uma classe geradora de relatórios depender da Web seja válido na arquitetura atual da aplicação.

Mas e se você quiser reutilizar essa relatório em uma outra aplicação ou em um software Desktop? Basta separar esse pacote de relatórios em um JAR separado e reutilizá-lo nesses outros softwares.

Seu módulo de relatórios deveria depender dos jars do JasperReports e do modelo de domínio da sua aplicação. Até aí, tudo bem.

Mas, além desses, também teria que depender dos jars da API de Servlets e do DWR. Mal cheiro no ar!

E conseguimos identificar esse mal cheiro apenas batendo o olho nos imports.

Na literatura

No excelente livro Growing Object-Oriented Software, Guided by Tests, os autores mencionam a análise dos imports no capítulo 17 (Teasing Main Apart).

In its current form, Main acts as a matchmaker but it’s also implementing some of the components, which means it has too many responsibilities. One clue is to look at its imports:

 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.awt.util.ArrayList;
 import javax.swing.SwingUtilities;
 import org.jivesoftware.smack.Chat;
 import org.jivesoftware.smack.XMPPConnection;
 import org.jivesoftware.smack.XMPPException;
 import auctionsniper.ui.MainWindow;
 import auctionsniper.ui.SnipersTableModel;
 import auctionsniper.AuctionMessageTranslator;
 import auctionsniper.XMPPAuction;

We’re importing code from three unrelated packages, plus the auctionsniper package itself.

O código acima tem 3 grupos de dependências:

  • Com as bibliotecas de UI do Java AWT e Swing: java.awt.* e javax.swing.*
  • Com o Smack, que implementa o protocolo XMPP:  org.jivesoftware.smack.*
  • Com o Auction Sniper, a aplicação que está sendo desenvolvida: auctionsniper.*

No resto do capítulo, os autores refatoram o código através de passos de bebê, com o objetivo de resolver os problemas de dependências indevidas.

Concluindo

Prestar atenção aos imports nos oferece pistas de problemas no gerenciamento de dependências do nosso código.

OO: Responsabilidades, Modelagem e Dependências

Ao projetar um software orientado por objetos, é possível usar três abordagens complementares:

Responsabilidade

Criar classes focadas (coesas) e com responsabilidades definidas é um dos objetivos das técnicas de Orientação a Objetos.

No clássico Applying UML and Patterns, Craig Larman descreve os princípios GRASP (General Responsibility Assignment Principle), que indicam várias maneiras de distribuir responsabilidades às classes que estão sendo projetadas.

Larman também aponta para a técnica Responsibility Driven Design (RDD), de Rebecca Wirfs-Brock. Ele cita:

Pense em objetos como pessoas com responsabilidades que colaboram com outras pessoas para fazer coisas. RDD enxerga projeto OO como uma comunidade de objetos colaborativos e responsáveis.

Uncle Bob fala sobre Single Responsibility Principle (SRP). De acordo com o SRP, um objeto deve ter uma, e apenas uma, responsabilidade.

A técnica CRC usa cartões para ajudar a descobrimos as Classes do nosso software, Responsabilidades de cada uma e as Colaborações entre essas classes. É uma prática recomendada no XP.

Modelagem

Modelar o domínio do problema que estamos resolvendo é um dos objetivos principais da Orientação a Objetos.

Na verdade, quando estamos pensando em termos de distribuição de responsabilidades, estamos modelando o nosso software. A sopa de letrinhas GRASP, RDD, SRP e CRC tem foco em responsabilidades, mas são técnicas de modelagem.

Criar um bom Domain Model, um modelo de alto nível do problema que queremos resolver, é uma das técnicas recomendadas por metodologias como FDD, DDD e RUP. O Domain Model ajuda na comunicação com os clientes, que são os especialistas no domínio.

Domain Driven Design (DDD) parte do Domain Model mas foca na importância de uma linguagem única na comunicação entre desenvolvedores e os clientes.

Gerenciamento de Dependências

Uncle Bob foca sua interpretação dos fundamentos de Orientação a Objetos em uma abordagem um pouco diferente. Além de modelagem (e responsabilidades), muita atenção deve ser dada às dependências entre as classes.

Os princípios SOLID descrevem algumas pontos importantes para um bom gerenciamento de dependências. O Single Responsibility Principle, mencionado acima, faz parte desses princípios.

Outro princípio SOLID, o Dependency Inversion Principle, recomenda que a dependência de objetos de módulos de alto nível em relação a módulos de nível mais baixo não deve ser direta. Essa dependência deve ser invertida, fazendo com que os módulos de alto nível dependam de abstrações.

A técnica de Injeção de Dependências é uma maneira elegante de atingir o princípio de Inversão de Dependências. Um efeito colateral interessante dessa técnica é a simplificação de testes unitários.

A abordagem de gerenciamento de dependências faz muito mais sentido quando evitamos criar softwares monolíticos e utilizamos módulos. A modularização facilita a manutenção, isola dependências e, se bem projetados, habilita o reuso. Alguns Padrões de Modularização foram documentados e estão altamente relacionados com Orientação a Objetos.

Objetos: Ignorantes, Apáticos e Egoístas

Ignorante, Apáticos e Egoístas

Kevlin Henney faz uma brincadeira interessante na palestra It Is Possible to Do Object-Oriented Programming in Java, ao dizer que objetos bem projetados são:

  • Ignorantes: não sabem de nada além do que tem contato direto
  • Apáticos: não estão nem aí para o que acontece ao seu redor
  • Egoístas: só tem interesse em seu próprio ponto de vista

Pensar em objetos dessa forma é pensar em termos de Responsabilidade Única, o que ajuda a minimizar suas dependências.

Identificando Oportunidades de Refatoração através de Métricas

Estava lendo o excelente artigo Medindo a complexidade do seu código no blog da Caelum e resolvi tirar da gaveta o artigo que fiz para a conclusão da Pós que fiz em Engenharia de Software + Métodos Ágeis em 2010/2011.

Por falta de tempo, aprofundei menos do que gostaria na pesquisa.

Se alguém quiser colaborar no estudo da relação entre métricas e refatorações, me dá um toque!

Segue o resumo do artigo:

A prática de refatoração é uma maneira efetiva de realizar a melhoria contínua do código fonte de um software, de maneira a permitir que mudanças nas necessidades de negócio dos clientes sejam implementadas com menos esforço.

Porém, a detecção de qual código deve ser refatorado depende muitas vezes da intuição de
especialistas.

Este artigo estuda o uso de métricas de código fonte na identificação de oportunidades de refatoração, de maneira a detectar pontos de melhoria de maneira quantitativa.

Foi realizado um estudo de caso de dois projetos comerciais em que métricas de código fonte foram levantadas e analisadas, de maneira a identificar pontos de melhoria e relacioná-los a possíveis refatorações.

Os resultados mostram o uso de métricas como uma ferramenta interessante para identificação de possíveis refatorações em uma base de código.

Porém, como uma mesma métrica pode estar relacionada a várias refatorações, a decisão de qual refatoração deve ser
efetuada ainda depende de intuição.

 

 

PDF para download:

Identificando Oportunidades de Refatoração através de Métricas