segunda-feira, 25 de julho de 2011

Pequenas coisas que incomodam em Ruby

Ruby é uma linguagem maravilhosa, mas tem alguns poréns. O que eu vou citar é algo bem pequeno mas a linguagem está repleta de coisas do tipo.

> class Foo
>   def bar; @bar; end
>   def bar=(other)
>     puts "bar= has been called"
>     @bar = other
>   end
> end

Basicamente escrevemos um setter para um variável.

> foo = Foo.new
> foo.bar = 1
bar= has been called
=> 1 

Ok, exatamente como esperávamos.

> foo.bar = foo.bar || 2
bar= has been called
=> 1 

Ok, sem surpresas ainda.

> foo.bar ||= 2
=> 1 

Ops, notem que bar= não foi chamado.

Portanto, ao contrário do que muitos pensam foo.bar ||= 2 é diferente de foo.bar = foo.bar || 2.

foo.bar ||= 2 só realiza a atribuição se bar não avaliar para uma expressão verdadeira.

É algo muito bobo e muito sútil, mas se o programador realizar alguma computação na atribuição, essa computação pode não ser executada em alguns casos.

Isso não é um grande problema, porém existem diversas coisas do tipo na linguagem. O Matz diz que a linguagem deve ter o comportamento que o programador espera, isso é bom porque a linguagem torna-se natural, mas e casos como esse, o que o programador espera?

Outra coisa decorrente é que isto dificulta outras implementações Ruby 100% compatíveis.

quinta-feira, 27 de janeiro de 2011

Um novo Object#tap

Implementação atual de tap:
class Object
  def tap
    yield(self)
    self
  end
end
Mas o Daniel aqui discorda. Minha proposta é:
class Object
  def tap
    yield(self)
  end

  def tee
    yield(self)
    self
  end
end
Object#tap
altera a method chain
Object#tee
não altera a method chain (atual Object#tap)
Para Enumerable, nós temos Enumerable#collect, que funciona exatamente igual ao meu tap.
[1,2,3].collect {|i| i + i} => [2,4,6]
Porém não temos isso para objetos:
1.tap {|i| i + i } => 2
Object#tap executa um bloco na method chain sem altera-la.
[1,2,3].collect {|i| i * i }.tap {|a| puts a.size }.inject {|sum, i| sum + i } => 14 (escreve 3 na STDOUT antes do retorno)
Porém se você quer justamente passar um bloco apenas para alterar a method chain? Não deveria ser justamente essa a função de Object#tap?
[1,2,3].collect {|i| i * i }.tap {|a| a + a }.inject {|sum, i| sum + i } => 28
A origem do atual Object#tap vem do comando tee. Portanto, nada mais justo que:
class Object
  alias tee tap
  def tap
    yield(self)
  end
end
É preciosismo e a discussão é meramente teórica, já que podemos implementar este novo Object#tap com outro nome e evitar o alias, o que exigiria um refactoring de todo código que utiliza Object#tap. Possíveis nomes para meu Object#tap: get, come, become, go e grow. Viajei muito?

quinta-feira, 18 de fevereiro de 2010

Use branches no Git!

Apesar da ampla adoção do Git, ainda são poucas as equipes que utilizam com eficácia os branchs. Acredito que a culpa disso seja em grande parte pelo baixo uso de branches no SVN ou CVS. Talvez isso tenha até contribuido para criar um certo medo de branches.

Porém, existe ínumeras situações do dia a dia em que o uso de branches irá poupar muito trabalho, viabilizar experimentos ou organizar o desenvolvimento.

É fundamental compreender o uso de branches para que você possa compreender a sua correta utilização.

A diretriz que eu tenho seguido é simples: criar um branch novo para qualquer linha de desenvolvimento. Caso seja necessário o desenvolvimento em conjunto com algum outro desenvolvedor, faça um branch remoto. Fazer pequenos commits irá te ajudar na hora de selecionar um commit defeituoso e a procurar erros com git bisect.

Realize merges entre as linhas de desenvolvimento o máximo de vezes que for permitido. Quanto maior a divergência entre as linhas de desenvolvimento, maior será o número de conflitos e mais díficil será o merge. O momento do merge é um gargalo, pois os conflitos tem de serem resolvidos localmente, ou seja, apenas um desenvolvedor pode resolver os conflitos entre dois merges.

Desconheço qualquer forma de resolver conflitos que não seja local e isso me parece uma limitação grave, se alguém souber de algo ficarei muito contente se me explicar.

Nos primeiros dias que segui essa abordagem, apanhei um pouco do Git (um pouco mais do que o habitual), mas foram os momentos em que mais aprendi sobre o seu funcionamento. Comece nesse exato momento a utilizar branches, mesmo que o seu projeto esteja atrasado 28 dias, 6 horas, 42 minutos e 12 segundos *.

Apenas para o não ficar incompleto, seguem as receitinhas de bolo mais simples para criação e remoção de branches remotos.

Criando um branch remoto

$ git push repos src:dst
repos
Repositório
src
Branch de origem no repositório
dst
Branch de destino no repositório

Deletando um branch remoto

$ git push repos :dst

* Este é o tempo que falta para o mundo acabar quando Donnie Darko encontra Frank pela primeira vez.