domingo, 7 de fevereiro de 2010

Single-Person Pair Programming

Dia desses me perguntaram no twitter o que eu achava de Pair programming. Não apenas eu gosto, como sou entusiasta! Pair programming tem um monte de vantagens, sendo que a principal delas é que o programa será escrito com dois pares de olhos. E como nos lembra a Lei do Beholder Lei de Linus: dados olhos suficientes, todos os bugs são fáceis.


Minha técnica predileta de pair programming é o Ping-Pong Pair Programming, que eu aprendi com o Miško. A idéia é mesclar as idéias do pair programming com o test-driven development. Você começa colocando dois teclados no computador, aí um parceiro escreve um teste, e o outro escreve o código que faz aquele teste passar. A vantagem desse método é que funciona mesmo se um dos programadores for preguiçoso, e, de fato, funciona até melhor assim!

Por exemplo, vamos supor que Alice e Bob querem escrever um programa bem simples: uma função que incrementa um número. Digamos que a assinatura dessa função será int increment(int value). Alice escreve um teste que valida essa função:

void teste1() {
  assertEquals(2, increment(1));
}

Um teste bem razoável. Se entrar um, tem que sair dois. Agora Bob vai escrever o código que faz esse teste passar:

int increment(int value) {
  return 2;
}

FAIL? O Bob é um cara preguiçoso, então ele escreveu um código que sempre retorna dois. Isso faz o teste passar, mas não era isso que a Alice tinha em mente!

Surpreendentemente, essa é a vantagem do ping-pong. Suponha que esse teste fosse o único teste que o código tinha. Agora digamos que alguém, anos depois, foi refatorar o código, mas fez uma bobagem no processo e agora a função que incrementa um número está retornando sempre 2. Nesse caso, o teste não vai detectar o erro!

A conclusão é que o teste inicial não era robusto o suficiente. Pra melhorar isso, Alice escreve um segundo teste:

void teste2() {
  assertEquals(3, increment(2));
}

Agora o código original do Bob não funciona, e ele precisa refatorar pra criar um código que passe os dois testes:

int increment(int value) {
  if (value == 1)
    return 2;
  else
    return 3;
}

E agora, FAIL? Não há dúvidas de que o conjunto com dois testes é mais robusto que apenas o primeiro teste, mas esse processo não parece prático. Afinal, os dois poderiam ficar no ping-pong até exaurir todos os valores do int, o que levaria um bocado de tempo.

Mas isso não acontece! Como sabemos, o Bob é preguiçoso. Na verdade, ele é tão preguiçoso, que atingiu o nível supremo da preguiça: a meta-preguiça. O Bob sabe que se ele continuar nesse ping-pong, ele vai ficar trabalhando até depois das seis, e ele quer ir pra casa ver a novela. Se a Alice escrever testes o suficiente, ela vai acabar forçando o Bob a escrever o código correto, porque é o código mais simples que resolve o problema.

void teste3() {
  assertEquals(4, increment(3));
}


int increment(int value) {
  return value + 1;
}

Agora sim! O resultado final é duplamente bom, nós temos um conjunto de testes robustos, e um código que é o mais simples possível (o que é sempre uma vantagem, esse código é o mais fácil de entender, tem o menor custo de manutenção, etc.)

Esse método me fascinou por dois motivos. Primeiro, ele é uma aplicação da Navalha de Occam em software: você parte de uma série de observações e deduz a teoria mais simples que modela o sistema. Segundo, o método é um indicativo de que é possível fazer um conjunto de testes que define a operação em questão.

Juntando as duas observações, a pergunta natural que faz é: pra que eu preciso do Bob? Eu poderia construir uma máquina que, dado um conjunto de testes, encontre o programa mais simples que os satisfaçam. Se a máquina conseguir jogar o ping-pong de maneira ótima, então acabamos de inventar o Single-Person Pair Programming!


Antes de tentar resolver esse problema, precisamos escolher alguma definição para "programa mais simples". Por exemplo, vamos escolher que o programa mais simples é o menor programa que resolve o problema. Uma maneira de achar esse programa é fazer um brute force: basta testar todos os programas possíveis!

Isso é fácil de visualizar em assembly. O conjunto de opcodes do processador é limitado, então a quantidade de programas em assembly com um único opcode é finito. Eu testo todos eles pra ver se algum satisfaz os testes: se algum passar, ele é a solução, senão, eu repito o procedimento com todos os programas de tamanho dois, e assim por diante. Esse algoritmo garantidamente acha o menor programa que satisfaz os testes.

Um problema teórico com essa abordagem é que ela está sujeita ao problema da parada de Turing. Eventualmente, algum desses programas que você está testando pode entrar num loop infinito e você não tem como detectar isso. Uma solução é sair pela tangente: a maioria dos problemas da vida real podem ser resolvidos com modelos computacionais mais fracos que a máquina de Turing. Em assembly, nós poderíamos proibir os saltos pra trás, o que resolve essa limitação.

Essa técnica para achar o programa mínimo se chama Superotimização, e hoje em dia há vários papers sobre o assunto. Em 2003 eu escrevi um superotimizador para assembly Z80, então foi fácil adaptá-lo para fazer Single-person Pair Programming.

Single-person Pair Programming escrito em C

Vamos testar. Se eu tenho um único teste, 1->2, o programa acha três soluções mínimas, sendo uma delas ADD A,A. É claro, com esse único teste, ele não sabe se estamos somando um ou multiplicando por dois. Colocando dois testes, 1->2 e 2->3, ele já converge para a solução única INC A.

Complicando: e se quisermos um incremento módulo 8 (ou seja, a=(a+1)%8)? Podemos definir isso com os testes 1->2, 2->3 e 7->0. Colocando essa suite no programa, temos o resultado abaixo:

INC A
AND 7

Ou seja, o Single-person Pair Programming funciona direitinho!

Bônus!

O vencedor do Ricbit Jam #1 foi o Davi Costa, parabéns! Por uma questão de logística que envolve a China eu ainda não tive como compilar os resultados, mas fiquem de olho lá no meu twitter que em breve eu farei uma página com soluções e comentários.

10 comentários:

  1. Ainda não, pra substituir o primeiro programador precisaria de um programa que entendesse a especificação do projeto em português ou inglês, e isso é um problema difícil de inteligência artificial.

    ResponderExcluir
  2. Muito bom essa técnica, mas o custo para o projeto para "substituir" o outro programador pode ser muito alto e para projetos pequenos e médios, acho que nem valeria a pena. Mas é muito interessante, Parabéns pelo post

    ResponderExcluir
  3. Ah, ressucitou a idéia do(a) Massalin ;-) Tem também uma gambiarra parecida que dá pra fazer: em Haskell, que tem inferência de tipos, você pode escrever o programa que o compilador escreve o tipo. Por causa do isomorfismo de Curry-Howard, é possível fazer também o inverso, já que o sistema de tipos é isomórfico à lógica intuicionista. Então você passa o tipo e o provador de teoremas encontra uma função que é a prova do teorema equivalente. Inútil, mas divertido ;-)

    ResponderExcluir
  4. Acabei de usar isso, sem querer, aqui no trabalho. Tinha um caso de teste que falhou, corrigi o software para resolvê-lo, gerei outro caso de teste que, por um efeito colateral da minha correção ingênua, entrou em loop.

    Agora passa nos dois :) Hora de gerar novo teste.

    ResponderExcluir
  5. Muito bom o artigo. Como já dizia um veterano meu, "A padroeira da computação é a preguiça" =P.
    Compartilhando uma tirinha do phd comics sobre a navalha de occam: http://www.phdcomics.com/comics/archive/phd101209s.gif

    ResponderExcluir
  6. Cadê o capítulo 30? Quero saber o que aconteceu com o Marcio, Phildrac e os outros!!! O Coelho conseguiu invocar os outros deuses?

    ResponderExcluir
  7. Ah, q chato... mas faz sentido, é a História sem Fim. Lembro qundo descobri ela numa ROM de SMS que veio numa revista de emuladores. Ficava imaginando como seria um filme dessa história. O último capítulo foi o mais bem formulado. Quando o Fox e a Dana apareceram, eu pensei na possibilidade do Coelho ser um ET disfarçado.

    Esse ping pong pair programming me lembrou muito o processo de como foi feito a HSF, onde cada um escrevia uma parte da história. Não sei programar em linguagem nenhuma, já tentei começar com Python, mas chegou uma hora que começou a complicar, e então pensei que programação deve ser algo melhor de se aprender com um professor, ao lado de outros que podem aprender juntos comigo.

    Acho que entendi agora a importância de se saber trabalhar em grupo. Vlw!

    ResponderExcluir
  8. Programar em pares não é somente isto, se não era uma boa botar um robô pra trabalhar por você =)

    Mas o que mais gosto em pair programer é compartilhar, aprender e ensinar. Dificilmente um robô aceitaria pitacos =P

    ResponderExcluir