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.

Caracteres inválidos no seeds.rb

Ao executar o db:seed para fazer uma carga inicial de dados no Rails 3.0.5, rodando com o Ruby 1.9.2 no RVM, obtive o seguinte erro:


invalid multibyte char (US-ASCII)

O problema era que, no meu arquivo seeds.rb, havia Strings com caracteres inválidos para o encoding ASCII padrão. Por exemplo:


Genre.create(:name => "Ação")

Para resolver isso, basta inserir na primeira linha do seeds.rb o seguinte comentário:


# encoding: UTF-8

Parece que é um problema do método load do Ruby.

____________

Uma referência básica sobre encoding é o artigo The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!), do Joel Spolsky.

Problemas com a RVM no Ubuntu 10.10 (e a solução!)

Instalei o Ruby 1.9.2 a partir da RVM no Ubuntu 10.10 e ocorreram uns erros ao instalar a gem do Rails (falta do pacote zlib) e ao executar uma aplicação Rails (falta do pacote openssl).

Parece que no CentOS ocorre esse erro também.

Uma boa explicação sobre os problemas (e a solução) está no link abaixo:
http://www.flaviamissi.com.br/instalando-rvm-ruby-e-rails-no-ubuntu-10-10/

________________

Daniel Lopes escreveu sobre como instalar corretamente o RVM (além do Ruby 1.9.2 e a última versão do Rails):
https://github.com/danielvlopes/ruby-unix/blob/master/pt/ubuntu_10.10.md

Pra mim, faltou executar $rvm notes e instalar o que estava faltando.