13.12.2008
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