24 de set de 2013

Métodos Estáticos de Fabricação: uma alternativa aos construtores, mais elegante e comunicativa

Métodos Estáticos e Fabricação, em inglês Static Factory Methods, servem como alternativa mais inteligível aos construtores.

Construtores são necessários, obviamente, mas eles tem um pequeno problema: não tem nome.

Considere uma classe Dado e um objeto instanciado a partir dela. Sabendo que cada Dado tem número de lados determinado, quantos lados tem este dado:

Dado dado = new Dado();
dado.joga();
System.out.println(dado);

Digamos que a implementação de Dado preveja como padrão 6 lados e dê a opção de construir um Dado com um número arbitrário de lados (sem validações para brevidade):

package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    public Dado() {        
        this(6); // cria um dado de seis lados por padrão
    }    

    public Dado(int lados) {
        this.lados = lados;
    }

    public int getNumero() { return numero; }

    public int getLados()  { return lados; }

    public void joga() { numero = (int) (random() * lados + 1); }

    @Override
    public String toString() { return String.valueOf(numero); }
    
}

Mesmo que instanciemos o Dado passando lados, quem lê apenas a construção e não tem acesso ao código-fonte não pode afirmar com certeza o significado do parâmetro, veja a seguir:

Dado dado = new Dado(10);
dado.joga();
System.out.println(dado);

É intuitivo pensar que Dado(10) é um Dado com 10 lados, mas não é algo implícito.

Embora eu programe com mais frequência em Java, também estou atento e programo em outras linguagens, que me faz melhor como programador Java. Como assim? Bem, aprender diferentes culturas de implementação te ajudam a ver os problemas de mais pontos de vista. Um ditado presente no Zen do Python diz:Explicit is better than implicit

Nesta implementação do Dado, o construtor é implícito, ou seja, nós deduzimos que Dado(10) é um Dado com *dez* lados pois está implícito.

Que tal tornar explícito? Não é complicado, basta adicionar um método fábrica. Veja exemplo:


package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    // método para fabricação de Dados
    public static Dado lados(int lados) {
        return new Dado(lados);
    }

    public Dado() {        
        this(6); // cria um dado de seis lados por padrão
    }    

    public Dado(int lados) {
        this.lados = lados;
    }

    public int getNumero() { return numero; }

    public int getLados()  { return lados; }

    public void joga() { numero = (int) (random() * lados + 1); }

    @Override
    public String toString() { return String.valueOf(numero); }
    
}

A grande vantagem de um método fábrica é que ele tem nome e com nome ele demonstra a intenção explicitamente. Veja uma situação de uso:

Dado dado = Dado.lados(10); // bem mais claro, certo
dado.joga();
System.out.println(dado);

Ainda é possível predefinir construções comuns com métodos sem parâmetros, por exemplo, dados de 6, 10 e 20 lados.

package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    public static Dado lados(int lados) {
        return new Dado(lados);
    }

    // métodos de fabricação com parâmetro constante
    public static Dado deSeisLados()  { return lados(6); }

    public static Dado deDezLados()   { return lados(10); }

    public static Dado deVinteLados() { return lados(20); }

    // ... restante omitido

Veja a clareza que fica:

Dado dado = Dado.deDezLados(); // bem mais claro que: new Dado(10)
dado.joga();
System.out.println(dado);

Mais detalhes podem ser vistos no livro do Josh Bloch:
http://www.submarino.com.br/produto/6800218/livro-java-efetivo

Vale a pena gasta um minuto para ler o Zen do Python:
http://www.python.org/dev/peps/pep-0020/

O código-fonte dos exemplos está disponível aqui: https://github.com/marciojrtorres/livro-aps/tree/master/static_factory_method

4 de ago de 2013

Return Early: um opção para simplificar expressões condicionais

Há uma grande polêmica sobre ter ou não vários returns em um método. Alguns defendem que deve haver apenas um retorno. Outros defendem que deve-se retornar assim que houver um valor a devolver.

Enfim, este artigo aplica a segunda técnica, onde retornamos o mais cedo possível. Esta técnica é conhecida como:

RETURN EARLY

Dar um retorno cedo ajuda a descomplicar expressões condicionais. Bem, tudo fica melhor com um exemplo não é? Considere uma classe Seguranca, usada para computar o Nivel de Acesso de um dado Usuario. Veja a seguir:

public class Seguranca {
 
 public enum NivelAcesso {
  Nenhum, Visitante, Operador, Administrador;
 }

 public static NivelAcesso privilegio(Usuario usuario) {
  NivelAcesso nivel = null;

  if (!usuario.isBloqueado()) {
   if (usuario.getCargo() != null) {
    if (usuario.getCargo() == Usuario.Cargo.Gerente) {
     nivel = NivelAcesso.Administrador;
    } else if (usuario.getCargo() == Usuario.Cargo.Funcionario) {
     nivel = NivelAcesso.Operador;
    }
   } else {
    nivel = NivelAcesso.Visitante;
   }
  } else {
   nivel = NivelAcesso.Nenhum;
  }

  return nivel;  
 }
 // ... 

Esta implementação aplica um único ponto de saída, um único retorno no fim do método. Isto é bom, por um lado, pois facilita acompanhar o fluxo da lógica, e ruim por outro, pois começa a aninhar as expressões condicionais.

A seguir uma implementação alternativa, uma refatoração, onde é usado o return early:

public class Seguranca {
 
 public enum NivelAcesso {
  Nenhum, Visitante, Operador, Administrador;
 }

 public static NivelAcesso privilegio2(Usuario usuario) {

  if (usuario.isBloqueado()) {
   return NivelAcesso.Nenhum;
  }

  if (usuario.getCargo() == Usuario.Cargo.Gerente) {
   return NivelAcesso.Administrador;
  }

  if (usuario.getCargo() == Usuario.Cargo.Funcionario) {
   return NivelAcesso.Operador;
  }

  return NivelAcesso.Visitante;
 }

 // ... 

A mecânica do return early é simples: usar as regras mais restritivas antes, por exemplo, no caso de usuário estar bloqueado já não é necessário checar cargo e demais condições. A regra geral fica no fim, ou seja, no exemplo, se não está bloqueado e não há cargo definido então é visitante.

Para finalizar, esta técnica é independente de linguagem, mesmo que os exemplos estejam escritos em Java.

Código fonte disponível aqui: https://github.com/marciojrtorres/livro-aps/tree/master/return_early

29 de mai de 2013

PHP: obtenha a melhor performance ao percorrer arrays

Os que me conhecem sabem que não sou grande fã do PHP, muito pelo contrário, é uma das linguagens mais deselegantes que conheço, contudo programo em PHP sempre que necessário e não tenho medo de sujar as mãos :)

Para ninguém dizer que não contribuo com a comunidade PHP trouxe este post, que partiu de um tópico na lista do curso de Análise e Desenvolvimento de Sistemas no IFRS, que fala a respeito de como iterar arrays no PHP obtendo a melhor performance.

Quando digo performance, não pense que seus programas PHP irão dobrar de velocidade, na verdade são micro-otimizações, ou seja, reduzem o tempo de resposta em milisegundos até décimos de milisegundos, mas que na quantidade de acessos simultâneos, por exemplo um Web Site com muitos page views, pode trazer ganhos significativos.

Indo ao ponto, os arrays no PHP, ou arrays associativos, não são como os arrays primitivos de outras linguagens, eles são na verdade um map, ou dicionário, e são armazenados como hash tables através de bindings em C. Dito isso, há características interessantes que trazem alterações na performance dependendo da maneira como é feito, por exemplo, este modo de iterar um array tem pouca performance:

$elementos = array();
 
for ($i = 0; $i < 1000000; $i++) $elementos[$i] = rand();    

$inicio = microtime(true);

for ($i = 0; $i < count($elementos); $i++) {
    $elementos[$i];
}    

$fim = microtime(true);

echo ($fim - $inicio);
Esta implementação executa em 0.366 segundos no meu humilde notebook que é muito tempo se comparada a este implementação a seguir que executa em 0.106 segundos:
$elementos = array();
 
for ($i = 0; $i < 1000000; $i++) $elementos[$i] = rand();    

$inicio = microtime(true);

for ($i = 0, $count = count($elementos); $i < $count; $i++) {
    $elementos[$i];
}    

$fim = microtime(true);

echo ($fim - $inicio);
A grande diferença está na função count, a qual é custosa em tempo, logo é melhor fazê-la uma única vez, na inicialização do for, em vez de introduzi-la na condição de saída. Mas não acabou ainda, esta implementação é ainda melhor (0.085 segundos):
$elementos = array();
 
for ($i = 0; $i < 1000000; $i++) $elementos[$i] = rand();    

$inicio = microtime(true);

foreach ($elementos as $e) {
    $e;
}   

$fim = microtime(true);

echo ($fim - $inicio);

O foreach é otimizado para atravessar o array através da estrutura de dados utilizada, uma lista ligada (linked list).

Existem outras micro-otimizações que trarei num próximo post.

24 de mai de 2013

Refatoração: substitua exceção por teste

Durante uma aula surgiu uma implementação feita por um aluno, créditos ao Matheus Cezar, onde se encaixa uma refatoração bem interessante: substituir exceção por teste.

A crônica é a seguinte: considere um método que retorne um elemento de uma coleção pelo índice ou nulo caso não exista o dado elemento. Um código cobaia pode ser visto a seguir.

public class Main {

    static String[] opcoes = {"zero", "um", "dois"};
    
    public static void main(String[] args) {
        
    }
    
    // retornar a opção ou nulo caso não seja encontrada
    static String getOpcao(int numero) {
        // implementação aqui
    }
}

Existem vários modos de cumprir a API do método, uma delas é introduzindo um mau cheiro no código, utilizando a captura de exceções como desvio condicional. É bem simples, veja a seguir:

public class Main {

    static String[] opcoes = {"zero", "um", "dois"};
        
    public static void main(String[] args) {
        System.out.println(getOpcao(9));
    }
    
    // retornar a opção ou nulo caso não seja encontrada
    static String getOpcao(int numero) {
        try {
            return opcoes[numero];
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
        
    }
}

Esta é uma implementação intuitiva a primeira vista, note que ela resolve o problema, ou seja, neste exemplo é impresso null, pois como não há o índice 9 o código opcoes.get(numero) lança a exceção ArrayIndexOutOfBoundsException que é capturada e suprimida, retornando null na situações excepcionais.

Existem um detalhe importante a respeito de exceções, elas introduzem um overhead, já que cada vez que acontecem, além do bloco condicional um objeto é criado para representar a exceção.

O que eu quero dizer é que esta implementação, baseada na captura da exceção, tem baixa performance se comparada com a realização de um teste, que é a refatoração proposta e inclusive documentada no livro do Fowler.

Escrevi um pequeno benchmark para instrumentar este código, veja a seguir:

public class Main {

    static String[] opcoes = {"zero", "um", "dois"};
    
    public static void main(String[] args) {
        long inicio = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            getOpcao(9);
        }
        System.out.println((System.currentTimeMillis() - inicio) + "ms");
    }
    
    // retornar a opção ou nulo caso não seja encontrada
    static String getOpcao(int numero) {
        try {
            return opcoes.get[numero];
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
        
    }
}
O tempo decorrido para executar 10000 (dez mil) consultas é de 110ms no meu modesto notebook equipado com um Intel Pentium Dual Core. A refatoração a seguir torna o código mais claro e também mais rápido, é a substituição de exceção por teste, veja a seguir:
public class Main {

    static String[] opcoes = {"zero", "um", "dois"};
    
    public static void main(String[] args) {
        long inicio = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            System.out.println(getOpcao(9));
        }
        System.out.println((System.currentTimeMillis() - inicio) + "ms");
    }
    
    // retornar a opção ou nulo caso não seja encontrada
    static String getOpcao(int numero) {
        if (numero >= 0 && numero < opcoes.length)        
            return opcoes[numero];        
        return null;
    }
}

Esta implementação executa em 5ms em média, bem melhor do que os 110ms anteriores, não? A mecânica é simples, em vez de confiar no catch nós fazemos um teste para verificar a validade do parâmetro.

É isso, deixe seu comentário se quiser.

7 de abr de 2013

Fail-Fast (Falhe-Rápido), contra-intuitivo mas necessário

Naturalmente, os programadores não querem que seus programas caiam, desapareçam na frente do usuário. Contudo isto pode ser necessário na maior parte das vezes, para que o programa não siga operando em um estado inválido, ou seja, frequentemente é melhor fazer o programa cair do que deixá-lo operando de forma incorreta, o que pode aumentar os danos.

O princípio de projeto que argumenta esta ideia é chamado de Fail-Fast, que significa Falhe-Rápido. Tanto o conceito como implementação são realmente simples: fazer o programa cair ao detectar uma operação ilegal em vez de tentar contornar e seguir operando.

Como um pequeno exemplo, considere um método (ou função) que realiza operações de soma sobre números. Poderia ser qualquer operação, mas neste exemplo vou trazer a simples adição de dois números:

Método para somar dois números:
class Numeros {
 
    public static int soma(int a, int b) {
        return a + b;
    }
}
 
// situação de uso:
int s = Numeros.soma(34, 23); // s == 57

Esta implementação parece funcionar bem, em vários testes ela dá um resultado previsível e correto, mas se somarmos quaisquer números que dê como resposta um número maior que 2147483647 (o valor máximo aceito pelo tipo INT) então teremos uma surpresa.

Quando a operação está incorreta:
class Numeros {
 
    public static int soma(int a, int b) {
        return a + b;
    }
}
 
int s = Numeros.soma(1000000000, 2000000000); // deveria dar 3000000000 (3 bilhões)
System.out.println(s); // mas imprime -1294967296

Este tipo de erro é chamado de Buffer Overflow. Quando a soma ultrapassar o limite do Inteiro, acontece a mudança de sinal e a diferença de valor. Note que esta operação não causa falha, ou seja, se o programa depende deste cálculo então ele recebe o valor incorreto e segue funcionando com base neste valor, provavelmente levando a mais operações incorretas e causando mais danos.

Este exemplo é um candidato a aplicação do Princípio do Fail-Fast, onde tentaremos lançar uma exceção sempre que o valor transbordar (overflows).

É bem simples, sempre que forem somados dois números positivos então a resposta deve ser positiva e se forem somados dois números negativos então a resposta deve ser negativa. O método seria implementado assim:

Protegendo contra Buffer Overflow fazendo a aplicação cair (Exception):
class Numeros {
     
    public static int soma(int a, int b) {
         
        int r = a + b;
         
        if (a > 0 && b > 0 && r < 0) {
            throw new RuntimeException("BufferOverflow");
        }
         
        if (a < 0 && b < 0 && r > 0) {
            throw new RuntimeException("BufferOverflow");
        }
                 
        return r;
         
    }
     
}
 
int s = Numeros.soma(1000000000, 2000000000);

// deveria dar 3000000000 (3 bilhões),
// mas daria -1294967296, logo se o resultado não é correto
// então o ideal é lançar uma exceção e deixar o programa cair:

Exception in thread "main" java.lang.RuntimeException: BufferOverflow
    at javaapplication556.Numeros.soma(Main.java:58)
    at javaapplication556.Main.main(Main.java:17)
Java Result: 1

Existe um excelente artigo sobre Fail-Fast escrito por James Shore disponível aqui: http://martinfowler.com/ieeeSoftware/failFast.pdf.

James diz que algumas pessoas recomendam fazer um software robusto que contorna os problemas e resulta na falha gradativa. Isto é, o programa continua funcionando após o erro mas acaba falhando mais adiante, entretanto de uma maneira estranha. Um sistema que falha rápido faz justamente o contrário. As pessoas relutam em aplicar pois faz parecer que o software é mais frágil, mas é justamente o contrário.

Jim coloca um exemplo onde é considerada a leitura de uma propriedade em um arquivo de configuração, e pode ser que a propriedade não esteja presente então é devolvido um valor padrão:

Um método que NÃO falha rápido (em C#):
public int maxConnections() {
 
    string property = getProperty(“maxConnections”);
 
    if (property == null) {
        return 10;
    } else {
        return property.toInt();
    }
}

Em contraste, um método que falha rápido seria escrito assim:

Um método que FALHA RÁPIDO (em C#):
public int maxConnections() {
 
    string property = getProperty("maxConnections");
 
    if (property == null) {
        throw new NullReferenceException("maxConnections property not found in " +
                                         this.configFilePath);
    } else {
        return property.toInt();
    }
}

Escolher se as operações em um sistema devem falhar ou seguir operando mesmo com valores incorretos e resultados imprevisíveis é uma decisão de projeto importante. Cabe ao desenvolvedor avaliar os riscos envolvidos e utilizar a melhor estratégia.

Particularmente sou a favor do Fail-Fast, por tornar o sistema mais fácil de depurar, trazendo informações da falha imediatamente quando ela acontece, em vez de expor algum "erro loco" lá adiante.

Cabe salientar que nem sempre Fail-Fast se aplica, como em alguns programas que devem seguir operando mesmo quando há erro pois não tem uma segunda chance, como um aplicativo embarcado em um foguete que está tentando pousar. É melhor tentar pousar mesmo após o erro do que abortar a missão que custou milhões.

22 de mar de 2013

Padrão de implementação VARARGS (funções variádicas) e sua relação com o princípio Zero One Infinity

É uma situação comum termos de escrever um método (ou função) que aceite um número variável de argumentos.

Existem algumas técnicas para atingir isso, por exemplo, usando sobrecarga ou varargs.

Para tratar destas técnicas vou começar com um estudo de caso baseado em uma API bem simples: considere uma classe utilitária com um método utilizado para gerar o caminho de pão, ou popular breadcrumb. Este método recebe um caractere separador a seguir os níveis 1, 2 e 3. Um separador e o nível 1 são argumentos obrigatórios, já o nível 2 e 3 são opcionais. Exemplo de uso da API:

// Imprime: > Home
System.out.println(WebHelper.caminhoPao('>', "Home"));
// Imprime: > Home > Eletrônicos
System.out.println(WebHelper.caminhoPao('>', "Home", "Eletrônicos"));
// Imprime: > Home > Eletrônicos > TV's
System.out.println(WebHelper.caminhoPao('>', "Home", "Eletrônicos", "TV's"));

Um modo de implementar, mesmo sendo uma implementação ingênua com sobrecarga de método, poderia parecer-se com isso:

class WebHelper {
     
    public static String caminhoPao(char separador, String nivel1) {
        return separador + " " + nivel1;
    }
     
    public static String caminhoPao(char separador, String nivel1, String nivel2) {
        StringBuilder caminho = new StringBuilder(separador + " " + nivel1);
        return caminho.append(" ")
                      .append(separador)
                      .append(" ")
                      .append(nivel2)
                      .toString();
    }
     
    public static String caminhoPao(char separador, String nivel1, String nivel2, String nivel3) {
        StringBuilder caminho = new StringBuilder(separador + " " + nivel1);
        return caminho.append(" ")
                      .append(separador)
                      .append(" ")
                      .append(nivel2)
                      .append(" ")
                      .append(separador)
                      .append(" ")
                      .append(nivel3)
                      .toString();
    }
}

Esta implementação problemas mais e menos evidentes (problemas de projeto, não de implementação).

O problema mais evidente é a duplicação, ou seja, o método viola o Princípio de Projeto DRY: não há reaproveitamento de código, os métodos tem o mesmo propósito, mas implementam toda a funcionalidade novamente. Como seria se precisássemos de quatro níveis?

O problema menos evidente é a rigidez, a falta de flexibilidade para o caso de, no futuro, precisarmos de mais níveis. Esta rigidez também viola um Princípio de Projeto pouco conhecido, o ZOI, Zero One Infinity Rule, em português Regra do Zero Um Infinito.

O ZOI afirma que não deve ser imposto qualquer limite arbitrário de instâncias de qualquer entidade. Em outras palavras, uma entidade deve ser totalmente proibida, ou uma instância pode ser aceita, ou então qualquer número de instâncias. É a típica sensação de, por exemplo, se são aceitos dois argumentos, então por que não três? E por que não quatro? Deveria haver um limite?


<off-topic>Isaac Asimov em seu livro "Os próprios Deuses" afirma que "o número 2 é ridículo e não deveria existir", se referindo a universos no sentido de, se tu aceitas que existe 2 universos, ou seja, que não existe um único universo, então tens que aceitar a possibilidade de existir n universos.</off-topic>


Toda esta explicação inicial é para embasar e dar motivação a aplicação de VARARGS, que é um modo mais elegante de permitir um número variável de argumentos em um método/função, consolidando uma Função Variádica. Cada linguagem tem uma notação especial para definir Funções (ou Métodos) Variádicas, em Python é usado *args, já em Java é usado Tipo... args. A seguir a implementação em Java:

class WebHelper {
     
    public static String caminhoPao(char separador, String nivel1, String... demaisNiveis) {
 
        StringBuilder caminho = new StringBuilder(separador + " " + nivel1);
                 
        for (String nivel : demaisNiveis) {
            caminho.append(" ")
                    .append(separador)
                    .append(" ")
                    .append(nivel);
        }
         
        return caminho.toString();
    }
}

Com essa assinatura no método fazemos com que o separador e o nivel1 sejam argumentos obrigatórios sendo opcional o último argumento, ou seja, passar os níveis 2, 3 e assim por diante. A variável definida no parâmetro variável é um array do tipo especificado e pode ser iterado para tratar os valores recebidos. Com a implementação acima é possível fazer qualquer uma das chamadas abaixo sem problemas:

// demaisNiveis = [] e Imprime: > Home
System.out.println(WebHelper.caminhoPao('>', "Home"));
// demaisNiveis = [] e Imprime: > Home > Eletrônicos
System.out.println(WebHelper.caminhoPao('>', "Home", "Eletrônicos"));
// demaisNiveis = ["Eletrônicos", "TV's"] e Imprime: > Home > Eletrônicos > TV's
System.out.println(WebHelper.caminhoPao('>', "Home", "Eletrônicos", "TV's"));
// demaisNiveis = ["Eletrônicos", "TV's", "LED"] e Imprime: > Home > Eletrônicos > TV's > LED
System.out.println(WebHelper.caminhoPao('>', "Home", "Eletrônicos", "TV's", "LED"));


Espero que tenha sido útil a informação e qualquer comentário, sugestão, dúvida fique a vontade para comentar.

12 de dez de 2012

Conversão de números em strings

Emergiu uma pequena dúvida, se é mais adequado converter números para string usando o "gambi mode":
int i = 4;
String s = i + "";
Ou usando a API do Java:
int i = 4;
String s = String.valueOf(i);
Bem, em outros tópicos já falei sobre o funcionamento de Strings no Java, e inclusive no meu blog, que anda desatualizado, publiquei um post sobre Strings: http://marciojrtorres.blogspot.com.br/2010/06/strings-no-java.html

A questão é que o primeiro modo resulta na criação de uma nova String resultando da operador + sobrecarregado, e a segundo a partir da estrutura interna de Strings e Integer para criar uma String a partir de um array de char, onde muitos são constantes de Integer, podem verificar o código-fonte.

Na prática, o segundo modo é mais rápido, podem usar o código a seguir para testes:
public static void main(String[] args) {
    converteInteiroStringGambi();
    converteInteiroStringJava();
}

private static void converteInteiroStringGambi() {
    long inicio = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = i + "";
    }
    System.out.println("Tempo decorrido: " + (System.currentTimeMillis() - inicio) + "ms");
}

private static void converteInteiroStringJava() {
    long inicio = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.valueOf(i);
    }
    System.out.println("Tempo decorrido: " + (System.currentTimeMillis() - inicio) + "ms");
}
Atenção, não estou fazendo apologia ao segundo modo, usando String.valueOf, na verdade, podem seguir usando o primeiro, mas com a consciência de que é mais lento. A diferença é ínfima em uma ou duas operações esporádicas, mas evidente quando é usando dentro de iterações, como no exemplo. Enfim, sabendo as regras, é só saber quando segui-las e quando quebrá-las, é uma prerrogativa de programar deliberadamente e não por coincidência.

15 de mar de 2012

Lanço uma Exceção ou não? Depende.

Esses dias estava conversando na aula de Aspectos Avançados sobre o tratamento de erros em métodos.

Tipicamente e basicamente, existem duas formas de lidar com isso no Java: com exceções ou sem exceções. Simples assim.

É possível usar, tradicionalmente, um código de erro no retorno, ou retornar um valor nulo ou equivalente, que é a melhor opção (e única) para linguagens que não possuem estruturas de Exceção.

Ou, a cada parâmetro inválido ou problema na computação de um resultado, lançar uma Exceção apropriada a ser tratada (ou não) pelo chamador.

Tá, e daí? Quais são as diferenças práticas?

Bem, claramente o uso de Exceções enriquece o tratamento, já que podem ser personalizadas e enquanto objetos podem carregar diversas informações sobre o erro. Por exemplo, uma mesma exceção de acesso a um SGBD pode carregar, além da mensagem de erro, o código de erro SQL, a instrução submetida ao banco, a versão do banco, etc.

Por outro lado, sendo objetos, criar e lançar Exceções tem um custo de processador, para criar, inicializar e destruir o objeto, e memória, para armazená-lo. Ou seja, usar Exceções implica em um decréscimo de performance.

Não, não sou contra o uso de Exceções, não me entenda mal. Como pensador de projetos de sistemas quero colocar que devemos avaliar os prós e contras de diversas abordagens e, baseado nesses prós e contras, tomar decisões.

Neste caso, temos (mais) um problema de "cobertor curto": maior riqueza de informação (bom), maior uso de recursos (cpu, mem, disk, ou seja, ruim), menor riqueza de informação (ruim), menor uso de recursos (bom). Enfim, é um dilema.

Para ilustrar uma diferença de performance no uso ou não de Exceções criei um pequeno programa para calcular volume. Ele simplesmente multiplica três inteiros, desde que sejam positivos e aí está o tratamento de erro, zero ou números negativos não são permitidos. O código-fonte está aqui: ExPerfTest.java

Na primeira abordagem, um parâmetro zero ou negativo invalida o cálculo e sempre retorna 0 como volume. Ou seja, se eu chamar volume(10, -20, 30) ele retorna 0, não lança Exceções. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 8 milissegundos em média. A seguir está o método:
public static int volume(int a, int b, int c) {
    return a < 0 ? 0 : b < 0 ? 0 : c < 0 ? 0 : a * b * c;
}
Na segunda abordagem, um parâmetro zero ou negativo invalida o cálculo e lança uma Exceção. Ou seja, se eu chamar volume(10, -20, 30) ele cria (new) e lança (throw) um objeto da classe Exception. No meu Notebook, a execução deste método 100 mil vezes com valor inválido é realizada em 190 milissegundos em média. A seguir está o método:
public static int volume2(int a, int b, int c) throws Exception {
    if (a < 0 || b < 0 || c < 0) throw new Exception("número inválido");
    return a * b * c;
}
Claro, tenho que deixar claro que, neste projeto, há uma sobrecarga da própria instrumentação (em especial o Late Binding da Interface). Na prática esses tempos são menores, mas aproximadamente na mesma proporção, o que não muda o fato de Exceções reduzirem a performance. O projeto inteiro (no NetBeans) pode ser baixado neste link: exception-perf-test.zip

Finalizando, é importante deixar claro que a redução de performance é visível em várias chamadas ao método. Executando o mesmo método com e sem exceções apenas 3 vezes a diferença é praticamente inexistente ( < 1ms ). Estes dados indicam que métodos de acesso aleatório ou pontual podem lançar exceções sem problemas. Para métodos que são acessados frequentemente ou recorrentemente, e provavelmente podem receber dados inválidos, pode ser escolhido uma implementação sem Exceção.

Cada caso é um caso, mas só um lembrete: Exceções são para situações Excepcionais. Se a Exceção acontece com frequência, ela não é Excepcional, concorda?

Quero agradecer ao Everton e Cristiano pela conversa em aula e que me levou a criação deste Post, obrigado.
Abraços,
Márcio Torres

20 de out de 2011

Todo mundo quer uma API extensível

Como programador Java mas entusiasta de novas linguagens, acabo por sentir falta de alguns recursos que facilitam muito o trabalho, ou que pelo menos para mim fazem mais sentido usar.

Posso começar com um estudo de caso simples: dada uma String retornar o número de palavras.

É assim mesmo, simples assim, inclusive muito fácil de implementar em qualquer linguagem, mas a questão não é essa, como desenvolvedor eu quero usar orientação a objetos no projeto.

Vamos a uma implementação tosca em Java:
String frase = "Uma frase  de   exemplo";
System.out.println(frase.trim().split("\\s+").length);
Deu. Mostra 4. Entretanto eu quero reutilizar esta funcionalidade então em componentizo, bem, mas como?

Posso usar outro estudo de caso para dramatizar mais a história: verificar se uma String está em branco, ou seja, semelhante ao vazio só que ignorando espaços.

A implementação é medíocre:
String texto = "    ";
if (texto.trim().length() == 0) {
    System.out.println("vazio");
}
Não obstante, como no primeiro caso, eu quero componentizar.

No Java, é clássico fazer uma classe utilitária, algo como TipoUtils, neste caso, StringUtils:
public class StringUtils {
    public static int words(String str) {
        return str.trim().split("\\s+").length;
    }

    public static boolean isBlank(String str) {
        return str.trim().length() == 0;
    }
}
E uso assim:
String texto = "    ";
if (StringUtils.isBlank(texto)) {
    System.out.println("vazio");
}
Ainda posso usar um recurso introduzido no Java 5, o import estático.
import static meu.framework.utils.StringUtils.*;
// algumas linhas abaixo
String texto = "    ";
if (isBlank(texto)) {
    System.out.println("vazio");
}
Qual o problema com esta implementação? É que ela soa totalmente procedural em contraste ao modelo OO escolhido, em outras palavras eu queria fazer isso:
String texto = "    ";
// quero o próprio objeto me diga se está em branco
if (texto.isBlank()) {
    System.out.println("vazio");
} else {
     // quero que o próprio objeto me diga quantas palavras tem
    System.out.println("Existem " + texto.words() + " palavra(s)");
}
Em Java, impossível. No Java a classe String é final, ou seja, não pode ser estendida.

Para constar, uma extensão da API base do Java já existe e é distribuída pelo projeto commons do Apache e uma classe StringUtils já existe para estender as funcionalidades relacionadas a Strings e é muito bem escrita.

Mas isso não me deixa contente por que outras linguagens lidam muito melhor com esta situação, dando melhor suporte para estender a API base.

Vamos ao mesmo exemplo usando C#. No C# é possível, de certo modo, estender qualquer classe, mesmo as finais (sealed em C#), usando somente a API pública da classe, que já é suficiente para realizar muitas proezas:
public static class StringUtils {
 public static int Words(this string str) {
  return Regex.Split(str.Trim(), "\\s+").Length;
 }
 public static bool IsBlank(this string str) {
  return str.Trim().Length == 0;
 }
}
Implementações de classes estáticas no C# permitem estender a funcionalidade qualquer classe. Na verdade é um açúcar sintático no qual ele passa o conteúdo da variável para o método estático, ainda sendo procedural, mas de melhor leitura e mais elegante que em Java. Eu posso chamá-lo assim:
string texto = "    ";
if (texto.IsBlank()) {
    Console.WriteLine("vazio");
} else {
    Console.WriteLine("Existem " + texto.Words() + " palavra(s)");
}
Bem mais OO não acha? Pode ficar ainda melhor em Ruby, onde as classes são abertas. Em outras palavras, é possível estender a funcionalidade até mesmo das classes básicas como String e FixNum (inteiros). Claro que esta liberdade deve ser usada com parcimônia, é bem mais fácil fazer caca, mas com responsabilidade dá para fazer isso:
class String
    def words
        self.split.length
    end
    def blank?
        self.strip.length == 0
    end
end
Então eu posso usar estes métodos assim:
texto = "uma frase"
print texto.words
print "vazio" if texto.blank?
Sinceramente, sinto falta dessa flexibilidade em Java.

Acho exagerado a recomendação do Pragmatic Programmers de que todo programador deveria aprender uma linguagem por ano, por outro lado entendo o motivo que é propiciar novas visões, pontos de vista, ser mais crítico ao seu código, saber que há alternativas para fazer, melhor ou pior, aquilo que tu já faz todo o dia.

18 de out de 2011

O operador 'Elvis'

O açúcar não deve ser consumido em excesso e nem deixar de ser consumido, assim como o sal, a gordura, etc.
Não, não virei nutricionista. É que este post é para falar de açúcar sintático, syntactic sugar em inglês, que é, em poucas palavras, o uso de operadores e construções da linguagem reduzem a escrita de um código maior e, assim como tudo, não pode deixar de existir na linguagens modernas e ao mesmo tempo não deve ser exagerado.

Nem sempre o açúcar sintático simplifica, pois às vezes tornam a leitura mais difícil, mas por outro lado economizam tempo -e tempo é dinheiro.

Vou citar um exemplo clássico, o operador ternário "? :"
Quem aqui né amigo não teve que escrever um código assim:
if (atraso > 10) {
    multa = 12.4;
} else {
    multa = 0.0;
}
Embora o código esteja em Java a construção é inerente a qualquer linguagem.

O mesmo código pode ser escrito usando o operador ternário, que é um açúcar sintático pois na prática ele compila, ou interpreta, para o código acima. Exemplo:
multa = atraso > 10 ? 12.4 : 0.0;
As linguagens estão cada vez mais adicionando açúcar sintático e uma que simpatizo muito é Ruby, entretanto como desenvolvedor Java sinto falta de um especialmente: o operador Elvis!

O "Elvis" -não me pergunte por que chamam assim, só sei o que dizem: "Elvis não morreu"- é uma simplificação do operador ternário para verificação de nulos. Por exemplo, imagine que desejes atribuir o valor de um parâmetro a uma variável, entretanto caso este parâmetro seja nulo é necessário atribuir um valor padrão. Vou propor um estudo de caso e uma implementação fictícia no código abaixo usando o clássico "if":
if (param.get("action") != null) {
    acao = param.get("action");
} else {
    acao = "listar";
}
O mesmo código poderia ser escrito assim:
acao = param.get("action") != null ? param.get("action") : "listar";

Esta sintaxe com operador ternário simplifica bastante porém ainda é possível simplificar ainda mais usando, bem, o Elvis! Abaixo uma implementação fictícia usando C#:
acao = param["action"] ?? "listar";
Bem melhor não concorda?
Java está meio atrasado nesta corrida por facilitação de sintaxe, o operador Elvis estava previsto para ser inserido no Java 7 e não rolou. A princípio ele teria a mesma sintaxe do Groovy, assim:
acao = param.get("action") ?: "listar";
Em Ruby ele é diferente sendo assim:
acao = param[:action] || "listar"
E pior, atualmente até o PHP tem, a partir da versão 5.3.

Enfim, o Java mostra sinais de idade e espero sinceramente que a Oracle com o Java 8 adicione mais liberdade a linguagem sem perder a elegância, a tipagem forte e robustez que o Java tem.