Closures em Javascript: o metodo each

Estou escutando uma discussão acalorada sobre Closures, Ruby e Javascript. Detalhes a parte, Javascript suporta funções anônimas (que não são closures) que podem ser passados como argumentos para outras funções.

Não seria otimo que um Array em javascript tivesse o método each para executar um bloco de código para cada elemento desse array? Usando o prototype da entidade Array eu posso adicionar um método each que faz isso:

Array.prototype.each = function(fun) {
	if (typeof fun != "function") return;
	for (var i=0; i<this.length; i++) {
		if(i in this) fun(this[i]);
	}
	return this;
}

var x = ["cat", "dog", "mouse", "bird"];
x.each(function(i) {
	alert(i);
});

Simples, não? Eu ainda posso adicionar outros métodos uteis como o map e o reduce.

São recursos interessantes que podemos utilizar quando for conveniente. Interessante foi descobrir a propriedade arguments, um array com todos os argumentos passados para a função que, se eu conhecesse alguns meses atrás, teria me solucionado alguns problemas :)

É claro que tudo isso ja vem no Prototype (né peleteiro).

Trabalhando com Serviços Assíncronos

Geralmente desenvolvemos serviços síncronos, mesmo quando uma grande quantidade de etapas precisa ser executada, em detrimento de serviços assíncronos.

Imagine um sistema que, em dado momento, precisa enviar emails. Podem ser aqueles emails para convidar outras pessoas para usar a plataforma, algo que vc faz uns 50 ou 100 de uma vez. O desenvolvimento natural é ler essa lista de emails e, iterativamente, enviar um convite para cada pessoa (com algum codigo que identifique esse convite para evitar fraudes, por exemplo). Coisa que, dependendo do numero de emails e como cada passo é processado pode demorar um pouco.

Precisa ser síncrono? Vc precisa enviar esses convites naquele momento? Faz diferença se demorar uns minutinhos? Como esta solução sincrona afeta a performance do resto da minha aplicação (coisa que posso avaliar num teste de carga)? São perguntas que podemos nos fazer e dão algumas pistas, abrindo alas para a verdadeira pergunta: como a minha aplicação escala?

Se esta etapa pode demorar um tempinho e ela afeta (ou pode afetar) negativamente a experiência do usuario ao demorar para enviar 50 ou 100 convites, pode ser o momento de separar a minha aplicação do serviço de ‘convites por e-mail’, que ira trabalhar assincronamente sem concorrer tanto com CPU (ex: usando nice ou rodando em outro servidor). A minha aplicação apenas iria dizer “essas são as informações: nome do usuario e lista de emails para convites” que o sistema sabe se virar.

E como essa integração é feita? Existem varias formas. Eu posso fazer através do banco de dados, inserindo numa tabela que o meu sistema lê, porém integração pelo banco nunca é algo simples e frequentemente causa problemas (principalmente se a documentação se perde e altera alguma coisa no banco, prejudicando outras aplicações que ninguem sonha que trabalhem assim). Uma boa forma seria passar uma mensagem com todos os dados para o servidor de alguma forma inequivoca, como um POST HTTP (que retorna “ok, recebido, logo vou processar isso”) ou, simplesmente, escrevendo num arquivo em um determinado diretorio. O serviço trata de ler essa requisição e colocar em uma fila ou outra estrutura de dados para atender no prazo que for possivel.

No caso de uma lista de emails escrever em um arquivo parece grosseria mas se eu estivesse trabalhando com fotos, em um serviço de geração de thumbnails, por exemplo, parece ser uma saida bem simples. Nesse caso em particular eu posso criar um “vigia de diretorios”, alguem que vigia uma pasta de tempos em tempos e, quando surge algo novo, executa alguma ação propriamente dita. A forma como eu trabalho pode ser bem simples, como: acordar, listar fotos, executar ação, apagar fotos. Se eu quiser trabalhar com uma maquina de estados eu posso criar pastas como: chegada, processando, fim. O serviço trata de olhar a pasta de chegada e, quando precisa, move as fotos ou outras entidades para o diretorio processando e, então, executa alguma ação.

Nesse ponto algumas coisas precisam ser acertadas: se eu vou trabalhar com estados das minhas fotos, convites, ou qualquer outra entidade, posso modelar este serviço de forma que, se a minha entidade esta na pasta X, então ela tem um determinado estado. Isso é interessante pois caso o meu serviço seja interrompido basta mover tudo o que estava sendo processado para a pasta de chegada! Nesse ponto temos que ter bem claro o que acontece em cada transição de estado, os fluxos possiveis e estabelecer os corretos tratamento de erros.

Se tudo der certo, eu posso rumar para uma aplicação que, para escalar, bastaria subir um segundo serviço em outra maquina (ou aproveitando outra CPU). É claro que existem muitas outras formas de chegar a este resultado e muitas tecnologias disponiveis, porém soluções simples e robustas são sempre bem vindas: percebam que existem muitos pontos de falha que temos que tratar então com a suites de testes apropriada e uma documentação consisa podemos ter um bom serviço assincrono que só vem a agregar valor.

Inversão de Controle e Injeção de Dependências

Fui surpreendido por uma pergunta hoje: o que é IOC (inversion of control)?

Lembrei agora que um bom exemplo de IOC é programação orientada a eventos: lembram daquelas telas de Visual Basic que criavamos arrastando os componentes e adicionando codigo no “onClick” de um botão? Isso é uma forma de IOC pois não criamos o código que, dado o clique em um determinado componente, executa a ação, apenas jogamos um trecho e deixamos que a IDE “se virasse” para adicionar aquilo ao programa principal. Assim deixamos de controlar todo o fluxo para controlar uma parte dele.

Podemos pensar que IOC tem haver com “o que” e não, diretamente, “como”. Uma leitura interessante é o artigo do Martin Fowler intitulado Inversion Of Control – Containers de Inversão de Controle e o padrão Dependency Injection, com exemplos em Java.

Certificação para testes – vale a pena?

Sou suspeito para falar, aprendi a testar por necessidade, começando por um sistema muito complexo e cheio de bugs com documentação zero. Certificação pode valer a pena quando vc tem que seguir um processo bem burocratico, onde vc precisa de 1024 documentos e mais diversas ferramentas de apoio. Pessoalmente eu discordo dessa abordagem por um bom motivo: ruído.

Pode haver muito ruído entre o que o cliente quer (ou acha que quer) e todos os envolvidos, uma vez que nem sempre o cliente sabe o que quer exatamente ou pode não saber se expressa muito bem. Imagine agora isso colocando em uma coleção de documentos que circula entre os envolvidos e que exige um esforço considerável para manter-se atualizado. Pode ser a receita para o desastre :)

Uma condição interessante para o sucesso do projeto é a dedicação dos mesmos frente a honestidade. Muitos analistas, programadores ou testadores querem mesmo é tirar o seu da reta, uma vez que vivem sob um mundo medieval onde os chefes são temidos pelos urros guturais e ameaças perturbadoras, nesse caso o sentimento gostoso de “estou num projeto bom que produz um software de boa qualidade” passa a ser “caramba, se isso não funcionar não é culpa minha e sim do X”. Um bom exemplo é testadores que se limitam a seguir um manual de testes de forma exata e não fazem nada alem disso. Se um bug for encontrado, o testador se defende “mas eu segui o documento de testes #542″. Nesse caso ninguem precisa se defender: testar não elimina bugs, eles podem ser encontrados em situações muito peculiares e de dificil reprodução, nesse caso um bug deveria alimentar um documento de testes e não gerar pânico, dor e morte. Todo o software tem bugs, paciência.

Lembro de uma tela que poderia receber o campo “dia util do mês”. A tela funcionava quando eu informava 01, 02, 03, 10, 20, 28, etc. A tela parecia 100% dentro do uso normal até que alguem colocou o dia 08: um grande erro de javascript aparecia para o usuario! E com 09 também. Depois de muita pesquisa descobriram que, em javascript, um numero que começa com 0 é considerado octal e, em octal, não existe 8 nem 9, nesse caso a máscara desse campo era a origem desse comportamento estranho. Esse tipo de coisa só se resolve com trabalho, pois poderiamos ficar discutindo sobre quem errou, o testador ou o desenvolvedor, por horas, até acabar o projeto. O que importa é que 8 e 9 são dias úteis válidos e a funcionalidade ficaria comprometida com esse bug.

Alias, testar é uma atividade muito legal, se vc é curioso. Basicamente vc tem que procurar comportamentos erráticos e descobrir como reproduzi-los. Hoje em dia eu ainda me pego testando SQL Injection em alguns sites ou estudando paginação de dados, etc. Testar através de ferramentas é mais divertido ainda, não aqueles monstros que funcionam na base do Record/Playback: testar sites usando Shell Script (curl + grep), Selenium ou soluções próprias (como uma classe que se conecta com um webservice, um script Perl que envia dezenas de requests ou mesmo incorporar o Internet Explorer numa dll escrita em C# para integrar com algum sistema complexo de testes) é uma arte que traz benefícios como eliminar horas de trabalho braçal propenso a erros.

Não que alguem certificado em testes seria desonesto ou covarde mas a minha percepção é que esse universo de testes utiliza documentos, ferramentas e certificações também subterfúgios, tiro isso de conversas com gerentes dessa área que querem saber: quem é o desenvolvedor que produz mais bugs, quem demora mais pra desenvolver, quem demora mais pra testar, quem testa de verdade, etc. Quando vc pergunta específicamente essas coisas vc não quer métricas, vc quer dados para ferrar com alguem. Neste tipo de ambiente, tão comum ai por fora, a honestidade é soterrada pela mediocridade de gerentes e outros figurões e, geralmente, se resolve mudando de emprego ou fazendo parte de uma célula que implanta uma mudança de pensamento dentro da empresa. Geralmente chegamos a essa opção quando cansamos de trocar de emprego.

Por fim: conhecimento não pesa nem ocupa espaço, biológicamente falando. IMHO honestidade para o desenvolvedor, testador ou gerente é muito mais importante do que o cinismo. Experiência é mais importante que certificações. Conhecimento é mais importante do que um mero diploma. O resto é dançar conforme a música.

Eu gosto de citar este artigo: Introduzindo Agilidade num Ambiente, do Phillip Calçado, que me mostrou um novo mundo e me inspirou a sair do mundo de testes para o mundo onde se faz de tudo um pouco. Então, se alguem se perguntar se certificação, mesmo para testes, vale a pena, lembre-se que a sua postura profissional conta muito mais.

O custo de adotar uma nova tecnologia

Estes dias eu estava pensando nos reais custos de adotar uma nova tecnologia. Imagine que vc sempre trabalhou com Java e tem que trabalhar com .Net, o que causou essa escolha? Que estratégia existe por trás?

Bom, o principal custo esta vinculado com a capacidade de um time ou equipe de entregar, ou não, uma determinada release. Entra ai a curva de aprendizado, que não pode ser encarado como aprender a sintaxe de uma linguagem e sim compreender os fundamentos da mesma, bem como entender a infra-estrutura necessária para que ela desenvolva o seu papel.

Quem aprendeu PHP sabe como desenvolver uma pagina dinâmica, trabalhar com formularios, sessão e acesso a banco de dados, porém sabe que basta editar um arquivo e subir para o servidor que ele é ‘executado’. Passar para um mundo J2EE onde vc tem uma estrutura de diretórios para respeitar, arquivos de configuração e, principalmente, entender a filosofia J2EE, a diferença entre os servidores de aplicação, as particularidades do garbage collector, etc, enfim, não é uma tarefa simples.

Imagine que vc tem 2 semanas para desenvolver uma aplicação: o que vc escolheria? Antes de pensar “ah, 2 semanas, pra web eu faria em Rails”, pense que vc tem que desenvolver um uma equipe que não conhece Rails. Nesse caso, 2 semanas pode ser tempo muito curto para se comprometer com uma entrega se vc pensa em termos de Scrumm por exemplo, pois o time não conhece a sua velocidade (e, nesse caso, não importa velocidade pois vc TEM que terminar o projeto no prazo). Se a equipe inteira conhece PHP, metade conhece Java e apenas um cara conhece Ruby on Rails, seria interessante considerar PHP pois essa experiência acumulada colabora para a solução do problema: entregar software funcionando numa determinada data.

Quando temos liberdade de escolha e tempo necessário, podemos explorar outras linguagens, frameworks e tecnologias, usando alguns Sprints para desenvolver a habilidade necessária de implementar e, principalmente, de dar manutenção.

Outro ponto importante é a testabilidade do codigo: se vc não tem uma equipe com proficiência em testes unitários, cobrar num projeto com prazo curto pode ser um tiro no pé pois prazo curso geralmente detona o humor dos envolvidos, prejudicando a adoção de novas ideias. Um projeto de tres meses, por exemplo, seria ideal pois eu posso usar um dia (ou parte dele) para efetuar um pequeno workshop ou dojo sobre TDD e tecnologias de teste, se necessário. E o ideal é começar com testes pois codigo legado nem sempre é facil de injetar testabilidade (e as vezes deixa de funcionar no processo, ai o telefone toca, vem um esporro, é lindo).

Passei por uma situação curiosa certa vez: um serviço crítico seria adicionado à uma api de serviços com algum tempo de vida. Como ninguem sabia como testar direito usando PHP (criar mocks não é tão simples, pelo menos naquela época), desenvolvi um pequeno script que efetuava testes funcionais: eu utilizava aquele serviço usando todas as combinações possiveis de parâmetros e situações.

O grande problema dessa abordagem é desenvolver algo que pode receber atualizações: naquele momento eu tinha criado um modelo de forma a descrever os testes como estruturas de dados, assim bastaria adicioanr novas estruturas que eu teria, de forma simples, novos testes. Testes automatizados são uma tecnologia que pode agregar qualidade e, portanto, tem um custo também. Não vou ser fatalista e dizer que software sem suite de testes não tem qualidade (vide um hello world), mas vc pode atingir um patamar de qualidade superior com uma suite de testes completa, tanto por causa dela quanto pela mudança cognitiva que isso acarreta. Eu explico: desenvolver testes muda a forma como programamos. Não precisa ser TDD ou BDD: basta programar com atenção e esmero, este ultimo o grande responsável pelo sucesso de um projeto.

O desafio agora é desenvolver scripts de teste no estilo Cucumber/RSpec. Falarei disso no futuro, ainda mais que estou reinventado a roda – mas por um bom motivo.