segunda-feira, 17 de junho de 2013

Descobrindo a poderosa engenharia de aplicações Java Web - Parte 2

Boa noite!
No POST anterior, dei uma pincelada bem por cima sobre a estrutura básica de uma aplicação WEB.
É hora de mais detalhes.

O servidor WEB
Quando vemos a palavra "servidor", estamos nos referindo tanto ao hardware como ao software. Um servidor WEB é capaz de processar requisições HTTP enviadas através de browsers (clientes). Isso significa que quando estamos em um site e preenchemos um formulário ou clicamos em um botão, o browser (navegador) irá enviar uma solicitação HTTP para o servidor web. Um dos servidores WEB mais conhecidos e utilizados até o momento é o Apache.

Mas por que eu preciso de um servidor WEB? 
Justamente para processar as requisições HTTP oriundas dos clientes. Mas não só por isso. Um servidor WEB é capaz de armazenar outras páginas que serão acessadas pelos usuários, armazenar sons, imagens, scripts auxiliares (PHP, Perl, etc). Ele é capaz de lidar com muitos acessos simultâneos, utilizando o mecanismo de threads. Ele é capaz de processar e criar páginas dinamicamente e devolver uma página diferente ao usuário, como por exemplo retornar uma página de erro ou de sucesso, se a determinada ação foi ou não bem sucedida.

O container
O container é um software que gerencia uma porção de coisas que são essenciais para sua aplicação. É ele que cuida do gerenciamento de segurança, do suporte a threads, gerenciamento do ciclo de vida das sessões, e muito mais! Um Web Container é um poderoso amigo que ajuda o servidor WEB a tratar solicitações em que o mesmo não é capaz de tratar, como solicitações dinâmicas. Exemplo? O usuário está em um site de compras e decide verificar se aquele CD que ele tanto quer está disponível em estoque. Então ele escolhe o CD e clica em "Pesquisar estoque". Bom, essa solicitação vai chegar no servidor WEB e ele dirá "opa, preciso de ajuda! Eu realmente não sei como verificar isso, vou chamar o meu amigo Container". Então o servidor WEB encaminha a solicitação ao Container. E é o container que vai direcionar essa solicitação para um de seus vários programas auxiliares que sabem como verificar o estoque para aquele CD. Depois que é feita essa verificação, o container responde ao servidor WEB que o CD está sim disponível no estoque, e o servidor web por sua vez "monta" uma resposta e a envia para o cliente. Simples, não?
Existem vários Web Containers disponíveis no mercado. Um deles é o Tomcat (desenvolvido inclusive pelo pessoal da Apache). O Tomcat acaba atuando como servidor web e container web, quando não se tem um servidor web instalado, mas nada comparado a robustez do Apache. Porém, o Tomcat não é um servidor JEE completo (ele consegue apenas entender Servlets e JSP). Um servidor JEE completo, contempla um servidor EJB + container web. Ainda não chegamos lá, mas um servidor JEE completo, por exemplo, é o JBoss, mas vamos com calma :)

A seguir, uma figura que exemplifica isso que expliquei acima, mostrando os dois tipos - Um servidor JEE completo e apenas um WEB Container:

Figura 1: Estrutura (adaptado de Head First - Servlets e JSP (pág. 65))

Por hoje é só pessoal!
No próximo POST é hora de começar a ver sobre Servlets!

Um abraço e até a próxima.



sábado, 15 de junho de 2013

Descobrindo a poderosa engenharia de aplicações Java Web - Parte 1

Boa noite.
Hoje começo uma série onde o objetivo é destrinchar a arquitetura de aplicações Java Web, incluindo aplicações JEE completas (utilizando EJB por exemplo).
Eu sempre foquei bastante na arquitetura JSE (antigamente chamada de J2SE), em questões de detalhes minuciosos desta arquitetura. Ainda tenho bastante a aprender, porém é hora de aprender a fundo os detalhes do JEE.
Uma coisa que sempre achei importante quando você está trabalhando com uma determinada tecnologia é conhecê-la profundamente. Pra mim, conhecer superficialmente uma coisa o faz ser um programador comum e menos cuidadoso. Sim, porque quando você tem um conhecimento mais aprofundado da tecnologia em si, sabe usar melhor os recursos, sabe projetar uma estrutura segura e resistente, além é claro de utilizar todo o poder que a tecnologia oferece. Então vamos juntos descobrir passo a passo como é que funciona essa coisa?

Deixando de ser "O site" para ser "A aplicação".
Antigamente, fazíamos "sites" na Internet onde apresentávamos conteúdo estático, como uma série de textos e imagens que eram somente aquilo e pronto. Não haviam interações com o usuário, geralmente haviam muitos GIF's pulando na tela e letras coloridas anunciando alguma coisa. Após isso começou a surgir os sites mais "inteligentes", onde você colocava um valor em um campo de texto, esse valor era processado e o site te retornava alguma coisa. E daí foram surgindo as aplicações Web, como e-commerces, sites de relacionamentos e diversos tipos.
E onde é que o Java entra nessa história toda? É aqui que tudo começa.

Criando corpo
Bom, quando você precisa de algo mais além de exibir páginas WEB estáticas, como por exemplo, acessar um banco de dados e retornar uma listagem de clientes cadastrados no sistema, você precisa escrever um "programa" que converse com o servidor WEB para que os dois trabalhem em conjunto. Ou seja, quando você clica em um botão "enviar dados", o servidor WEB precisa dizer "Ok, eu sei que eu não consigo tratar disso sozinho, então vou pedir ajuda a um amigo meu que pode fazer isso que ele quer". Esse "amigo" é uma aplicação que pode realizar tarefas mais complexas. Então o usuário acessa uma página da WEB, preenche uma série de campos em um formulário e clica em "OK". Esses dados precisam ser processados em algum lugar, onde serão feitas validações e outros procedimentos, e então o usuário verá outra página dizendo que tudo deu certo ou que houve algum tipo de erro. A imagem a seguir explica como esse processo funciona:
Figura 1: Estrutura básica

Bom, não tenho dons artísticos, mas é mais ou menos assim que a coisa funciona.

E como essas estruturas se conversam?
Essa é uma pergunta muito interessante, e poderíamos escrever um livro inteiro só disso (a propósito, tudo está BEM resumido aqui, é só a primeira parte). Bom, o servidor WEB Apache utiliza o protocolo HTTP para interpretar as requisições feitas pelo usuário, através de um browser. Seu papel então é receber as solicitações dos clientes (através de métodos GET ou POST). Como o Browser também fala a mesma língua que o servidor, então quando o servidor envia uma resposta no formato HTML, o browser pega essa resposta e formata para exibir num formato amigável para o usuário. Ou seja, o servidor recebe uma requisição HTTP e responde em formato HTML (apesar de que o servidor pode enviar vários tipos de resposta, como um áudio, vídeo, um PDF, etc.). E qual é o papel do Servlet? É exatamente executar tarefas mais complexas, como validação de dados, acesso ao Banco de Dados, etc., e o Servlet é chamado pelo Container WEB. 

Mas era só isso?
Infelizmente (ou felizmente, dependendo do ponto de vista) não. Há MUITOS detalhes que precisamos descobrir ainda. Esse foi apenas um resumão do que vem pela frente. Nos próximos POSTS pretendo detalhar o que é exatamente esse tal de Servlet, por quem ele é gerenciado, o que é o Tomcat, enfim, destrinchar mesmo. Afinal, você não quer passar vergonha com seu amigo quando não souber explicar como funciona aquele maravilhoso site de compras on-line! 

Por hoje é só, um abraço e até a próxima!

quarta-feira, 12 de junho de 2013

Mas o que diabos essa interrogação está fazendo em meu SQL?

Boa noite galera.
Primeiramente, quero parabenizar a todos os casais de namorados nesse dia 12/06/2013! Aproveitem seu dia! :)
Se você, assim como eu, não tirou a noite para namorar, e ainda por cima quer aprender algo novo, então está no lugar certo.
O "mão na massa" de hoje trata-se de como tornar seus SQL's mais legíveis em sua aplicação, torná-los mais inteligentes e menos suscetível a falhas. Bom, para início de conversa, escrever SQL's no meio do código é algo muito deselegante, mas quando você não usa um Framework para auxiliá-lo, infelizmente não há muito o que se fazer. Mesmo que você separe as suas SQL's em um arquivo de propriedades, por exemplo (.properties), em algum lugar você estará escrevendo querys complexas, longas e feias. Como por enquanto é o nosso caso, vou partir do princípio que suas querys estão devidamente alojadas em algum DAO da vida e que não estejam misturadas com sua regra de negócio.
O problema que venho tentar resolver é muito simples: como tornar minha SQL legível e inteligente? Bom, o que vínhamos fazendo até hoje em nossos códigos, é algo muito parecido com isso:
PreparedStatement stmt = Conexao.prepareStatement("select * from table where field1 = ? 
and field2 = ?");

stmt.setString(1,"name");
stmt.setString(2,"address");
Aí você me diz: "Certo, mas está muito claro que o parâmetro 1 (a primeira interrogação) refere-se ao campo 1 e a segunda ao campo 2". Muito bem, você está certo. Mas agora me diga, o que acontece se eu fizer o seguinte:
PreparedStatement stmt = Conexao.prepareStatement("select * from table where field2 = ? and field1 = ?");
stmt.setString(1,"name");
stmt.setString(2,"address");
Já até sei o que você vai dizer: "Mas é óbvio que daí teremos que trocar a configuração dos parâmetros, o que era 1 vira 2 e o que era 2 vira 1. Essa é obrigação do programador!". Muito bem outra vez, só que essa query tem apenas dois parâmetros. E uma query com dez, vinte parâmetros? Onde está seu Deus agora? Brincadeiras a parte, mas você quer realmente ficar com cabelos brancos todas as vezes que precisar mexer em uma listagem de parâmetros de uma SQL? Ou pior, você quer realmente ficar conferindo o que cada "?" dessas quer dizer? 
Eu não. E aposto que muita gente também não. E uma dessas mentes incomodadas, assim como eu, resolveu fazer um trabalho magnífico, que é de fazer um objeto que entende NamedParameters! E o que seria?
Imagine algo como isso, considerando o mesmo exemplo anterior:
PreparedStatement stmt = Conexao.prepareStatement("select * from table where field1 = :name
and field2 = :address");
stmt.setString("name","Eduardo");
stmt.setString("address","Rua sem fim");
Parece que aqui as coisas estão ficando um pouco melhores, não?
Veja que interessante, agora mesmo que mudemos a ordem dos parâmetros na nossa query, nunca mais precisaremos dar manutenção na configuração dos parâmetros! Veja, se o "field2" viesse antes do "field1", não precisaríamos mexer em nada, pois sabemos que o "field2" é o endereço, independentemente da posição em que o mesmo se encontra. Não é genial? 
Bom, mas para isso ser possível, alguém teve que escrever uma classe com esse comportamento. O nome do (in)feliz é Adam Crume, e a classe dele está publicada aqui. Eu não vou colar a classe aqui, pois ela é bem extensa, caso alguém precise, me comunique. Até aqui tudo muito bom, tudo muito bem. Só que eu, nunca satisfeito, com certeza fiquei coçando a cabeça tentando tornar o meu trabalho ainda mais genérico possível. Eu tenho um sério problema de querer automatizar muita coisa, e isso dá (muito) trabalho. Mas eu não desisti, e criei uma solução em conjunto com a do Adam. 
Na solução citada, podemos perfeitamente setar os nossos parâmetros agora através de nomes (não é por acaso que a classe foi batizada de NamedParameterStatement). Ela funciona da seguinte forma:
NamedParameterStatement p = new NamedParameterStatement(conexao,query);
p.setString("name","Eduardo");
p.setString("address","Rua sem Fim");
p.executeQuery();
Pois bem, ainda, de certa forma, precisamos setar os parâmetros na "mão" de alguma forma, mesmo que agora ficou ridiculamente mais fácil. Mas, por que não tornar isso um pouco mais inteligente? E se disséssemos assim: "eu vou passar a minha query para você. Você é quem cuide dela, afinal, você é quem sabe o que deve configurar nos parâmetros". E é aí que fiz uma solução para isso. 
A minha solução foi a seguinte, para cada bean que eu tiver, eu configuro um mapa de parâmetros (chave - valor). E para cada chave, eu coloco o valor do próprio bean. E quando eu precisar configurar os parâmetros da query, eu peço para que a query busque os seus parâmetros no bean que está sendo executado! Vamos ver um exemplo?
...
Map<String,Object> parametros = new HashMap<String,Object>();
public void setMapParameters() {
  parametros.put("chave", this.pk);
  parametros.put("nome", this.nome);
  parametros.put("endereco", this.endereco);
  parametros.put("telefone", this.telefone);
}

No código acima temos o nosso bean "Pessoa", onde para cada campo, eu coloquei em um mapa. Se eu vou usar ou não, pouco me importa, no futuro pode ser necessário. Então para cada campo de pessoa, eu coloquei o próprio valor desse bean. Lembre-se, no futuro sua SQL poderá mudar, então já deixe sua estrutura preparada.

Agora vem a parte old school. Sim, deu um trabalho danado para fazer isso. Essa segunda parte tratamos de fazer com que a SQL preencha corretamente cada um de seus parâmetros. Vamos ver como fica?


public void mapParameters(NamedParameterStatement p, String qry, Pessoa p) throws SQLException {
        int beginIndex = 0;
        int endIndex = 0;
        String parametro = "";
        String sqlOriginal = "";
        sqlOriginal = qry;
        for (int i = 0; i < qry.length(); i++) {
            if (qry.charAt(i) == ':') {
                beginIndex = i;
            }
            if (beginIndex != 0) {
                if (qry.charAt(i) == ' ' || qry.charAt(i) == ',' || qry.charAt(i) == ')' || i + 1 == qry.length()) {
                    endIndex = i + 1;
                    if (!Character.isLetter(qry.charAt(i))) {
                        endIndex = i;
                    }
                    parametro = qry.substring(beginIndex + 1, endIndex);
                    parametro = parametro.trim();
                    beginIndex = 0;
                    endIndex = 0;
                    Object valor = p.parametros.get(parametro.trim().toLowerCase());
                    if (valor instanceof Integer) {
                        p.setInt(parametro, (Integer) valor);
                    } else if (valor instanceof Timestamp) {
                        p.setTimestamp(parametro, (Timestamp) valor);
                    } else {
                        p.setString(parametro, (String) valor);
                    }
                    sqlOriginal = sqlOriginal.replace(":" + parametro, valor + "");
                }
            }
        }
        System.out.println(sqlOriginal);
    }
Legal, certo? E agora a parte mais legal, onde mostro como usar isso:
Pessoa p = new Pessoa();
p.nome = "Eduardo";
p.telefone = "9999";

String qry = "select * from pessoa where nome = :nome and telefone = :telefone";
NamedParameterStatement stmt = new NamedParameterStatement(conexao,qry);

this.mapParameters(stmt,qry,p);
stmt.executeQuery();
Veja, todo o trabalho de setar os parâmetros, ficou com o método mapParameters() que se encarregou de setar cada parâmetro no seu devido lugar. Nosso código ficou muito mais limpo e com chances de falhas quase 0, porque mesmo que eu adicione mais parâmetros na consulta, meu bean já está preparado com todos os parâmetros, e dinamicamente ele irá se entender. Essa é mais uma prova de que podemos sim melhorar nossos códigos para que fiquem o mais genérico possível, seguindo o lema "write once, run anywhere".

Ufa, o POST de hoje foi bem legal, não?
Um abraço, e até a próxima!

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! =)