12.12.08
Dojo 60 – Snap em Haskell
- Data: 09/12/2008
- Participantes: Bani, Breno, Jac, Juca, Hugo, Mari, Pedro, Ricardo e Tiago
- Problema: Snap
- Código: http://github.com/dojosp/participant-s-projects/tree/master/60-Snap-Haskell
O Dojo iniciou com a votação do problema e da linguagem de programação. As linguagens sugeridas foram Haskell, Ruby e Java. A idéia de fazer o Dojo em java seria respeitando umas regras que o pessoal da Thoughtworks montou:
http://binstock.blogspot.com/2008/04/perfecting-oos-small-classes-and….
Essas 9 regras são exercícios para aperfeiçoar a escrita de boas implementações de Orientação a Objetos. São elas:
- Usar somente um nível de identação por método.
- Não usar a palavra reservada ‘else’.
- Envolver os tipos primitivos e Strings.
- Usar somene 1 ponto por linha de código.
- Não abreviar nomes.
- Manter entidades pequenas.
- Não usar mais que duas variáveis de instância na classe.
- Usar classes de coleções que sejam as mais genéricas.
- Não usar getters e setters.
Porém o problema escolhido por votação foi o Snap e a linguagem Haskell.
Problema
O problema Snap se trata de clicks (definidos como pontos) e desenhos de vértices. Sendo que se o click estiver em uma área de atração de um determinado ponto, a posição do click será a própria posição do ponto de atração. O site desse problema está detalhado aqui.
Codando
Teve uma novidade da rotação das duplas. Após os 7 minutos, entrava uma pessoa direto na programação e quem estava codando passaria a ser o “co-piloto”.
Para a resolução do problema, foram criados dois arquivos: Tests.hs (para os testes) e Snap.hs (implementação do problema). Bem ao estilo TDD, a classe de teste começou com as linhas:
Tests.hs
module Main where
import Test.HUnit
import Snap
main = runTestTT testes
testes = TestList [testeCalculaDistancias]
testeCalculaDistancias = TestList[
"Distancia entre um ponto e ele mesmo deveria ser 0" ~:
distancia (Ponto 0 0) (Ponto 0 0 ) ~?= 0
]
Esse código simplesmente significa que o teste vai assegurar que a distância entre o Ponto na posição x = 0 e y = 0, e outro Ponto na posição x = 0 e y = 0, a distância é zero (Linha 11). É claro, pois eles estão na mesma posição !
Disparando esse teste, vão ocorrer erros, pois não existe a definição de “Ponto” e “distância”. Portanto vamos implementá-los.
Snap.hs
module Snap where
data Ponto = Ponto Int Int
distancia :: Ponto -> Ponto -> Int
distancia _ _ = 0
Na linha 3 é definido o Ponto, que será representado por ‘Ponto’ seguido de dois valores numéricos (Int Int). Na linha 5 é definido a função ‘distância’ que recebe dois pontos por parâmetro. Na linha 6 diz que distância, que recebe qualquer coisa no primeiro parâmetro e qualquer coisa no segundo parâmetro, retorna 0 (zero). Agora o teste passa com sucesso!
Porém ainda não está correto dizer que a distância entre qualquer ponto é zero. Então vamos escrever mais testes.
Tests.hs
"Distancia entre o ponto 0 0 e 0 1 deveria ser 1" ~:
distancia (Ponto 0 0) (Ponto 0 1 ) ~?= 1
Incluindo essas duas linhas, agora o teste falha. A distância entre o ponto 0 0 e o ponto 0 1 é 1, porém o método ‘distancia’ sempre retorna zero. Vamos alterar a implementação desse método:
Snap.hs
distancia :: Ponto -> Ponto -> Int
distancia (Ponto 0 0) (Ponto 0 1 ) = 1
distancia (Ponto 0 0) (Ponto 0 2 ) = 2
distancia _ _ = 0
As linhas 2 e 3 foram incluídas e agora o teste passa! Porém, apesar de estarmos roubando para o teste passar, esses passos pequenos são muito importantes. São chamados de baby steps. Baby step é uma técnica de metodologias ágeis que facilita a simplicidade do código, pois só precisamos desenvolver o que é realmente necessário para passar no teste.
O ciclo do TDD é:

Portanto, agora que os testes estão passando, vamos refatorar para o código ficar menos hard coded. As linhas:
Snap.hs
distancia (Ponto 0 0) (Ponto 0 1 ) = 1
distancia (Ponto 0 0) (Ponto 0 2 ) = 2
distancia _ _ = 0
Podem agora ser substituídas por:
distancia (Ponto 0 a) (Ponto 0 b ) = b-a
Agora nossa regra está funcionando para vértices na vertical. Porém ainda não está pronto. Vamos escrever mais um teste:
Tests.hs
"Distancia entre o ponto 0 0 e 1 0 deveria ser 1" ~:
distancia (Ponto 0 0) (Ponto 1 0 ) ~?= 1
Está passando 2 testes mas esse último falha. Vamos à implementação para esse teste incluído passar:
Snap.hs
distancia :: Ponto -> Ponto -> Int
distancia (Ponto 0 a) (Ponto 0 b ) = b-a
distancia (Ponto a 0) (Ponto b 0 ) = b-a
Antes só passavam os testes para vértices na vertical. Agora está passando para vértices na horizontal também. Vamos fazer um teste e ver se passa na diagonal. Incluindo isso no teste ficaria:
Tests.hs
"Distancia entre o ponto 1 0 e 1 1 deveria ser 1" ~:
distancia (Ponto 1 0) (Ponto 1 1 ) ~?= 1
A implementação para passar nesse teste, ainda roubando, ficaria assim:
Snap.hs
distancia (Ponto 1 0) (Ponto 1 1 ) = 1
Essa implementação está hard coded, mas é importante que seja dessa maneira pois precisamos manter o foco em ficar verde no teste, e aí sim fazer a refatoração.
Ok, como os testes estão passando novamente e está tudo no verde, vamos refatorar. As linhas:
distancia (Ponto 0 a) (Ponto 0 b ) = b-a
distancia (Ponto a 0) (Ponto b 0 ) = b-a
distancia (Ponto 1 0) (Ponto 1 1 ) = 1
Porem ser substituídas pela linha:
distancia (Ponto x1 y1) (Ponto x2 y2 ) = x2-x1 + y2-y1
Tudo passando! Vamos escrever mais testes para ver se a regra está certa:
Tests.hs
"Distancia entre o ponto 0 3 e 4 0 deveria ser 5
distancia (Ponto 0 3) (Ponto 4 0 ) ~?= 5
Com esse teste o resultado falha, portanto a fórmula acima ainda não está pronta. O correto seria implementar o teorema de pitágoras para calcular a distância entre os pontos:
Snap.hs
data Ponto = Ponto Float Float
distancia :: Ponto -> Ponto -> Float
distancia (Ponto x1 y1) (Ponto x2 y2 ) = sqrt ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
Concluindo
Vamos parar por aqui. O problema ainda não está resolvido, mas mostrei esses passos para percebermos a importância do baby step e como a modelagem pode ficar melhor e com mais qualidade. Para ver o que foi codado, entre no site do Github aqui.
O TDD nos direciona para uma maior simplicidade para ser desenvolvido somente o necessário para passar no teste. Além de fornecer mais confiança para devidas refatorações.
Retrospectiva
No final teve, como de costume, a nossa retrospectiva e os pontos a melhorar foram:
• “Não precisa do b-a”, sobre um dos baby-steps que poderia ser desnecessário
• “Nano baby step => seguro mas muito lento”. Sobre passos muito pequenos.
• Alguns passos grandes/rápidos em alguns momentos.
• Faltaram mais explicações sobre o que é baby-step durante o código
• “Faltou tão pouco” para terminar o problema. Apesar que esse não é o objetivo final do Dojo.+
• Teve gente que não gostou desse esquema de rotação invertido.
• Projetor estava ruim.
• Baby-step? Não “grown-up wannabe steps”.
• Perdemos muito tempo criando templates do projeto.
• Emacs é melhor que Gedit para codar Haskell
Os pontos positivos foram:
• Tipos de dados no Haskell
• Haskell
• Pessoas gostaram no rodízio invertido.
• Problema legal, simples e bom para TDD
• Baby steps
• ” ˜ é sempre antes.” Sobre a notação no Haskell.
Assuntos em Parking lote foram
• TODOs ajudam ou atrapalham?
• Geradores do ruby são ótimos para gerar templates
André Faria Gomes said,
13.12.08 at 2:19 am
Muito bom! Estou me organizando para voltar ao Dojo no ano que vem.
Abraço a todos.
Mudando o paradigma com Haskell « Manifesto na Web! said,
14.12.08 at 9:08 pm
[...] estive no DOJO SP e comentei sobre um problema que resolvemos com Haskell nesse blog. Interessante que o problema foi muito bom para entender e aplicar [...]