quinta-feira, dezembro 28, 2006

Swing, ame-o ou deixe-o. Não concordo.

Lendo Swing vs. SWT, do Amin Ahmad fiquei bastante indignado com o ponto de vista dele. Na prática, o artigo dele é um contra-ponto (bastante forte) sobre o artigo SWT Happens. Amin parece defender o SWT com unhas e dentes. Mr Ed, parece ser mais pragmático.
Pelo que minha pequena mente percebe, o sr. Amin faz uma comparação entre o Swing e o framework do Eclipse. Daí é Davi contra Golias e isso não serve.
Comparar uma API contra um framework não tem sentido algum. O Swing é uma ótima API MVC, com recursos escaláveis, flexíveis, robusta, estável e com bom (ainda não ótimo) suporte para look and feel. Tem mais coisas, mas isso basta. O SWT é uma contrapartida da IBM que objetiva maior facilidade de desenvolvimento (e por isso seu modelo MVC não é completo), performance mais elevada e comportamento nativo. Na prática, até que me provem o contrário, o SWT é o seguinte: se o controle existe no Sistema Operacional, então o SWT usa ele (tal como o AWT faz o conceito de peered controls); se o controle não existe no SO, então o SWT renderiza ele totalmente (via API 2D) na forma mais parecida com o controle nativo. É do comportamento peered do SWT que surge a necessidade de uma DLL no Windows ou uma SO no Linux para seu funcionamento.
O Swing renderiza tudo sempre. E por isso dizem que ele é mais lento. Mas nem tanto (e tenho dúvidas, às vezes). Bom isso é outra discussão.
Quanto ao artigo, não é possível comparar a API do Swing com um JFace, nem a java.util.prefs com o Preferences Framework do Eclipse. Isso é ridículo.
Concordo que a comunidade Swing carece de uma integração (que deve vir por parte da Sun) para construção de um framework de mais alto nível, com suporte a coisas simples, como construção de Wizards, janelas arrastáveis, agendamento de tarefas e outras coisas mais; todas prontas no framework Eclipse. Entretanto, isso não faz parte do Swing e, portanto, não pode ser comparado.
Acredito que o advento do Eclipse (foi em 2001, isso?) foi como graxa na engrenagem para quem usa Swing. A Sun apostou muito na Web, com as JSPs (e toda a tralha de JSRs associadas) e no Java como middleware, com os EJBs. Nesse cenário, o Swing ficou em segundo plano, apenas na correção de bugs.
Agora com o Java 6 o Swing ganhou algumas melhorias, como o suporte à famosa barra de tarefas. Mas foi só.
O pessoal do Netbeans tá se puxando com o desenvolvimento de soluções de alto nível, tal como existe no Eclipse, mas ainda não está perto, infelizmente. O suporte ao arraste de janelas, padronização de operações, suporte a plugins e a estruturação geral do projeto ainda é bem melhor no Eclipse que no Netbeans.
Mas a engrenagem está girando e agora com ela azeitada, as coisas devem seguir bem mais rápido.
Espero sinceramente que surja logo algo como o Eclipse Framework para o Swing. Nada de coisas como o Jide, o InfoNode ou o rudimentar FlexDoc, mas sim uma coisa prática, fácil de usar e disponível no próprio JDK. Talvez tenhamos que esperar uma JSR, mas isso demora.
Termino dizendo que o sr. Amin foi infeliz no artigo que escreveu e que, embora o Swing tenha limitações, podemos estendê-lo com outros produtos e, daí sim, compará-lo de igual para igual com o Eclipse.

quarta-feira, dezembro 13, 2006

Pontes e Cadastros

Cadastros não são pontes, mas adorariam ser. Pontes nunca, mas nunca gostariam de ser cadastros.

Cadastros são flexíveis, podem ser gerados automaticamente, possuem look & feel, podem ser multiplataforma e mesmo conectar em variados banco de dados.
Pontes são fixas, nascem e morrem no mesmo lugar.
Cadastros podem ser validadados, existem alguns simples, outros complexos e outros muito mais complexos ainda. Não obstante, existem mestre-detalhe, mestre-detalhe hierárquicos, multi-modais e até multi-modais paralelos (progamadores, cuidem os deadlocks!).
Pontes se resumem a isso: peso, altura e largura suportadas.
Cadastros evoluem. A cada versão do software eles podem adicionar recursos, reconsiderar comportamentos, mudar a ordem de tabulação, valores default, teclas de atalho (páro aqui). Podem corrigir erros, aumentar a performance, mudar elementos de interface, mudar conteúdo de elementos e ter inúmeras funcionalidades dispersas em lindos botõezinhos pelos arredores da tela.
Pontes sempre funcionam da mesma maneira: entramos num lado e saimos no outro.
Cadastros podem ter cores variadas, efeitos, gradientes, figuras, sons multimídia e qualquer conteúdo embedded (Flash é "da hora, mano"!).
Pontes, não muito além, são cor de cimento. E só.
Cadastros são construídos por fervorosas sessões de brainstorming, reuniões confusas, entrevoltos em atas, diagramas interacionais, navegacionais, estruturais, etc. Dão ênfase aos argumentos dos analistas, ao choro de projetistas, à alegria dos usuários e à penitência de programadores. Não sem histórico, são vistos como exuberantes consumidores de tempo e dinheiro. E de fato, o são.
Pontes são construídas com base na necessidade: fixa ou não; coberta ou não. E só.

Eu poderia elencar mais uma série de diferenças que, grosso modo, dizem o seguinte: cadastros são graciosos e podem funcionar; pontes são inertes e funcionam. E porque será que uma ponte não gostaria de ser um cadastro?

Pontes não querem ser cadastros
Pontes não querem ser cadastros porque, enquanto os cadastros são balizados pelos adjetivos, as pontes são norteadas por objetivos. E termina aqui a história.
Uma ponte serve para passar de um lado para outro. E pronto. Um cadastro serve para ver e modificar dados. E pronto também, ora essas!

E porque as pontes funcionam e os cadastros simplesmente gostariam de funcionar? Não vou menosprezar nossa classe dizendo que as pontes funcionam porque são feitas por engenheiros; isso porque nós informatas, também conhecemos (ou deveríamos conhecer) Cálculo, Integrais e Derivadas (poderia até argumentar em nosso favor, dizendo que somos nós que construímos os programas que os engenheiros utilizam, mas isso não resolve problema, apenas aquece a discussão).

Pontes não querem ser cadastros por elas têm vantagens:

1. Usuários não reclamam de pontes; de cadastros, todos têm o que falar.
2. Pontes têm vida útil expressa; cadastros sempre podem ser ajustados.
3. Pontes que não funcionam são implodidas; os cadastros são alterados.
4. Pontes que não suportam o peso (ou largura ou altura) recebem uma placa de limite máximo; cadastros são alterados.
5. Pontes têm comprimento fixo; cadastros podem ser redimensionados (aleluia aos gerenciadores de layout e ao saco dos programadores!)

...e a lista segue...

Lógico. Lógico?
Mas que raio de coisa é essa que cada vez mais que eu comparo uma ponte com um cadastro, uma ponte fica parecendo uma coisa tão tosca e mesmo assim não posso dizer que um cadastro é melhor que uma ponte? Um cadastro faz maravilhas, uma ponte não faz nada de mais além de permitir que eu atravesse de um lado para o outro! Porque uma ponte não quer ser um cadastro? Talvez porque tenhamos um círculo vicioso...

O Fim, o Começo e o Ciclo
Pontes são seletas: o usuário deve ser assim, se não for eu não aceito (em suma, tome meia-volta e vai procurar outra ponte, seu ignóbil). Cadastros não submissos, não escolhem o usuário, simplesmente o suportam.

PAUSA PARA O CAFÉ
O Engenheiro valida uma ponte em prancheta, no Design, via cálculo. Ele não coloca um cargueiro sobre uma ponte para testar se ela aguenta o peso. Ele coloca sim, uma placa avisando a tonelagem máxima.
O Informata especifica um conteúdo válido, mas não garante nada para o cadastro. Ele enche de validadores a sua tela e cruza os dedos esperando que a sequência de Murphi não ocorra enquanto ele for o responsável pelo bendito artefato.


Talvez por esse fim inglório é que os cadastros não funcionem (e consequentemente o porquê das pontes serem pontes mesmo). Não sei quem disse que tem que ser assim (na verdade eu sei, foi n-íade {cliente, vendedor, gerente disso, gerente daquilo, gerente daquilo outro, projetista, programador, analista...}, mas não posso dizer...), mas o fato é que é assim e pronto. Esse é o Fim.

E por esse fim tristonho é que, de tão flexível, robusto, etc. (olha os adjetivos aí gente!) é que um cadastro queria ser uma ponte, mas não vice-versa. Sendo ponte, ele seria simples e funcional (e óbvio, seu nome não seria Cadastro, seria Ponte). Por que não exibir cod_cliente ao invés de "Código do cliente" e pronto? Ah, claro! Fosse assim, não seria um cadastro, seria uma ponte!

Engenheiros (mesmo) são como advogados: é assim, o preço é esse e assino em baixo "se e somente se". Informatas são promíscuos, gananciosos... (vou procurar no Aurélio se Informática e termos correlatos não possuem algo como: Veja também > Adjetivo) garantem que tudo vai funcionar. Não poderiam ser humildes (esse adjetivo é bom, me lembra o Scott Ambler) e dizer: olha isso não pode ser assim (entenda-se, vou estudar mais) e eu não vou fazer (entenda-se, não sou ganancioso). Ou você acha que um engenheiro assinaria o projeto de uma ponte se não tivesse plena certeza (em outras palavras, matematicamente correto - e não simplesmente porque compilou...) do que ela faz e, principalmente, do que ela não faz? Isso é o Começo.

Finalmente, os engenheiros fazem pontes e aprendem com os projetos que não funcionam. Os informatas dizem que aprendem. E esse é o Ciclo.

Enfim, vou ser humilde (adjetivos, agrh!) e dizer que não sei a resposta porque os cadastros podem ser tão bonitos (e dá-lhe adjetivos!) e mesmo assim, as quase-feias e inflexíveis pontes não querem como eles.

sábado, dezembro 02, 2006

Projeto de Interfaces : A Relevância em Contexto

Hoje, iniciei um novo projeto. Nas apresentações iniciais para formação da equipe de trabalho, eu e um dos analistas discutíamos ferrenhamente quanto ao uso do Hibernate em aplicações OO: eu favorável, ele contrário.
Seu argumento era que o Hibernate trazia dados desnecessários na grande maioria das vezes, além de não ter a capacidade de fazer operações rowset. Deu um exemplo de cadastro (eu adoro esses exemplos!). Deixei ele falar:

"Nos sistemas OO, geralmente uma tela de cadastro tem relação 1-1 com um objeto de dado (javabean, por exemplo). Assim, quando esse objeto for muito grande e tiver muitas dependências, seu carregamento é demorado, mesmo sendo um objeto só. Veja por exemplo um objetão (palavras dele) com vários relacionamentos n-1 que geram combos. Suponha que essa foreignkey aponte para uma tabela com 1 milhão de registros (sempre usam esse número). Quando o cadastro for exibido, só essa combo demorará vários segundos para ser preenchida. Isso sem contar as outras dependências, que podem ser 1-n e até n-n. Eu prefiro SQL, é muito mais rápido. [E continuou, enquanto seu café esfriava...]

Argumentei várias coisas em favor do Hibernate, como consultas nomeadas, mapeamento explícito, códigos personalizados e, claro, SQL nativo em certos casos. Ele franziu a testa e continuava duvidando, dizendo que seu histórico com tudo isso não era nada agradável...

Foi então que me ocorreu de falar que, de fato, o problema que ele tanto comentava não era o Hibernate, mas sim algo muito anterior a ele. O problema estava no projeto da interface do usuário. Ele fez uma cara estranha, os outros dois analistas puxaram suas cadeiras mais para perto e ele tomou aquele café gelado num gole só. Talvez louco para saber a besteira que eu estava prestes a falar...

Interfaces lentas: a origem do problema
Era sim um problema de interface de usuário, disse eu. Você pode ter um (e na maioria das vezes terá) um objeto associado a uma tela de cadastro, isso até facilita a implementação de patterns, como o Memento e outras coisas, como relações um-para-um entre propriedades e controles, etc. E isso não implica necessariamente em degradação de performance usando o Hibernate. O que você precisa é construir uma interface baseada em contexto de uso, tendo precisa noção da carga de dados realmente necessária para essa interface [gostei da cara dele e dos analistas quando falei isso]. Ele pediu 2 minutos para buscar outro café enquanto um dos analistas encheu um chimarrão nesse meio tempo.

Tentei dar um exemplo simples, e esse mesmo exemplo vou mostrar aqui.

Relevância em contexto
Imagine uma tela bem simples, com uma caixa de seleção (combobox) apenas. Nessa tela, o objetivo do usuário é escolher um entregador de mercadorias e, a partir dessa seleção saber quais são os seus pontos de entrega. Esses pontos de entrega são mostrados em uma listagem, que está logo abaixo, como na figura ao lado:
Aqui, eu disse a ele, está um exemplo do que tu falou: Uma tela que pode ser extremamente lenta, caso muitos registros de entregadores existam (por exemplo, uma empresa internacional que mostre todos os entregadores cadastrados no mundo todo - desculpem, mas foi isso que me veio à mente na hora). Quando essa tela for chamada, provavelmente ela vai demorar alguns bons segundos para carregar. Ele concordou avidamente.
Disse a ele: Aqui está um erro de interface, porque você está carregando muitos registros na combo que, mesmo ordenados, vão ser de difícil manuseio, porque a barra de rolagem fica muito reduzida (item 1 na figura) e, principalmente, porque a maioria dos registros será inútil. Inútil porque o usuário vai escolher somente um deles. Assim, se você tiver 1 milhão de registros, a taxa de acerto da interface para esse controle é de 0,0001% (1 interação de usuário x 1 registro escolhido x 1 milhão de registros na listagem). Continuei.
Como vê, não é o Hibernate que está errado, mas o programador que fez a tela e, antes que ele, o projetista que não especificou como essa caixa de seleção deveria ser carregada.

Melhorando o desempenho
Seria de bom grado se nessa caixa de seleção estivessem presente somentes os registros com maiores possibilidades de serem escolhidos pelo usuário. Ele concordou (os analistas também). Nesse sentido, como no item 1 da figura 2, poderiam ser exibidos na caixa de seleção somente os entregadores vinculados à filial onde o sistema está instalado (uma regra de negócio). Também poderia ser provido um valor padrão (com combo carregada durante sua expansão ao clique do usuário - e mesmo em uma thread auxiliar). Outra alternativa ainda seria o analista ter definido um valor padrão inicial fixo junto ao usuário - item 2 da figura 2. Tudo isso são coisas que os analistas poderiam identificar e propor antecipadamente. São opções inicais boas e simples, disse eu, que melhorariam a taxa de acerto para algo entre 0,2% (1 para ~500 itens na combo) e 15% (considerando um elemento padrão com 10% de corerência e sendo generoso ao admitir que o usuário pode digitar 1 caractere inicial e ter uma faixa de 20 elementos para pesquisar - claro, considerando uma distribuição uniforme dos 500 nomes nas 26 letras do alfabeto ocidental) - uma melhora de 15.000% na taxa de acerto e uma redução de carga de 2000x no BD. Nesse momento, ganhei o público, mas os analistas não gostaram muito de saber que eles poderiam ser culpados da demora na tela (...)

Melhorando um pouco mais o desempenho
Como eu tinha conquistado a atenção, continuei. A interface poderia ser melhorada caso fosse colocado em evidência o contexto do usuário. Além do valor padrão e da exibição dos entregadores vinculados à filial, poderiam ser os elementos da seleção restringidos pelo perfil do usuário em conjunto com seu contexto de uso. Restringir pelo perfil do usuário é simples, marcando os registros selecionados ao longo do tempo e armazenando-os em um cache de segundo ou terceiro níveis (item 3, figura 3). Isso é facilmente implementado por uma LRU (Last Recently Used) em memória, não com objetos nativos do banco (javabeans), mas DTOs (Data Transfer Objects), que são comuns em ambientes enterprise (item 3, figura 1). Outra alternativa, seria utilizar uma LFU (Last Frequently Used) em memória, que marcaria os registros com maior propabilidade de acerto considerando uma média de acerto durante o tempo, e não os últimos utilizados. Mesclar essas duas opções para formar o elemento padrão poderia ser outra tentativa para minimizar o trabalho do usuário.

Quando esses recursos são utilizados, é necessário permitir ao usuário a seleção do restante dos registros, coisa simples de fazer ao providenciar um elemento como "Exibir todos..." (item 2, figura 2), que retornaria o restante dos registros. Mesmo nessa busca, outro cache LRU (ou LFU) poderiam ser utilizados, sendo a ordenação baseada também nesses conceitos (ordenar por LRU ou LFU ao invés de alfabeticamente). Isso tudo, além de reduzir a busca no banco à menos de - sei lá - uma centena de registros (ou mesmo a zero se os LRU e LFU forem bons), elevaria a taxa de acerto a algo superior a 50-60%, ou seja, uma interface 600x mais performática que a primeira.

Conclusões
Terminei dizendo que implementar esses recursos é legal porque a aplicação aprende com o usuário. Ela transforma-se em um ser vivo, e não apenas um monte de retângulos estáticos e coloridos na nossa frente.
Como consequência (não uso trema porque meu teclado é US, sorry), o Hibernate tem menos objetos para carregar e os problemas de tempo de carga simplesmente não ocorrem. Em outras palavras deixamos de perder tempo para resolvê-lo-os para ganhamos tempo (a) evitando-os e (b) dando ao usuário um sistema mais proativo.

Os dois analistas pareciam meio perturbados com o que eu falei. O terceiro, que tinha puxado o assunto, acabara de voltar do limbo para perceber que, novamente, seu café estava frio. Quando à mim, bem, pedi um chimas pro colega da frente, uma vez a nossa cuia estava gelada, idem...