quinta-feira, 6 de junho de 2013

Melhorando performance em ETL com Java

Boa noite.
A linguagem Java nos proporciona a fazer milhares de aplicações a nosso gosto, integrações de vários tipos, processamentos complexos, carga de dados e outras "baixarias". Um dos processos mais onerosos que talvez possamos ter é efetuar transferência de dados de uma origem para um destino, em grande volume. Não, não é aquela sua tabela de clientes que possui 30.000 registros, 50.000 ou 100.000. Estamos falando de centenas de milhares de linhas, onde elas precisam ser transferidas para uma base estatística. O que estou falando aqui é de um processo típico de ETL. Sim, eu sei que o mercado oferece várias alternativas bem interessantes para fazermos cargas de dados para ambientes de Data Warehouse, tais como o Kettle (feito em Java inclusive), e o Integration Services da nossa amiga Microsoft. A plataforma SAP também possui um módulo de ETL, no entanto esqueci o nome da ferramenta. Enfim, todas essas ferramentas são ótimas no que fazem, só que tem um pequeno detalhe: quando sua regra de negócio começa a ficar muito complexa, as dificuldades começam a aparecer, e talvez se pudéssemos ter um controle melhor da situação, poderíamos ficar um pouco mais confortáveis com o processo que está sendo executado. Tudo bem, você pode programar tudo via banco de dados (PL/SQL), transferindo todo o overhead para o BD. Aí vai depender de caso a caso.
Sim, então vamos fazer em Java, muito legal não? Afinal, eu conheço Java, é só criar uns objetos ali, deixar tudo organizadinho e partir para o abraço... que bom se fosse fácil assim.
Primeiramente, você precisa ter em mente que você irá trabalhar com milhares e milhares de registros ao mesmo tempo, e precisa de um tempo razoável para fazer toda a transferência, fazendo corretamente todos os tratamentos necessários. Fazer um processo de ETL em Java envolve muito mais conceitos do que uma aplicação "comercial". Há preocupações sérias aqui, como por exemplo, o heap space da JVM e outras coisas. Mas, vamos ao que interessa. Se você tem um processo parecido e está enfrentando alguns problemas de performance, cito algumas dicas que pode auxiliá-lo a melhorar um pouco isso.
  1. Não invente moda. Sim, os frameworks estão aí para nos ajudar, deixam o código mais bonito e tudo, mas deixe-os para trás aqui. Considere usar JDBC puro para fazer esse tipo de coisa. No final das contas, ao usar um Framework, por "baixo dos panos" isso tudo vai ser convertido em milhares de PreparedStatements da vida, então, facilite e faça o acesso direto.
  2. Seja cuidadoso. Cuidado ao deixar recursos de conexão espalhados em seu código sem serem encerrados, como ResultSet's e PreparedStatement's.
  3. Ajude o GarbageCollector. Considere  dar uma "ajuda" básica ao seu coletor de lixo, liberando seus objetos através da atribuição = null.
  4. Processamento em lote. Se você faz todos os tratamentos e depois precisa inserir no banco de dados, porque não juntar uma quantidade considerável de linhas e mandar o banco executar tudo de uma vez só, em vez de linha a linha? Isso torna o processamento muito mais rápido! Agora, tenha cuidado para que as linhas que você irá mandar para o batch (lote) estejam todas corretas. Por corretas quero dizer que não tenham potencial para violar uma constraint por exemplo.
  5. Processamento paralelo. Se você pode carregar dados de duas tabelas ao mesmo tempo e as duas não tiverem dependência, considere o uso de Threads para auxiliá-lo!
Algumas dicas são básicas, outras nem tanto. O fato é que esse tipo de processamento é bem complicado, e nos tira noites de sono. Carga é sempre um assunto delicado.

Por hoje é só pessoal! Obrigado e até logo.

sexta-feira, 24 de maio de 2013

TDC 2013 - Navegando em um mar de novidades

Boa noite.
Hoje tive a oportunidade de participar do meu primeiro The Developers Conference (TDC) que ocorreu aqui em Florianópolis, onde participei de uma trilha de Java (Java University).
Durante todo o dia, vários palestrantes falaram sobre Java, novidades, tendências, enfim, foi um evento muito bacana e que com certeza participarei novamente. Pena que pude ir somente em um dos três dias, mas já foi excelente a experiência.
A primeira palestra foi sensacional. Tratava sobre as siglas do mundo Java. A primeira impressão que tive é que essa palestra iria ser muito básica. Mas foi sensacional! O palestrante conseguiu explicar de uma maneira muito interessante como todas as siglas interagem entre si. Uma das coisas que marcou foi que não é preciso conhecer TODAS as tecnologias envolvidas. É interessante que muitas tecnologias tratam de uma mesma coisa, e que uma é apenas uma variação da outra, de forma melhorada. Por exemplo, JDBC, JPA e Hibernate estão relacionados a persistência. JSP, JSF referem-se a camada WEB. Servlets e Struts também entram na parte WEB mas como controladores. Temos também o EJB (separar a lógica de negócio com camada de estrutura), Tomcat (servidor web), JBoss (servidor de aplicação - entende EJB). Após explicar tudo isso de maneira bem sucinta, falou-se um pouco sobre arquitetura, com exemplos práticos.
A segunda palestra tratou de desenvolvimento de jogos em uma plataforma chamada Greenfoot. Ele utiliza a mesma IDE que o BlueJ, e o Framework é quase Java! Fiquei impressionado com a facilidade de programar jogos divertidos com essa ferramenta, além de ser uma plataforma muito interessante para quem quer aprender Java brincando! Depois de um "almoço" (sim, deu tempo de comer um Subway patrocinado pelo evento), uma das palestras mais bacanas do dia foi uma espécie de "História do Java". Contamos com a ilustre presença de Bruno Souza (o Javaman). Poxa, e que palestra! Foi muito interessante. Nesse meio tempo tivemos também as novidades do Java 8. Uma coisa que chamou a atenção em um dos novos recursos foi a possibilidade de implementação de um método "default" em interfaces Java. Poxa, isso foi sensacional, porque se quisermos sobrescrever o método padrão, tudo bem, se não, ele irá se comportar da maneira "default". Gostei muito desse recurso. Gostei também do assunto sobre Lambda. Tenho que aprender isso inclusive, achei interessante no pouco que vi, mas parece ser bem complexo. Por fim, um pouco (mas pouco mesmo) sobre refatoração e um bate papo legal.
Vamos em frente, e até o próximo TDC Floripa! :)


quinta-feira, 23 de maio de 2013

Refatorar é preciso!

Quantas vezes olhamos para nossos projetos, torcendo o nariz e falando aos quatro ventos "hoje eu faria tudo diferente"? Pois é. Para quem se preocupa com a arquitetura de seus projetos, além da funcionalidade em si, esse parece ser o calcanhar de Aquiles de muitas aplicações que encontramos hoje. Muitos sistemas legados com alta complexidade necessitam de uma atenção especial na refatoração. Porém, tudo na vida tem um limite, e aqui este limite chama-se bom senso. A refatoração muitas vezes é (mal) interpretada como "jogar tudo fora e começar novamente". Nós que trabalhamos com codificações intensas sabemos que isso é inviável. Então, o que fazer, como começar?
Abaixo cito algumas dicas de quais pontos você pode identificar em seu projeto em que poderá fazer uma refatoração:

1) Identifique as fraquezas de seu projeto.
Você sabe. Sempre tem aquele módulo do sistema que não gostamos nem de abrir o fonte, por "medo" de alterar alguma coisa e tudo parar de funcionar de uma hora para outra. Se isso está acontecendo em seu projeto, é um claro sinal de que este módulo foi mal projetado. As alterações não deveriam causar tanto estardalhaço no que já está funcionando, e esse "medo" não deveria existir. Procure separar as partes críticas deste módulo em camada e torne-as coesas. Isso simplifica as coisas. Faça com que cada módulo tenha sua responsabilidade específica e nada mais. Claro, se você está em um ambiente OO isso fica muito mais transparente, mas é possível sim aplicar a mesma coesão e baixo acoplamento em outros tipos de estruturas (funções, procedures, etc).

2) Pense no futuro.
Ok. Aí você vai me dizer que NUNCA esse módulo/funcionalidade vai ser usada em outra situação, que é um problema muito específico. Aí no mês seguinte alguém pede uma coisa muito parecida, com algumas alterações apenas. Já que estamos refatorando nossa aplicação, por que não torná-la um pouco mais "genérica" e menos inútil? Você sabe qual é a única constante no desenvolvimento de Software? Se respondeu ALTERAÇÃO, está no bom caminho. Muitas vezes isso exige mais escrita de código. Mas pense em você, que no futuro não vai querer ter que decifrar o que esse maldito código está fazendo.

3) Faça o uso de boas práticas de programação.
No seu módulo legado você tem variáveis com nomes totalmente insignificantes, como "x" e "y", ou "percentual". Não custa seguirmos as boas práticas de programação. Leia os padrões de projeto que gurus resolveram utilizando a mesma linguagem que você utiliza. É muito recompensador você ler um código depois e ver que ele está organizado e limpo, e que outras pessoas conseguirão compreender com facilidade.

4) O fim.
 Você saberá a hora certa de virar a página. Não tente refatorar o projeto para que se torne "o sistema perfeito". Como já dito, tenha bom senso. A hora que o sistema estiver estável e que novas alterações que possam vir a surgir no futuro não sejam uma terrível dor de cabeça, você está pronto para de uma vez por todas colocar um ponto final, e a partir disso só manter.

Enfim, há com certeza mais de quatro dicas para incentivá-lo a refatorar, mas por hora isso já basta. Lembre-se, quanto menos seu projeto for frágil, mais feliz você será e menos retrabalho terá. Consequentemente, poderá alocar mais tempo em outros projetos.

Por hoje é só, um abraço.

quinta-feira, 7 de fevereiro de 2013

Mãos à obra!

Olá! Hoje resolvi escrever sobre um padrão de projeto muito interessante, chamado Builder, um dos padrões criados pelo GoF. 
Antes de falar do Builder especificamente, precisamos entender qual é a motivação para utilizar padrões de projetos. Provavelmente você já enfrentou um problema "cabeludo" e perdeu vários dias tentando resolvê-lo, enquanto se lamentava ao pensar que ninguém havia passado por este problema antes. Mas você estava enganado. Muito enganado. Provavelmente muitas pessoas já enfrentaram situações iguais ou muito parecidas que você já enfrentou, e algumas dessas pessoas ficaram tão frustadas quanto você ao perder horas (ou até dias) tentando resolve-las. Só que um grupo chamado GoF decidiu criar alguns padrões para resolver problemas cotidianos. Por que? Simplesmente para melhorar a vida das pessoas. Tornar o código mais limpo, mais elegante e de fácil manutenção. Quando aplicamos padrões de projeto em nosso código, estamos tornando-o muito mais profissional, e menos suscetível a erros.Geralmente é uma boa ideia a utilização dos padrões de projeto em nossos sistemas.
Mas vamos ao que interessa, que é o tal do Buider. Um código vale mais do que mil palavras, então vamos começar com o seguinte cenário:
public class Person {

  private String name;
  private int age;
  private String adress;
  private String lastName;
  private String phoneNumber;
  private String jobDescription;
  
  public Person(String name, int age, String adress, String lastName,
                String phoneNumber, String jobDescription) {
    this.name = name;
    this.age = age;
    this.address = address;
    this.lastName = lastName;
    this.phoneNumber = phoneNumber;
    this.jobDescription = jobDescription; 
  }
}
E nosso programa principal se pareceria com isso:
public class Main {
  public static void main(String[] args) {
     Person p = new Person("Paul", 32, "Street one",
         "MCcain","555-4444","Java Programmer");

     //do something                
  }
}
Nossa, quantos parâmetros! Eu realmente não quero lembrar de todos esses parâmetros ao instanciar um objeto Person, são muitos! Além do mais, é difícil ter que ficar verificando a ordem correta de cada um deles. É muito fácil, por exemplo, trocar o argumento "Street one" por "MCcain", o que causaria inconsistência no objeto Person, já que teríamos configurado o endereço como sendo seu sobrenome e seu sobrenome como o endereço de sua residência.
Será que não teria uma maneira mais "intuitiva" ao construir objetos Person? Felizmente, a resposta é SIM! O pessoal do GoF também achou muito chato isso e resolveu criar o padrão Builder, que traduzindo do inglês seria um "construtor". E sim, é ele que vai nos ajudar a criar novos objetos Person. Como? Veja abaixo:
public class Person {

  private final String name;
  private final int age;
  private final String address;
  private final String lastName;
  private final String phoneNumber;
  private final String jobDescription;
  
  private Person(Builder builder) {
      this.name = builder.name;
      this.age = builder.age;
      this.address = builder.address;
      this.lastName = builder.lastName;
      this.phoneNumber = builder.phoneNumber;
      this.jobDescription = builder.jobDescription;
  }

  public static class Builder {
    private String name;
    private int age;
    private String address;
    private String lastName;
    private String phoneNumber;
    private String jobDescription;
    
    //vamos exigir que o parâmetro obrigatório seja
    //somente o nome da pessoa
    public Builder(String name) {
      this.name = name;
    }

    public Builder age(int age) {
      this.age = age;
      return this;   
    }

    public Builder address(String address) {
      this.address = address;
      return this;
    }

    public Builder lastName(String lastName) {
        this.lastName = lastName;
        return this;
    }
    
    public Builder phoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
        return this;
    }
    
    public Builder jobDescription(String jobDescription) {
        this.jobDescription = jobDescription;
        return this;
    }
    
    public Person build() {
        return new Person(this);
    }
  } 
}
E nossa classe principal ficaria assim:
public class Main {

    public static void main(String[] args) {
        Person p = new Person.Builder("Paul")
                .age(32)
                .address("Street 1")
                .jobDescription("Java Programmer")
                .lastName("MCcain")
                .phoneNumber("555-4444")
                .build();
    }
}
Olha que bacana. Ficou muito mais fácil e lógico instanciar um objeto Person, muito mais intuitivo, não acham? Perceba que o objeto Person é imutável neste caso. Não importa a ordem em que acessamos os parâmetros, eles sempre serão alocados da maneira correta. E lendo este código, fica muito mais fácil ver que estamos setando cada atributo individualmente, numa espécie de bind (recurso encontrado na linguagem Python, por exemplo). Por mais que tivemos que escrever mais código, o resultado final ficou muito melhor do que o primeiro exemplo. Mas daí vem a questão, vou ter que montar toda uma estrutura para resolver um problema aparentemente simples? Como usar um canhão para matar uma formiga? A questão é que seu software SEMPRE irá mudar, esteja preparado para isso. E quanto melhor estruturá-lo, menos dores de cabeça teremos ao precisar modificá-lo. Lembre-se, a única constante que temos no desenvolvimento de software é a palavra MUDANÇA. Outro fator é que provavelmente outras pessoas irão mexer em seu código, você quer mesmo que elas pensem que você não foi inteligente o suficiente por não utilizar um padrão de projeto, deixando seu código feio, sujo e difícil manutenção? É claro que não quer.

Bom, como tudo na vida é perfeito, vou listar também as desvantagens em utilizar o padrão Builder:

  1. Como o construtor de Person é private, a única forma de obter um Person é construir um builder antes;
  2. Classes com poucos atributos não justifica o uso do padrão. Mas lembre-se, mais tarde sua classe poderá mudar;
  3. Cuidar ao adicionar novos atributos, pois terá que adicioná-los tanto no Builder quanto na própria classe, e fazer a atribuição correta no construtor.
Tirem suas conclusões e vejam o que é melhor, não necessariamente essa é a única solução, mas é uma ótima solução caso queira "profissionalizar" seu código.
Bom, por hoje é só! Chega de Java por hoje, amanhã estarei indo para minha terra querida. Desejo à todos um ótimo feriado, pulem bastante Carnaval (mas com responsabilidade!).

Até a próxima! =)

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!