quinta-feira, dezembro 20, 2007

Closures para quê?

“Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.”—Alan Perlis, “Epigrams on Programming”

(Esse artigo tem exemplos em Java e Ruby. Estou ignorando as sutis diferenças entre closures e blocos de código no Ruby)

Outro dia, num papo nerd com um colega (“O Francês”), ele me disse que não via nenhuma utilidade para closures com bons olhos a inclusão de Closures no Java. Que até já tinha olhado e brincado um pouco, mas que tudo que closures fazem é perfeitamente possível de fazer sem usá-las.(É... ele me fez corrigir o texto :) )

Bom, ficou bem difícil argumentar aquilo no momento. Realmente, tudo que se pode fazer com closures também é possível fazer sem, da mesma maneira que é possível escrever um programa web em C (muita gente ainda faz e adora) ou escrever um aplicativo comercial em Assembly.

Depois dessa conversa comecei a tentar catalogar usos interessantes de closures. Pode ser que isso não convença ninguém de que são úteis, mas ao menos me serviu para organizar minhas próprias idéias. A seguir listo uma série de usos bem interessantes e bastante comuns de closures.

Bom, minha intenção não é explicar o que são closures – para isso o Akita tem um excelente artigo – mas sim mostrar onde o uso de closures é legal.

Callbacks

Talvez um dos melhores usos de closures sejam callbacks), ou seja, uma função que será executada posteriormente. Trocando em miúdos, é um código que queremos que seja executado quando algum evento acontece no sistema, por exemplo quando um botão é pressionado (conhecido como “events”...), quando uma aplicação inicia, quando algo é gravado ou alterado (“triggers” ou “listeners”...?), ou quando algum estado no sistema muda (“hooks”...?).

Cada linguagem tem sua solução para implementar callbacks, como ponteiros para funções (C e afins), a função como “cidadão de primeira classe” (Python, Javascript), classes anônimas (Java), “delegates” (C#) ou … closures, óbvio (que é solução para Smalltalk e Ruby)!

Cada um defende sua linguagem como gosta, mas independente disso, closures são muito práticas. Eu posso definir um callback de um botão da seguinte forma:

(Código em Ruby/GTK2)

 # Cria um botão
 button = Gtk::Button.new("Click me")

 # Quando clicar, imprime "Cliquei no botão"
 button.signal_connect("clicked") { puts "Cliquei no botão" }

A parte do ’{ puts “Cliquei no botão” }’ é um closure. Nesse caso, é o código que vai ser usado quando o botão for ativado (“puts” é para imprimir o texto na console).

Simples assim, sem declarações, sem pré-requisitos, sem detalhes. Simplesmente “toma aqui esse código para executar depois”.

Iteradores (“visitors”)

Estamos sempre processando coleções e iterando sobre elementos. Na maior parte dos casos, um simples loop resolve o problema, mas em outros, somos obrigados a repetir o mesmo loop em uma série de métodos diferentes pois as operações são parecidas, mas não idênticas. Em outros casos a lógica de iteração é um pouco mais complexa e se mistura com a lógica que processa cada elemento.

Closures são fantásticos para resolver esses problemas. Vejamos um exemplo que aconteceu recentemente comigo. Eu precisava substituir o mapeamento do Hibernate em umas 400 classes java. Fiz um pequeno método que encontrava as classes e para cada uma encontrada, eu aplicava a lógica de substituição.

O método para percorrer as classes ficou assim:

 def on_java_classes
   Find.find(@root) do |file_name|
     yield(file_name) if file_name =~ /\/vo\/.*\.java$/
   end
 end

Esse método percorre toda a árvore de diretórios abaixo de @root e se o arquivo encontrado estiver sob um diretório “vo” e terminar com ”.java”, o bloco de código é executado.

E para usar o método para trocar um mapeamento fica assim:

 on_java_classes do |file_name|
   change_hibernate_mapping(file_name)
 end

É lógico que eu poderia usar esse loop e meu método tudo em um bolo só, mas agora eu tenho um método genérico que me permite percorrer essas classes e executar o que eu quiser com elas sem duplicar a lógica que itera sobre as classes. Posso, por exemplo, listar todos os mapeamentos, contar quantos “VOs” tenho, vasculhar se existem mapeamentos fora do padrão e outras coisas do tipo.

A coisa fica tão transparente, que posso usar coisas como “next” (para processar o próximo elemento) e “break” (para parar a iteração) dentro dos closures e eles funcionam como esperado. Por exemplo, se eu quisesse apenas fazer um teste com os 10 primeiros arquivos, poderia colocar um “break” no meio, dessa forma:

   i = 1
   on_java_classes do |file_name|
       break if i > 10 # passou de 10 arquivos, caia fora do método
       change_hibernate_mapping(file_name)
       i += 1
   end

Processamento “durante”

Quase todas as operações que fazemos de IO precisam de algo para “abrir” a operação e garantir que ela seja “fechada” quando terminamos o que precisamos. Isso vale para streams, arquivos, sockets, transações, record sets e sei lá mais quantas coisas.

Isso implica em uma lógica bastante incoveniente que costuma ser repetida à exaustão: abra conexão, execute o que precisar, garanta que foi fechado (normalmente com um bloco para tratamento de exceção).

Conexões com bancos de dados são particularmente um porre, porque costumam abrir e fechar vários objetos.

Com closures, resolvemos isso com facilidade. Aqui um exemplo de como se usar o Ruby-DBI da maneira tradicional. O exemplo foi tirado da própria página do Ruby-DBI, mas com o tratamento de exceções apropriado.

 require 'dbi'

 dbh = nil

 begin
   dbh = DBI.connect('DBI:Mysql:test', 'testuser', 'testpwd')

   sth = nil
   begin
     sth = dbh.prepare('select * from simple01')
     sth.execute

     while row = sth.fetch do
       p row
     end

   # Precisamos tentar fechar o statement, não importa qual o erro
   ensure
     sth.finish if sth
   end

 # Precisamos tentar fechar a conexão também
 ensure
   dbh.disconnect if dbh
 end

Com closures, a coisa fica bem mais simples. Podemos criar coisas para serem executadas ao redor do bloco de código e simplesmente esquecermos da necessidade de ficar abrindo e fechando objetos. (Aviso: o DBI já tem métodos que funcionam assim).

Primeiro, criamos dois métodos:

 # Esse método cuida de abrir a conexão, executar algo
 # e garantir q a conexão seja fechada.
 def connect(uri, user, password)
   dbh = nil
   begin
     dbh = DBI.connect(uri, user, password)
     yield(dbh)
   ensure
     dbh.disconnect if dbh
   end
 end

 # Esse método executa algo para cada registro recuperado
 # em um SQL. Ele cuida de fechar os objetos envolvidos
 # no processo. 
 def for_each_record_in(dbh, sql)
   sth = nil
   begin
     sth = dbh.prepare(sql)
     sth.execute

     while row = sth.fetch do
       yield(row)
     end
   ensure
     sth.finish if sth
   end
 end

E agora, o código para fazer exatamente a mesma coisa que o primeiro exemplo passa a ser:

 connect('DBI:Mysql:test', 'testuser', 'testpwd') do |connection|
   for_each_record_in(connection, 'select * from simple01') do |record|
     p record
   end
 end

Toda vez que precisamos de um código “de preparação” e um código “de finalização” podemos usar closures para poupar trabalho. Além das óbvias vantagens de redução de duplicação, fica bem mais difícil esquecer de fechar algo :)

Só para constar, existe um padrão pra isso, é o “Open During” do Kent Beck. Está no livro dele de “Best Smalltalk Patterns”.

Lógica condicional

Não somente de “ifs” e “switches” vivem os condicionais. Com closures, podemos simular condicionais para condições mais complexas.

Digamos que eu tenha um analisador de texto que me retorne palavrões encontrados em um HTML qualquer. Caso eu encontre qualquer palavrão, quero mostrar uma mensagem de erro.Uma maneira de fazer isso seria assim:

 # O método "find_dirty_words me retorna a coleção de palavrões
 # encontrados no texto.
 dirty_words = html.find_dirty_words
 unless words.empty?
   show_error_message(html, dirty_words, "Bad, bad, no cookies for you.")
 end

Por outro lado, posso criar isso em um método com closures dessa maneira:

 class Html
   def when_dirty_words_found
     dirty_words = html.find_dirty_words
     yield(dirty_words) unless words.empty?
   end
 end

E agora, posso usar assim:

 html.when_dirty_words_found do |dirty_words|
   show_error_message(html, dirty_words, "Bad, bad, no cookies for you.")
 end

Aliás, agora posso executar qualquer lógica com esses palavrões. Posso tentar limpá-los, contar sua frequência (estatística?), colocá-los em uma lista negra e assim por diante.

O melhor exemplo real de execução condicional com closures talvez seja o uso de REST no Rails (veja o método “respond_to” no “Listing.5”).

Domain Specific Language

Arrisco dizer que é impossível implementar uma boa DSL Interna (definição segundo Martin Fowler) sem closures.

Isso porque eles permitem uma grande flexibilidade na sintaxe, permitindo que você use os closures como blocos de código que podem ser armazenados, executados, repetidos, omitidos, reestruturados, postergados ou quaisquer que sejam as transformações necessárias para sua DSL funcionar.

Exemplos ótimos de DSL são o XML Builder e o Rake. Com pouco esforço você conseguem sintaxes perfeitas que se encaixam dentro do Ruby como se fosse parte da linguagem.

Veja por exemplo, como construir um xml com o XML Builder:

 require 'builder'

 xml = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
 xml.usuario(:id => 127) do
 xml.nome("Ronie")
 xml.sobrenome("Uliana")
 xml.idade(31, :type => "integer")
 xml.preferencias do
   ["strogonoff", "lasanha", "bolo de carne"].each do |item|
     xml.comida(item)
   end
 end

Isso gera o seguinte xml:

<usuario id="127">
 <nome>Ronie</nome>
 <sobrenome>Uliana</sobrenome>
 <idade type="integer">31</idade>
 <preferencias>
   <comida>strogonoff</comida>
   <comida>lasanha</comida>
   <comida>bolo de carne</comida>
 </preferencias>
</usuario>

Nesse exemplo, cada aninhamento é um closure, e o builder os rastreia para saber como montar o xml. Muito simples e prático, na verdade.

Generators

Esse eu confesso que nunca me resolveu nenhum grande problema até o momento, mas é uma técnica boa para se ter no cinto de utilidades.

Basicamente, é uma função que retorna outra, a primeira “configura” a segunda. Um exemplo simples que achei foi esse aí abaixo. Ele gera um closure que adiciona sempre o mesmo número ao número passado como parâmetro. Por exemplo, gero uma closure que adiciona sempre 5 a qualquer número que eu passe para ela. No exemplo abaixo criei dois desses, um “adicionador de cincos” e um “adicionador de oitos”.

O exemplo é meio bobo, mas se você pensar um pouco vai achar possibilidades interessantes para isso. Não sei se essas idéias seriam práticas, mas com certeza serão interessantes.

 def create_adder(number)
   lambda {|x| number + x}
 end

 add5 = create_adder(5)
 add8 = create_adder(8)

 puts add5.call(10) # retona 15, ou seja, 10 + 5

 # "add5.call(10)" é a mesma coisa que fazer add5<sup><a href="#fn10">10</a></sup>
 # Pra facilitar, vamos usar essa notação nos exemplos abaixo
 puts add8[ 30 ] # retona 38, ou seja, 30 + 8
 puts add8[ add5[ 1 ] ] # retona 14, ou seja, 1 + 5 + 8
 puts add5[ add5[ add5[ 0 ] ] ] # retorna 15... adivinha porquê :)

Um bocado complicado de entender? Bom, “lambda” é uma função que pega um bloco de código (o que está entre ”{}”) e retonar uma closure de verdade. Na prática, estou retornando uma função que será atribuída a uma variável para ser executada mais tarde. A parte legal é que essa função é “configurada” com o parâmetro “number”.

Esse é realmente embaçado, acho que fazer um equivalente em Java pode ajudar alguns (só espero não complicar o resto do povo)

 public class ClosureExample {

   public static interface Adder {
     public int call ( int x );
   }

   public static Adder createAdder ( final int number ) {
     return new Adder () {
       public int call ( int x ) {
         return x + number;
       }
     };
   }

   public static void main ( String [] args ) {
     Adder add5 = createAdder ( 5 );
     Adder add8 = createAdder ( 8 );

     System.out.println ( add5.call ( 10 ) );
     System.out.println ( add8.call ( 30 ) );
     System.out.println ( add8.call ( add5.call ( 1 ) ) );
     System.out.println ( add5.call ( add5.call ( add5.call ( 0 ) ) ) );
   }
 }

Com uma pequena variação disso da para fazer acumuladores. Ou seja, uma função que preserva a memória cada vez que é executada.

 def create_accum
   memory = 0
   lambda{|x| memory += x}
 end

 accum = create_accum
 puts accum[ 5 ] // retorna 5
 puts accum[ 10 ] // retorna 15
 puts accum[ 130 ] // retorna 145

Conclusão

Closures são muito úteis para várias tarefas comuns da computação. Não é porque sua linguagem favorita não tem e você acha que se vira muito bem nela que Closures deixam de ter sua utilidade. :)

Fechando o artigo do mesma maneira que abri:

“Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.”—Alan Perlis, “Epigrams on Programming”

Have a nice coding! :)

segunda-feira, outubro 08, 2007

Off-Topic: O Ronie foi abduzido (de novo!!!!)

Só pra avisar... Vários colegas andam tentando me contactar e nada d'eu responder. Não fiquei mal-educado de repente, não mais que o habitual, pelo menos. Mas estamos numa das fases mais críticas do projeto, bem no esquema "ou vai ou racha". Com isso, estou há algumas semanas sem comer ou dormir muito bem, sem abrir meu e-mail pessoal, sem postar no RubyOnBr, sem responder Skype ou Messenger e coisas do tipo. Sei que é uma tremenda viadagem, mas nesse ponto, quero fazer esse projeto funcionar. Dado esse estado de "podreira-meio-aviadado-não-responde-ao-maldito-e-mail" que eu me encontro, por favor, peço sua compreensão e uma doação pra pagar meu analista, ou então um abraço coletivo. Juro que antes de Dezembro volto a responder os e-mails.

domingo, setembro 23, 2007

Pra que ORM se não se usa OO?

Pra que usar um sistema de mapeamento objeto-relacional se você trata seus objetos como dados? Objeto sem comportamento é exatamente igual a uma estrutura de dados. Reza o mito que você deve ter "classes de negócio" que executam regras. Bom... essas classes fazem coisas com suas classes sem comportamento... então... qual a diferença disso para uma função PHP que manipula linhas em tabelas? Bom, UMA das diferenças, certamente, é que usamos um monte de traquitanas a mais para obter os dados. Outra é que ganhamos alguns métodos convenientes para buscar dados relacionados. Mas fora isso, qual o ganho? Sinceramente, mais de uma vez vejo gente defendendo com unhas e dentes frameworks de ORM e usando ele para fazer sistemas que são basicamente, um PHP com mais camadas. E batem no peito pra dizer que é um "Sistema Grande". Veja, não tenho nada contra ORM. É algo absolutamente necessário se você usa um sistema realmente OO... Caso contrário, é só um trambolho a mais para cuidar. Como vejo gente demais fazendo isso, eu devo estar deixando escapar alguma coisa muito importante. Mas alguém me diga o quê.

quarta-feira, setembro 12, 2007

Esbarrando com gente conhecida...

Ontem (ou foi anteontem?), entrevistei o Kenobi (posta muito no GUJ) para uma vaga na empresa em que eu trabalho. Fiz a maior confusão com as vagas e acabei falando a vaga errada pra ele :( ê vida besta. É um cara bastante seguro de si, só acho que causou uma má impressão na moça que comecou a conversa com ele. Engraçado como a gente vive esbarrando com gente "virtualmente" conhecida. Acho que o mundo da informática (pelo menos no Brasil) é menor do que eu suspeitava... ou então preciso viajar mais para o resto do país e sair um pouco da Cidade da Garoa :p

Melhorando o detector de "bullshit"

Sabe quando você está lendo um texto, não chegou nem na metade e pensa: "bullshit". Poisé, faz um tempo tenho um "detector mental de bullshit" pra esse tipo de coisa. Funciona igual aos "code bad smells": você está numa conversa, ou está lendo algo, e algumas palavras fazer o detector tilintar. Aí você lê com mais atenção pra ver se é balela mesmo ou se tem fundamento. Tem algumas palavras que fazem o detector simplesmente gritar :) Muitas delas juntas, então, quase dão dor de cabeça!
  • Enterprise (top top!)
  • Arquitetura (ou pior, um texto todo em português, mas usando o termo em inglês)
  • Flexível
  • Robusto
  • Ágil (todo mundo virou ágil depois que isso virou power word)
  • Escalável
  • Performance (principalmente se não vem com NENHUMA medição)
  • "O correto é" ("correto" depende muuuuito do que você está fazendo)
  • Grande porte (parece que o povo mistura "muitas regras de negócio", "muitos usuários" ou "transações bagaraio" sem muita discriminação)
Entre outras... Recentemente fiz um "upgrade" no detector. Adicionei uma regra: "discussões sobre méritos de tecnologias em que o cara dá argumentos sobre performance". Muito, mas muito raramente mesmo, isso tem algum embasamento maior do que mero "achismo". E, pessoalmente, "rápido o suficiente" já está bom. Por outro lado, tem palavras que entram no "white list" e aliviam a coisa:
  • Teste
  • Padrão ou Anti-padrão
  • Pragmático
É... minha white list tá pequena! :D E aí? Alguém tem outras palavras que fazem o "detector de bullshit" tocar? (Garanto que tem gente que coloca a palavra "Rails" na lista :D)

segunda-feira, setembro 10, 2007

Da série "Notas Mentais": Pessoas Sérias

Não consigo levar a sério pessoas que se levam muito a sério... Por quê? Bom, cedo ou tarde, vamos todos virar adubo pra grama, que é a mesma função que um belo monte de merda faz. Aliás, o "belo monte de merda" deve ser um adubo melhor do que nós seremos... :p Aí que está a parte complicada... O cara vem todo importante, botando marra e arrotando peru... aí me vem esse pensamento em mente e não consigo ficar sério. :)

segunda-feira, agosto 27, 2007

"Pseudo" encapsulamento

Junto com "herança" e "polimorfismo", "encapsulamento" é o terceiro pilar da programação orientada a objetos. Aliás, tentar fazer da programação um aglomerado de "caixas pretas" é uma coisa que tentamos desde que inventaram o Assembly. A idéia sempre foi ocultar detalhes para que se possa modificar o programa facilmente ou, no mínimo, não esquentar a cabeça com detalhes. Um dos melhores patterns que já vi sobre o assunto faz parte de um catálogo de patterns do Brian Foote chamado "The Selfish Class". Ele não foca exatamente sobre mudança, mas sobre facilidade de uso. Mas no fim da contas, fazer muito através de uma interface mínima é o melhor ponto sobre encapsulamento. O que vemos muito por aí é encapsulamento "pra inglês ver". Só funciona na teoria, porque na prática, é igual tentar se esconder tampando os olhos, no melhor estilo: "se eu não o vejo, ele também não me vê". Ao longo do tempo, comecei a encarar alguns items como bad smell de "encapsulamento pra inglês ver" e gostaria de compartilhar eles: Métodos de acesso : Talvez o bad smell mais comum e o mais polêmico. Métodos de acesso (gets e sets) quebram encapsulamento. Não sou o único a pensar assim (procure no google, você vai achar mais alguns doidos, inclusive com muito mais cacife que eu). Basicamente a idéia é a seguinte: você está expondo seus dados internos, mesmo que seja atráves de um método. Isso não é muito problema, desde que, em 90% do tempo, você trate o objeto como um todo ao invés de ficar cutucando seus dados (do objeto, não os seus) Mas quando você começa a pegar os dados de uma tela web e jogar campo a campo no seu objeto, isso significa que você quebrou o encapsulamento. Uma maneira "caixa preta" de fazer isso seria jogar os dados todos pra dentro do objeto e ele pegar o que precisa OU fazer a atribuição via reflexão. O ponto é o seguinte: se você alterar um campo e tiver que mexer em algum lugar que não seja tela, banco ou regra de negócio, então seu encapsulamento está quebradão. Caso típico é quando você precisa ficar"repassando" os campos. Não concorda? Não tem problema, é um feeling meu (e de alguns outros doidos). Coloca o seu aí, então, ué :) Delegate : Delegates são uma ótima forma de diminuir o número de objetos que você tem que trabalhar. Mas quando você começa a delegar TUDO, ou então os mesmo dados (parâmetros e retorno) são simplesmente passados sem alteração ou qualquer outro comportamento por 2 ou 3 métodos... hmmm... tem algo estranho. Já vi isso sendo usado como uma forma de manter as coisas no lugar quando se está em uma arquitura com muitas camadas. Mas sinceramente, isso é só uma maneira muito esquisita de violar camadas, não de preservá-las. Se você precisa de algo lááá do fundo em uma camada da frente, sem alterações, melhor repensar sua arquitetura nesse ponto. Iteradores externos : (essa vai fazer alguém querer comer meu fígado... sem tempero!!!) Essa viola a máxima do "Tell, don't ask" sem dó nem perdão. A moral da história é a seguinte, ao invés de pedir para as coleções trabalharem para você, somos obrigados a pedir licença, pegar tudo o que elas têm e ficar item a item vendo o que precisa ser feito. A solução para isso seriam iteradores internos, implementados lindamente com closures (viva Smalltalk, 30 anos atrás já fazia isso!). À primeira vista, quem não conhece vai achar que dá na mesma, só quando você olha mais de perto vê que várias operações básicas que fazemos todos os dias podem ser encapsuladas com isso. Tais como "selecionar o primeiro que satisfaz uma condição", "fazer um 'de->para' com os objetos da coleção", "acumular valores segundo uma fórmula qualquer", e por aí vai. "Data Transfer Objects" ou "Value Objects" : Essa viola o "objetos devem ter comportamento". Veja bem, um Objeto (com "O" maíusculo) é justamente a junção de DADOS + FUNCIONALIDADE (comportamento), se você tem dados em um objeto, então a funcionalidade que atua sobre esse objetos está em outro lugar... separando dados e funcionalidades voltamos a programação procedural (ou funcional). E no que isso viola encapsulamento? Viola porque outros objetos precisam conhecer os detalhes do seu DTO para poderem trabalhar com ele. Trocando em miúdos, esse "pseudo-objeto" está com suas estranhas completamente expostas e a mercê de outros objetos malignos que querem comer seu coração (do objeto, não o seu). Okay, como tudo na vida, existem lugares onde DTOs são legais e bemvindos. Mas quando eles começam a aparecer demais, aí dá pra começar a ficar desconfiado. Se a gente realmente caprichar no uso dos DTOs, vamos acabar com um sistema OO com a cara de um ASP dos antigos, mas com mais camadas... No geral, são esses meus bad smells de encapsulamento. E são realmente bad smells, ou seja, pode ser que não seja nada e que o uso esteja correto, mas não custa dar uma verificada. Outra coisa é que encapsulamento é legal, mas não é nenhuma vaca sagrada (é?). Violo ela com muito gosto e uma pitada de sal se for para deixar meu sistema mais simples. Infelizmente, normalmente é o contrário... :)

sexta-feira, agosto 10, 2007

Linguagens do futuro

Um colega meu ("O Francês") me apontou alguns links que o pessoal aposta no Erlang como próximo sucessor do java. Isso me deu vontade e exercitar minha "veia futuróloga". Não que eu seja um bom futurólogo, mas é que é divertido ficar fazendo extrapolações. Na minha humilde opinião, acho que existe um buraco a ser preenchido para a "Próxima Grande Linguagem". Explico: Nos anos recentes, ando observando uma crescente valorização das linguagens de script, acho que principalmente por causa do Javascript, Ruby e Python (não descartando PHP) que vêm ganhando atenção. Com isso, vi que certas coisas, como "closures" ganharem bastante importância conforme o pessoal começou a perceber que isso facilita muito a programação. Essas funcionalidades todas, até onde percebo, são justamente as que facilitam a criação de DSLs (Domain Specific Languages). Nota pessoal: um bom sistema Orientado a Objeto é praticamente uma DSL. As boas práticas de programação OO parecem levar inadvertidamente a uma DSL. Se esse pressuposto for correto, a próxima grande linguagem deveria ter funcionalidades boas para gerar DSLs. As melhores que conheço hoje são Lisp e depois Ruby. Para o Lisp virar a "Próxima Grande Linguagem", me parece que faltam duas coisas: uma sintaxe mais digerível para os desenvolvedores que estão acostumados com linguagens de sintaxe "C-like" e uma "killer application" (ou deveria dizer "killer framework") para web. Digo isso porque aplicações web são o que está quente hoje, com tendência a ficar mais quente :) Para o Ruby virar a "Próxima Grande Linguagem", sinto que falta uma boa máquina virtual, talvez com compilação para alguma linguagem intermediária. Aí entra a competência do YARV ou do JRuby (ou IronRuby também). Conclusão: acho que existe um buraco aí que poderia ser preenchido por algo que compilasse em bytecode de Lisp, mas que tivesse uma sintaxe mais similar ao que a grande maioria dos desenvolvedores está acostumado. Eu estava trabalhando em algo na linha, só por farra, mas minhas habilidades em compiladores ainda é meio crua e vai demorar um bom tempo até aparecer algo que valha a pena brincar :) Vamos ver no que dá.

segunda-feira, julho 30, 2007

Da série "Notas mentais": Pessoas

Elas são como livros, só mais inconstantes...

Da série "Notas mentais": Livros

1 - Nunca acredite piamente em um livro. Exceto pela matemática (e mesmo alguns desses), o resto são apenas opiniões e hipóteses. 2 - Nunca duvide demais de um livro. Mesmo em um livro totalmente errado, algumas idéias podem estar certas. (A idéia que um relógio quebrado ainda está certo duas vezes ao dia) 3 - Consolide sua opinião frequentemente, a maior parte do que é certo hoje se torna errado cedo ou tarde, basta dar tempo o suficiente. Pode me chamar de "descrente", se é que isso tem a ver com "crer" :D

quinta-feira, junho 28, 2007

Candidatos a idiomas no Java

Apesar de focar muito em Ruby, é o Java quem paga o pão nosso de cada dia. E apesar de eu ter algumas encrespadas com a linguagem, ela é muito competente no que faz. Um pouco travada, talvez, mas ninguém é perfeito :) O interessante é que depois de um tempo, vamos pegando alguns "idiomas", ou seja, mini-padrões que vivem se repetindo. Acho que já li esses dois em algum lugar (muito provável), mas como estou usando eles com muita frequência, gostaria de deixá-los registrados:
Idioma: Parâmetros Opcionais Explícitos Problema: Você tem um método quem tem parâmetros opcionais. Frequentemente, a opção não passada é "null" ou tem um valor padrão bem definido. Solução: Crie um método para cada configuração de parâmetros que você tenha e faça os com menos parâmetros chamarem os de mais parâmetros. Apenas o método com mais parâmetros tem a lógica. Reasoning: Sempre usar um único método pode ser um problema se na maior parte das vezes o valor passado é o mesmo. Se você quiser alterar o "padrão", tem que caçar em todo o código. Por outro lado, travar um valor fixo faz você ter que alterar o código se quiser algo ligeiramente diferente. Oferecendo um bom padrão e , ao mesmo tempo, flexibilidade, resolve os dois usos e o custo é baixo. Exemplo:
public Autocomplete ( String className, Object parameter, int maxSize,
 String highlightTemplate ) {
    try {
        this.klass = Class.forName ( className + "Option" );
        this.parameter = parameter;
        this.maxSize = maxSize;
        this.highlightTemplate = highlightTemplate;
        initialize ();
    } catch ( ClassNotFoundException e ) {
        throw new RuntimeException ( "Class not found for \"" + className + "\"", e );
    }
}

public Autocomplete ( String className, Object parameter, int maxSize ) {
    this ( className, parameter, maxSize, "$1" );
}

public Autocomplete ( String className, Object parameter ) {
    this ( className, parameter, 8 );
}
Parece que dá um pouco de trabalho (e até dá), mas é uma maneira muito clara de você passar a idéia de ter parâmetros opcionais, e depois que você acostuma, é completamente trivial alterar os valores padrão. Como experiência própria, só procure evitar "explodir" o número de métodos, procure só fazer os métodos mais usados como padrão e usar a versão "expandida" para flexibilidade total. ---- Idioma: Parâmetros Nomeados Problema: Você tem uma classe que precisa de muitos parâmetros (principalmente opcionais) para ser configurada. Solução: Para cada parâmetro, crie um método "set" que retorna o próprio objeto, assim, você pode encadear os métodos ("method chaining") em um única chamada. Reasoning: Criar construtores com muitos parâmetros torna a classe difícil de usar, e forçar um série de métodos "set" repetindo o mesmo objeto torna o código chato de digitar e às vezes pouco óbvio de ser lido. Exemplo: (Tirado da biblioteca JDOM)
new Element("book").setAttribute ( "id", "1" ).setText("Java Idioms");
O equivalente sem o idioma seria:
Element element = new Element("book")
element.setAttribute ( "id", "1" )
element.setText("Java Idioms");

Bom, é isso. Nenhuma invenção nova, só catalogando idiomas que vivo esbarrando e acho legais :)

quinta-feira, maio 03, 2007

Estaticamente trabalhoso

Tava observando um amigo meu trabalhar em um framework ontem e percebi umas coisas interessantes. O processo foi mais ou menos assim: O framework era em Java e ele precisava pegar uns registros no banco de dados pra montar uma combo. O framework tinha sido criado em cima do Hibernate e já cuidava de toda parte xarope de sessions e afins. Pra pegar os dados ele criou um Criteria e passou pro framework. Como não havia um DAO para aquele objeto, ele precisou criá-lo, declarar a coisa no Spring e montar o método que buscava o DAO de lá (tá acompanhando? Se perdeu? Inda não acabou). Para preservar a tipagem estática e encapsular as chamadas nas camadas corretas, ele ainda teve que fazer mais três ou quatro declarações de métodos onde a única diferença era o tipo da variável. Fiquei observando e percebi que a única coisa que realmente havia mudado era o Criteria, o resto, todo o resto incluindo o DAO, só foi feito para preservar a tipagem estática e "isolar" as camadas (isso mesmo, "isolar" entre aspas. Tenho ressalvas com isso, mas é papo pra outro post). Não acho tipagem estática ruim e não estou pregando contra divisão de software em camadas, mas fiquei me perguntando o quão longe a gente vai e o quanto a gente complica as coisas onde não há necessidade real. Nesse exemplo não havia. Um simples hql "select x from Classe as x" enviado para um DAO genérico resolveria de sobra. Será que a gente não anda inventando mais coisa do que seria necessário só por questões de "assim o desenvolvedor idiota não vai usar errado" e "assim fica mais flexível"? Precisamos de verdade dessa proteção e dessa "flexibilidade"? O tempo passa, vou ficando mais cético. Só queria saber se pra bem ou pra mal.

sexta-feira, abril 20, 2007

E o que deu o SunTechDays

No fim das contas, as palestras estavam todas atrasadas mais de hora e a que eu queria assistir ficou muito tarde. Poisé, fica pro próximo ano... O que valeu mesmo foi ver um carinha dançando pra ganhar uma jaqueta (mó legal a jaqueta). O cara literalmente "se desdobrou", só quem estava lá pra entender. Nunca ri tanto!

Ronie no SunTechDays

(essa porcaria de computador do evento nao estah com os acentos configurados... jah viu como vai sair o post) Depois de todo esse tempo mexendo com Java, resolvi vir a um SunTechDays pra "tirar a virgindade..." Vim soh hoje de manha (dia 20) e pulei o dia 19. Evento bem organizado, tudo muito bonito (sem brincadeira, eh bonito mesmo!). Fui experimentar a palestra do Bruno Souza... ai... ai..., mesma pagacao de pau de sempre, tive que sair logo, antes que ele conseguisse me convencer de que Java era a primeira maravilha do universo e, se Deus usasse Java, o Big Bang teria sido open-source e que eu conseguiria fazer ele no meu celular... Mas o cara eh o "Javaman", entao tah certo. De resto, soh estou esperando, espero que a palestra sobre Scripts rodando na VM seja boa. Pena nao poder ficar o evento todo...

quarta-feira, abril 18, 2007

Grandes decepções

Eu estava comentando com uns colegas sobre "grandes idéias brochantes", ou seja, idéias que inicialmente pareciam muito boas, mas que depois descobrimos alternativas melhores, ou descobrimos que não eram lá tão boas assim. [AVISO! O conteúdo a seguir é de alta octanagem e pode causar flame wars ao menor contato com trolls e outros cabeçudos de plantão. Lembre-se que essas opiniões são baseadas na minha experiência pessoal. Sua opinião pode ser diversa e sua experiência pode ser outra, por isso, se quiser dizer que estou errado e sou um idiota, seja no mínimo educado, pra não passar por troll. (E se me ofender, vou aí chutar sua bunda!)] Vou colocar meus 2 centavos de "grandes idéias brochantes" que aconteceram comigo: Brochante 1: RUP Em idos de 1998, quando descobri RUP, achei que era "O" processo. Que a coisa era a solução definitiva para os problemas de projetos de software mal feitos. Comecei a estudar com afinco, e pensei "Nossa, quanta coisa! Mas um bom processo deve ter definição pra TUDO, então tá certo." Depois, tive que fazer um projeto que usávamos um pouco de RUP. No início achei que tínhamos problemas porque estávamos usando pouca coisa, aí aumentamos nossa "aderência" ao RUP. Resultado: tempo gasto com documentação, menos desenvolvimento, documentos desincronizados, mais tempo com documentação, menos desenvolvimento... e no final, ainda muitos bugs e nenhuma estimativa confiável. Fiquei meio com um pé atrás, será que eu tinha errado? Primeira experiência com a coisa, acho que não tinha usado certo. Aí conheci XP. Primeiro projeto besta que apareceu, experimentei. Ótimos resultados. Apareceu um projeto grande, experimentamos XP. A cada prática que colocávamos do XP, melhoravam os resultados. Conclusão: brochei do RUP. Brochante 2: SOAP A primeira vez que vi, achei que web services com soap eram a melhor invenção desde o pão fatiado. Ainda mais a integração que ele tinha com a plataforma .NET, super simples e fácil. Um pouco mais tarde, descobri que o negócio era meio complicado, já que, fora do .NET, mexer com SOAP era um pesadelo (era o tempo do Apache-SOAP ainda, nem existia o Axis). Daí, tive que fazer integração com alguns clientes que estavam usando simples "XML Vai, XML Vem" (protocolo "XV2") com posts e gets. Foi simples, fácil e muito rápido. Pronto, brochou geral. Hoje minha mente buzina "muita solução pra pouco problema" toda vez que penso em SOAP. E olha que isso foi antes d'eu conhecer RESTful Web Services. Brochante 3: Aspectos Quando conheci aspectos, achei a coisa mais poderosa do mundo. Simples, não intrusivo e resolvia grandes problemas. Comecei a estudar. Até ajudei um colega a fazer um chat P2P de brincadeira em que a encriptação das mensagens era feita por aspecto. Colocava aspectos, pum, mensagens encriptadas; tirava aspectos, pam, mensagens plain text. Fantástico! Comecei a sonhar com uma camada de persistência não intrusiva, toda feita com aspectos. Aí conheci RUBY e o "alias". Fiz tudo que fazia com aspectos sem esquentar a cabeca com pointcuts, advices e outros blah blah blahs. Resultado: brochei de Aspectos. Hoje, quando Aspectos me vêm à mente, tem uma tag mental escrita: "classes abertas + alias, ponto final". Já tentei não pensar nisso, mas não adianta, encalacrou... Brochante 4: Portlets Quando vi, achei uma boa idéia. Era a componentização para web, finalmente! Com um pouco mais de trabalho, a idéia serviria pra fazer componentes que poderiam ser utilizados em aplicações "não-portal". Comecei a estudar... De cara, achei muito tosco, submeter todo o form e tentar manter o estado de cada Portlet era uma gambiarra sem fim... Mas vá, ossos do ofício, já que a web funciona assim. Comecei a achar que componentes pra web não eram lá uma idéia muito boa (ainda acho isso...). Aí apareceu o Gmail, com intenso uso de AJAX. Olhei pros Portlets, pensei em como fazer eles em AJAX e pensei... "poisé, tou perdendo tempo"... Sei que AJAX tem muitas limitações, conheço elas porque uso muito AJAX. O que só reforça minha idéia de que "aplicação web não pode ser pensada como se fosse desktop". Mas sinceramente, entre eles e Portlets... : Brochante 5: Java Essa foi engraçada, brochei antes de conhecer a linguagem. Em meados de 2000 eu trabalhava com Perl Orientado a Objetos (é... existe sim), e fiquei curioso quanto ao Smalltalk, já que todos os lugares falando sobre orientação a objeto mencionavam Smalltalk de um jeito ou de outro. Resolvi aprender... Fiquei maravilhado com a linguagem: simples, poderosíssima, ambiente integrado onde eu alterava os sistemas "à quente". UAU! Aí apareceu a oportunidade de trabalhar com Java, uma vontade que eu tinha desde os tempos de faculdade, pois todo mundo elogiava. Cheguei cheio de espectativas: "Java é bem mais novo, é orientado a objetos e deve ter todos os conceitos do Smalltalk, mas com tipagem forte!" ... Frau frau frau... Brochação geral... Collections ridículas, sem closures, pouca extensibilidade, ciclo de compilação full e nada de alterar a coisa "à quente". E a tipagem estática mais atrapalhava do que resolvia. Conclusão: Hoje gosto muito do Java e desde lá venho usando ele 8 horas por dia, 5 dias por semana. Mas se me perguntarem se é a melhor linguagem pra programação... beeeeemmmm... tenho saudades de collections com closures... Lições aprendidas... O que foi que aprendi com essas "brochações"? Aprendi que nem toda idéia do "mainstream" é uma boa, e a olhar propaganda com muita desconfiança. Aprendi que é bom olhar o underground da computação em busca de idéias novas, ou pior, de velhas idéias fantásticas que nunca caíram no gosto da maioria. Hoje sei que sou um programador melhor a cada vez que gasto tempo aprendendo algo que me faz pensar diferente... ando aprendendo LISP... espero que dê frutos (já deu, pra ser honesto)

segunda-feira, março 19, 2007

Arquitetura três camadas é um mito sobre desacoplamento...

Uma coisa que aprendemos desde nosso primeiro "if-else" em programação é que aplicações devem ser feitas em 3 camadas (ou mais), a saber: apresentação, aplicação e persistência. Uma arquitetura muito similar a ela é a popular MVC, que separa as coisas de forma parecida, mas a camada de persistência some dentro do "model" e a camada de aplicação é dividida entre "model" (com regras de negócio) e "controller" (que funciona como "cola" entre View e Model). Hoje em dia, boa parte das arquiteturas pra web é um misto de MVC com 3 camadas, num bororô formado por frameworks de apresentação, controllers temperados com "Dependency Injection" e um mapeador objeto-relacional. Tudo isso somado fornece o melhor nível de desacoplamento possível no sistema. É possível até mesmo trocar de banco de dados Oracle pra SQL-Server trocando apenas umas poucas linhas... Mas será que é desacoplado mesmo? Que tal experimentar o desacoplamento com uma alteração bastante comum como, vejamos... adicionar um campo? Bom, temos que mexer na view pra adicionar o campo, tb temos que mexer na camada de negócio para tratar o campo e validar, aí temos o transporte (V.O.s?) onde temos que adicionar o campo, e finalmente, adicionar no banco (o Hibernate até gera o campo pra vc, só que ele não migra os dados quando acontece a evolução do esquema, é na base da recriação da tabela). Okay, o que temos aqui? Uma simples alteração gerou uma "shotgun surgery" no código, necessitando de alterações em todas as camadas. Agora a questão é, como eu ISOLO isso? E, sendo mais cínico, de que me adianta trocar de banco com uma linha (e muito raramente eu faria isso), se para adicionar um simples campo eu preciso espalhar as alterações pra todo lado? Eu já vi a algum tempo atrás na web alguns artigos com a mesma preocupação, eles tinham até um nome pra isso, mas sinceramente não consigo me lembrar, e por consequência, não acho mais os artigos :( Se alguém achar, me dá um toque.

quinta-feira, janeiro 11, 2007

De volta de lugar nenhum, não fui mesmo.

Depois de dar um gás no RubyOnBr e ele ocupar todo tempo livre q eu tinha pra escrever besteira, resolvi voltar a blogar. E como jurei solenemente pela tira dos malvados que ia ser um cara legal esse ano, vou dar um tempo com meu outro blog :) Agora é pau na máquina e no código.