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.
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.
Marcadores: assembly, code, metaprogramming, pair programming, sistemas formais, strange loop, tdd
10 Comentários:
Dá para programar com dois robôs?
Por ila fox, Às 7 de fevereiro de 2010 19:29
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.
Por Ricardo Bittencourt, Às 7 de fevereiro de 2010 19:34
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
Por Valdemar Júnior, Às 7 de fevereiro de 2010 21:52
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 ;-)
Por Ricardo Herrmann, Às 8 de fevereiro de 2010 14:52
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.
Por Luís Guilherme Fernandes Pereira, Às 12 de fevereiro de 2010 08:38
Cadê o capítulo 30? Quero saber o que aconteceu com o Marcio, Phildrac e os outros!!! O Coelho conseguiu invocar os outros deuses?
Por Juan, Às 21 de fevereiro de 2010 11:35
A história não tem fim!
Por Ricardo Bittencourt, Às 21 de fevereiro de 2010 12:50
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!
Por Juan, Às 21 de fevereiro de 2010 22:20
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
Por gandbranco, Às 24 de fevereiro de 2010 17:30
Postar um comentário
Links para esta postagem:
Criar um link
<< Início