Porque armazenar numéricos como String?

Uma dúvida comum para quem está começando é como armazenar coisas como CPF, RG, Telefone ou coisas do gênero. Devemos usar números ou texto?

São valores, à princípio, compostos por números. É natural pensar em armazenar como inteiro: int ou Integer no Java; INTEGER nos bancos de dados que atendem ao padrão ANSI).

Algumas pessoas pensam em valor com pontos flutuantes (float ou double) porque podem representar dígitos nas casas decimais.

Além disso, armazenar um número é muito mais eficiente (em termos de espaço) que armazenar um texto: no MySQL, um INT pode armazenar valores de até 9 dígitos em 4 bytes enquanto um VARCHAR teria que usar até 10 bytes.

Mas será que economizar bytes de armazenamento é tão importante em uma época que você pode levar uns 32 bilhões de bytes no bolso?

Armazenar RG, CPF ou Telefone como texto é vantajoso porque:

  • zeros à esquerda são importantes e números iriam removê-los;
  • valores negativos não fazem sentido (e não existem unsigned ints em Java);
  • não faremos cálculos ou contagens com esses valores;
  • o importante não são os números, mas os caracteres que, por acaso, são numéricos;
  • o RG, por exemplo, pode ter letras. É regra em Minas Gerais;
  • guardar a formatação original com, p. ex., parênteses e traços pode ser interessante.

Ao modelar a sua aplicação, é fundamental comparar as alternativas. Facilidade de manutenção, corretude, rapidez de desenvolvimento (não gosto de usar o termo produtividade) devem ser favorecidas em relação a performance crua.

Sequências Infinitas em Scala e Java

Sequências Infinitas?

Em um workshop com o pessoal do trabalho, iríamos começar mais uma implementação de Fibonacci.

Acabamos definindo uma API parecida com a abaixo:

public List<Integer> fibonacci();

Porém, para conseguir fazer algo do gênero, é necessária uma maneira de representar uma sequência infinita de valores.

No workshop, como precisávamos de foco, mudamos a API para receber um número e retornar o número de Fibonacci correspondente.

Só que há uma maneira de representar essa sequência infinita em um computador…

Fibonacci?

Só para lembrar,  conforme está descrito na Wikipedia, a sequência de Fibonacci “é uma sequência de números naturais, na qual os primeiros dois termos são 0 e 1, e cada termo subsequente corresponde à soma dos dois precedentes”.

Lazy Evaluation

Na teoria de linguagens de programação, há o conceito de Lazy Evaluation, em que o runtime adia a avaliação de um trecho de código até quando esse código realmente precisar ser executado.

Esse conceito está presente principalmente em Programação Funcional, em linguagens como Haskell, Scheme ou Scala.

Em Scala

Com a linguagem de programação Scala, é possível criar uma  uma função recursiva que calcula a sequência de Fibonacci em que não há condição de parada!

def fibonacciSequence : Stream[Long] = {
    def fibonacciFrom(a: Long, b: Long) : Stream[Long] = a #:: fibonacciFrom(b, a+b)
    fibonacciFrom(0, 1)
}

Mas como isso é possível? Como o código acima não entra em recursão infinita até gerar um StackOverflowError?

No código acima, é utilizada uma estrutura de dados chamada Stream, que é uma lista cuja avaliação da concatenação (o #::) é lazy. Por isso, a chamada recursiva só é feita quando realmente é necessária.

Se chamarmos diretamente a função fibonacciSequence a partir do REPL do Scala será retornado:

scala> fibonacciSequence
> res0: scala.collection.immutable.Stream[Long] = Stream(0, ?)

Observe que só o primeiro valor, que no caso é 0, será retornado. O resto da sequência ainda não foi avaliado, o que é representado como ?.

Se utilizarmos a função take para pegarmos os 5 primeiros valores dessa sequência, ainda assim apenas o primeiro valor é avaliado:

scala> fibonacciSequence take 5
> res1: scala.collection.immutable.Stream[Long] = Stream(0, ?)

Para realmente avaliar os valores, temos que utilizá-los, por exemplo, tranformando para uma lista que não é lazy com um toList ou percorrendo-os com um foreach:

scala> fibonacciSequence take 5 toList
> res2: List[Long] = List(0, 1, 1, 2, 3)

scala> fibonacciSequence take 5 foreach println
> 0
| 1
| 1
| 2
| 3

Em Java

Recentemente, o polêmico Uncle Bob tem escrito sobre como utilizar conceito de Programação Funcional em Java e tem recebido diversas críticas.

Em um de seus artigos, Uncle Bob mostrou como utilizar lazy evaluation em Java utilizando um Iterator. Há um exemplo mostrando o uso de Iterator para criar uma sequência infinita de inteiros elevados ao quadrado.

Na verdade, Neal Ford já havia mostrado como usar um Iterator para obter lazyness com Java. O exemplo utilizado foi o cálculo de números primos.

Com Iterator

Através da técnica do Iterator, é possível fazer um código bem simples para gerar uma sequância “infinita” com os números de Fibonacci:

public class FibonacciSequence implements Iterator<Long> {
    private long n = 0;
    private long a = 0;
    private long b = 1;

    public boolean hasNext() { return a + b > 0; }

    public Long next() {
        long fib;
        if(n == 0 || n == 1) {
            fib = n;
        }
        else {
            fib = a + b;
            a = b;
            b = fib;
        }
        n++;
        return fib;
    }

    public void remove() {}
}

As variáveis a e b armazenam os valores de Fibonacci dos números anteriores. O número atual é mantido na variável n.

Os dois primeiros valores da sequência 0 e 1, são retornados diretamente, sem atualizar as variáveis a e b.

O Fibonacci de um dado número n é a soma dos números de Fibonacci precedentes, portando é a + b.  Para preparar a sequência para os próximos valores, as variáveis a e b são atualizadas com o número de Fibonacci anterior e o atual, respectivamente.

A implementação do método hasNext considera se a soma de a + b ainda é positiva, para evitar Integer Overflow, o que faria com que os números a e b ficassem com valores negativos.

Podemos utilizar o código acima, por exemplo, para pegar os 10 primeiros números na sequência:

FibonacciSequence fs = new FibonacciSequence();
for(int i = 0; i < 10 && fs.hasNext(); i++){
    System.out.println(fs.next());
}

Podemos criar o objeto Taker criado por Uncle Bob para simular a função take do Scala:

public class Taker<T> {
    public List<T> take(int n, Iterator<T> xs) {
        List<T> taken = new ArrayList<T>();
        for (int i = 0; xs.hasNext() && i < n; i++) {
           taken.add(xs.next());
        }
        return taken;
    }
}

Se utilizarmos o Taker para gerar a sequência dos 25 primeiros números de Fibonacci, teríamos o seguinte código:

List<Long> fibonacciUpto25 = new Taker<Long>().take(25, new FibonacciSequence());

Com Functional Java

A biblioteca Functional Java tem uma estrutura de dados Stream parecida com a do Scala, que permite lazy evaluation.

Baseados em um exemplo da documentação da biblioteca, poderíamos definir uma variável no nosso programa que contém a sequência de Fibonacci:

public static final Stream<Long> fibonacciSequence = new F2<Long, Long, Stream<Long>>() {
    public Stream<Long> f(final Long a, final Long b) {
        return Stream.cons(a, curry().f(b).lazy().f(a + b));
    }
}.f(0L, 1L);

O método cons da classe Stream é correspondente ao #:: do Scala.

A classe Stream provê um método take. Assim, para obter nossa lista com os 25 primeiros números da sequência de Fibonacci:

List<Long> fibonacciUpto25 = fibonacciSequence.take(25).toList();

Com Totally Lazy

Já a biblioteca Totally Lazy tem uma função embutida na classe Numbers que gera a sequência de Fibonacci. Essa função é implementada da seguinte forma:

public static Sequence<Number> fibonacciSequence  =
computation(Pair.<Number, Number> pair(0, 1), reduceLeftShift(sum)).map(first(Number.class));

Para obter nossos 25 primeiros números de Fibonacci:

List<Number> fibonacciUpto25 = fibonacciSequence.take(25).toList();

A abordagem do Totally Lazy é bem diferente das demais.  Observe que a classe Number é utilizada, ao invés de Long.

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.