Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante




Me recomende

Flickr
Ver todas »
Amsterdam - Oude KerkAmsterdam - Oude KerkAmsterdamAmsterdamAmsterdam - St. NicolaaskerkAmsterdam - Royal PalaceAmsterdam - Oude KerkAmsterdam - Oude Kerk

Waterfall é xadrez. Agile é futebol.

Essa é uma boa analogia quando necessário comparar esses dois tipos de filosofia/metodologia (é apenas uma analogia, não uma comparação perfeita, mas ajuda bastante).

Waterfall é xadrez: você usa muito tempo para pensar e planejar, fazendo o máximo de esforço para tentar prever cada possível movimento durante o jogo. Dada essa natureza de muito planejamento antes da execução, faz sentido apenas em domínios muito estáveis e conhecidos, sem mudanças na demanda (geralmente em software de alto risco, como para voos espaciais).

Agile é futebol (ou qualquer esporte coletivo): as decisões durante o jogo devem ser tomadas muito rapidamente, sem tempo para análise de todas as possíveis consequências. Por isso, é “ideal” para domínios altamente dinâmicos e instáveis, com constantes alterações na demanda (como software para a web). Outra semelhança muito importante a ser notada é: mesmo que você tenha os melhores jogadores, se a equipe não jogar bem junta, o time não vence.


Slides e notas da minha sessão no FISL10

Pessoal, abaixo está o link para download, em formato PDF, dos slides com notas que eu utilizei na minha sessão ontem no FISL10, entitulada “TDD e Rails: Mais rápido, mais forte e melhor”.

Download.

Obrigado a todos que apareceram por lá! Qualquer feedback é muito bem-vindo. :)


Sobre mudanças

Há um mês algumas coisas mudaram e, hoje, resolvi escrever sobre isso aqui.

Por conta de mudanças estratégicas que não cabe a mim revelar, a WebCO mudou alguns de seus rumos e, com isso, deixou de ser um lugar em que eu gostaria de trabalhar. Nada grave, apenas coisas do mundo dos negócios. Decidi, então, deixar a empresa. Muito aprendizado, novas amizades, um ano muito divertido e, sem dúvida, inesquecível. Agradeço, mesmo, a todos pela oportunidade única que tive.

Dado isso, busquei por um projeto forte e com potencial em que eu pudesse ajudar e, também, crescer. Recebi proposta de alguns e, após um processo de bastante ponderação (sempre é difícil), decidi me juntar ao pessoal da Spix, que hoje tem como projeto principal o Busk (pronuncia-se “busqui”).

O projeto e a visão são excelentes e o futuro é promissor. Equipe pequena (somos apenas dois desenvolvedores, eu e o ArthurGeek), pouquíssima interferência de processos e muito espaço pra experimentar e descobrir os melhores caminhos para o projeto.

Fique de olho no Busk. Muita coisa nova vem por aí!

PS: Agora também sou mais um no crescente time do home-office. Vejo vocês na Starbucks mais próxima! ;)


Testes devem revelar a intenção do código

Essa frase não é novidade para ninguém – ou, pelo menos, não deveria ser. No entanto, é muito mais difícil fazer isso acontecer do que falar sobre o assunto.

É muito bom que a mentalidade de testes esteja sendo cada vez mais difundida. Com isso, desenvolvem-se as abordagens às práticas de desenvolvimento orientado por testes como, por exemplo, os frameworks. Enquanto eles se tornam cada vez mais extensíveis, eficientes e com DSLs mais limpas e bonitas, muitas pessoas esquecem que, independentemente da ferramenta, da sintaxe e da abordagem técnica a ser utilizada, a intenção do código desenvolvido é que deve ficar explícita.

Observação: ao longo desse texto, utilizarei a expressão “desenvolvimento orientado por testes”, mas você pode substituir por “desenvolvimento orientado por comportamento” ou qualquer abordagem similar.

O que é a intenção do código?

A intenção do código tem tudo a ver com algo que vem sendo muito discutido: o comportamento do software. Uma definição de comportamento é “a resposta observável de um sistema a um estímulo“. Logo, em última análise, podemos dizer que o que importa é verificar se a resposta observável de um componente de software é a esperada, dado um ou mais estímulos pré-definidos.

Em alguns casos isso significa apenas definir um estado, chamar algum método e verificar o resultado. Em outros, significa verificar também a propagação dos efeitos em outros objetos (caso onde os mocks são muito úteis). Isso vai depender do componente em desenvolvimento e da abordagem utilizada.

Como?

A pergunta é: como testes ajudam a revelar a intenção do código?

Se você escrever um monte de código confuso e, após isso, resolver escrever alguns testes para validar esse trabalho, esses testes não vão ajudar em nada. Mas, se utilizar testes para orientar seu processo de desenvolvimento, descobrirá o papel fundamental que eles terão para criar código claro, fácil de entender, utilizar e modificar. Ao especificar um cenário (o estado pré-definido onde ocorrerá o estímulo), fica muito mais simples projetar e verificar o comportamento necessário.

Geralmente, ao escrever testes antes da implementação, obtêm-se outros benefícios, tais como: uso de boa nomenclatura, simplicidade e facilidade no refactoring. Tudo isso deve-se ao fato de que, ao praticar o desenvolvimento orientado por testes, você efetivamente pensa mais sobre o que vai escrever, antes de o fazer.

Exemplo

Vou tentar exemplificar os passos de criação de um componente simples com e sem testes para orientar a implementação. Assim, ao final, teremos uma perspectiva melhor dos efeitos do processo.

Quero escrever uma aplicação para cadastrar minha coleção de filmes. Preciso de uma classe que representará o filme:

class Movie
  attr_accessor :title
end

Isso é o suficiente para o exemplo.

Agora vamos iniciar o processo de implementação da classe responsável pelo gerenciamento da coleção. Primeiro, sem teste algum.

Sei que preciso de uma classe que possua uma lista de filmes e permita que eu posso adicionar, remover, procurar e listar todos os filmes. Vamos lá:

class MovieShelf
  attr_accessor :movies
 
  def initialize
    @movies = []
  end
 
  def lookup(movie)
    @movies.detect {|m| m == movie}
  end
 
  def list
    @movies.each do |movie|
      puts movie.title
    end
  end
end

Ok, muito simples. Em dois minutos está pronto. Um exemplo de uso (no irb):

>> shelf = MovieShelf.new
=> #<MovieShelf:0x36eb88 @movies=[]>
>> m = Movie.new
=> #<Movie:0x33b328>
>> m.title = "Juno"
=> "Juno"
>> m2 = Movie.new
=> #<Movie:0x39968>
>> m2.title = "Transformers"
=> "Transformers"
>> shelf.movies << m
=> [#<Movie:0x33b328 @title="Juno">]
>> shelf.movies << m2
=> [#<Movie:0x33b328 @title="Juno">, #<Movie:0x39968 @title="Transformers">]
>> shelf.list
Juno
Transformers
=> [#<Movie:0x33b328 @title="Juno">, #<Movie:0x39968 @title="Transformers">]
>> shelf.lookup m2
=> #<Movie:0x39968 @title="Transformers">
>> shelf.movies.delete m2
=> #<Movie:0x39968 @title="Transformers">
>> shelf.list
Juno
=> [#<Movie:0x33b328 @title="Juno">]

Tudo bem, funciona e parece estar de acordo com os requisitos. Mas o código não está muito consistente. A coleção de filmes, que na prática é um objeto da classe Array, está exposta e pode ser manipulada diretamente. Isso pode ser muito ruim caso algum método da classe MovieShelf faça ações adicionais ao operar sobre a coleção. A nomenclatura também não é muito clara, entre outros pequenos problemas. Também não gostei da forma como estou criando os objetos que representam os filmes.

Agora, vamos começar com uma pequena especificação do que é preciso. Com isso, vou ter mais tempo para pensar em bons nomes e numa forma de uso limpa e direta.

context "a movie shelf" do
  setup { @shelf = MovieShelf.new }
 
  should "let the user store a movie" do
    juno = Movie.new("Juno")
    shelf.store juno
    assert shelf.contains?(juno)
  end
end

Somente estabelecendo essa primeira expectativa quanto ao comportamento do componente, já temos idéia de um início de implementação:

class Movie
  attr_accessor :title
 
  def initialize(title)
    self.title = title
  end
end
 
class MovieShelf
  def initialize
    @movies = []
  end
 
  def store(movie)
    @movies << movie
  end
 
  def contains?(movie)
    @movies.include? movie
  end
end

Continuando:

context "a movie shelf" do
  setup { @shelf = MovieShelf.new }
 
  should "show how many movies are stored" do
    juno = Movie.new("Juno")
    transformers = Movie.new("Transformers")
    shelf.store juno
    shelf.store transformers
    assert_equal 2, shelf.movies_count
  end
end

Implementando:

class MovieShelf
  def initialize
    @movies = []
  end
 
  def store(movie)
    @movies << movie
  end
 
  def contains?(movie)
    @movies.include? movie
  end
 
  def movies_count
    @movies.size
  end
end

É claro que o exemplo é bem bobo, mas perceba como a interface fica melhor e também como vamos descobrindo novas funcionalidades ao longo do processo de especificação. Este é um dos principais benefícios do desenvolvimento orientado por testes: interfaces ricas através de código modular, flexível e de intenção clara.

Não vou continuar o exemplo com as demais funcionalidades para não alongar ainda mais o artigo. Acredito que consegui expor aqui a importância da intenção do código e como deixá-la clara e fácil de entender.

O que você pensa sobre isso? Deixe sua opinião, comentário, exemplo, crítica ou sugestão. :)

Leia também:


Obtendo informações de uma instalação do Ruby

Existem algumas maneiras de obter informações sobre uma instalação do Ruby, mas uma que conheci hoje é através do próprio irb, utilizando uma biblioteca chamada rbconfig, presente na instalação padrão do Ruby.

Para iniciar o irb já com essa lib carregada, basta executar o comando: irb -r rbconfig (a flag -r diz ao irb para carregar uma lib ao iniciar). Para obter algumas configurações da instalação Ruby, acesse o hash Config::CONFIG. Veja alguns exemplos (clique para ver a imagem maior):

irb with rbconfig

Nota: em algumas instalações, essa biblioteca já é carregada com o irb por padrão.


← Anterior Próxima →