Treetop: Interpretação direta

September 28th, 2009 § 2 comments § permalink

Introdução

Continuando a série sobre Treetop, é hora de ver como realmente utilizar um parser para produzir resultados de execução. Até o momento, vimos somente como montar a gramática e como obter uma árvore que pode ser utilizada de alguma forma.

Como a árvore sintática que o parser gera já é bem completa, a tentação seria de usar a mesma e processá-la recursivamente através de um rotina que identificasse os dados necessários ou alguma espécie de pattern com o Visitor.

Não é uma estratégia de todo ruim, mas o Treetop provê mecanismos para ajudar nisso sem a necessidade de processar a árvore diretamente nó a nó; antes, é possível processar somente os nós necessários e já fazer algum tipo de interpretação direta dos elementos reconhecidos da linguagem.

Esse processamento pode ser feito de duas maneiras. Primeiro, pela injeção de código Ruby diretamente no parser, permitindo o processamento posterior através de algum callback. Segundo, pela construção de alguma árvore sintática específica à linguagem que espelhe a árvore inicial gerada mas de uma maneira mais processável, por assim dizer.

O tema desse artigo é a primeira técnica, que veremos logo a seguir.

Introduzindo código Ruby no parser

Vamos voltar a nossa gramática inicial:

grammar SimpleMath

  rule expression
    term ('+' / '-') expression / 
    term
  end
  
  rule term
    factor ('*' / '/') term / 
    factor
  end
  
  rule factor
    '(' expression ')' / 
    number
  end
  
  rule number
    [0-9]+
  end
  
end

Digamos que queremos ter o resultado da expressão aritmética que está sendo reconhecida pela linguagem. O processo não é complicado.

Obviamente, precisamos começar tendo o valor dos números que compõem a unidade final da gramática. Em seguida, pelo modo como nossa gramática está estruturada, precisamos ter o valor da multiplicação ou divisão entre fatores e termos, expresso pela segunda regra. E assim por diante.

Uma pergunta é: por que não precisamos de processar cada um das regras. A resposta está na própria forma como a gramática é reconhecida. Em última instância, por exemplo, um termo é também um fator (como expresso pela segunda opção dentro da regra term). E, pelo mesmo ponto de vista, um fator, em última instância pode ser um número. Isso implica que em alguns casos, basta o reconhecimento da regra final para permitir o acesso.

Vamos aplicar, então, esse conhecimento à gramática, reconhecendo inicialmente números. A gramática ficaria assim:

grammar SimpleMath

  rule expression
    term ('+' / '-') expression / 
    term
  end
  
  rule term
    factor ('*' / '/') term / 
    factor
  end
  
  rule factor
    '(' expression ')' / 
    number
  end
  
  rule number
    [0-9]+ {
      def eval
        text_value.to_f
      end
    }
  end
  
end

Note o código Ruby entre as chaves, em negrito. Essa é a forma que o Treetop usa para inserir código diretamente dentro do nó sintático que aquela regra representa.

O código acima introduz um novo método chamado eval ao nó que reconhece a regra números. Esse código diz que o valor daquele nó é o seu valor textual convertido para um número de ponto flutuante. O método text_value é inerente ao Treetop e existe em todos os nós.

Podemos usar agora isso da seguinte maneira:

>> require "treetop"
=> true
>> require "simple_math"
=> true
>> p = SimpleMathParser.new
=> #
>> p.parse("1")
=> SyntaxNode+Number0 offset=0, "1" (eval):
  SyntaxNode offset=0, "1"
>> p.parse("1").eval
=> 1.0

Note duas coisas. Primeiro, o método eval está disponível diretamente na árvore sintática, como mostrado pelo resultado do parsing. Segundo, embora a regra inicial da gramática seja expression, somente a regra number foi reconhecida dessa vez. Isso prova o que foi explicado acima, sobre o cascateamento de expressões.

Note também o uso de próprio nome de uma regra (expression nesse caso) dentro do método. Qualquer não-terminal dentro de uma regra será exposto pelo Treetop dessa forma.

Vamos agora evoluir para reconhecer fatores, a regra imediatamente acima que usa números ou reconhece uma expressão dentro de parênteses. A idéia é a mesma e a implementação é igualmente simples.

grammar SimpleMath

  rule expression
    term ('+' / '-') expression / 
    term
  end
  
  rule term
    factor ('*' / '/') term / 
    factor
  end
  
  rule factor
    '(' expression ')' {
      def eval
        expression.eval
      end
    } /
    number
  end
  
  rule number
    [0-9]+ {
      def eval
        text_value.to_f
      end
    }
  end
  
end

Agora é possível fazer a interpretação de expressões como a mostrada abaixo, o que constrói o nosso próximo nível:

>> require "treetop"
=> true
>> require "simple_math"
=> true
>> p = SimpleMathParser.new
=> #
>> p.parse("(1)")
=> SyntaxNode+Factor1+Factor0 offset=0, "(1)" (expression,eval):
  SyntaxNode offset=0, "("
  SyntaxNode+Number0 offset=1, "1" (eval):
    SyntaxNode offset=1, "1"
  SyntaxNode offset=2, ")"
>> p.parse("(1)").eval
=> 1.0

Com isso pronto, é hora de reconhecer multiplicação e divisão, ou seja, operações sobre termos, o nosso próximo item nas regras. A modificação não é muito mais complexa:

grammar SimpleMath

  rule expression
    term ('+' / '-') expression / 
    term
  end
  
  rule term
    factor operator:('*' / '/') term {
      def eval
        factor.eval.send(operator.text_value.to_sym, term.eval)
      end
    } / 
    factor
  end
  
  rule factor
    '(' expression ')' {
      def eval
        expression.eval
      end
    } /
    number
  end
  
  rule number
    [0-9]+ {
      def eval
        text_value.to_f
      end
    }
  end
  
end

Esse código faz uso de uma característica nova do Treetop: a possibilidade de nomear um não-terminal ou terminal reconhecido para uso na implementação internal. No caso acima, como o resultado do reconhecimento da regra pode ser tanto o sinal de adição quando o de multiplicação dentro da escolha ordenada entre parênteses, precisamos de uma forma para referenciar isso. O label colocado à frente do não-terminal se encarrega disso, criando um novo método que o representa e pode ser usado como qualquer outro termo interno.

O código também faz uso do fato de que o Ruby é uma linguagem (quase que) 100% orientada a objetos e que mesmo elementos primitivos da linguagens podem receber mensagens.

Para que não conhece detalhes do Ruby, essencialmente, o código acima faz o seguinte:

  • Avalia o fator, o lado esquerdo da expressão. Esse método eval foi introduzido no passo anterior;
  • Avalia o termo restante, o lado direito da expressão;
  • Finalmente, envia o método representado pelo operador ao termo, com o parâmetro sendo o resultado do termo.

Digamos que o fator tenha tido como resultado 2 e o termo tenha tido o resultado 3 como o operador +. A expressão final é equivalente a:

2.send(:+, 3) # => 2 + 3 = 5

Para fechar a interpretação, o código que lida com termos é o mesmo:

grammar SimpleMath

  rule expression
    term operator:('+' / '-') expression {
      def eval
        term.eval.send(operator.text_value.to_sym, expression.eval)
      end
    } / 
    term
  end
  
  rule term
    factor operator:('*' / '/') term {
      def eval
        factor.eval.send(operator.text_value.to_sym, term.eval)
      end
    } / 
    factor
  end
  
  rule factor
    '(' expression ')' {
      def eval
        expression.eval
      end
    } /
    number
  end
  
  rule number
    [0-9]+ {
      def eval
        text_value.to_f
      end
    }
  end
  
end

Agora é possível rodar expressões arbitrárias como demonstrado abaixo:

>> require "treetop"
=> true
>> require "simple_math"
=> true
>> p = SimpleMathParser.new
=> #
>> p.parse("1+2*3").eval
=> 7.0
>> p.parse("(1+2)*3").eval
=> 9.0
>> p.parse("(1+2)*(3-4)").eval
=> -3.0
>> p.parse("(1+2)*(3-4/5)").eval
=> 6.6

Como é possível ver, as regras de precedência normais são reconhecidas. Isso acontece pela forma como a gramática foi desenhada e não por causa de propriedades inerentes da linguagens.

Caso fosse possível acompanhar a execução de cada expressão acima, seria fácil perceber que os métodos são recursivamente invocados para gerar o resultado final, ou seja, para a expressão (1+2)*(3-4/5), o processo acontece da seguinte forma:

      1.0 +
      2.0
    3.0
  3.0 *
      3.0 -
          4.0 /
          5.0
      0.8
    2.2
  2.2 =
6.6

Conclusão

Como esse artigo demonstra, introduzir código Ruby em um parser gerado pelo Treetop é algo bem simples e facilmente controlável. Embora o artigo tenha mostrado somente a inserção de um método simples, é possível introduzir qualquer quantidade de métodos, públicos ou não, para utilização.

Por exemplo, para uma gramática um pouco diferente (mostrada abaixo de forma incompleta), utilizando não-terminais para operadores, seria simples fazer algo do tipo:

grammar SimpleMath

  rule expression 
    operand1:term operator:SUM_OP operand2:expression {
      operator.apply(operand1, operand2)
    } / 
    term 
  end
  
  rule SUM_OP 
    '+' {
      def apply(operand1, operand2)
        operand1 + operand2
      end
    } / 
    '-' {
      def apply(operand1, operand2)
        operand1 - operand2
      end
    }
  end    
  
   # ...
  
end

Como é possível ver, não há limite para o que se possa fazer com essa técnica.

Até o momento, vimos como usar uma gramática para não só reconhecer uma linguagem formal como também para executar instruções sobre essa linguagem. Na maioria dos casos, isso é suficiente mesmo para tarefas mais complexas. Entretanto, algumas vezes é necessário utilizar um reconhecimento mais sofisticado. Esse será o tema do próximo artigo.

Nota: Para seguir a série completa, siga a categoria Treetop deste blog.

Treetop: Básico

September 25th, 2009 § 10 comments § permalink

Introdução

Uma das tarefas mais comuns em programação é transformar uma representação qualquer de dados e/ou código em outra necessária para a execução de uma tarefa.

Quem acompanha o meu blog há mais tempo, sabe que eu sou um grande fã de Domain Specific Languages, uma das formas de fazer esse tipo de transformação sem sair do escopo da própria linguagem em que se está desenvolvendo. Muitas vezes, entretanto, é necessário algum tipo de conversão mais específica–uma compilação, por assim dizer.

Existem várias formas de fazer esse tipo de compilação mais específica, que podem ser baseadas em estruturas tão “simples” como expressões regulares a estruturas bem mais complexas como gramáticas livre de contexto (CFG) que lidam com ambigüidades e recursividade de maneira mais completa. De uma forma mais simples, tanto expressões regulares como CFGs são linguagens formais que, por sua vez, expressam como uma outra linguagem específica pode ser gerada ou analisada para criar a transformação necessária.

Esse tipo de formalização é válida para qualquer transformação e é exatamente a mesma técnica, por exemplo, aplicada na descrição de uma linguagem de programação. O que varia, na maioria dos casos, é a complexidade final da linguagem que está sendo descrita, que limita o tipo de gramática e formalização que precisa ser usada. Para ilustrar isso, considere o seguinte exemplo em C:

x * y;

A princípio, isso pode ser interpretado como uma simples multiplicação de duas variáveis x e y cujo valor é descartado sem ser usado. Por outro lado, isso também pode ser interpretado com a declaração de uma variável y que é um ponteiro deferenciado do tipo x. Esse tipo de ambigüidade e complexidade limita quais ferramentas e técnicas podem ou precisam ser utilizadas. Um exemplo final dessa ambigüidade é o problema do else solto.

Nos últimos anos, uma técnica ou ferramenta que tem surgido com uma alternativa para tornar o trabalho de definição de linguagens e transformações de maneira mais fácil são as Parsing Expression Grammars, das quais o Treetop, assunto desse artigo, é um gerador.

Uma parsing expression grammars (ou PEG) é um tipo de gramática formal que descreve uma linguagem e pode ser usada para criar a base de uma transformação mais específica e mais poderosa do que uma simples linguagem de domínio baseada em uma linguagem de programação qualquer.

Uma característica fundamental de uma PEG é que ela é, por definição, analítica, isto é, ela reconhece texto em uma linguagem e gera uma árvore sintática do que reconheceu--ou, obviamente, falha. Isso difere da capacidade geradora de outras classes de gramáticas que são capazes, como o nome indica, de gerar todas as possíveis expressões representadas por uma linguagem formal.

Uma outra característica fundamental de uma PEG é que ela não pode ser ambígua. Dado um texto qualquer a ser reconhecido como uma parte de linguagem, aquele texto possui uma e somente uma árvore sintática. Isso não quer dizer que a linguagem não seja capaz de ter construtos ambíguos--eles somente não serão reconhecidos diretamente na fase em que a PEG está envolvida e requerem algum processamento posterior. Significa, entretanto, que se há alguma ambigüidade inerente na linguagem que precise ser resolvida em tempo de análise sintática, uma PEG não será capaz de lidar com a mesma.

Finalmente, uma terceira característica de uma implementação de PEG é que a fase de análise léxica e análise sintática são feitas de uma única vez.

Para terminar com essa breve introdução teórica--que, por admissão, nem arranha a superfície do assunto--um item final é que a implementação do parser de uma PEG possui certas características de performance de execução que podem inviabilizar a implementação de uma gramática qualquer. Para lidar com isso, normalmente a implementação é feita usando um packrat parser, que é uma forma de implementação que troca uso de memória para conseguir rodar em tempo linear. Basta dizer que o Treetop é um exemplo desse tipo de gerador, criando packrat parser em Ruby puro.

Descrevendo uma PEG

A descrição de uma PEG, ou seja, a formalização de qual linguagem a mesma reconhece usa uma notação que mistura elementos de expressões regulares e BNF, com uma interpretação ligeiramente diferente para tornar o reconhecimento mais simples.

Os elementos fundamentais dessa notação são:

  • Uma regra de início da gramática
  • Um conjunto de terminais, isto é, elementos que geram uma representação final da linguagem, que não podem ser quebrados em unidades menores sem perder significado
  • Um conjunto de não-terminais, isto é, elementos que podem ser decompostos em outros, inclusive em referências a si próprios
  • Um conjunto de regras que descrevem essa decomposição

Essas regras podem ser definidas pelos seguintes operadores:

  • Seqüência: e1 e2 -- a regra é composta por esses dois elementos na ordem dada
  • Escolha ordenada: e1 / e2 -- a regra é composta por um ou outro dos elementos
  • Zero ou mais: e*
  • Um ou mais: e+
  • Opcional: e?
  • Predicado E: &e
  • Predicado Não: !e

A regra acima que explica a diferença essencial entre uma PEG e uma CFG é que o operador de escolha é ordenado, ist é, se a primeira alternativa tem sucesso, a segunda é completamente ignorada. Não existe então a possibilidade de que duas árvores sintáticas sejam geradas. Em caso de ambigüidade, a escolha ordenada pode ser usada para escolher uma cominho específico.

Além disso, os predicados E e Não podem ser usados para remover ainda mais ambigüidades por permitir olhar à frente nas regras que estão sendo analisadas e tomar decisões com base nisso. Por exemplo, é possível dizer que uma regra qualquer é válida desde que não seja seguida por tais e tais construtos. A possibilidade de olhar à frente (lookahead) de maneira ilimitada é responsável pelas complicações mencionadas acima que requerem a implementação de packrat parsers.

Para dar um exemplo de uma gramática e tornar esse questão toda mais real, vamos olhar uma gramática capaz de reconhecer expressões matemáticas simples:

expression := term ('+' / '-') expression / term
term := factor ('*' / '/') term / factor
factor := '(' expression ')' / number
number := [0-9]+

A gramática acima descreve as seguintes regras:

  • Uma expressão é um termo, seguido de um operador de adição ou subtração, seguido de uma outra expressão OU um simples termo;
  • Um termo, por sua vez, é um fator seguido de uma operador de multiplicação ou divisão, seguido por um outro termo OU um simples fator;
  • Um fator é uma expressão entre parênteses OU um número;
  • Finalmente, um número é uma seqüência de um ou mais dígitos.

Note o uso do operador de escolha ordenada para descrever as regras. Por ser ordenado, o operador automaticamente estabelece regras de precedência, ou seja, uma adição, por aparecer primeiro, tem maior precedência do que uma subtração. O mesmo é válido para a multiplicação e divisão.

Por outro lado, isso não significa que haja automaticamente uma precedência específica entre regras diferentes. No caso acima, pela forma como as regras estão estruturadas, multiplicação e divisão possuem maior precedência do que subtração e adição por serem reconhecidas primeiro.

Uma outra maneira de descrever a mesma gramática é:

expression := sum
sum := product (('+' / '-')) product)*
product := value (('*' / '/')) value)*
value := number / '(' expression ')'
number := [0-9]+

Ambas as formalizações acima descrevem a mesma gramática de formas diferentes. A árvore final gerada será diferentes mas tem a mesma interpretação. A grande diferença, talvez, seja que a segunda gramática é mais fácil de entender do que a primeira, embora mais difícil de ser estendida.

Essa gramática descreve as seguintes regras:

  • Uma expressão é uma soma;
  • Uma soma é uma seqüência de zero ou mais adições ou subtrações de produtos;
  • Um produto é uma seqüência de zero ou mais multiplicações ou divisões de valores;
  • Um valor é um número ou uma expressão entre parênteses;
  • Finalmente, um número é uma seqüência de um ou mais dígitos.

Dependendo de como o parser é gerado, pode ser mais ou menos fácil lidar com uma gramática em particular. Nesse caso, a escolha de quais produções são especificadas pela gramática pode ser crucial para facilitar a implementação da linguagem.

Note que as gramáticas acima não descrevem reconhecimento de espaços brancos entre as produções. A gramática acima reconheceria 1+2*3 mas não 1 + 2 * 3. Para mudar isso, algo a mais ter que ser definido. Mostrando um pedaço de uma gramática adaptada, as regras ficaram, por exemplo:

value := [0-9]+ / '(' spaces? expression spaces? ')'
spaces := [ \t\r\n]+

Essa é uma visão simples do processamento de espaços opcionais, mas válida.

Usando o Treetop

Depois de toda essa introdução é hora de finalmente colocar o Treetop em ação. Assumindo que você tenha uma versão recente do Ruby, a instalação do Treetop é simples:

~$ sudo gem install treetop

Com o Treetop instalado, podemos escreve a nossa gramática. Não coincidentemente, a sintaxe do Treetop é uma PEG em também--para completar, inclusive, o Treetop possui é capaz de fazer o seu próprio bootstrap e esse é o seu método atual de desenvolvimento.

A sintaxe é bastante trivial. Usando a primeira gramática acima de exemplo, teríamos algo assim:

grammar SimpleMath

  rule expression
    term ('+' / '-') expression / term
  end
  
  rule term
    factor ('*' / '/') term / factor
  end
  
  rule factor
    '(' expression ')' / number
  end
  
  rule number
    [0-9]+
  end
  
end

Essa é uma forma simples e direta de expressar a gramática. É possível, porém, ter uma gramática tão sofisticada e legível quanto se deseje. Veja a mudança abaixo para descrever exatamente a mesma gramática de maneira um pouco mais legível:

grammar SimpleMath

rule expression term SUM_OP expression / term end

rule term factor MUL_OP term / factor end

rule factor LPARENS expression RPARENS / number end

rule number [0-9]+ end

rule SUM_OP '+' / '-' end

rule MUL_OP '*' / '/' end

rule LPARENS '(' end

rule RPARENS ')' end

end

A diferença é que está possui mais elementos não-terminais--o que não impacta de forma alguma o processamento da mesma--mas impacta talvez seu uso já que elementos que seriam anteriormente inseridos diretamente na árvore como terminais, não gerando sub-expressões, agora o fazem. Veremos mais sobre isso adiante.

Usar a gramática acima, é fácil. Digamos que a gramática tenha sido salva em um arquivo chamado simple_math.rb. Para carregar isso em um programa Ruby, basta usar:

require "treetop"
Treetop.load "simple_math"

O resultado é a disponibilização de um parser chamada SimpleMathParser que reconhece a gramática em questão. Se o arquivo possui a extensão .treetop é possível usar require diretamente:

require "treetop"
require "simple_math"

Para usar o parser, basta instanciá-lo e tentar o parsing de uma expressão qualquer:

>> require "treetop"
=> true
>> require "simple_math"
=> true
>> p = SimpleMathParser.new
=> #
>> p.parse("1+2*3")
=> SyntaxNode+Expression0 offset=0, "1+2*3" (term,expression):
  SyntaxNode offset=0, "1":
    SyntaxNode offset=0, "1"
  SyntaxNode offset=1, "+"
  SyntaxNode+Term0 offset=2, "2*3" (factor,term):
    SyntaxNode offset=2, "2":
      SyntaxNode offset=2, "2"
    SyntaxNode offset=3, "*"
    SyntaxNode offset=4, "3":
      SyntaxNode offset=4, "3"

Note o resultado de um parsing bem sucedido: uma árvore sintática válida que expressa cada elemento da linguagem e o que foi reconhecido em cada ponto da mesma. Note que a árvore possui um detalhamento preciso de cada nó, incluindo terminais e não terminais.

Note que cada nó da árvore sintática é uma instância da classe SyntaxNode anotada com elementos que fornecem uma indicação do que aquele nó representa. Os nomes Expression0 e Term0 representam módulos Ruby que implementam detalhes dos nós sintáticos. Da mesma forma, os nomes entre parênteses (term, expression, etc) são as partes da linguagem reconhecidas dentro daquele nó.

Caso você queira ver o parser como ele realmente foi gerado, basta fazer o seguinte:

~$ tt simple_math.treetop

Um arquivo simple_math.rb será gerado com a implementação do parser em si. O código é bem legível e demonstra como o Treetop faz o reconhecimento descendente e mutuamente excludente dos terminais e não-terminais.

Continuando, vejamos o que acontece em caso de falha:

>> p.parse("1+2*")
=> nil
>> p.failure_reason
=> "Expected ( at line 1, column 5 (byte 5) after *"

Como essa é uma gramática simples, a mensagem pode não ser tão informativa. Mesmo assim, ela fornece uma indicação precisa do que é esperado e fica para o implementador a tarefa de converter isso em mensagens úteis de acordo com o contexto necessário.

Um exercício interessante, que fica para o leitor, é fazer com que a gramática anterior seja capaz de reconhecer espaços.

Conclusão

Até o momento, vimos uma breve introdução teórica e como criar a gramática em si. Ter somente a capacidade de reconhecer uma linguagem, entretanto, não é algo muito útil. Precisamos agora fazer um uso das informações. Para isso, o Treetop oferece duas formas bem convenientes de acessar o que a árvore sintática reconhecida. No próximo artigo, portanto, veremos como fazer esse uso.

Nota: Para seguir a série completa, siga a categoria Treetop deste blog.

Usem linguagens dinâmicas

August 11th, 2009 § 46 comments § permalink

Senhoras e senhores da classe de 2009:

Usem linguagens dinâmicas.

Se eu pudesse lhes oferecer somente um conselho para suas carreiras futuras de programação, seria o de usar linguagens dinâmicas. Os benefícios a longo prazo das linguagens dinâmicas já foram provados por milhares de programadores enquanto o resto dos meus conselhos não tem qualquer outra base se não minha própria conturbada experiência.

Eu lhes darei esses conselhos agora.

Aproveite o poder e expressividade de uma linguagem homoicônica. Ou esqueça que elas existem. Você nunca vai entender o poder e expressividade de uma linguagem homoicônica até passar quarenta horas acordado depurando um heisenbug. Mas acredite quando eu digo que, daqui a vinte anos, você vai olhar para trás, para todo código que você escreveu e desejar que ele tivesse sido escrito em uma linguagem homoicônica. Seu código atual é elegante, mas nem tanto.

Não se preocupe com a quantidade de linhas que você escreve. Ou preocupe-se, mas saiba que contar linhas de código é tão efetivo quando tentar contar parênteses em Lisp. O tipo de métrica que realmente vai lhe trazer problemas é a quantidade de declarações de tipo presente em seu código–justamente o código que vai falhar em uma madrugada movida a cafeína e fazer você amaldiçoar o compilador com todas as suas forças pelo pretenso sistema de tipagem segura.

Escreva uma linha de código a cada dia que assuste outros programadores.

Comente.

Seja cuidadoso com o código das outras pessoas. Não tolere pessoas que não são cuidadosas com seu código e introduzem problemas de manutenção nas elegantes estruturas que você construiu.

Não use marcações TODO, HACK ou FIXME em seu código.

Não perca tempo em discussões sobre linguagens de programação. Algumas vezes a sua está à frente no índice TIOBE, outras vezes ela está atrás. A corrida para entrega do código é longa e, no final das contas, suas linhas são as únicas quem contam.

Lembre-se dos forks e patches que seu repositório recebeu. Esqueça os comentários sobre a qualidade do código. Se conseguir fazer isso, me diga como.

Jogue fora a documentação obsoleta. Guarde o código antigo.

Faça forks do código alheio.

Não se sinta culpado por ainda não ter aprendido Assembly. Os melhores programadores que eu conheço não aprenderam até precisarem. Alguns dos mais excepcionais programadores que eu conheço preferem não aprender.

Beba café em quantidades moderadas. Seja bondoso com suas mãos. Você vai sentir falta delas quando a LER atacar.

Talvez você escreva um compilador, talvez não. Talvez você escreva um driver para o kernel do Linux, talvez não. Talvez você programe sistemas de inteligência artifical em ML, talvez não. O que quer que você faça, lembre-se que isso é tão relevante quando decidir se você vai usar o Emacs ou o Vi.

Aproveite bem os testes que você escreveu. Use-os da melhor maneira que puder. Não tenha medo dos que os outros dizem sobre TDD ou o que as pessoas pensam sobre BDD. Sanidade no desenvolvimento é a maior ferramenta que você vai ter em toda sua carreira.

Comemore cada build bem sucedido mesmo que você esteja sozinho no datacenter e ninguém mais possa compartilhar sua alegria.

Escreva um Makefile pelo menos uma vez, mesmo que depois nunca mais você vá usar algo similar.

Não leia revistas sobre tecnologias da Microsoft. Elas somente vão deprimir você pela pura estupidez da re-implementação.

Conheça os luminares de programação. Você vai sentir falta de saber o que Alan Turing e Donald Knuth fizeram algum dia. Seja gentil com seus colegas programadores. No futuro, eles são aqueles que provavelmente vão lhe apontar para o código que você precisar no momento certo.

Entenda que linguages aparecem, se tornam populares e desaparecem com a mesma facilidade mas há algumas que você deve prezar. Trabalhe muito para reconhecer as características boas de cada linguagens que você usa porque, quanto mais tempo de programação você tiver, mais vai precisar reconhecer quando e para quê certas técnicas e linguagens servem.

Programe em C durante um tempo, mas abandone a linguagem antes que ela lhe convença que controle manual de memória é uma coisa boa. Programe em Haskell algum tempo, mas abandone a linguagem antes que ela lhe convença que mensagens de erro absurdas são parte do fluxo normal de programação. E lembre-se de aprender uma nova linguagem de quando em quando.

Aceite certas verdades inalienáveis: linguagens do mercado como Java e C# são uma porcaria, tipagem dinâmica é melhor do que tipagem estática e sua carreira de programação vai terminar um dia. E quando ela terminar, você vai fantasiar que, quando você era um pogramador hot shot, linguagens de mercado eram boas–mas só pelo dinheiro–, que tipagem estática era mais segura e que sua carreira não terminaria nunca.

Respeite aqueles cujas carreiras já terminaram porque eles contribuíram bastante para o lugar em que você está.

Não espere que ninguém lhe ensine como programar melhor. Talvez você tenha um bom mentor. Talvez você tenha acesso a bons livros e documentação. Mas você nunca sabe quando essas coisas vão desaparecer.

Tenha uma biblioteca reusável mas não coloque coisa demais nela ou, quando você precisar, vai descobrir que a maioria do código lá está obsoleto ou é horrível demais para ser usado.

Seja cuidadoso com os algoritmos de terceiros que você usa, mas seja paciente com aqueles que os criaram. Algoritmos são como bichos de estimação. As pessoas que os criaram sempre pensam que eles são confiáveis, limpos e rápidos mas a verdade é sempre diferente e eles raramente valem o bytecode que geram.

Mas confie em mim quando eu falo de linguagens dinâmicas.


Melhor desfrutado ao som de “Wear Sunscreen” do qual é uma óbvia paródia.

Programming Talk

June 29th, 2009 § 4 comments § permalink

Sabe a diferença semântica entre class << self e métodos de classe no Ruby? Já programou meta-decorators em Python? Quer implementar OMeta em sua linguagem favorita? Já programou em Nu ou Newspeak? Já está na curva descendente de uso de meta-programação em sua linguagem diária?

Se respondeu sim a alguma dessas questões ou simplesmente gosta de discutir linguagens de programação de maneira mais profunda, junte-se a nós no grupo programming-talk.

Aceitamos newbies de qualquer natureza–já que todos somos iniciantes em alguma coisa–mas não para fazer perguntas idiotas sobre como usar a feature XYZ em seu trabalho diário. Por causa disso, o grupo é invite-only.

Se tem interesse, acesse o grupo e peça um convite. Justifique sua entrada, a propósito (ou a entrada será automaticamente negada). :)

Reduzindo nomes no Git e SSH

June 4th, 2009 § 2 comments § permalink

Para quem está cansado de digitar longos nomes de repositórios no Git, o Git possui uma modo de criar alias para URLs de maneira bem simples.

Vamos usar o GitHub como exemplo, embora a técnica seja, obviamente, válida para qualquer tipo de repositório e protocolo:

O comando, que deve ser aplicado globalmente, isto é, à sua configuração geral do Git, tem a seguinte forma (substitua, no comando abaixo, rferraz pelo seu nome do usuário do GitHub):

git config --global url."git@github.com:rferraz/".insteadOf "github:"

Com o comando acima, é possível usar o seguinte comando ao clonar um repositório:

git clone github:eleusis-server.git

O comando será automaticamente expandido para:

git clone git@github.com:rferraz/eleusis-server.git

Para reduzir ainda mais, no caso de repositório cujo protocolo é SSH, pode-se editar o arquivo ~/.ssh/config para adicionar um alias para o próprio servidor em questão. No caso do Git Hub, o trecho de arquivo seria:

Host github
        Hostname github.com
        User git

Com essa configuração, o comando original de alias poderia ser reduzido para:

git config --global url."github:rferraz/".insteadOf "github:"

E o resultado é o mesmo.

Fica a dica para quem sempre está brincando com vários repositórios, especialmente quando os nomes são longos.

Joie de Vivre

June 4th, 2009 § 6 comments § permalink

Alguns anos atrás, trabalhei por alguns meses em uma empresa pequena–quatro funcionários na época–fazendo aplicações corporativas em PHP. Eu estava cansado de programar em ASP e queria espairecer um pouco em outras arenas.

Infelizmente, a alegria durou pouco. Por várias razões–falta de planejamento, dificuldade em conseguir recursos, falta de pessoal qualificado, etc–a empresa não vingou. Na época, meio do primeiro governo Lula, isso não era tão fora do comum mas faltou também uma pitada de sensatez de todo mundo para lidar com a situação. O meu tempo lá não foi de todo perdido. Rendeu boas estórias e duas amizades queridas que ainda preservo mesmo com a distância.

Por outro lado, foi a única vez em que pedi demissão em ira. Ira por planos que não chegavam a lugar nenhum. Ira por promessas não cumpridas. Ira por várias outras razões que na época pareciam bem válidas. Parti para outra, mas carregando aquele peso comigo de assunto não resolvido.

Demorou muito tempo para perceber que, na verdade, a minha ira não derivava dos problemas que eu percebia na empresa. Ao contrário, vinha de um sentimento de que faltava joie de vivre no ambiente de trabalho.

E joie de vivre –em uma tradução direta e meia-boca, alegria de viver–era algo que eu sentia mais falta no intercâmbio com meus pares de desenvolvimento do que em relação à própria empresa. Como em outras empresas que trabalhei desde então, a sensação de que os desenvolvedores ao meu lado não experimentavam isso era a parte mais terrível de qualquer situação. Eu, que sempre tive um pé no mundo livre, em projetos paralelos, conseguia derivar isso mesmo na ausência de outros fatores.

Não é de se estranhar, por exemplo, que o grande selling point do Rails tenha sido o fato de que ele devolvia ao programador a alegria de desenvolver. O Rails não se tornou um dos frameworks mais bem-sucedidos de todos os tempos por causa de suas proezas técnicas. Ruby mais Rails se tornaram um combinação imbatível em trazer joie de vivre aos desenvolvedores.

Não há nada que compre joie de vivre . É algo que só você pode conseguir e somente em certas circunstâncias. Não dá para construir nem criar, exceto por prover [condições apropriadas para que ele aconteça][1]. E como eu queria que outros pudessem experimentar isso também. Pena que quase nunca seja o caso nos ambientes que temos.

[1]: http://logbr.reflectivesurface.com/2009/04/22/a-pobreza-das-conexoes/

Versionamento Ágil com Git

May 14th, 2009 § 3 comments § permalink

Como prometido, aqui estão os slides da palestra Versionamento Ágil com Git (PDF, 5.1MB), dada por mim e pelo Tiago Jorge no Scrum Gathering Brazil. Obrigado a todos que apareceram por lá e nos fizeram dezenas de perguntas: a conversa depois foi excelente.

Essa é essencialmente a mesma palestra que faremos no Agile 2009 (com algumas modificações provavelmente, já que o processo está sempre em ajustes).

Eu fico com a baleia

April 8th, 2009 § 23 comments § permalink

Essa história toda do Twitter me impressionou particularmente por um aspecto: como pessoas que jamais tiveram experiência com sistemas de grande porte acham que são capazes de dar opiniões completamente válidas sobre os mesmos (independentemente de que linguagem usam ou preferem).

Eu não vou ofender meus leitores dizendo que tenho vasta experiência no assunto, e também não vou cometer o erro de dizer que sei alguma coisa a mais do que um bom engenheiro de software saberia. Minha experiência mais recente com o assunto–atual, por assim dizer–é acompanhar o desenvolvimento de uma aplicação que no momento serve 60 milhões de page views por mês e cujo crescimento mensal tem sido constante pelos últimos meses.

Essa aplicação é escrita inteiramente em Ruby on Rails e olhando os desafios que manter, evoluir e operar essa aplicação representam, eu já tenho toda simpatia pelos engenheiros do Twitter. Manter uma aplicação do porte do Twitter no ar, com toda a complexidade distribuída que o núcleo do mesmo precisa é algo para se aplaudir.

O que torna mais impressionante como as pessoas são rápidas para assumir que o código do Twitter é uma completa porcaria. Mesmo que fosse–e mesmo assumindo que pode ser–as críticas nesse aspecto ainda são completamente inválidas. Por mais que uma aplicação acrua débitos ténicos, o balanço entre os mesmos e o valor entre ao usuário–que é inegável no caso do Twitter–é uma decisão de negócio inquestionável.

Martin Fowler fala muito bem desse balanço um texto seu sobre débitos ténicos:

The metaphor also explains why it may be sensible to do the quick and dirty approach. Just as a business incurs some debt to take advantage of a market opportunity developers may incur technical debt to hit an important deadline. The all too common problem is that development organizations let their debt get out of control and spend most of their future development effort paying crippling interest payments.

Do meu ponto de vista, o fato de que o Twitter experimentou com outras tecnologias, fez benchmarks de plataformas, procurou melhores soluções é uma indicador claro de que estão tentando resolver seus débitos. Pedir mais do que isso é assumir uma postura de arrogância e desconhecimento, principalmente de como negócios são operados e de como código real é produzido.

Admitir publicamente problemas e tentar criar um discurso é algo que respeito; dizer coisas do tipo “As far as I’m concerned, Twitter is a case-study in how Ruby on Rails does scale, even in their hands”, por outro lado, é algo que tira qualquer possibilidade de um discurso racional. Os fanboys e pundits da comunidade Rails tem muito do que se envergonhar no seu tratamento da questão.

Programadores não operam em mundos ideais. E até que os detratores do Twitter me mostrem que fizeram o seu dever de casa em lidar com essas questões, eu fico com o Twitter e sua baleinha simpática. Proper for humans, after all.

O brouhaha sobre o Twitter e por que evangelistas puristas de linguagens/frameworks são imbecis

April 5th, 2009 § 10 comments § permalink

O cantinho de desenvolvimento de Rails na Web está quente de novo com a discussão sobre o fato de que o Twitter está reescrevendo alguns de seus sistemas de backend em Scala (1, 2, 3. 4). A coisa toda começou com a palestra que o Alex Payne, desenvolvedor responsável pela API do Twitter, deu no Web 2.0 Expo SF.

Obviamente, os evangelistas do Rails já pularam em cima da oportunidade para despejar baboseira sobre o assunto como se a questão fosse a mais importante do mundo e como se o mundo Rails estive para implodir depois das declarações bombásticas do Alex Payne.

Eu me rio.

Eu estou indo para uma década e meia de desenvolvimento esse ano, quase todos na Web. Não é muito mas foi o suficiente para por sistema em produção usando Object Pascal, C/C++, VBScript, PHP, Python, Ruby, Perl, Java, C#, Smalltalk para citar algumas das linguagens que já usei em vários e vários frameworks. Caramba, eu cheguei a desenvolver um framework Web mais um ORM em Delphi que foram usados em produção para um sistema durante quatro anos até que o projeto foi eventualmente reescrito em alguma coisa mais recente depois que eu já saíra do mesmo há muito.

O que eu aprendi nesse anos em relação a linguagens e frameworks é que todos são bons e todos são ruins e que evangelismo purista é uma das expressões mais ridículas no mundo de desenvolvimento. Qualquer desenvolvedor que se sinta compelido a defender sua linguagem/framework (e pior, os que fazem isso uma carreira como os vários Javamen, Rubymen, Scalamen, Whatever-Language-Men que existem por aí) não merecem um aceno patético de respeito.

Eu assisti a palestra do Payne na Web 2.0 Expo e o que vi foi alguém em amor com uma tecnologia nova. A palestra (como a maioria das outras técnicas no evento) foi corrida e não apresentou nada concreto. De fato, durante quase 20 minutos tudo o que Payne fez foi falar sobre as características de Scala e como ela é fantástica sem apresentar um exemplo de código que fosse. Foi o suficiente para despertar curiosidade sobre a linguagem mas não prova nada–como não deveria. No meio da apresentação, Payne fez a declaração que suscitou a ira da comunidade Rails: O Twitter está movendo vários dos seus sistemas de backend para Scala onde o Ruby não está agüentando. Por algum motivo, isso foi distorcido para dizer que Rails não escala e o resto é história.

Eu já dei minha resposta sobre a questão do Rails escalar ou não em um tom mais irônico mas as pessoas parecem gostar de perder tempo sobre isso. Evangelistas de Rails, principalmente, adoram a questão. Parece que há um fator magnético associado à afirmação. Fale isso e caem dúzias de Railers incensados do céu.

O fato de que tecnologias não escalam mas design sim parece passar inteiramente despercebido. Eu já vi sistemas limpos em Rails que não são capazes de servir pouco mais do que algumas dezenas de usuários em hardware similar ao que a empresa para qual eu trabalho usa para extrair 60 milhões de requisições por mês de um backend em puro Rails. Rails escala? Pergunta errada.

Assim, quando um Obie Fernandez diz que sua resposta evangelística ao evangelístico Payne é razoável, eu digo: Bullshit! (Só não vou dizer que é pior porque veio do homem que quer o mercado Rails todo para si porque seria maldade. Ops, que maldade…) Mais engraçado do que isso só ver nego pulando de uma conclusão para outra usando o mesmo artigo como base para evitar ficar associado com o estigma. Sigh…

Anyway, para resumir meus devaneios aqui. Se você é um programador de uma linguagem/framework só que precisa defender sua tecnologia a cada rumor, precisa seriamente repensar sua carreira. Eu, por mim, acredito em segundas chances.

Testes, primeira temporada, episódio um

February 4th, 2009 § 2 comments § permalink

No último artigo, falei um pouco sobre a preocupação arquitetural que deve permear os testes e fiquei devendo um exemplo.

Para não entrar em detalhes específicos do Rails e viciar a discussão, me lembrei de um exemplo que li há vários anos, quando estava começando a estudar XP, de um sessão entre dois programadores experientes fazendo pair-programming para chegar em um corpo de testes expressivo com base em um problema bem específico.

O exemplo é real e foi protagonizado por Robert C. Martin, um dos assinantes originais do Agile Manifesto e Robert S. Koss. Para ler o exemplo, siga para a página do mesmo.

Como é possível ver claramente pelo exemplo, a implementação secundária–representada pela classe Scorer–não possui um teste em particular. Entretanto, está fielmente testada através do seu relacionamento com o objeto mais importante. A preocupação arquitetural está no que as duas classes fazem em conjunto e não com o que cada uma delas possui como função. Note, inclusive, que classes secundárias são consideradas e descartadas sem que as mesmas sejam objeto de testes mais do que de passagem.

Obviamente, em uma arquitetura saudável, classes dominantes aparecerão e terão um foco maior em testes. O importante, então, é manter a visibilidade do domínio que está sendo testado.

No caso específico do Rails, o grande problema está em como aplicações são geralmente desenvolvidas. Por causa da separação convencionada do domínio MVC da aplicação, muitos desenvolvedores acreditam que essa é a única forma que se pode desenvolver aplicações usando o framework. O alvo, quando usando o Rails, não deve ser aplicar essa estrutura cegamente mas conseguir segregar papéis e funções de modo que o todo seja coerente.

Essa é uma visão bem geral do assunto e poderíamos nos estender em vários aspectos. Perguntas e comentários são bem-vindos.

Where Am I?

You are currently browsing the Desenvolvimento category at Superfície Reflexiva.