quinta-feira, 31 de janeiro de 2013

Vivendo e aprendendo

Olá. No POST anterior, prometi falar um pouco sobre o Polimorfismo, porém ficará para uma próxima postagem, afinal, quando a gente está com "o fio da meada" na cabeça, é uma ótima oportunidade para transferir isso para um papel ou para um próprio blog! :) Desta forma então, hoje irei abordar um assunto de extrema importância: como trabalhar com a API JDBC do Java, mais especificamente, boas práticas em como utilizar tais recursos. No final do "artigo", há um estudo de caso. Provavelmente você já precisou fazer uma interface de conexão entre a sua aplicação e uma base de dados qualquer. Basicamente, não há mistério algum nisso. Um típico programa que se conecta com um banco de dados qualquer e executa alguns comandos simples se parece com isso:
1:  public class Foo {  
2:  public static void main(String[] args) {  
3:    Class.forName("oracle.jdbc.OracleDriver");    
4:    Connection c = DriverManager.getConnection(url, usuario, senha);  
5:    StringBuilder sql = new StringBuilder();  
6:    sql.append("select * from bar");  
7:    PreparedStatement stmt = c.preparedStatement(sql.toString());  
8:    try {  
9:     ResultSet rs = stmt.executeQuery();  
10:     while (rs.next) {  
11:      //faz bastante coisas aqui  
12:     }  
13:    } catch(SQLException e) {  
14:     e.printStackTrace();  
15:    }  
16:  }  
17:  }  
Como vimos, não há mistério nenhum nisso. Só que quando sua aplicação começar a "tomar forma", podemos tomar algumas ações para que seu código esteja protegido, que seja fácil de reutilizar e é claro, que não use recursos de forma aleatória e desnecessária.
Dica 1: Considere ter uma classe que lide com as particularidades de sua conexão. Conforme vemos no exemplo acima, expomos demais os detalhes de nosso banco de dados, como a URL de conexão, usuário e senha por exemplo, além de existir um complexo código para obter a conexão. O ideal seria criar uma classe ConnectionFactory que faria todo este trabalho para você, vamos ver como ficaria?
1:  public class ConnectionFactory {  
2:    private Connection conn;  
3:    private String driver;  
4:    private String usuario;  
5:    private String senha;  
6:    private String url;  
7:    public static ConnectionFactory conexao;  
8:    private ConnectionFactory() {  
9:     Class.forName(driver);  
10:     conn = DriverManager.getConnection(url,usuario,senha);     
11:     init();  
12:    }  
13:  public static getInstance() {  
14:     if (conexao == null) {  
15:       conexao = new ConnectionFactory();  
16:     }  
17:     return conexao;  
18:    }  
19:    private void init() {  
20:     //considere aqui ler um arquivo de propriedades que alimentará os 
        //campos necessários para a conexão  
21:    }  
22:  }  
E nossa classe principal ficaria assim:
1:  public static void main(String[] args) {   
2:    Connection c = ConnectionFactory.getInstance();  
3:    //restante do código omitido  
4:   }   
A propósito, nossa classe de conexão implementa um padrão de projeto chamado Singleton. Sugiro uma lida sobre Design Patterns.
Dica 2: Fique atento aos recursos de conexão! Como? Não podemos deixar recursos de conexão "largados" no servidor de banco de dados. Isso consome recursos do servidor, além de ocupar conexões desnecessárias. Se o limite de conexões simultâneas no seu servidor de banco de dados for relativamente baixo, isso pode ser um problema. E dos sérios. Até descobrir que o problema é na sua aplicação porque ela não faz a devida liberação de recursos, poderemos gastar horas e horas tentando achar o problema. (A propósito, quando as pessoas descobrirem isso, é melhor você correr - ou mudar de cidade). Nosso código ainda NÃO ESTÁ FAZENDO ISSO. Vamos ver como funciona?
1:  public static void main(String[] args) {   
2:  //código omitido  
3:    try {   
4:     ResultSet rs = stmt.executeQuery();   
5:     while (rs.next) {   
6:     //faz bastante coisas aqui   
7:     }   
8:    } catch(SQLException e) {   
9:     e.printStackTrace();   
10:    } finally {  
11:      rs.close();  
12:      stmt.close();  
13:      c.close();   
14:    }  
15:   }   
16:   }  
Veja o bloco finally em nosso código. Ele faz a liberação dos recursos sempre que o processamento terminar, nos livrando de vários problemas no futuro. Apesar de estarmos nos preocupando em fechar os recursos de conexão, ainda não estamos fazendo da forma correta. Mas como assim? O que está faltando? O bloco "finally" não garante que ele sempre será executado? Correto, mas não necessariamente tudo o que tiver no bloco finally será executado com sucesso!. Imagine a seguinte situação, se por algum motivo, na linha 11, rs.close() gere uma exceção. stmt.close() e c.close() jamais serão executados! Pense bem, a aplicação será abortada pois aconteceu alguma coisa estranha na chamada de rs.close(). Mas como então garantir de uma vez por todas que, mesmo que haja exceção em alguns desses comandos, o erro de um não influencie nos outros processamentos, evitando assim de fechar os recursos que já foram utilizados? A solução é claro, você já sabe: devemos executar cada close() em um bloco try...catch! Desta forma:
1:  public static void main(String[] args) {   
2:     //código omitido  
3:    try {   
4:        ResultSet rs = stmt.executeQuery();   
5:        while (rs.next) {   
6:           //faz bastante coisas aqui   
7:        }   
8:    } catch(SQLException e) {   
9:        e.printStackTrace();   
10:    } finally {  
11:        try {  
12:           rs.close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        }  
16:        try {   
17:           stmt.close();   
18:        } catch(SQLException e) {  
19:           //logar exceção     
20:        }  
21:        try{  
22:           c.close();   
23:        } catch(SQLException e) {  
24:        //logar exceção  
25:        }  
26:    }  
27:   }  
Agora sim! Mesmo que aconteça alguma coisa errada com rs.close(), stmt.close() e c.close() ainda serão executados. O mesmo vale para stmt.close() ou c.close().
Dica 3: Tenha CERTEZA que os cursores criados durante o processamento de suas SQL's foram corretamente encerrados! Ahn? Como assim? Para cada chamada que o ResultSet recebe de stmt.executeQuery(), é criado um cursor implícito no servidor de banco de dados. Só que há um limite de cursores que podem ficar abertos. Quando esse limite é atingido, o servidor lança um erro dizendo que não pode mais lidar com tantos cursores abertos, te dá uma mensagem feia de erro e sua aplicação é interrompida. Esse erro é muito complicado de se resolver, dependendo do tamanho do seu projeto, pois você tem que se certificar em todos os lugares que faz conexão/manipulação com o servidor de banco de dados se seus recursos estão sendo devidamente encerrados. Isso tudo é muito legal, mas COMO garantir que os cursores serão devidamente encerrados? E é aí que entra um pouco de experiência da linguagem. O comando abaixo NÃO ENCERRA O CURSOR:
11:    try {  
12:           rs.close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        } 
Mas isso aqui encerra:
11:    try {  
12:           rs.getStatement().close();  
13:        } catch(SQLException e) {  
14:           //logar exceção  
15:        } 
Veja a jogada! rs.getStatement().close(). É desta forma que você garante o encerramento dos cursores. Se formos analisar pela lógica da coisa, faz todo sentido. Quando chamamos rs.close(), estamos dizendo para o Java que não queremos mais trabalhar com o conjunto de resultados retornados pelo ResultSet. Ao fazer rs.getStatement().close(), estamos dizendo que queremos que aquela instrução executada seja "finalizada", ou seja, o cursor responsável por trazer a TUPLA para o ResultSet não necessita mais ficar aberta. Deu para entender? :) Estudo de caso Como eu mencionei no início do "artigo", vou compartilhar um estudo de caso que enfrentei por dois dias (sim, dois dias). Estou trabalhando com Oracle em um processo que envolve vários registros ao mesmo tempo, além de existirem conexões com duas bases de dados simultâneas. Ao trazer os registros para a memória, o Oracle me apresentava o seguinte erro: ORA-01000: maximum open cursors exceeded Ora, mas eu estava (aparentemente) fechando todos os meus recursos, como exemplifiquei aqui. Porém eu não tinha me tocado do rs.getStatement().close, sempre insistindo em apenas fechar o ResultSet. Foi só aplicar uma mudança de lógica do negócio e chamar rs.getStatement().close e pronto! Problema resolvido. Mas fiquei horas e horas trabalhando nisso, quando poderia estar implementando outras partes de minha aplicação. Então aí vai uma dica, cuidado! Não perca tempo como eu e já faça seu código livre desse erro maldito! :) Ufa! Por hoje é só, acabei me estendendo mais do que deveria, mas era isso que queria apresentar aqui hoje. Até a próxima!

segunda-feira, 21 de janeiro de 2013

Seja coeso e desligue-se um pouco :)

Hoje irei falar um pouco sobre coesão e baixo acoplamento. 
Primeiramente, o que significam esses termos?

1) Coesão: no Português claro, coesão significa que todo um contexto deve ter algum tipo de relação, uma harmonia. Se estivermos fazendo uma frase de um determinado assunto por exemplo, esta frase deve conter somente elementos pertinentes à aquele assunto, e não uma bagunça de outras coisas. No mundo da Informática, isso não é muito diferente. Devemos ter nossas estruturas coesas para evitar a complexidade dos nossos códigos. Vamos ver um exemplo na prática:

public class Impressora {
   public void imprimir() {
      conectarBancoDeDados();
      Documento documento = Documento.getDocumentosNaFila();

      System.out.println("Imprimindo documentos pendentes...");
      //Faz a comunicação com hardware para enviar os documentos para a impressora configurada. 
   } 

   public void conectarBancoDeDados() {
      Class.forName("oracle.sql.Driver");
      Connection conn = DriverManager.getConnection(urlBanco, usuario, senha); 
      PreparedStatement pstmt = conn.prepareStatement("select * from usuario");
      
      ResultSet rs = pstmt.executeQuery();
      //faz outras coisas complexas aqui  
   }
} 

Bom, ao ler esse código, claramente percebemos que a classe Impressora cuida de muitas coisas que ela não deveria cuidar, como podemos ver no método "conectarBancoDeDados". Analisando essa lógica, será mesmo que uma impressora deveria se conectar com um banco de dados? É aí que entra a coesão! Esta classe não está coesa, pois está executando tarefas além de sua responsabilidade. Devemos ter nossas estruturas fazerem o que é da alçada de cada um. Uma solução para esse exemplo seria mais ou menos desta forma:

public class Impressora {
   public void imprimir(Documento documento) {
      System.out.println("Imprimindo documentos pendentes...");
      //Faz a comunicação com hardware para enviar os documentos para a impressora configurada. 
   }
} 

Veja que retiramos a conexão com o banco na classe impressora, e que o documento que irá ser impresso já vem certo no método imprimir. Agora sim temos uma classe coesa, que faz somente o que foi projetada para fazer.

O papo está muito bom, mas e quanto ao baixo acoplamento? Nada como um exemplo prático:

public class Impressora {
   public void imprimir(Documento documento) {
      int numeroDePaginas = documento.numeroDePaginas;
      List<Autor> autores = new ArrayList<Autor>();
      for (Autor a : documento.autores) {        
         autores.add(a);  
      }
      System.out.println("Imprimindo documentos pendentes...");
      //Faz a comunicação com hardware para enviar os documentos para a impressora configurada. 
   }
}

Veja como conhecemos profundamente a classe Documento. E isso não é bom. Sabemos detalhes sobre esta classe, ou seja, a classe Impressora e Documento estão fortemente acopladas, consegue perceber?
Este tipo de coisa deve ser evitado, pois compromete toda uma estrutura. Imagine que alguém, em um belo dia, decide por algum motivo mudar a estrutura de armazenamento dos autores do documento, onde ao invés de List, irá armazenar numa estrutura Set. Nosso código do método imprimir() já estaria furado, pois teríamos que adequá-lo à nova estrutura, abrindo brechas para erros que antes não aconteciam. Encapsule o máximo de coisas que você puder, assim evitará grandes dores de cabeça!

Não sabe o que é encapsulamento? Então aguarde a próxima postagem, onde terei o maior prazer em tentar explicar.

Até logo. 

terça-feira, 15 de janeiro de 2013

Valores pré definidos no seu código? Utilize Enum!

Olá pessoal.

Hoje resolvi falar de um recurso muito interessante mas que por vezes passa despercebido em nossos códigos. Se você é um desses que armazena valores pré determinados em suas classes, este é o seu lugar: largue essa vida e utilize tipos enumerados.
Mas como é isso? Bom, vamos a um exemplo simples. Digamos que você esteja trabalhando com um aplicativo que controle uma lanchonete. Dentre os vários produtos que o seu estabelecimento oferece, o que mais se destaca são os deliciosos milk shakes de morango, que são vendidos em três tamanhos diferentes: pequeno (300ml), médio (500ml) e grande (enormes 700ml). No seu código, você tem algo parecido com isso:

public void realizarPedido(String tamanho) {
   if (tamanho.equals("pequeno")) {
      ml = 300;
   } else if (tamanho.equals("medio") {
      ml = 500;
   } else {
      ml = 700;
   }
}

Mas aí você me fala, "essa não é uma abordagem tão ruim assim, só tenho três if's aninhados!".
Bom, até hoje não deu problema algum, mas sua lanchonete está vendendo tantos milk shake's de morango que você decidiu fazer um tamanho "especial", de 1 litro! Bom, então o código acima, modificado, ficaria assim:


public void realizarPedido(String tamanho) {
   if (tamanho.equals("pequeno")) {
      ml = 300;
   } else if (tamanho.equals("medio") {
      ml = 500;
   } else if (tamanho.equals("grande")) {
      ml = 700;
   } else {
      ml = 1L;
   }
}


E assim seu código vai ficando mais complexo, com mais if's aninhados, sujo, e de difícil manutenção. Qual seria uma solução adequada para este caso? Poderíamos simplesmente criar uma estrutura que contemplasse o tamanho e a quantidade de cada milk shake! Mas não uma classe, e sim um Enum! Veja o exemplo abaixo:

public Enum TipoMilkShake {
   PEQUENO(300), MEDIO(500), GRANDE(700), GIGANTE(1000);

   public int tamanho;
 
   public int getTamanho() {
      return tamanho;
   }

   public TipoMilkShake(int tamanho) {
      this.tamanho = tamanho;
   }
}

Mas qual a vantagem disso? Vejamos agora nosso novo e melhorado método "realizarPedido":
public void realizarPedido(TipoMilkShake tipo) {
   ml = tipo.getTamanho();
 
   System.out.println("Você pediu um milk shake " + tipo);
}

Vejam só, a complexidade do nosso código se reduziu a apenas duas linhas! Agora eu não preciso mais ficar testando qual é o tipo de milk shake passado para determinar a quantidade de ML, pois o Enum já sabe disso. E quando eu precisar adicionar mais tamanhos ao meu cardápio, sem problemas, eu só vou mexer no tipo Enumerado, o restante do código automaticamente já estará pronto e funcionando para o novo tamanho!

Resumindo: este foi um exemplo bem simples, mas existem situações em que você vai perceber a real necessidade em utilizar tipos enumerados. Lembrando que eles são aceitos em testes condicionais switch/case também.

Por hoje é só, até mais e um abraço!