July 24th, 2006Agile 2006 – Dia 2

Segundo dia de conferência, e mais 3 sessões interessantes. Não é todo dia que você participa de uma palestra
com Jeff Sutherland, Ward Cunningham, Ron Jeffries e Michael Feathers.

Beyond the Manifesto: Readings for Agile Developers – Peter Coffee

Caminhamos até o hotel da conferência mais cedo para dar tempo de tomar café antes do Keynote com Peter Coffee.
A sessão estava cheia e ele basicamente recomendou 4 livros relacionados aos 4 valores do
Manifesto Ágil:

Após o Keynote, as sessões começaram pra valer: a programação estava cheia
de opções sobre os mais variados temas relacionados aos métodos ágeis. Decidi então começar o dia com o pé
direito.

Intro to the Agile Manifesto – Jeff Sutherland, Ward Cunnigham, Ron Jeffries e Michael Feathers

Apesar de ser uma sessão de para iniciantes (“Begginer's Track”),
eu não pude resistir à tentação de assistir uma palestra com 4 nomes tão conhecidos:
Jeff Sutherland, um dos inventores do Scrum,
Ward Cunningham, invertor do Wiki,
Ron Jeffries, um dos principais ativistas de XP e
Michael Feathers, o autor do livro
“Working Effectively with Legacy Code”
(que eu já li e estou devendo um review aqui).

Cada um ficou responsável por falar sobre um dos itens do manifesto ágil. O primeiro a falar foi Jeff Sutherland,
sobre:

“Indivíduos e interações são mais importantes que processos e ferramentas”

Sob o ponto de vista do Scrum, Jeff mostrou dados muito interessantes a respeito do poder do trabalho em equipe. A diferença
de produtividade entre um programador brilhante e um programador normal pode chegar a 25:1. Porém, quando você leva em
consideração a produtividade de um time inteiro, a diferença pode chegar a 2098:1!! Estou para começar a ler o livro de
Scrum e entendi a importância do time ser responsável pela conclusão das tarefas, ao invés das tarefas serem atribuídas
por alguém (gerente de projeto ou cliente). O próximo foi Ward Cunningham, falando sobre:

“Software funcionando é mais importante que documentação completa e detalhada”

Esse foi provavelmente o melhor tópico. Ward conseguiu expressar em poucas palavras a importância de termos uma suite
de testes: armazenar conhecimento sobre o código. Quando estamos desenvolvendo, o código inevitavelmente vai
guardar um histórico de decisões que tomamos no meio do caminho e que com certeza iremos esquecer depois de um tempo.
Testes de unidade “falam” numa linguagem voltada para os programadores sobre as decisões tomadas durante a vida de
um sistema, enquanto os testes de aceitação falam numa linguagem que o cliente entende. Não posso deixar de ressaltar
uma frase que ele disse no meio da palestra (o homem é uma máquina de frases de impacto): “Refatorar é a
álgebra do seu código”
: você pega uma equação complexa e vai reduzindo até transformá-la em algo que
consiga entender. O próximo a falar foi Ron Jeffries, sobre:

“Colaboração com o cliente é mais importante do que negociação de contratos”

A negociação de contratos parte de uma premissa errada, de que não há confiança entre a equipe de desenvolvimento e o
cliente. Quando você diz que fará apenas o que o cliente assinou, ele fica com medo de perder funcionalidade e
demora para chegar a um acordo sobre o que deve ou não entrar no contrato. E o fato é: ninguém sabe o que irá acontecer.
Tentar escrever um contrato sobre algo que será construído depois de 1 ano é tentar prever o futuro e sabemos que os
seres humanos não são bons em previsões de longo prazo (muito menos quando o assunto é o escopo de um sistema
complexo). Por último, Michael Feathers falou sobre:

“Adaptação a mudanças é mais importante que seguir um plano inicial”

O ato de planejar é a única parte importante ao fazer um plano. Quantas vezes você já não fez planos sobre as coisas
que faria no próximo mês ou ano e não conseguiu cumprir? Michael Feathers falou muito bem sobre a forma com a qual
geralmente criamos nossos planos: nós tentamos impor condições sobre o futuro que não serão necessariamente
verdadeiras na hora de executá-lo. Um exemplo interessante que ele mostrou é sobre o planejamento urbano em
algumas cidades européias: Quando alguém define que devemos olhar para o farol para atravessar a rua e que o
carro deve parar antes da faixa para não atropelar os pedestres, está definitivamente fazendo um plano para evitar
acidentes de trânsito. Porém, o fato de colocar esses elementos como regra, faz com que desviemos nossa atenção
para o farol e para a faixa ao invés de olharmos para o que realmente importa. Na Europa, algumas cidades estão
construindo ruas onde não há distinção entre calçada e asfalto e com pouca sinalização, para fazer com que os
motoristas e pedestres estejam sempre atentos. O que ele quis mostrar com esse exemplo é que apesar da importância
do planejamento, é mais importante você focar nas coisas que estão acontecendo e se adaptar conforme conhece
mais o ambiente.

The Lego XP Game – Sam Newman, Dan North e Mike Hill

Essa foi definitivamente a sessão mais divertida até o momento. Sam e Dan são desenvolvedores da
ThoughtWorks e mostraram o jogo que eles usam para ensinar as principais
práticas de XP num curto período de tempo. Um jogo onde cada time recebe especificações sobre um animal e devem
construí-lo, iterativamente, usando peças de Lego! Genial! Meu time acabou perdendo desenvolvedores no break e tive
que mudar de equipe no meio do caminho, então acabei ficando com duas versões:

First Team

Second Team

É interessante como foi possível mostrar partes do Jogo do Planejamento,
do desenvolvimento iterativo-incremental e da importância de medir a velocidade do time em cada iteração, usando apenas blocos de Lego!

Post to Twitter

Após 12 horas totais de vôo e muita fila para fazer o check-in, tanto no Brasil quanto em NY, cheguei ontem em
Minneapolis, MN para participar da Agile 2006 como estudante voluntário.
Essa será a segunda edição da conferência, que é o resultado da junção de 2 outras, Agile Development Conference (ADC) e
XP/Agile Universe.

Dia 0

Apesar de um pouco cansado da viagem, o plano funcionou conforme o previsto. Eu havia combinado de ficar num albergue
perto do Hotel da conferência (uma caminhada saudável de
20 minutos) e dividir o quarto com um outro voluntário que conheci por e-mail:
Sergei Golitsinski. Ele é russo, mora nos EUA há 7 anos e, para minha sorte,
numa cidade relativamente perto de Minneapolis. Como estaria de carro, ele se ofereceu para me buscar direto no aeroporto.
Fomos direto para o albergue e, depois de se perder um pouco nas ruas da cidade, chegamos ao
Minneapolis International Hostel. Para minha surpresa, o quarto que eu
havia reservado era melhor que eu esperava, com 2 camas, banheiro privativo (não é normal em albergues) e até
internet wireless de graça no quarto! Muito legal! Só não tem TV e outras regalias de um Hotel normal, mas é bom
o suficiente para ficar durante esses dias de conferência.

Saímos de carro para conhecer a vizinhança e comer alguma coisa (a fila do check-in em NY demorou tanto que nem deu
tempo de almoçar na conexão), indo logo depois para o hotel da conferência ajudar os outros voluntários a preparar
as malas dos participantes e dobrar camisetas. Depois de trabalhar na linha de produção das malas, voltamos para
o albergue para tomar um banho. A noite fui assistir um filme,
“Lady in the Water”, de M. Night Shyamalan, o mesmo diretor de
“Sexto Sentido” e “Sinais”, mas estava com tanto sono que acabei perdendo umas partes do filme e acabei não
gostando :-) Na verdade a história é exatamente um daqueles contos de fada que contamos para crianças e,
se você está esperando algo brilhante ou lógico, será surpreendido por algo extremamente simples e direto. Não
vou falar muito pois vou acabar estragando a alegria daqueles que querem assistir o filme. Nem preciso dizer
que depois do filme voltei pro albergue e capotei.

Dia 1

Hoje de manhã viemos para a primeira reunião geral com todos os voluntários para entender o que devemos fazer para
ajudar na mesa de registro e nas palestras e sessões da conferência. A única coisa que me deixou um pouco chateado
é que não ganhamos a bolsa da conferência com informações como: o programa detalhado (com horários), preceedings
e outras informações. Não tanto pelas coisas que vêem dentro da bolsa ou pela bolsa em si, mas sim pelo fato de
dizerem que os voluntários só irão ganhar após ter certeza que todos os pagantes tenham recebido suas bolsas. No
total não devem ser mais que 20 voluntários que se registraram para ajudar. Enfim… não é nada que irá piorar
meu aproveitamento da conferência então vou deixar pra lá e falar sobre a parte importante: as sessões.

Durante a parte da tarde participei de 2 sessões: uma “Discovery Session”
com um pessoal da ThoughtWorks muito interessante, onde fizemos uma coleta de
dados sobre o estado atual das metodologias ágeis de desenvolvimento de software. Primeiro fomos para a parte externa
do prédio do hotel e formamos vários “gráficos humanos”, onde cada pessoa representa um ponto no gráfico (por exemplo:
“Quão feliz você está com seu trabalho atual” X “Há quanto tempo usa métodos ágeis”). Depois voltamos para a sala e
nos dividimos em grupos, fazendo uma retrospectiva sobre os seguintes tópicos:

  • As 3 coisas que mais me atraíram nos Métodos Ágeis
  • As 3 coisas que eu mais gosto de fazer quando uso Métodos Ágeis
  • As 3 principais decepções que tive com Métodos Ágeis
  • As 3 coisas que mais me surpreenderam sobre Métodos Ágeis

Os resultados serão publicados no wiki da conferência mais tarde e estarão
disponíveis para quem quiser. Por enquanto mando uma foto parcial do mural com alguns dos post-its:

Discovery Session Sticker Wall

A outra sessão que assisti foi do Scott Ambler sobre dois questionários online que
ele publicou no site Dr. Dobb's para tentar captar o ponto de vista da comunidade em
relação ao estado atual das práticas e princípios dos métodos ágeis. Uma boa discussão sobre as perguntas da pesquisa
e sobre os dados levantados. Quem estiver interessado pode tirar suas próprias conclusões a partir dos
dados obtidos.

Para fechar o dia, tivemos um “Ice Breaker” muito legal no maior saguão do hotel, um evento com a finalidade de
integrar todos os participantes da conferência e entretê-los. Tinha comida a vontade (muita comida), bebidas, doces,
um trio de jazz, um mágico, pessoas fazendo chapéu de balão e até um cartunista, que me desenhou assim:

Caricature at Agile 2006

Por enquanto estou aproveitando bastante a conferência e a experiência está sendo fantástica. Não sei se conseguirei
escrever todos os dias sobre as novidades, mas vou tentar fazer um resumo a cada dois dias. Amanhã promete ser mais
um dia de muitas palestras, troca de conhecimento e aprendizado.

Post to Twitter

É hora de codificar um pouco: mostraremos o passo-a-passo de como programar no ritmo de TDD, desenvolvendo as regras de ataque do jogo Combate. Vamos identificar também a preocupação constante com o Design Simples, e como ele se manifesta quando desenvolvemos os testes antes do código.

Pensando no Design

No último post, conhecemos as peças que compõem o jogo e as respectivas regras de ataque/defesa. Como a idéia de hoje é implementarmos essas regras de ataque, precisamos pensar um pouco sobre como modelar nossas classes (lembrem-se, é permitido pensar sobre o design antes de começar a implementação. O que XP tenta evitar é o chamado BDUFBig Design Up-Front – onde o design do sistema inteiro é definido e escrito num documento, com diversas páginas e diagramas de classe que será lido por outra pessoa na hora da implementação). O Design Incremental de XP faz com que você se preocupe somente com a mínima quantidade de design necessária para atender suas necessidades atuais. No nosso caso, precisamos apenas pensar em onde implementar as regras de ataque.

Um ataque pode ter 3 desfechos diferentes: a peça que atacou vence e ocupa o lugar da peça de defesa (que é removida do tabuleiro); a peça que atacou perde e é removida do tabuleiro ou ambas as peças são removidas do tabuleiro no caso de um empate. Minha primeira idéia era definir um método de ataque que retornasse a peça perdedora, porém no caso do empate, eu precisaria retornar as duas peças, exigindo uma assinatura que devolvesse uma lista de peças ou um par de peças, o que não achei uma boa idéia. Tive então outra idéia para implementação: fazer com que o método de ataque avisasse cada peça perdedora, numa espécie de método callback sem retorno. Porém, como um objeto Peça faria para se remover do tabuleiro ao descobrir que perdeu? Ele precisaria conhecer o Tabuleiro ou avisar o Tabuleiro, criando dependência bi-direcional entre as classes, o que também não parece ser uma boa idéia (nem estamos pensando no design do Tabuleiro ainda, só queremos implementar as regras de ataque).

A solução sugerida, depois de algumas discussões com o RBP, foi implementar um método de comparação (no estilo de um cmp() em Python ou compareTo() em Java) que devolve 0 se houve empate, um número positivo se a peça que está atacando venceu ou um número negativo se a peça atacante perdeu. Só faltava escolher onde esse método seria definido. Minha primeira idéia foi colocá-lo numa classe Stratego que representa o jogo, porém depois de alguns minutos de codificação, percebi que o melhor lugar para definir esse método seria na própria peça. Uma peça sabe como se portar quando ataca um inimigo. A última decisão de design foi definir um rank numérico para cada peça, para representar a hierarquia utilizada durante os ataques. Com a tabela seguinte em mãos, pudemos recomeçar a implementação, no ritmo de TDD:

Rank Peça Rank Peça Rank Peça
0 Bandeira 4 Sargento 8 Coronel
1 Espião 5 Tenente 9 General
2 Soldado 6 Capitão 10 Marechal
3 Cabo-Armeiro 7 Major 11 Bomba

Criando as peças

O primeiro passo é sempre escrever um teste que falha. Como queremos criar as peças acima, defini um conjunto de testes que verifica se as peças estão sendo criadas com o rank esperado, num arquivo chamado StrategoTest.py (que armazenará os testes):

# StrategoTest.py
import unittest
import unittestgui
from Stratego import *
 
class CreatePieceTest(unittest.TestCase):
    def testCreateSoldier(self):
        assert Piece("soldier").rank == 2
 
suite = unittest.makeSuite(CreatePieceTest)
 
if __name__ == "__main__":
    unittestgui.main("StrategoTest.suite")

Esse código não compila e não consegue ser executado. Percebam como o teste pede pela codificação: definindo um construtor para peça que recebe uma string, definindo uma propriedade rank para a peça e definindo o módulo onde a classe será definida. Com isso, precisamos do seguinte trecho de código para termos nossa primeira barra verde (definido no arquivo Stratego.py):

# Stratego.py
 
class Piece:
    def __init__(self, name):
        self.rank = 2

Essa é a implementação mais simples que faz o nosso teste passar. Percebam como o TDD nos auxilia a evitar generalizações precoces no código. Mesmo sabendo que esse código não atende a todas as necessidades, precisamos nos disciplinar e focar no mínimo necessário. O terceiro passo seria a refatoração, mas ainda não temos nenhum “mal-cheiro” no código, então podemos partir para o próximo teste:

# StrategoTest.py
 
class CreatePieceTest(unittest.TestCase):
    def testCreateSoldier(self):
        assert Piece("soldier").rank == 2
    def testCreateMiner(self):        assert Piece("miner").rank == 3

Ao executar a suite de testes, nos deparamos mais uma vez com uma barra vermelha. É hora de implementarmos um pouco mais para torná-la verde:

# Stratego.py
class Piece:
    def __init__(self, name):
        if name == "soldier":
            self.rank = 2
        else:
            self.rank = 3

Alguns podem dizer que o código acima já está começando a cheirar mal, porém ainda não vejo tanta repetição, então resolvo escrever o próximo teste que falha:

# StrategoTest.py
 
class CreatePieceTest(unittest.TestCase):
    def testCreateSoldier(self):
        assert Piece("soldier").rank == 2
    def testCreateMiner(self):
        assert Piece("miner").rank == 3
    def testCreateSergeant(self):        assert Piece("sergeant").rank == 4

Para fazer com que os testes fiquem verdes novamente, preciso codificar mais um pouco:

# Stratego.py
 
class Piece:
    def __init__(self, name):
        if name == "soldier":
            self.rank = 2
        elif name == "miner":
            self.rank = 3
        else:
            self.rank = 4

Agora estou numa barra verde, porém o código não parece muito bonito. Fica evidente que a cada novo teste, uma nova ramificação elif precisará ser adicionada ao código do construtor. É hora de refatorar (a regra é: “sempre refatore no verde”), deixar o código mais limpo e simples sem alterar seu comportamento, ou seja, sem quebrar nenhum dos testes existentes:

# Stratego.py
 
class Piece:
    __pieces = {"soldier":2, "miner":3, "sergeant":4}
    def __init__(self, name):
        self.rank = self.__pieces[name]

Definindo um dicionário com as possíveis peças, o código do construtor fica mais simples, pois se transforma numa simples consulta ao dicionário. Seguindo o mesmo ritmo para construir as outras peças e definindo uma exceção para a tentativa de criação de uma peça inválida, o Desenvolvimento Orientado a Testes nos leva ao seguinte código final:

# StrategoTest.py
 
import unittest
import unittestgui
from Stratego import *
class CreatePieceTest(unittest.TestCase):
    def testCreateSoldier(self):
        assert Piece("soldier").rank == 2
    def testCreateMiner(self):
        assert Piece("miner").rank == 3
    def testCreateSergeant(self):
        assert Piece("sergeant").rank == 4
    def testCreateLieutenant(self):
        assert Piece("lieutenant").rank == 5
    def testCreateCaptain(self):
        assert Piece("captain").rank == 6
    def testCreateMajor(self):
        assert Piece("major").rank == 7
    def testCreateColonel(self):
        assert Piece("colonel").rank == 8
    def testCreateGeneral(self):
        assert Piece("general").rank == 9
    def testCreateMarshal(self):
        assert Piece("marshal").rank == 10
    def testCreateSpy(self):
        assert Piece("spy").rank == 1
    def testCreateBomb(self):
        assert Piece("bomb").rank == 11
    def testCreateFlag(self):
        assert Piece("flag").rank == 0
    def testCreateInvalidPiece(self):
        try:
            Piece("invalid")
        except InvalidPiece:
            pass
        else:
            fail("expected InvalidPiece exception")
 
suite = unittest.makeSuite(CreatePieceTest)
 
if __name__ == "__main__":
    unittestgui.main("StrategoTest.suite")
 
# Stratego.py
 
class Piece:
    __pieces = {"flag":0, "spy":1, "soldier":2, "miner":3, "sergeant":4,
                "lieutenant":5, "captain":6, "major":7, "colonel":8,
                "general":9, "marshal":10, "bomb":11}
    def __init__(self, name):
        try:
            self.rank = self.__pieces[name]
        except KeyError:
            raise InvalidPiece()
 
class InvalidPiece(Exception):
    pass

Implementando o método de ataque

Agora que temos as peças criadas, podemos implementar as regras de ataque. Apenas para relembrar, as peças com maior rank vencem as de menor rank. Algumas exceções à regra são:
 

  • O espião vence o marechal quando o ataca
  • As bombas e a bandeira não podem atacar
  • Qualquer peça que atacar a bomba perde, exceto o cabo-armeiro

Para não perder o costume, começamos com um teste que falha:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0

Rapidamente, fazemos o teste passar:

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):        return 1    # (...) Exceptions

Novamente, apesar desse código não ser o que desejamos, é o suficiente para fazer o teste passar e para permitir que possamos implementar o próximo teste:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):        assert Piece("miner").attack(Piece("colonel")) < 0

Mais uma vez, implementamos o mais simples para fazer o teste passar:

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):        return self.rank - defender.rank    # (...) Exceptions

O teste para verificar uma situação de empate já funciona sem nenhuma mudança na implementação, assim como o teste para confirmar que o marechal vence o espião quando o ataca. Isso pode acontecer às vezes, permitindo que passemos para o próximo teste que deixará a barra de testes vermelha. Isso acontece com o teste que verifica se o espião vence um ataque contra o marechal:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):
        assert Piece("miner").attack(Piece("colonel")) < 0
    def testTie(self):        assert Piece("major").attack(Piece("major")) == 0    def testMarshalWinsSpy(self):        assert Piece("marshal").attack(Piece("spy")) > 0    def testSpyWinsMarshal(self):        assert Piece("spy").attack(Piece("marshal")) > 0

Uma simples validação do caso excepcional, faz com que os testes funcionem novamente:

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):        if self.rank == 1 and defender.rank == 10:            return 1        else:            return self.rank - defender.rank    # (...) Exceptions

O próximo caso excepcional acontece quando o cabo-armeiro ataca uma bomba:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):
        assert Piece("miner").attack(Piece("colonel")) < 0
    def testTie(self):
        assert Piece("major").attack(Piece("major")) == 0
    def testSpyWinsMarshal(self):
        assert Piece("spy").attack(Piece("marshal")) > 0
    def testMarshalWinsSpy(self):
        assert Piece("marshal").attack(Piece("spy")) > 0
    def testMinerWinsBomb(self):        assert Piece("miner").attack(Piece("bomb")) > 0

O necessário para fazê-lo passar é adicionar mais uma validação para o novo caso excepcional:

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):        if (self.rank == 1 and defender.rank == 10) or (self.rank == 3 and defender.rank == 11):            return 1        else:            return self.rank - defender.rank    # (...) Exceptions

Com a barra verde, podemos refatorar o código. Nesse caso, podemos eliminar a duplicação na verificação dos casos excepcionais criando uma lista dos pares que vencem quando atacam (apesar de possuírem um rank menor). O código refatorado fica assim:

# Stratego.py
 
class Piece:
    __pieces = {"flag":0, "spy":1, "soldier":2, "miner":3, "sergeant":4,
                "lieutenant":5, "captain":6, "major":7, "colonel":8,
                "general":9, "marshal":10, "bomb":11}
    __winsAttacking = [(1,10), (3,11)]    # (...) Constructor
    def attack(self, defender):
        if (self.rank,defender.rank) in self.__winsAttacking:            return 1
        else:
            return self.rank - defender.rank
    # (...) Exceptions

Para terminarmos nossa implementação, só falta lidar os casos de ataque inválido. Primeiro vamos escrever um teste para garantir que uma bomba não pode ter iniciativa de ataque:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):
        assert Piece("miner").attack(Piece("colonel")) < 0
    def testTie(self):
        assert Piece("major").attack(Piece("major")) == 0
    def testSpyWinsMarshal(self):
        assert Piece("spy").attack(Piece("marshal")) > 0
    def testMarshalWinsSpy(self):
        assert Piece("marshal").attack(Piece("spy")) > 0
    def testMinerWinsBomb(self):
        assert Piece("miner").attack(Piece("bomb")) > 0
    def testBombCannotAtack(self):        try:            Piece("bomb").attack(Piece("miner"))        except InvalidAttack:            pass        else:            fail("expected InvalidAttack exception")

E o código para fazer o teste passar é uma nova validação no método attack():

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):
        if self.rank == 11:            raise InvalidAttack()        if (self.rank,defender.rank) in self.__winsAttacking:
            return 1
        else:
            return self.rank - defender.rank
    # (...) Exceptions
 
class InvalidAttack(Exception):
    pass

O último teste é para evitar que uma bandeira tenha iniciativa de ataque:

# StrategoTest.py
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):
        assert Piece("miner").attack(Piece("colonel")) < 0
    def testTie(self):
        assert Piece("major").attack(Piece("major")) == 0
    def testSpyWinsMarshal(self):
        assert Piece("spy").attack(Piece("marshal")) > 0
    def testMarshalWinsSpy(self):
        assert Piece("marshal").attack(Piece("spy")) > 0
    def testMinerWinsBomb(self):
        assert Piece("miner").attack(Piece("bomb")) > 0
    def testBombCannotAtack(self):
        try:
            Piece("bomb").attack(Piece("miner"))
        except InvalidAttack:
            pass
        else:
            fail("expected InvalidAttack exception")
    def testFlagCannotAtack(self):        try:            Piece("flag").attack(Piece("lieutenant"))        except InvalidAttack:            pass        else:            fail("expected InvalidAttack exception")

Podemos adicionar uma nova validação para evitar o ataque de uma bandeira:

# Stratego.py
 
class Piece:
    # (...) Constructor
    def attack(self, defender):
        if self.rank == 11 or self.rank == 0:            raise InvalidAttack()        if (self.rank,defender.rank) in self.__winsAttacking:
            return 1
        else:
            return self.rank - defender.rank
    # (...) Exceptions

Para finalizar, podemos aproveitar a barra verde para remover uma última duplicação, na validação das peças que não podem atacar. Após a refatoração, o código completo e final fica assim:

# StrategoTest.py
 
import unittest
import unittestgui
from Stratego import *
class CreatePieceTest(unittest.TestCase):
    def testCreateSoldier(self):
        assert Piece("soldier").rank == 2
    def testCreateMiner(self):
        assert Piece("miner").rank == 3
    def testCreateSergeant(self):
        assert Piece("sergeant").rank == 4
    def testCreateLieutenant(self):
        assert Piece("lieutenant").rank == 5
    def testCreateCaptain(self):
        assert Piece("captain").rank == 6
    def testCreateMajor(self):
        assert Piece("major").rank == 7
    def testCreateColonel(self):
        assert Piece("colonel").rank == 8
    def testCreateGeneral(self):
        assert Piece("general").rank == 9
    def testCreateMarshal(self):
        assert Piece("marshal").rank == 10
    def testCreateSpy(self):
        assert Piece("spy").rank == 1
    def testCreateBomb(self):
        assert Piece("bomb").rank == 11
    def testCreateFlag(self):
        assert Piece("flag").rank == 0
    def testCreateInvalidPiece(self):
        try:
            Piece("invalid")
        except InvalidPiece:
            pass
        else:
            fail("expected InvalidPiece exception")
 
class AttackTest(unittest.TestCase):
    def testHigherRankWins(self):
        assert Piece("sergeant").attack(Piece("soldier")) > 0
    def testLowerRankLoses(self):
        assert Piece("miner").attack(Piece("colonel")) < 0
    def testTie(self):
        assert Piece("major").attack(Piece("major")) == 0
    def testSpyWinsMarshal(self):
        assert Piece("spy").attack(Piece("marshal")) > 0
    def testMarshalWinsSpy(self):
        assert Piece("marshal").attack(Piece("spy")) > 0
    def testMinerWinsBomb(self):
        assert Piece("miner").attack(Piece("bomb")) > 0
    def testBombCannotAtack(self):
        try:
            Piece("bomb").attack(Piece("miner"))
        except InvalidAttack:
            pass
        else:
            fail("expected InvalidAttack exception")
    def testFlagCannotAtack(self):
        try:
            Piece("flag").attack(Piece("lieutenant"))
        except InvalidAttack:
            pass
        else:
            fail("expected InvalidAttack exception")
 
suite = unittest.TestSuite((unittest.makeSuite(CreatePieceTest),
                            unittest.makeSuite(AttackTest)))
 
if __name__ == "__main__":
    unittestgui.main("StrategoTest.suite")
 
# Stratego.py
 
class Piece:
    __pieces = {"flag":0, "spy":1, "soldier":2, "miner":3, "sergeant":4,
                "lieutenant":5, "captain":6, "major":7, "colonel":8,
                "general":9, "marshal":10, "bomb":11}
    __winsAttacking = [(1,10), (3,11)]
    __cannotAttack = [0,11] 
    def __init__(self, name):
        try:
            self.rank = self.__pieces[name]
        except KeyError:
            raise InvalidPiece()
 
    def attack(self, defender):
        if self.rank in self.__cannotAttack:            raise InvalidAttack()        if (self.rank,defender.rank) in self.__winsAttacking:
            return 1
        else:
            return self.rank - defender.rank
 
class InvalidPiece(Exception):
    pass
 
class InvalidAttack(Exception):
    pass

Conclusões e próximos passos

Terminamos o segundo post com 70 linhas de código de teste e 26 linhas de código de produção. Como vocês puderam notar, o ritmo imposto pelo TDD é baseado em passos pequenos e resultou num código simples e comunicativo. Por enquanto, temos apenas uma classe para representar uma peça do jogo. No próximo post implementaremos as regras de movimentação no tabuleiro, não percam!

Atualização 03-Out-06: Conforme sugestões, estou disponibilizando para download o código fonte final dos testes e das classes de produção para os interessados não precisarem copiar/colar/formatar tudo novamente.

Post to Twitter

Você já ouviu falar em Desenvolvimento Orientado a Testes (ou TDD)? Já escreveu testes antes do código? Esta é a primera parte de uma série onde aplicaremos as idéias de TDD num exemplo prático, desenvolvendo um clássico jogo de tabuleiro que não depende da sorte, mas sim da estratégia: Combate.



Introdução

Conforme eu adiantei no último post, começaremos hoje um exemplo de aplicação de TDD num exemplo prático. Essa idéia começou quando um colega de trabalho, Rodrigo (mais conhecido como RBP ou simplesmente R), sugeriu fazermos uma rápida sessão de Programação Pareada, para que eu mostrasse na prática como programar os testes antes do código. Desde então, discutimos alguns problemas simples, porém interessantes (e por que não divertidos?), que poderíamos utilizar no nosso exemplo, chegando ao clássico jogo de tabuleiro Combate.



Seguindo o exemplo de Ron Jeffries de ensinar TDD através de exemplos (desenvolvendo um programa para calcular o total de pontos em uma partida de boliche), começaremos hoje falando sobre os princípios de TDD e sobre as regras do Combate. Ainda na linha experimental e educativa dessa série de posts, vou aproveitar os conhecimentos do RBP e aprenderei um pouco de Python. Ainda não sabemos quantos posts serão necessários e nem se estamos sendo ambiciosos demais, porém nossa idéia é mostrar o crescimento incremental da aplicação, podendo até se transformar em algo “jogável” (com interface gráfica e funcionando em rede).



Regras do jogo: TDD – Test Driven Development

O Desenvolvimento Orientado a Testes é uma técnica para programadores (e não para testadores), cujo objetivo é a melhoria contínua do design. Essa técnica é fortemente baseada em testes automatizados, que são escritos antes do código, influenciando e orientando o design e a divisão das responsabilidades entre as classes do sistema. Dessa forma sua aplicação evolui em passos pequenos construindo apenas o design necessário para atender aos requisitos atuais do sistema. Como efeito colateral, você acaba desenvolvendo uma bateria de testes automatizados que pode ser executada a qualquer momento e que funciona como uma rede de proteção na hora em que as mudanças começarem a aparecer (e elas irão aparecer).



Quando desenvolvo com TDD, me vejo muito mais preocupado com decisões de design do que quando utilizo uma abordagem diferente para codificação. Ao escrever os testes do ponto de vista de um cliente da classe em questão, as preocupações com encapsulamento, divisão de responsabilidade e colaboração entre as classes se fazer muito mais presentes. Além disso, ao contrário do que possa parecer, desenvolver os testes junto com o código traz um aumento na produtividade, pois evita desperdícios futuros com depuração, reduz riscos durante a integração e melhora o design geral da aplicação, investindo somente o necessário um pouco antes e um pouco depois da implementação (evitando generalizações desnecessárias).



O Densenvolvimento Orientado a Testes impõe um ritmo constante e incremental baseado em passos pequenos. O passo inicial é pensar um pouco sobre o que será desenvolvido e elaborar uma pequena lista dos testes que serão implementados. O ritmo de TDD pode ser resumido num ciclo de iterações com os seguintes passos principais:

  1. Vermelho – Escreva um teste que falha (execute-o e veja falhar, para garantir que estará implementando algo relevante)
  2. Verde – Rapidamente, faça o teste passar (Escreva apenas o código necessário para que o teste passe, lembre-se: não se preocupe com o que você não vai precisar)
  3. Refatore – Refatore para eliminar duplicação e aumentar a expressividade do código. Evolua em direção ao design simples, reduzindo o acoplamento e aumentando a coesão.

Por fim, algumas recomendações: nunca escreva mais de um teste por vez e nunca refatore no vermelho.



Regras do Jogo: Combate

Combate (ou Stratego) é um jogo de estratégia, jogado num tabuleiro de 10×10 entre 2 jogadores, cada um com 40 peças representando os oficiais de um exército (e suas respectivas patentes). As peças são posicionadas de qualquer forma nas primeiras 4 fileiras de cada jogador, de forma que o adversário não saiba distinguir o valor de cada peça. O objetivo do jogo é capturar a bandeira do adversário ou deixá-lo com tão poucas peças que não possa se mover no tabuleiro.



Cada jogador move uma peça no seu turno (tanto na horizontal quanto na vertical). Todas as peças, com exceção dos soldados, só podem se mover 1 quadrado por vez. Se uma peça é movida para um espaço ocupado pelo adversário, suas patentes são reveladas e a mais fraca é removida do tabuleiro. Em caso de empate, ambas as peças são removidas. O valor numérico determina o vencedor na maioria dos casos, porém existem peças especiais: as bombas (não se movem e eliminam qualquer outra peça, exceto o cabo-armeiro), a bandeira (não se move) e o espião (a única peça que vence quando ataca o marechal, porém perde para qualquer peça que o atacar, incluindo o próprio marechal).



A quantidade de peças de cada jogador, em ordem de patente (da mais forte para a mais fraca), é:

Rank Peça Qtd Observação
10 Marechal 1  
09 General 1  
08 Coronel 2  
07 Major 3  
06 Capitão 4  
05 Tenente 4  
04 Sargento 4  
03 Cabo-Armeiro 5 Pode desarmar bombas
02 Soldado 8 Pode andar mais de um espaço por vez (como uma torre no xadrez)
S Espião 1 Vence quando ataca o Marechal, porém perde para qualquer peça que o atacar, incluindo o próprio Marechal
F Bandeira 1 Não se move
B Bomba 6 Não se move e elimina qualquer outra peça, exceto o Cabo-Armeiro

No ISF (International Stratego Federation) você encontra as regras oficiais do jogo.

A seguir…

No próximo post, iniciaremos a implementação da engine do jogo. Nossa idéia é desenvolver o jogo de forma incremental, então pensamos nos seguintes passos (lembrem-se, um plano não é um comprometimento sobre o que irá acontecer. Mudanças são bem-vindas!):

  1. Regras de ataque e de movimentação
  2. Controle do estado do jogo e das rodadas
  3. Gerenciar posicionamento inicial das peças
  4. Desenvolver uma interface textual
  5. Desenvolver uma interface gráfica
  6. Cliente/Servidor para jogar em rede

Post to Twitter

April 24th, 2006Design Incremental em XP

Nesse post falaremos um pouco sobre alguns dos mitos relacionados ao design em XP e sobre a nova abordagem de Kent Beck para garantir a qualidade e a simplicidade do design de um sistema.



Na primeira edição do livro Extreme Programming Explained, Kent Beck propôs 3 práticas que estavam diretamente relacionadas ao design do sistema: Design Simples, Refatoração e Metáfora. Dessas três, a última é a que mais gerou confusão nos praticantes de XP, sendo uma das menos utilizadas na prática. Com relação às outras duas, já ouvi diversas críticas e dúvidas:

Algumas dessas dúvidas estão relacionadas à forma como elas foram apresentadas na primeira edição, enquanto outras se baseiam numa interpretação errônea da prática no contexto de XP. A resposta para essas perguntas está melhor estruturada numa nova prática da segunda edição do livro: o Design Incremental. Invista no design do sistema um pouco a cada dia e se esforce para que o design seja excelente para resolver as necessidades atuais do sistema.

  • Quando?: Ao contrário de pensar pouco ou nunca pensar no design, a estratégia de XP é pensar sempre no design.
  • Como?: Faça mudanças em passos seguros e pequenos. A disciplina que facilita a evolução constante do design em XP é a refatoração, que melhora a qualidade do código sem alterar o comportamento do sistema.
  • Onde?: A heurística mais simples e eficaz é eliminar código duplicado. Adicionar uma feature num único lugar é muito mais simples e rápido do que ter que mexer em código espalhado por diversas classes.
  • Por quê?: Um design simples é mais comunicativo e mais fácil de ser entendido e alterado. Se o objetivo de XP é manter o custo da mudança constante, a facilidade de alterar o design a qualquer momento é fundamental.

Os novos critérios propostos por Kent Beck para avaliar a simplicidade são, em ordem de prioridade:

  1. Apropriado para o público-alvo: O código deve ser facilmente entendido pelas pessoas que vão trabalhar com ele.
  2. Comunicativo: Todas as idéias e intenções devem estar presentes no código, facilitando o entendimento do time e de um futuro leitor.
  3. Fatorado: A existência de código duplicado dificulta as alterações e o entendimento.
  4. Mínimo: Cumprindo as três restrições acima, um sistema deve ter o menor número de elementos possível. Menos classes implicam em menos código para testar, documentar e comunicar.

Para auxiliar o entendimento dos critérios de XP para avaliar e produzir código de qualidade, uma outra prática – que a princípio não parece ter relação direta com o design – se mostra muito importante: o Desenvolvimento Orientado a Testes (TDD – Test Driven Development). Ela será o tema dos próximos posts e, ao contrário do que possa parecer, está tão relacionada à produção de testes automatizados quanto à produção de código mais simples, comunicativo, fatorado e mínimo.

Post to Twitter

April 9th, 2006META-INF

Este é o post de estréia do meu blog. Decidi escrever um blog para divulgar e compartilhar algumas das minhas experiências e idéias relacionadas ao desenvolvimento de software. Em especial, pretendo focar meus primeiros posts nas práticas de XP (eXtreme Programming), um dos mais conhecidos métodos ágeis de desenvolvimento.



Mas antes de entrar nos assuntos mais específicos, resolvi falar um pouco sobre o principal motivo que me levou a escrever um blog: Comunicação. A comunicação é o primeiro valor do manifesto ágil, que diz que “indivíduos e interações são mais importantes que ferramentas e processos”. O blog possui todos os componentes necessários para que a comunicação aconteça:

  • Emissor, receptor e mensagem: Eu, você e o conteúdo do meu post. :-)
  • Canal de propagação: a Internet, possibilitando que pessoas de qualquer parte do mundo possam compartilhar suas opiniões (a princípio só as que entendem português).
  • Meio de comunicação: Escrita, que infelizmente nunca será um bom substituto para uma conversa.
  • Resposta (Feedback): Deixe seu comentário que eu terei o prazer de responder e discutir suas opiniões.

Por fim, o nome do meu blog (“We can change!”) é uma alusão ao quarto valor do manifesto ágil“resposta à mudança é mais importante que seguir um plano” – além de ser uma homenagem ao álbum conceitual “Be” de uma das minhas bandas preferidas, o Pain of Salvation.



Espero que aproveitem meu blog de alguma maneira e entrem em contato comigo caso tenham alguma dúvida ou sugestão para os próximos posts. Abraços e até a próxima!

Post to Twitter


© 2007-2009 Danilo Sato | Powered by Wordpress

Page optimized by WP Minify WordPress Plugin