Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante




Me recomende

Flickr
Ver todas »
Soft Modded Wii :DLET'S PLAY!!!Wii-wiiCapcom Bowling Arcade Video Game MachineArcade Machine wheel mountingCapcom Bowling arcade programmingArcade coin mechCapcom Bowling Operator adjustments

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). Outro 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.


Evitando over-stubbing com Mocha

Não é segredo que não sou “fã” da maneira como a comunidade de desenvolvedores utiliza mocks e stubs. A meu ver, trata-se de mal uso de uma ferramenta muito útil.

Com esse tipo de uso surgem alguns problemas, tais como over-mocking e over-stubbing, ou seja, o uso abusivo de mocks e stubs. O abuso de mocks torna seus testes quebradiços, isto é, testes que falham sem que haja alteração de comportamento do componente sob verificação. O abuso de stubs torna seus testes fracos, isto é, testes que saem de sincronia com o código, verificando comportamento que já não é real, mas continuam passando.

Um exemplo de abuso de mocks:

it "should be successful" do
  Account.expects(:new).with(:current_user => @user, :first_name => "Test", :last_name => "Test").returns(@account)
  @account.expects(:login_method=).with(CONFIG::LOGIN::OpenID)
  @account.expects(:has_email_and_password=).at_least_once
  @account.expects(:has_openid=).twice
  do_edit
  response.should be_success
  response.should render_template("edit")
end

Note que até métodos de atribuição são colocados sob expectativas. Se a implementação sofrer uma alteração simples, fazendo, por exemplo, com que algum desses métodos não seja mais chamado, mesmo que não haja qualquer modificação no comportamento, o teste falhará.

Um exemplo do abuso de stubs:

it "has been modified a lot of times" do
  Dependency.stubs(:a_method)
  NoLongerADependency.stubs(:something).returns(false)
  AnotherDependency.stubs(:nonexistent_method).returns(10)
  MyClass.new.do_something
end

Com esse tipo de abordagem, você pode acabar com problemas como fazer stubs de métodos que não existem mais ou até mesmo de objetos que já não são mais uma dependência.

Uma forma de evitar isso com o Mocha (além, é claro, de estudar o uso correto das ferramentas) é utilizar algumas configurações providas pelo framework através de sua classe Configuration:

Mocha::Configuration.prevent(:stubbing_non_existent_method) #impede stubbing de métodos inexistentes
 
Mocha::Configuration.prevent(:stubbing_method_unnecessarily) #impede stubbing de métodos não utilizados
 
Mocha::Configuration.prevent(:stubbing_non_public_method) #impede stubbing de métodos não-públicos
 
Mocha::Configuration.prevent(:stubbing_method_on_non_mock_object) #impede stubbing em objetos "reais"

Nos exemplos acima, caso encontre um dos usos “indevidos”, o Mocha lançará uma exceção do tipo Mocha::StubbingError. Isso ocorre por termos utilizado o método prevent. Há outras configurações possíveis, sendo providos três nívels de configuração:

# padrão - permite a condição
Mocha::Configuration.allow(condition)
 
# apenas emite um aviso quando ocorre a condição
Mocha::Configuration.warn_when(condition)
 
# lança um erro quando ocorre a condição
Mocha::Configuration.prevent(condition)

Essas configurações podem ser um auxílio para o aprendizado do uso correto e equilibrado da ferramenta.

Leia mais:
Mocha::Configuration RDoc
Mocha Configuration - James Mead


Treinando seu Mac Fu! Dicas para Mac OS X

Post rápido, com algumas dicas para melhorar a já muito boa experiência de uso no Mac OS X:

  • Forçando o esvaziamento da lixeira: às vezes o Mac OS se recusa a limpar algum arquivo da lixeira, pois este, teoricamente, está em uso. Uma maneira de forçar o esvaziamento da lixeira é abrir uma sessão no Terminal e executar o comando:
    rm -rf ~/.Trash/
  • Invocando o Quicksilver via terminal: um modo muito útil do Quicksilver é o “Command Window with Selection”, onde a janela do programa se abre com um arquivo selecionado, possibilitando a execução rápida de arquivos sobre o mesmo. Além de configurar um atalho para isso nas preferências, é possível também instalar o plugin “Command Line Tool” e, na linha de comando, invocar o Quicksilver com um arquivo selecionado através do comando:
    qs nome_do_arquivo

    Para mais informações, execute:

    qs --help
  • Melhorando o alternador de janelas (Cmd+Tab): instale o Witch, configure um atalho e você terá um alternador de janelas mais prático e informativo do que o padrão do Finder. Vale a pena dar uma olhada nas outras aplicações da Many Tricks.
  • Mudando o mapeamento das teclas modificadoras: entre os usuários do emacs é comum mapear a tecla Caps Lock como a tecla Control, pois facilita a digitação. Para fazer isso, entre outras coisas, basta acessar System Preferences, Keyboard & Mouse, acessar a aba Keyboard e clicar no botão “Modifier Keys…”. No pop-up que se abre é possível modificar a ação executada por cada tecla modificadora.

Agile não é para todos

Agile não é para todos. Waterfall também não. Nem RUP, nem qualquer método, técnica ou filosofia.

Assim como nem todos são bons jogadores de futebol ou bons em matemática, nem todos serão bons ou se adaptarão ao desenvolvimento ágil de software.

Note o destaque em negrito na frase acima, pois estou aqui falando de práticas ágeis para o desenvolvimento de software, uma coisa que o Scrum não é (o Scrum é apenas um framework gerencial “ágil”, podendo ser aplicado à projetos de software ou de outros mercados). Extreme Programming já vai além, sendo, essa sim, uma metodologia de desenvolvimento ágil de software (que, por usa vez, usa muitos conceitos gerenciais do Scrum no que diz respeito à comunicação intra e extra-equipe).

Cada metodologia possui um conjunto de princípios básicos, algumas possuem práticas e, outras, formatos bem específicos de documentos a serem seguidos fielmente. Algumas possuem tudo isso junto.

De acordo com o perfil psicológico, social e profissional, é comum que pessoas envolvidas em desenvolvimento de software (gerentes, desenvolvedores, designers, testadores etc) possuam suas preferências quanto à metodologias e práticas. Alguns se dão muito bem apenas com waterfall, outros apenas com agile, uns poucos com ambas e muitas outras. E isso é extremamente normal. As metodologias ágeis são vistas como a salvação do mundo do desenvolvimento de software, principalmente num mercado dinâmico como a internet. Mas, não se engane, nem todos os profissionais conseguem trabalhar num ambiente realmente ágil. Eles são piores do que os que conseguem? Não. São apenas pessoas diferentes.

Ambientes “ágeis” exigem um conjunto de habilidades bem diferentes em relação a outros tipos de ambientes. Geralmente, exigem também muito sacrifício pessoal em detrimento do próprio ego, sendo essa uma das principais barreiras para que se consiga implementar uma cultura de desenvolvimento ágil de software.

Alguns profissionais (desenvolvedores e outros) não estão acostumados e não querem praticar coisas como feedback rápido, desenvolvimento orientado por testes, daily meetings e as outras práticas que as acompanham. Desenvolvimento ágil de software não é apenas escrever centenas de post-its e colá-los num quadro branco ou numa parede. É estar disposto a mudar muitos hábitos e fazer alguns sacrifícios, aceitar algumas “imposições” das metodologias, trabalhar em equipe e para a equipe.

Se a pessoa não se encaixa nesse tipo de mentalidade, tudo bem. Não significa nada em termos de qualidade profissional, mas uma empresa que deseja trabalhar com métodos ágeis definitivamente não é o lugar certo para ela, já que ambos os lados sairão perdendo.


Ruby Quick Tip: Aprendendo através de testes

Se você não conhece (completamente ou pacialmente) alguma biblioteca, uma boa forma de fazer isso é através de um caso de testes.

Um benefício resultante dessa prática é poder executar os testes contra várias versões do Ruby e, assim, verificar alterações em sua estrutura.

Se você usa o TextMate, é muito fácil criar um caso de testes. Basta abrir uma janela do editor, ativar o bundle do Ruby, digitar tc e pressionar a tecla tab:

TextMate

test case scaffold

Com isso temos um “esqueleto” de um caso de testes pronto. Pressionando tab, o foco do cursor passa por pontos importantes, como o require da biblioteca a ser testada, o nome do caso de testes etc. Agora, basta modificar de acordo com o que queremos testar e, através do atalho command+R, executar os testes e verificar o resultado:

test

ishot-41

Veja também:

Aprendizado orientado por testes


Seu framework não faz BDD

Eu sinto uma “pontada” no cérebro quando ouço ou leio coisas como “o RSpec (ou Shoulda, test/spec etc) é um framework BDD”.

Não existe algo como um “framework BDD”. Tenha em mente que quem pratica ou não o BDD é o desenvolvedor. O que existe são frameworks ou bibliotecas que adicionam uma boa dose de açúcar sintático para facilitar o estilo de escrita para quem deseja praticar essa técnica de testes.

Os maiores proponentes do BDD enfatizam que a maior mudança em relação às técnicas tradicionais é mesmo a sintaxe. Porém, isso não quer dizer que basta utilizar uma sintaxe “mais legível” (o que é discutível também) para pular no vagão do BDD. A sintaxe bonitinha é um auxílio, uma ferramenta com o objetivo de diminuir as dificuldades na transição de um modo de pensar, que é onde a verdadeira diferença reside. Muitas dessas dificuldades são, inclusive, ditas de origem “psicológica”, como a abolição da palavra “test” nos nomes dos métodos para forçar o pensamento no sentido de uma verificação de comportamento ao invés de um teste de estado (medida defendida através da hipótese de Sapir-Whorf).

De fato, posso afirmar que a maior parte das suítes de testes escritas em RSpec, Shoulda ou test/spec que já tive a oportunidade de examinar (seja trabalhando, seja “navegando” por projetos open source - uma grande fonte de aprendizado), falha miseravelmente na tentativa de adotar o BDD, tropeçando feio em erros como verificar estado ao invés de comportamento, testar funcionalidades da linguagem ou framework e, principalmente, overmocking - chegando ao ponto em que um teste não verifica mais nada, apenas mocks e stubs - o que nos leva a testes quebradiços ou testes que não falham mesmo que você apague o código que eles deveriam verificar.

Esse último item leva a um outro ponto importante (e, aqui, uma visão muito pessoal - acredito ser compartilhada por poucos): acredita-se que o uso de mocks configura o uso do BDD, pois estabelecer expectativas em relação à interfaces e, consequentemente, isolar horizontalmente os componentes testados, passa a ser, “automaticamente”, verificação de comportamento. O uso de mocks não estabelece nada além de uma expectativa de interação - uma interface.

Como um classicista evito o uso de mocks (e dublês em geral), reservando-os para onde realmente é difícil ou pouco prático o uso de um objeto real ou, como deveria ser seu principal uso, uma ferramenta de design de código através da qual modelamos interfaces ainda não existentes à medida em que praticamos o TDD (ou seja, escrevemos testes antes da implementação do código real) - e os substituo assim que a classe real é implementada, quando aplicável.

Então, não acredite que por utilizar alguma ferramenta facilitadora, você, instantâneamente, estará praticando BDD. Estude a técnica e a filosofia envolvida e você poderá utilizá-la com qualquer ferramenta para testes.


← Anterior