domingo, 18 de novembro de 2012

Ruby, Colecções, Representação de Dados: Perigos da Meta-Programação

Oi,

Hoje queria falar do perigo que ás vezes representa extender objectos dinâmicamente e de como isso representou uma utilização indevida de memória no projecto em que trabalho correntemente. Consideremos uma classe normal:

 class Bola  
 end  

Se agora nós quisermos marshalizar (http://en.wikipedia.org/wiki/Marshalling_%28computer_science%29) uma instância da mesma, a sua representação é
relativamente minimal. Observemos:

 require 'yaml'  
 b = Bola.new  
 b.to_yaml  
 --- !ruby/object:Bola {}  

(vou apresentar os resultados em formato Yaml porque é mais legível e serve o propósito do post melhor). Portanto, a representação do objecto é relativamente sucinta. Adicionemos-lhe agora uns quantos atributos:

 class Bola  
   attr_accessor :material, :marca, :dono  
   def initialize  
   end  
 end  

instanciemos de novo o objecto:

 require 'yaml'  
 b = Bola.new  
 b.to_yaml  
 --- !ruby/object:Bola {}  

A mesma representação obtida anteriormente. Apesar de a classe ter sido extendida, um objecto que não tenha esses atributos preenchidos não os irá marshalizar. Passemos então ao caso de uso:

 b.material = "cautchu"  
 b.to_yaml  
 --- !ruby/object:Bola  
 material: cautchu  

E aqui temos: o valor do nosso atributo preenchido é usado na representação marshalizada do nosso objecto b. O valor do nosso atributo não é só usado, ele próprio é também marshalizado:

 "cautchu".to_yaml  
 --- cautchu  

 Imaginemos então que um dos nossos atributos é preenchido, em vez de com um valor de um tipo "primitivo" de ruby, com um objecto de uma classe criada por nós:

 class Dono  
  attr_accessor :nome  
  def initialize(nome)  
   @nome = nome  
  end  
 end  
 b.dono = Dono.new("Ronaldinho")  
 b.to_yaml  
 --- !ruby/object:Bola  
 material: cautchu  
 dono: !ruby/object:Dono  
  nome: Ronaldinho  

Interessante. Experimentemos algo agora totalmente alucinado:

 class Dono  
  attr_accessor :bola  
  def initialize(nome, bola)  
   @nome = nome  
   @bola = bola  
   end  
 end  
 b.dono = Dono.new("Pelé", b)  
 b.to_yaml  
 --- &2151878980 !ruby/object:Bola  
 material: cautchu  
 dono: !ruby/object:Dono  
  nome: Pelé  
  bola: *2151878980  

Graças a Deus, houve reconhecimento da referência circular neste caso. De forma curiosa, admitamos. Foi atribuído um endereço ao nosso b, que depois é referenciado dentro do dono. No entanto, vamos insistir:

 class Dono  
  attr_accessor :nome, :bolas  
  def initialize(nome, bola)  
   @nome = nome  
   @bolas = [Bola.new, Bola.new]  
   @bolas.each { |b| b.dono = self }  
   @bolas << bola  
   end  
 end  
 b.dono = Dono.new("Pelé", b)  
 b.to_yaml  
 --- &2152946720 !ruby/object:Bola  
 dono: &2153008060 !ruby/object:Dono  
  nome: Pelé  
  bolas:  
  - !ruby/object:Bola  
   dono: *2153008060  
  - !ruby/object:Bola  
   dono: *2153008060  
  - *2152946720  

Começamos a notar um padrão aqui: esse endereço pode ser uma outra coisa qualquer... continuemos:

 
 class Dono < String  
  attr_accessor :bolas  
  def initialize(nome, bola)  
   super(nome)  
   @bolas = [Bola.new, Bola.new]  
   @bolas.each { |b| b.dono = self }  
   @bolas << bola  
   end  
 end  
 b.dono = Dono.new("Pelé", b)  
 b.to_yaml  
  --- &2165175520 !ruby/object:Bola  
 dono: !ruby/string:Dono  
  str: Pelé  
  bolas: &2165264600  
  - !ruby/object:Bola  
   dono: !ruby/string:Dono  
    str: Pelé  
    bolas: *2165264600  
  - !ruby/object:Bola  
   dono: !ruby/string:Dono  
    str: Pelé  
    bolas: *2165264600  
  - *2165175520  

E agora adensou. Pelos vistos, um objecto tem uma representação marshalizada por defeito. Se for um PORO, é utilizado o id do objecto. No caso de o valor ser uma String, mesmo que extendida, a sua representação vai conter elementos da mesma.

Onde poderá isto ser um problema, perguntarão vocês? A resposta é: em todo o sítio onde a sua representação é marshalizada/yamlizada: serialização, filas de mensagens, etc... Uma representação interminavelmente extensa pode dar origem a uma falha inesperada. Isso aconteceu com o nosso projecto na semana passada: A nossa aplicação Rails utiliza a gem Delayed Jobs para gerir as filas de mensagens. Quando "retardamos" um procedimento, o que a gem faz é criar um registo para a tarefa, com um campo chamado handler onde é guardada a representação yamlizada do método a chamar, dos seus argumentos e do âmbito onde o método é chamado, geralmente um objecto. Este registo é posteriormente persistido na base de dados. Para o tal campo handler é utilizado o tipo TEXT (usamos MySql, já agora). Este tipo é erroneamente interpretado como sem-limite, ou seja, adapta-se ás circunstâncias, mas é errado: tem um limite de 65000 bytes. Quando retardamos uma chamada a um procedimento de um modelo (utilizamos ActiveRecord), esse modelo é yamlizado nesse handler. Felizmente no caso do ActiveRecord, as associações não são yamlizadas, somente os atributos directos e as chaves estrangeiras (ver http://apidock.com/ruby/Object/to_yaml_properties para entender como a gem Delayed Jobs consegue isso). Logo, na maior parte dos casos (salvo data serializada), tipos simples. Agora, vamos imaginar que um destes campos é uma string. Mas na minha lógica de negócio, essa String é extendida mais ou menos na forma descrita nos exemplos acima (mas com mais alguns níveis de profundidade, obviamente). Vamos dizer que atribuímos essa String extendida a um dos campos do nosso modelo ActiveRecord. É uma String. É persistida com sucesso. Mas é a representação marshalizada idêntica? Pois, não é... Imaginemos que temos um modelo AR chamado Bola (tema recorrente, não gosto de usar o nome "Teste" para testes), e que a nossa bola guarda o nome do seu dono num VARCHAR com limite 255, mapeado para uma String na lógica de negócio. numa situação Normal:

 b = Bola.new(:dono => Dono.new("Pelé", b))  
 b.save  
 b.to_yaml  
 --- !ruby/ActiveRecord:Bola  
 attributes:  
  id: 1  
  dono: !ruby/string:Dono  
  str: Pelé  
  bolas: &2165264600  
  - !ruby/object:Bola  
   dono: !ruby/string:Dono  
    str: Pelé  
    bolas: *2165264600  
  - !ruby/object:Bola  
   dono: !ruby/string:Dono  
    str: Pelé  
    bolas: *2165264600  
  - *2165175520  
 b.reload  
 b.to_yaml  
 --- !ruby/ActiveRecord:Bola  
 attributes:  
  id: 1  
  dono:Pelé  

Pois é, duas representações diferentes, dependendo de como a String é instanciada. Acontece então que ocorreu um caso em que a representação yamlizada do objecto sobre o qual determinada chamada tinha sido retardada era simplesmente incomportável pelo campo da base de dados. Não foi motivo de queixa para a base de dados, ela simplesmente guardou a capacidade que suportava. O pior foi quando o script do delayed job tentou executar essa tarefa: obviamente, não conseguiu parsear a representação yamlizada porque era inválida. A representação era deveras gigantesca. Não vou entrar em pormenores sobre o porquê, porque aí teria que vos contar algumas inconsistências da classe extendida de String utilizada (uma pergunta retórica: alguém conhece alguma estrutura de dados para colecções em que os seus elementos guardam um ponteiro para a colecção a que pertencem? Isso foi parte do problema.) Fui falar com o meu colega que escreveu este código. Ele viu a representação e achou que a solução seria passar de TEXT para MEDIUMTEXT ou LONGTEXT (TEXT com mais capacidade). Portanto, se tá gordo, estica, que concerteza não engorda mais... Defendeu a sua ideia quanto baste, porque não queria admitir que a sua preciosa refactorização das definições da aplicação tinha umas quantas fugas de memória. Como disse, colecções em que elementos apontam para a colecção, como será que um garbage collector interage com isto... Então resolvi o problema forçando a conversão para String na atribuição de valores desse tipo extendido... criando um outro problema que foi a propagação de chamadas ao to_s... Para além de a) não ter a garantia que o próximo desenvolvedor não chegue lá e simplesmente se esqueça da conversão e b) persistir o problema se algum dia tiver que retardar a chamada a uma rotina de uma dessas Strings extendidas. Uma grande salganhada proporcionada pela meta-programação de Ruby, uma ferramenta óptima em teoria mas uma fonte de fugas de memória, bugs não-rastreáveis e mau desenho de arquitectura geral. Podemos argumentar que a culpa não é tanto da ferarmenta em si mas do desenvolvedor que falseia o seu uso. Eu diria que cada caso é cada caso.

sexta-feira, 19 de outubro de 2012

Por que um método e não uma função em Ruby?

Oi,

Depois de uns quantos aninhos a trabalhar em Ruby, reparei que regularmente refiro "funções" e "métodos" na mesma frase para o mesmo tipo de entidades, ás vezes para a mesma. Não está totalmente incorrecto, no entanto, dada a natureza OO-kamikaze do Ruby, pode levar a interpretações incongruentes das minhas palavras. Para os mais esquecidos, o que é uma função e o que é um método? Ora, uma função é no fundo um procedimento, uma sequência de instruções identificadas por um nome (falemos de funções anónimas numa outra altura) e reconhecidas pelo sistema. Um método é uma função. O termo vem do mundo Java, que baptizou muitos termos associados a programação orientada a objectos, e designa uma função definida no corpo de uma classe, logo uma função de qualquer instância desta. Dito de outra forma, um método tem um contexto específico, e uma função teria um contexto global.

Então onde se encontra o erro de utilizar a denominação "função" em Ruby? Bom, primeiro vamos ver a documentação: http://www.ruby-doc.org/core-1.9.3/ Podem procurar, mas não vão encontrar uma designação para "função". Mas encontram lá a classe Method, certo? Pois é, então vamos abrir a caixa de Pandora: Ruby é uma linguagem orientada a objectos pura. Que significa pura? Tudo é um objecto. Tudo. Tudo herda da classe Object. As próprias classes são objectos. Instâncias da class Class. Que por sua vez também é uma instância. O ambiente global? Também é um objecto! (irb: self.class) E é aqui que reside o segredo da inexistência de funções: se o próprio ambiente global é um objecto, então qualquer "função" definida nesse ambiente será um método do ambiente global. Sempre método. Nunca outra coisa. Pode até ser um procedimento. Mas nunca uma função. Então façam-me um favor: não façam como eu. Não misturem conceitos. Apesar de métodos serem funções intrinsecamente, existe uma diferença residual que em determinado contexto pode ser a diferença entre conceitos mal-absorvidos e bases de programação orientada a objectos fundamentadas.

Metodologizem em paz,
Chuck

quinta-feira, 27 de setembro de 2012

Frustrações com o ActiveRecord - parte 1

Oi,

Hoje o texto vai incidir sobre um dos módulos mais conhecidos da ferramenta Ruby on Rails, o ORM (mapeamento relacional de objectos) por excelência (e defeito), o mal-amado ActiveRecord.

No auge do sucesso inicial do RoR, o ActiveRecord (baseado num padrão de desenho) foi considerado um passo à frente em relação a outros ORMs de outras plataformas (Hibernate para java, por exemplo), pois fazendo uso do adágio preferido da comunidade Rails (convenção em vez de configuração), permitiu a rápida inserção de novas estruturas de dados na base de dados e o seu motor de mapeamento em memória (o modelo). Para isso faz uso de uma camada de migrações, em que os dados são inseridos na base de dados, mapeando os atributos automaticamente no modelo, não necessitando de declaração alguma. O próprio modelo mapeia directamente para a tabela com o mesmo nome na forma plural, mais uma convenção do Rails. Que quer isto dizer? Quer dizer que, se eu inserir uma nova tabela chamada actores e nessa tabela existir um campo chamado nome, então no modelo respectivo eu não preciso de declarar o campo, ele estará automaticamente disponível. Para além disso, fornece um modo de declaração limpo de associações para o modelo, validações de campos, procedimentos de retorno (callbacks), é facilmente extensível, é modular (bom, desde o Rails 3...), permite delegações, etc... um sem fim de vantagens.


Então porquê a frustração, perguntam vocês?

Pois é, se forem seguidas as convenções do ActiveRecord, não há muito de que se queixar. No entanto, toda esta limpeza da linguagem específica ao domínio esconde um perigo em potência: a distorção do significado de mapeamento relacional de objecto. Um modelo ActiveRecord, no contexto de uma aplicação Rails, é muito mais do que um simples mapeamento de dados. Também age como um modelo de negócio. E um modelo de apresentação. E uma pseudo-camada de filtragem de um pedido. E possui uma linguagem não muito clara para interagir directamente com a base de dados. Que quero eu dizer com isto? Ora vamos então por ordem:

- "Também age como um modelo de negócio"

Tudo o que é lógica associada ao nosso modelo vai encontrar espaço no modelo AR. Em vez de providenciar só o mapeamento, também é função do modelo executar operações relativas a esses mesmos dados. Aquilo que nas arquitecturas mais associadas a aplicações web em Java se chamava os modelos de negócio. Porquê aqui e não noutro lado? Porque foi convenção do Rails. E fiquemos por aqui.

- "E um modelo de apresentação"

Na arquitectura MVC das aplicações Rails não existe o conceito de vistas de objecto, geralmente modelos a que lhes somente é permitido ler, e que encapsulam geralmente lógica associada à geração da apresentação HTML. Nas aplicações Rails existe a camada dos helpers, que se destinam portanto auxiliam a renderização. É-lhes permitido fazer coisas que um modelo AR não pode, como aceder a métodos auxiliares que retornam HTML. No entanto, para muitas coisas, esses helpers usam a instância do modelo AR para certas operações, como geração de URLs REST. Ou geração de formulários HTML. A magia é muita nesta camada. Muitos de vocês podem argumentar que os modelos não são activamente usados na camada de apresentação, mas através de funções auxiliares. Mas aí eu argumento que esses modelos, quando chegam à camada de apresentação, nem têm proibição de escrita. Existe muita possibilidade para falhas dada a possibilidade de falhar. É exactamente o que eu acho que se passa neste caso. Algumas extensões para Rails já tentaram colmatar esta falha, providenciando uma representação de um modelo de acordo com o modelo das vistas (view_models, draper), mas ainda não parecem soluções maturas, principalmente a primeira, que já nem é mantida. Vamos concluir simplesmente que o Rails se esqueceu desta parte e continuar.

- "E uma pseudo-camada de filtragem de um pedido"

A camada de Controladores dispõe de declaração de filtros. No entanto, esta camada não é claramente separada da camada dos Controladores, o que faz com que se polua os controladores com declarações de filtros. Mas para mim, pior que isso é ver como, por exemplo, no seguimento da submissão de um formulário, os parâmetros são (por convenção, novamente) passados directamente para o modelo, e aí são geralmente re-ordenados, bloqueados, é verificada a atribuição em massa... funções que geralmente a camada de filtragem deveria fazer. Não, só isso, a validação dos modelos insere os erros de validação dentro do modelo (numa sub-camada), fazendo com que este forçosamente seja usado na re-renderização do formulário com mensagens de erro (aqui, voltámos ao ponto anterior). Outro exemplo interessante é a checkbox de confirmação de algo, cujo preenchimento é geralmente tratado nos modelos, pelas razões supra-citadas e porque é mais fácil para o desenvolvedor.

- "E possui uma linguagem não muito clara para interagir directamente com a base de dados"

Esta pode não ser perceptível ao início, especialmente agora que existe o ARel (o qual aprecio), mas o ActiveRecord não permite uma interacção fora-da-caixa na comunicação com a base de dados para além de pesquisas.  E se eu quiser executar um stored procedure? E se a query for mais complicada? Pois, para isso existe o execute e o find_by_sql, métodos de último recurso. Resumindo e concluindo, funcionalidades específicas das bases de dados não suportadas. Outro exemplo são as funções SQL como STRLEN, entre outras. Essas têm que ir dentro de uma string criada para esse propósito. Ou seja, o AR já é limitado na medida em que não possui suporte para todos os tipos de bases de dados (como por exemplo as NoSQL). A partir daí, é sempre a acumular.


Porque são tão importantes estes pontos para mim? Se a arquitectura for fraca, a possibilidade de os desenvolvedores abusarem das possibilidades desta solução é enorme. Eu tenho testemunhado casos desses. Não é nada bonito manter código feio.

E pronto, estes são os principais pontos. De lado deixei as migrações (que se acumulam ad eternum... ver o DataMapper para uma solução interessante para o problema), e o apegamento a más práticas em relação a bases de dados, como a omissão de constrangimento de chaves estrangeiras (foreign key constraints) ou a forma como vendem estratégias como o Herança de Tabela Única (Single Table Inheritance) não fornecendo suporte para Herança por Classes de Table (Class Table Inheritance) e as associações polimórficas directamente inseridas na tabela, práticas por demais rejeitadas nas comunidades de engenheiros de base de dados. Entre outros. Fica aqui matéria para mais um texto um dia destes.

sábado, 21 de julho de 2012

O que é um construtor em Javascript?

Oi,

Hoje vou aqui divagar sobre o meu amor/ódio de estimação: o Javascript. Porque é que amo/detesto este monte de boas ideias/jorda? É, é difícil explicar mesmo.

Em parte a minha relação de amor explica-se pela falta de alternativas. Um pouco como quando se estuda numa universidade de engenharia em que apenas 10% dos estudantes são do sexo feminino e aí tenta-se encontrar a alma gémea menos feia que se consegue. Mas até aí tem mais escolha. No caso do Javascript, apesar de servir para muita coisa, é a única porra que corre scripting do lado do cliente nos navegadores (sim, tem implementações diferentes em cada navegador, tou ciente disso).  No entanto, tem muito boas ideias/conceitos espalhados um pouco pela sua especificação, sim, muitos deles roubados, mas mesmo assim válidos. Um bom exemplo é essa prototipização, que foi a maneira de eles auxiliarem algo que se assemelhe a linguagem orientada a objectos, mantendo o formato mini da linguagem, fácil de aprender e acessível a profissionais e amadores.

Mas o que detesto são todas as incongruências que derivam de uma linguagem ter tipificação fraca e dinâmica. Essa combinação tóxica permite fazer coisas como:



var x = 2; var y = "2"; x == y;



dica: isto devolve true. Se isto não vos dá a volta ao estômago, então vocês são gente aventureira e amante do risco. Sugiro mudança de vida, risco não combina bem com a profissão de engenheiro.

No entanto, uma discussão recente no seio da equipa do projecto onde trabalho fez-me investigar mais um pouco dos conceitos da programação orientada a objectos em Javascript. Um dos meus colegas decidiu reorganizar o nosso código executado do lado do cliente no seu navegador, que consistia maioritariamente de funções de primeira ordem executadas mediante determinado contexto presente na estrutura HTML carregada, alguns widgets, entre outras salganhadas. Decidiu aplicar os conceitos de prototipização do Javascript, desenhando uma estrutura em que uma função define determinado tipo de prototipização, outra extende essa prototipização e depois passaa uma como protótipo da outra, de forma a criar uma relação de herança, em que um dos protótipos trata de funcionalidade geral, e outro de funcionalidade específica. Enfim, relativamente limpinho. Que são protótipos, devem estar vocês a perguntar? Bom, é complicado de entender, mas vamos lá tentar.

Em Javascript, existem essencialmente dois tipos de estruturas complexas (não ignoremos os tipos primitivos, claro): funções e objectos. Um objecto, que segue a notação de objecto Javascript (JSON para os conhecedores, outra das grandes ideias de estrutura e representação de dados que saiu do javascript), podemos definir como um saco de associações propriedade -> valor. Quase como um mapa de dados. Qual é aqui a nuance? o valor pode ser qualquer coisa, sendo esse qualquer coisa do tipo primitivo, outro objecto JSON ou uma função. Sendo que a definição da função dentro do objecto ocorre num âmbito específico, esta função somente poderá ser chamada ou dentro desse âmbito ou referenciando o objecto. Algo como isto:


var a = {

 "palavra" : "diami",

 "numero" : 2,

 "b" : {"c" : 1},

 "func" : function() {return "balls";}

};




a.palavra #=> "diami"

a.numero #=> 2

a.func #=> function()

a.func() #=> "balls"



Notem que à propriedade "func" foi atribuída uma função anónima. Sim, outras das nuances do Javascript é o suporte à definição de funções anónimas. O que me leva ao seguinte ponto: e o que são as funções em Javascript afinal? Pois, aqui é que tudo fica mais complicado. Vamos descomplicar.

Ao contrário de outras linguagens de programação, funções em Javascript são objectos de primeira ordem. Podem ser atribuídas a variáveis:


var a = function t() {return "bong";}; a() #=> "bong"


Podem chamar-se a si mesmas:


function () {return "balls"}(); #=> "balls"


Mas o mais misterioso de tudo, e a grande diferença em relação aos objectos JSON, é que todas as funções têm uma propriedade interna associada a que se convencionou chamar o seu protótipo (prototype). Podem acessar a mesma do seguinte modo:


var a = function() {}; a.prototype #=> {}



Mas esse protótipo não é um objecto normal. Por definição tem uma propriedade associada chamada 'constructor'. E o que é esse 'constructor' mesmo? Pois é, vamos ver:


var a = function() {}; a.prototype.constructor #=> function()


Concluímos que esse 'constructor' aponta para uma função. Mas que função é essa? Vamos explorar mais esta ideia:


var a = function balls() {}; a.prototype.constructor #=> balls()


Respiremos fundo... Então, a variável a tem uma função atribuída, que por sua vez tem uma propriedade chamada 'prototype', que por sua vez, tem uma propriedade chamada 'constructor', que por sua vez aponta para a função onde esse prototype está definido. Pura loucura!! Como poderá isto ser útil?

Mas é. Agora preparem-se. Vamos entrar no mundo do this.

this é uma palavra reservada em Javascript. Se escreverem this na consola do vosso browser vai aparecer a "window" (mais sobre o objecto window num outro post). Dentro de uma função podem também escrever e utilizar o this. E o que será o this nesse caso? O this não é mais do que o objecto/contexto no qual aquela função é executada. Logo:


function() {return this;}() #=> window

 var a = {x : function() {return this.y;}, y : 2}; a.x() #=> 2


Agora o imbróglio fica mais claro. Vamos baralhar e dar agora.

Como sabem de outras linguagens, uma chamada a uma função dá-se recorrendo aos parênteses. Mas o Javascript tem mais de uma forma de chamar uma função:


var a = function() {return this;};

a() #=> window

a.call() #=> window

a.apply({x : 2}) #=> {"x" : 2}


Conclui-se que podemos passar explicitamente o contexto interno da função, ou se quisermos ser mais explícitos, o protótipo acessível da função, em contraponto ao tal protótipo inacessível do qual falámos antes. Onde é que estes se misturam?

Eis que chegamos à última peça do puzzle, a palavra reservada new. O new tem uma função muito particular no Javascript. Primeiro, não pode ser chamada em objectos JSON:


var a = new {a : 2}; #=> erro!!! 


mas pode ser chamado numa função:


var a = new function() {return "balls";}; a; #=> 



no entanto, nem lhe é atribuído o valor retornado pela função, nem este valor é retornado para a consola. Simplesmente perdeu-se. e a variável a passou a ter como valor um objecto JSON vazio. O que se passou então?

O que se passou foi que, quando se chama o new numa função, ocorrem os seguintes eventos:

1 - inicializa um objecto JSON vazio;
2 - este objecto é por sua vez interpolado com o protótipo da função;
3 - o construtor do protótipo é chamado utilizando como contexto o novo objecto interpolado.

E puf, fez-se programação orientada a objectos em Javascript. Ora brinquemos um pouco:



var Geral = function() {

    this.atributoGeral1 = 1;

    this.atributoGeral2 = 2;

}

var Especifico = function() {

    this.atributoEspecifico1 = 3;

    this.atributoEspecifico2 = 4;

}



Especifico.prototype = new Geral();



var variavelGeral = new Geral(),

      variavelEspecifica = new Especifico();



variavelGeral.atributoGeral1 #=> 1

variavelGeral.atributoGeral2 #=> 2

variavelGeral.atributoEspecifico1 #=> undefined

variavelGeral.atributoEspecifico2 #=> undefined



variavelEspecifica.atributoGeral1 #=> 1

variavelEspecifica.atributoGeral2 #=> 2

variavelEspecifica.atributoEspecifico1 #=> 3

variavelEspecifica.atributoEspecifico2 #=> 4


O Javascript consegue ser tão horrivelmente simples, né? Essa magia negra descrita toda em cima permite que no final se consiga "simular" uma hierarquia de heranças perfeitamente fácil de seguir.


Qual foi o motivo da discussão descrita mais acima então?

Bom, o meu problema foi com a nomenclatura dada. O meu colega decidiu poluir o nome dado ás variáveis que definem as nossas "pseudo-classes" (não conheço a definição oficial, quem conhecer ajude). Em vez de algo relativamente simples de entender para quem vem de linguagens de herança por classes:


var Bola = function() {

    this.cor = "azul_e_branca";

};

var joao = {

   "bola" : new Bola()

} 

joao.bola.cor #=> "azul_e_branca";


decidiu "sufixar" essas variáveis com "Constructor":


var BolaConstructor = function() {

    this.cor = "azul_e_branca";



};

var joao = {

   "bola" : new BolaConstructor()



} 

joao.bola.cor #=> "azul_e_branca";



Eu achei aquilo semânticamente errado. Dá a entender que existe um construtor central de bolas, que quando chamado cospe bolas. É um pouco confuso de se ler. Mas o meu colega, que adora defender os seus cavalos de Tróia, insistia que aquela variável à qual é atribuída a função anónima É o construtor, e portanto tem que ser nomeada como tal. Mais tarde, mesmo depois de dar a mão à palmatória que realmente a nomenclatura tornava difícil a comprensão, insistia que essa função continuava de facto a ser o construtor. Ora, fui mais uma vez dar uma olhada na documentação, e basicamente respondo-lhe com ela assim que surgir a oportunidade: somente protótipos têm originalmente um construtor em Javascript; lá porque estes chamam a função em que existem, ou são passados com a mesma para o objecto criado com a chamada ao new, não faz das mesmas construtores, simplesmente são referenciados/chamados no contexto do protótipo.

Pelo menos é esta a minha leitura. Talvez vocês tenham outra.


terça-feira, 3 de julho de 2012

Política de mesma origem e as fontes CSS

Oi,

Hoje surgiu uma daquelas situações interessantes no trabalho que me serviu para descodificar mais um pouco essa linguagem poderosa que é o protocolo de comunicação HTTP. É sem dúvida uma prova da capacidade de adaptação e flexibilidade dos engenheiros que desenvolveram o protocolo que esta especificação já de si antiga consiga se manter actual (com alguns acrescentos ligeiros) e responder à maior parte dos requisitos que vão aparecendo na rede. Mas não é pra professar a fé e espalhar o evangelho que estou aqui.

Hoje estava eu, na minha, tentando resolver uma situação na página relativa à documentação do nosso código do lado do cliente (javascript); mais propriamente, o ficheiro referente à camada de apresentação (CSS) estava a ser carregado directamente do domínio principal da aplicação. Eu achei estranho, porque de um modo geral, tudo o que é recursos (imagens, scripts, .css, etc...) costumam ser carregados de um outro domínio, geralmente considerado o domínio dos recursos (assets em bom inglês).

Porquê?

Escalabilidade. Repartindo a carga (load balancing em bom inglês) por vários servidores e salvaguardando o domínio principal para a entrega do nosso hipertexto, conseguimos desse modo optimizar a entrega do documento HTML ao cliente, permitindo ao agente utilizador (geralmente o navegador web) continuar a carregar o documento enquanto paralelamente carrega os recursos necessários para a sua renderização-apresentação (excepção feita aos scripts não-deferentes). O utilizador consegue visualizar informação mais rapidamente, o domínio principal optimiza a entrega do conteúdo mais simples, permitindo até compactá-lo, e servidores paralelos optimizados para a distribuição de conteúdo mais complexo tratam do resto, evitando o efeito de congestionamento "gargalo da garrafa" (bottleneck) caso fossem requisitados todos os recursos ao mesmo providenciador e deste modo salvaguardando o poder de execução do servidor principal para a resposta aos mais diversos pedidos provenientes de clientes interagindo com as páginas web, que de um modo geral (e especialmente em páginas carregadas de interacção por pedidos AJAX) têm que forçosamente requisitar informação do mesmo domínio, culpa da nossa velha conhecida política de mesma origem (same-domain-policy). Existem diversas formas de potenciar a concorrência de pedidos simultâneos, que variam de servidor para servidor web, como fazer caching desses recursos, mas de um modo geral, e dado que os URIs aceites nas tags HTML link e script não estão restringidos por esta política, a utilização de servidores paralelos optimizados para entrega de recursos continua a ser a resolução mais unânime para o problema da escabilidade.

Então porque é que aquele recurso estava a ser carregado do domínio principal? Pois, também não percebi. O que percebi foi que estava a ser escrito à pata na diagramação do documento que gerava a página da documentação. E o ActionView do Rails tem uma dessas funções mágicas (stylesheet_link_tag, para quem não conhece) à qual se dá o caminho relativo do nosso recurso de apresentação e o ActionView gera um caminho absoluto possível completo com um dos domínios referenciados como servidor de recursos nas configurações. Dado que a geração da documentação estava a ser feita através de um desses scripts que gera documentação estaticamente, tive só que incluir o módulo correcto no motor de renderização para que a função estivesse disponível. Claro que demorou o seu tempo todo este processo até à solução, e foi no entretanto que eu deparei-me com algo que me fez coçar a cabeça valentemente.

No processo de teste do caminho absoluto do recurso, ia comparando o caminho gerado com o caminho gerado para os recursos em produção do nosso sítio web principal. E foi aí que reparei que, em produção, os ficheiros .css estavam a ser carregados do domínio principal. Achei aquilo uma falha terrível, que só não era tão grave devido ao facto de ainda não termos assim tantos utilizadores, logo, o tráfego ainda não era proibitivo. No entanto, a minha avó já dizia que os problemas evitam-se, antes prevenir que remediar, etc e tal, entre outros ditos. Comecei a fazer perguntas. Não podia ser um simples erro de programador distraído. E aí obtive a minha resposta: foi tudo por causa das fontes CSS.

Para quem não conhece, uma fonte CSS é uma das propriedades CSS referente ao tipo de letra em que determinado texto encapsulado numa regra HTML será apresentado. Os navegadores web geralmente suportam uma miríade delas (Times New Roman, Arial, etc... nomes não tão estranhos para nós) nativamente, mas de vez em quando os designers acordam com o rabo virado para a lua e acham que o que as pessoas realmente querem ver é aquilo que não está tão facilmente disponível (tudo bem, ás vezes é assim mesmo), sendo que existe a possibilidade de costumizar fontes e torná-las disponíveis para a camada de apresentação, providenciando um URI a uma regra CSS chamada @font_face, possibilitando desta forma o carregamento assíncrono e a utilização por parte do navegador web de um tipo de letra que não suporta nativamente. Qualquer coisa como isto:

@font-face {
  font-family: GeometricModern;
  src: url(font.ttf);
}

p {
  font-family: GeometricModern, sans-serif;
}

desta forma, podemos dar uso ao tipo de fonte GeometricModern. Parece fácil não é? Pois é, existe um "senão". Neste caso, o Firefox decidiu implementar uma restrição aos URIs que podem ser passados nas regras @font-face, nomeadamente este URI tem que ter como origem o domínio do documento do qual faz parte. O que isto quer dizer? Se um pedido for enviado a www.exemplo.com, e o domínio de onde o ficheiro css estiver a ser carregado tiver o domínio recursos.exemplo.com, o pedido pela fonte terá de ser feito ao domínio www.example.com, caso contrário, o navegador vai bloquear a fonte. No caso acima, iria dar barraca, porque (font.ttf) é um caminho relativo, logo, se o domínio de onde o ficheiro CSS estiver a ser carregado for recursos.exemplo.com, esse também vai ser o domínio do caminho da fonte.

Eis que estava encontrada a justificação do colega responsável. O domínio da origem do documento era  www.exemplo.com, e consequentemente seria também o domínio da origem dos recursos CSS, e consequentemente, das fontes, e tudo batia certo. Já agora, e porquê a restrição?

http://dev.w3.org/csswg/css3-fonts/#font-face-rule

Pois, mais um instrumento de cross-site-scripting em potência. Mas depois perguntei-me: "O Google não faz isto. O Facebook não faz isto. Como é que eles resolveram o assunto?". Li alguma documentação, bati um pouco mais com a cabeça na parede, suportei a pressão dos meus colegas de trabalho, que viram nesse momento o seu trabalho posto em causa e acusaram a minha investigação um pouco, como se estivesse a pisar os pés de alguém, e voltei à casa de partida. E foi aí que li a alínea relativamente bem vísivel da ligação aqui acima:

User agents must also implement the ability to relax this restriction using cross-site origin controls[CORS] Sites can explicitly allow cross-site downloading of font data using the Access-Control-Allow-Origin HTTP header. 

E o HTTP veio para salvar o dia! Com esse campinho do cabeçalho da resposta do recurso, tinha tudo para dar certo. Só precisava de introduzir o seguinte no cabeçalho:

Access-Control-Allow-Origin: http:/www.exemplo.com
 
e era tudo o que bastava. A minha questão seguinte foi: Como fazer isso em Rails? É que o pedido e a resposta são montados na camada de middleware, à qual eu tenho um acesso relativamente restrito e uma compreensão relativamente baixa da complexidade do bicho. Mais umas pesquisas e descobri que existe uma gem que já fazia o que eu queria:

https://github.com/rubymaverick/font_assets/

Maravilha! Acrescentei, compilei, testei, e lá estava aquela fonte de merda horrível que não acrescentava pão mas que o designer achou tão necessária. Foi o pico de felicidade do meu dia.

Ainda fui verificar o suporte dos navegadores, mas depois lembrei-me que esta é por enquanto uma restrição exclusiva do Firefox. Restrição essa que vai existir eventualmente em todos os navegadores, incluindo esse monte de banha da cobra chamado Chrome. E assim resolveu-se um problema que se iria colocar eventualmente: como partilhar recursos entre domínios diferentes tendo a possibilidade de restringir esses domínios.
Lição do dia: o HTTP domina demais.

segunda-feira, 18 de junho de 2012

Rails: Auxílio a migrações complexas

Oi,
Penso que estamos de acordo quanto ao Rails ser uma ferramenta poderosa, flexível (a partir da versão 3.0) e Pesada. Se não estamos, podemos mandar vir umas cervejas e discutir o assunto. Mas uma das facetas que tenho descoberto e apreciado mais desde a reformulação que atingiram com a versão 3.0 é como a dissociação dos vários módulos que introduzem comportamento específico em determinadas componentes permite que esses mesmos módulos sejam individualmente ferramentas poderosas para efectuar tarefas que reflectem esse comportamento específico, mas fora do contexto onde estes módulos são normalmente utilizados. Soou um pouco confuso, mas passo a explicar com um exemplo específico.

Há cerca de 8 meses, no projecto em que correntemente trabalho, começámos a desenvolver uma aplicação web que interagiria com os dados da aplicação principal que estávamos (ainda estamos) a manter. A decisão tomada na altura foi a de desenvolver a segunda aplicação em cima da primeira, com acesso à mesma base de dados, achando que iriamos poupar tempo com isso, sendo os dados unicamente referentes à segunda aplicação guardados em tabelas com o âmbito explicitamente definido no nome (nome-tipo: aplicacao2_users, aplicacao2_profiles, etc...), o código-fonte referente à segunda aplicação protegido dentro do mesmo âmbito (namespace), e sendo criado um encaminhamento (routing) específico para esse âmbito sobre um hóspede (host) diferente. Deste modo, conseguimos criar uma coisa inaudita, um verdadeiro "pague 2 compre 1", uma salganhada tão grande que agora estamos a sofrer as consequências dessa decisão. O que estava errado aqui? Pois, quis-se desenvolver 2 aplicações completamente diferentes, com lógica completamente diferente, uma em cima da outra, em vez de se dissociar os ambientes e posteriormente providenciar um mecanismo de sincronização para os dados comuns relevantes. Interagir com os mesmos dados não significa necessariamente interagir com a mesma representação dos mesmos. Ao invés, aumentámos uma base de código já de si gigantesca, misturámos dados de âmbitos diferentes nas mesmas tabelas, e criámos ainda mais confusão na camada de apresentação (CSS) e do scripting do lado do cliente (o qual não vou abordar em pormenor agora).

Depois de assumido o erro, chegou-se à conclusão que era necessário separar as aplicações. Qualquer coisa de herculeano para uma aplicação que já está em produção faz 6 meses. Fiquei encarregue do primeiro passo nessa direcção: criar uma segunda base de dados e migrar os dados relevantes para segunda aplicação para ela. Algumas migrações eram fáceis: somente copiar uma tabela de um lado para o outro e zás. Outras envolviam alguma dor de cabeça: uma inserção aqui, outra deleção ali, uma actualização acolá... e tudo testadinho, que era para a barra não cair quando fosse para produção.

Não sei quanto a vocês, mas o meu uso de migrações em Rails sempre foi geralmente limitado a criação de tabelas, remoção/inserção de colunas, com o ocasional preenchimento de colunas utilizando directamente o comando execute. Então o que aconteceu quando comecei a escrever as migrações? O meu conhecimento estava limitado ao execute (e também a alguns métodos de selecção), então bombei no execute para tudo o que era peixe. Saiu o maior mufufu. Aqui fica um exemplo:



# pesquisar atributos
attrs = select_all("SELECT * FROM assets i WHERE i.owner_id = 1 AND i.image_owner_type = 'User::Lawyer'")
# actualizar para a nova tabela
attrs['origin'] = "app1"

attributes, values = attrs.to_a.transpose
values.map!{|attr| attr.nil? ? "NULL" : attr.kind_of?(Time) ? "\"#{attr.to_s(:db)}\"" : attr}
execute "INSERT INTO images (#{attributes.join(",")}) VALUES (#{values.join(',')});"
image_id = Integer(select_value("SELECT id FROM images ORDER BY id DESC LIMIT 1;"))
# usar o image_id em outras operações...


É, o resultado foi o previsto. Migrações que funcionavam, testadinhas e bonitinhas, mas que ninguém conseguia ler (e com algumas limitações, se quiserem investigar esse pedaço de código mais a fundo). Surgiu mais tarde o caso em que tinha que introduzir mais algumas operações no meio desta barafunda, e foi aí que eu pensei que estava na altura de descobrir como é que realmente se fazia aquilo a que eu me propunha, e como é que o Rails fazia para gerir toda esta comunicação com a base de dados, pois seguramente que não usavam o execute. Resolvi então começar a estudar alguma documentação, e comecei... pelo execute.

E foi aí que entrei no maravilhoso mundo da comunicação com a base de dados auxiliada por Rails. Como alguns de vocês  conhecem, as migrações são uma sub-componente de uma sub-componente do Rails, o nosso conhecido ActiveRecord. Graças ao esforço de dissociação empregue na versão 3 do AR, tudo o que envolve a comunicação com a base de dados foi extraído para um módulo à parte chamado ConnectionAdapters (http://apidock.com/rails/ActiveRecord/ConnectionAdapters). No domínio deste âmbito encontram-se definidos adaptadores para vários tipos possíveis de base de dados que herdam de um adaptador abstracto (AbstractAdapter). Nesse adaptador abstracto é incluído um módulo chamado DatabaseStatements, que era exactamente o que estava à procura. Neste módulo estão definidos todos os métodos com os quais se podem enviar declarações para a base de dados, cada um deles reflectindo o comportamento-base que a base de dados tem para com cada uma destas declarações. Logo, existe um método chamado insert, que retorna o id da última entrada inserida; o método update, que retorna o número de linhas afectadas na tabela utilizada, entre outras funções úteis... ah, o execute também tá definido aqui! (http://apidock.com/rails/v3.2.1/ActiveRecord/ConnectionAdapters/DatabaseStatements)
Tornou-se claro para mim que este módulo tinha que ser incluído nas migrações, dado que o execute andava por ali à mão de semear. Dei uma olhada no código-fonte da classe ActiveRecord::Migration, e dei conta que as migrações são corridas no contexto da classe ActiveRecord::Base. Tive a minha confirmação. Daí à reescrita das migrações foi um passo:

  


# pesquisar atributos
attrs = select_all("SELECT * FROM assets i WHERE i.owner_id = 1 AND i.image_owner_type = 'User::Lawyer'")
# actualizar para a nova tabela
attrs['origin'] = "app1"

attributes, values = attrs.to_a.transpose
attributes.map!{|a|quote_column_name(a)}.join(",")
values.map!{|a|quote(a)}.join(",")
  image_id = insert "INSERT INTO images (#{attributes}) VALUES (#{values});"
# usar o image_id em outras operações...


Poupei duas linhas inconsequentes e encurtei outras tantas. Mas continuo ali com aquela declaração SQL explícita que me dá comichão na garganta... mas, enquanto o pessoal do Rails não encontrar um modo de "ARelizar" essas chamadas aos métodos do DatabaseStatements, penso que isto é o melhor que se consegue (corrijam-me se estiver enganado). Ah, ali aquelas chamadas a quote_column_name e quote são naturalmente funções auxiliares que colocam as aspas de segurança respectivas ao redor dos atributos e valores. Outra das boas lições desta viagem pelo código-fonte do Rails.

Como a minha tarefa seguinte seria criar um módulo que migrasse dados de uma aplicação para a outra, e como esta migração teria que se passar directamente entre as duas bases de dados (o objectivo final é ter 2 aplicações), decidi implementar o conhecimento adquirido. Criei então um Mixin que tratava da sincronização (criação, actualização e deleção) de uma entidade na base de dados 1 com a base de dados 2. Para isso, criei uma classezinha que encapsulava todas as rotinas de uma forma bonitinha e que podia ser chamada aplicando uma variante similar ao ARel, um DSLzinho limitado a esse âmbito. O resultado foi a classe seguinte:

 

      class BaseStatements
        attr_reader :connection

        def initialize(connection)
          @connection = connection
        end

        def select_all(*fields)
          conditions = fields.extract_options!
          table_name = fields.pop
          @connection.select_all("SELECT #{fields.empty? ? '*' : quote_keys(fields).join(",")} FROM #{table_name}
                                 #{' WHERE ' unless conditions.blank?}#{parse_conditions(conditions)}")
        end

        def select_value(field, table_name, conditions)
          @connection.select_value("SELECT #{field} FROM #{table_name} WHERE #{parse_conditions(conditions)}")
        end

        def insert(table_name, attributes)
          keys, values = attributes.to_a.transpose
          @connection.insert("INSERT INTO #{table_name} (#{quote_keys(keys).join(",")}) VALUES (#{quote(values).join(",")})")
        end

        def update(table_name, attributes, conditions)
          @connection.update("UPDATE #{table_name} SET #{quote_keys(attributes)}=#{quote(v)}"}.join(",")} 
                                                   WHERE #{parse_conditions(conditions)}")
        end

        def delete(table_name, conditions)
          @connection.delete("DELETE FROM #{table_name} WHERE #{parse_conditions(conditions)}")
        end

        private
        def quote_keys(keys)
          keys.map{|a|@connection.quote_column_name(a)}  
        end
        
        def quote(values)
          values.map{|a|@connection.quote(a)}
        end 
 
        def parse_conditions(cond)
          return @connection.quote(cond) unless cond.kind_of?(Hash)
          cond.map do |k, v|
            case k
                when :not then "NOT #{parse_conditions(v)}"
                when :in then "IN (#{v.map{|el|@connection.quote(el)}.join(",")})"
                else "#{@connection.quote_column_name(k)}#{v.kind_of?(Hash) ? ' ' : v.nil? ? ' IS ' : ' = '}#{parse_conditions(v)}"
            end
          end.join(" AND ")
        end
      end

Instanciando um objecto desta classe, podia então realizar declarações SQL com algum nível de complexidade numa linguagem mais descritiva e legível. Alguns exemplos:

handler = BaseStatements.new(ActiveRecord::Base.connection)
 
handler.select_all(:id, :name, :users, :email => "donald@ducks.com", :city => "Buxumburra")
# "SELECT id, name FROM users WHERE email = 'donald@ducks.com' AND city = 'Buxumburra';"  
handler.insert({:email => "donald@ducks.com", :city => "Buxumburra"}, :users)
# "INSERT INTO users (email,city) VALUES('donald@ducks.com','Buxumburra');" 
handler.update({:city => "Cidade do Cabo"}, :users, {:email => "donald@ducks.com})
# "UPDATE users SET city='Cidade do Cabo' WHERE email='donald@ducks.com';" 
handler.update({:email => "donalda@ducks.com", :users, :city => {:not => {:in => ["Buxumburra","Kinshasa"]}} 
# "UPDATE users SET email='donalda@ducks.com' WHERE city NOT IN ('Buxumburra','Kinshasa');" 
handler.delete(:users, :id => 1) 
# "DELETE FROM users WHERE id=1;"
 
 
Como disse, é um DSLzinho, não cobre todas as possibilidades, foi uma solução para um contexto específico. Claro que existem outras questões que se colocam em relação ao tipo de solução que eu encontrei para este tipo de problema. Não estaria mais correcto fazer tudo na base de dados através de um stored procedure? Sim. Não seria mais moderno e elegante no contexto web comunicar com a aplicação 2 através de um serviço remoto, por exemplo uma web API? Possivelmente. Em todo o caso, valeu a pena descobrir e explorar este pedacinho do mundo do Rails.

domingo, 17 de junho de 2012

Tipificação de variáveis e programação defensiva

Oi,


Surgiu há uns dias uma discussão interessante no trabalho. Um colega meu refactorizou uma componente que tinha programado fazia uns meses, e questionava-se quando ao melhor modo de se proteger contra aquilo que chamou a tipificação fraca na linguagem de programação Ruby (e com o que eu na altura concordei). Ele tomou a decisão de, segundo ele, verificar o tipo das variáveis passadas como argumento a cada função, protegendo-de dessa forma contra comportamentos inesperados, possíveis excepções que fossem lançadas, etc., e chamou ao que estava a fazer "programação defensiva". Quis discutir o resultado comigo, e aí começaram a surgir as diferenças na opinião de cada um. Eu achei o que ele estava a fazer contra-producente, visto a própria linguagem de programação (que dizem ser uma "linguagem orientada a objectos pura") já providenciar algum tipo de identificação desse tipo de excepções, ás quais ele podia aplicar algum tipo de tratamento, mas pior que isso, aplicar esse tipo de abordagem em cada método iria só "poluir" visualmente estes, desviando a atenção da lógica da rotina, e desviando-se também das práticas aconselhadas na programação em Ruby. Outro possível efeito secundário seria a ocorrência de algum tipo de "falha silenciosa".



def plus(a, b)
return nil unless a.kind_of?(Integer)
return nil unless b.kind_of?(Integer)
a + b
end



A discussão começou a aquecer, pelo que achei que era melhor rever os conceitos antes de continuar a "bater na cabeça do ceguinho".

Comecemos então pelo conceito acima referenciado de "tipificação fraca na linguagem de programação Ruby". Algo que, para que fique escrito em algum lado, eu repeti (erradamente) várias vezes em diversas ocasiões. Conceptualmente, o que diferencia a tipificação dita "fraca" da dita "forte" é a imposição (ou falta) de restrições na interoperabilidade entre valores de tipos diferentes. De acordo com esta perspectiva, Ruby é uma linguagem com tipificação forte (com algumas excepções). Então porquê a confusão estabelecida no início? Pois, é a falta de declaração do tipo de uma variável. Mas esse já é um conceito diferente. Trata-se agora de um outro conceito: o dinamismo da linguagem. Uma linguagem é considerada de tipificação estática quando o tipo  do valor referenciado pelas variáveis é verificado em tempo de compilação, e dinâmica quando este é verificado em tempo real. Em Ruby, uma variável não é mais do que um "ponteiro", cujo valor pode ser qualquer coisa. Daí ser possível fazer coisas em Ruby como:


a = 2
a = "a"


No entanto, algo como isto:


a = 1
a = a + "2"


lançaria uma excepção. (Curiosamente, isto funcionaria em Javascript, o que só aumenta mais o meu desprezo pela especificação de uma linguagem tão útil como pessimamente estruturada).

Logo, concluindo, Ruby é uma linguagem "dinâmica e fortemente tipificada".

Passemos agora para a programação defensiva. O objectivo-base deste conceito é assegurar a continuação do funcionamento de uma componente de software protegendo-se contra mudanças eventuais futuras no código-fonte. Ou seja, eliminar a lei de Murphy equação. Segundo o artigo da Wikipedia sobre o assunto, propõe-se portanto a aumentar a qualidade do código-fonte e reduzir o número de erros, tornar o código-fonte compreensível, a fazer o software comportar-se de uma maneira previsível. Acho isso tudo óptimo. Aliás, acho cada um desses pontos um tema em si, e já um pouco para lá daquilo que a programação defensiva supostamente previne. Mas assim de repente, uns quantos pontos pessoais:
  • eliminar a lei de Murphy; bonito, mas... isso é só para Deus e para o Chuck Norris. Existe uma razão para se falar tanto da lei de Murphy - ela é omnipresente;
  • erros evitam-se de diversas formas, por exemplo testando o código, deste modo providenciando uma especificação de uso para quem vier pegar nesse código seguidamente;
  • melhorar a legibilidade do código é óptimo, e eu sou um apologista disso, mas existem outras formas de complementar a compreensão do código escrito, tal como documentá-lo (ainda sou mais apologista deste ponto);
  • fazer o software comportar-se de maneira previsível é um objectivo ambicioso. O que é definido como "previsível"? Que fazer nos casos excepcionais? Será que estes serão automaticamente compreendidos pelas rotinas que têm que interagir com esses valores "previsíveis"? Nestas alturas, é sempre bom vistoriar os requisitos de novo. 

Então, voltando ao ponto inicial, é a situação do exemplo descrito no início do texto programação defensiva?

  • Se as rotinas que utilizarem aqueles métodos nunca testarem os casos excepcionais, sejam eles valores nulos, excepções customizadas, etc., a lei de Murphy vai aparecer... para além do esforço acrescido de testar os casos excepcionais;
  • Ele evitaria mais erros testando e criando uma especificação de uso (como eventualmente fez) para outros programadores, minimizando o impacto do erro humano;
  • A legibilidade não foi melhorada, na minha opinião. Aquelas duas linhas que testam se o tipo do valor é um número inteiro toldam a minha vista um pouco (sim, estou a ser sarcasticamente exagerado, mas acho que compreendem onde quero chegar);
  • A rotina comporta-se de forma previsível? Pois, não sei. Se o requisito da rotina é identificar os casos em que os valores não são inteiros, e depois tratá-los de forma adequada, tudo bem; caso contrário, o erro persiste. E se eu tiver que tratar sequências de caracteres de forma diferente de números reais? Será que posso usar a rotina com métodos reais? Será que produz um resultado esperado?

Resumindo e concluindo, acho que foi tudo uma boa ideia, mas aquele tratamento de erros não faz bem nem mal, simplesmente não acrescenta nada. Chamei-lhe "programação paranóica".

Manifesto de intenções

Oi

Pareceu-me bem iniciar com um "Oi" o blog, é curto, seco e directo ao assunto. A sua função é saudar de uma forma cordial e semi-informal quem quer que seja que esteja a ler o que foi, é e vai ser escrito por aqui. Deixa ver o que acontece se eu repetir.

Oi

E pronto, está feito. Passemos à fase seguinte, passando a responder ao "quem somos", "para onde vamos", "de onde vimos".

"Ngimbo" é uma palavra do português (se não é, passou a ser) que assim por alto significa "foice". Eu gosto mais de traduzir como "ferramenta de trabalho". Porquê "Ngimbo"? Porque dá um contexto ao blog. Já lá chegamos.

Eu sou alguém. Que vive em parte incerta. Porquê a anonimidade? Bom, por enquanto, porque sim. Estudei Engenharia Informática no Instituto Superior Técnico em Lisboa e presentemente trabalho no ramo, mais especificamente planificando e desenvolvendo aplicações-web. Porque revelei esta informação pessoal? Porque, sendo o objectivo deste blog divagar um pouco mais pelos assuntos relacionados com as tecnologias de informação em geral, achei que seria melhor estabelecer uma espécie de perfil profissional que me qualifique de certo modo para falar sobre o assunto em questão.

Porquê um blog? E porquê agora? Bem, por várias razões.
Já me questionaram em várias entrevistas de emprego se eu administrava ou participava em algum tipo de blog cujo tema fosse relacionado com as tecnologias de informação. Eu geralmente respondia que sim, já administrei e participei em blogs (com uma frequência díspar, como podem comprovar "cuscando" o meu perfil), mas em nenhum deles alguma vez se tinha escrito alguma coisa sobre tecnologias de informação. Ao que se seguiam geralmente um olhar que era um misto de desapontamento e preconceito (profissional). Tornou-se claro para mim que, para se ser alguém no ramo, é preciso não só saber, mas também mostrar que se sabe (de preferência por esta ordem, mas não obrigatoriamente).
Por outro lado, já por várias vezes senti a necessidade de extravasar alguma tensão e desapontamento com a forma como alguns processos decorrem nos projectos em que eu trabalho e divagar/discutir sobre eles. Sendo uma pessoa de opiniões fortes, gosto de defender algumas ideias que acho importantes para o bom funcionamento de um projecto nesta área, sempre tentando ser flexível com a minha opinião e aceitando sugestões ou mesmo críticas quando estou errado. Errare humanum est, e eu gosto sempre de espaço para errar, de preferência não muitas vezes.
Porquê um blog em português sobre tecnologias de informação? Bem, em primeiro lugar, é a minha língua nativa. Pessoalmente acho que escrevo e me expresso melhor em português que em qualquer outra língua. Por outro lado, estou farto de inglês. Todos os dias sou bombardeado com inglês, escrito ou falado, por mim e por outros. E a maior parte das vezes por gente cuja língua nativa está longe de ser o inglês. Ou seja, na maior parte das vezes, sou bombardeado com inglês errado. Sei de muita gente, lusófona e não só, que tomou a decisão de escrever os seus textos em inglês, porque achou que desse modo, chegava a uma audiência mais abrangente. Acho muito bem. Acho também que desse modo, enchem a Rede com mais conteúdos em inglês errado, perpetuam o estatuto do inglês como a língua de facto franca da actualidade, dessa forma minificando tanto a importância da sua língua nativa como a riqueza que a língua inglesa possa ter, e contribuem indirectamente para a suave e aborrecida mutação da raça humana para o mínimo denominador comum. O que é engraçado, porque vai totalmente no sentido contrário da tecnologia. O primeiro, e durante algum tempo, único esquema de codificação de caracteres usado na representação de texto em computadores chama-se ASCII, acrónimo para American Standard Code for Information Interchange, ou em português"Código Padrão Americano para o Intercâmbio de Informação". Hoje em dia existem mil-e-um esquemas de codificação que representam todos os alfabetos existentes à face da Terra de várias formas. Até há bem pouco tempo, o esquema para URIs (Identificador Uniforme de Recursos) só aceitava caracteres do alfabeto inglês. Hoje em dia já começam a ser aceites outro tipo de caracteres. Ou seja, a tecnologia tenta expandir o seu âmbito de forma a chegar a mais gente. Se continuar a existir este esforço de standardização da massa humana, será que não é uma perda de tempo esta constante adaptação da tecnologia?
Portanto, em português. Não é um sentimento de posse ou nacionalismo que me move, mas mais uma questão de juntar o útil ao agradável. Quem souber ler, sabe. Quem não souber, ou aprende ou usa um desses tradutores online que existem. Jesus falava em aramaico. Hoje em dia ninguém fala aramaico e toda a gente conhece Jesus e sabe do que ele falou. Não queria comparar-me a Jesus, mas se os jogadores de futebol o fazem a toda a hora...

Para finalizar, queria dizer que este blog não vai ser escrito de acordo com o novo Acordo Ortográfico. Os meus manos brasileiros que me desculpem, não o faço por apelo nacionalista ou o que quer que seja, simplesmente porque não aprendi essa versão do português, e isso só me iria dificultar a comunicação um pouco mais. Falem quando não entenderem, e a "galera" traduz, valeu?

Saudações