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.