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.

Documentação Leve

Documente o mínimo possível.

O propósito de você documentar é comunicar o que você fez (ou o que precisa ser feito) para alguém distante temporal ou fisicamente.

Documentação é uma das formas de se levar ao Entendimento, mas o foco deve ser o Entendimento.

Documentação é apenas um meio e, em boa parte dos casos, não é o mais eficiente.

O que importa é o Entendimento, não o documento.

É importante evitar o padrão recorrente de só se comunicar por documentos. O analista manda um .doc para o cliente que aprova (sem ler). O desenvolvedor recebe o documento e, apesar de ver erros, fica calado e faz o que está escrito. Isso é péssimo e deve ser evitado.

Comunicando a parte técnica

No caso de gente nova na equipe, crie um workshop mostrando a motivação do sistema e percorrendo as telas, se for o caso. Isso dá o contexto geral.

Também é importante mostrar a arquitetura do sistema, uma visão de alto nível.

Ler código não é a melhor forma de comunicar a arquitetura.

Usar UML, nesse caso, é uma boa. Você pode se basear no modelo 4+1 de visão arquitetural, mas usar de maneira mais leve:

  • Modelo de domínio – com as entidades importantes em termos de negócio e os relacionamentos entre elas
  • Diagrama de componentes – mostrando os principais pacotes em que o sistema está organizado
  • Diagrama de implantação – mostrando como é implantado fisicamente o sistema quando em produção

Mas é importante que estes documentos estejam atualizados. Se não estiverem, utilize a oportunidade para atualizá-los.

Comunicando com o cliente

Para novos clientes, fazer um workshop e demonstrar o sistema e casos de sucesso parece algo bem profissional e, de certa forma, um marketing ativo.

É interessante ter manuais do usuário com telas e informações relevantes, além de um FAQ. Talvez, a melhor forma de publicar essa documentação é em um site ou wiki.

É importante focar nas funcionalidades mais complexas. Não há a necessidade de colocar funcionalidades básicas nos manuais.

Dica: se o seu software precisar de muitos manuais e FAQs, pode ser que ele precise de um investimento em usabilidade.

Também pode ser interessante fazer vídeos que demonstram como utilizar as funcionalidades mais avançadas.

Para ler
Alguns pensamentos sobre “documentação ágil”, do Guilherme Chapiewski