Brain Dump

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.

Marcadores: , , , , , ,

domingo, 15 de novembro de 2009

A Lei de Ricbit

Já faz quinze anos que eu assino mailing lists, e uma coisa sempre foi constante em todo esse tempo: as flamewars. Aparentemente, listas na internet têm o poder de enfurecer os participantes e transformá-los em monstros incontroláveis. Ironicamente, essas brigas pela internet são tão previsíveis, que podemos até extrair padrões delas, como a Lei de Godwin.

Dia desses eu fiz uma observação sobre a natureza dessas brigas que os meus amigos apelidaram de Lei de Ricbit. A premissa é simples: as flamewars acontecem em qualquer lista de discussão, mesmo em listas sobre matemática e computação, onde supostamente os leitores são agentes racionais. Como isso pode ocorrer? Será que uma briga poderia acontecer mesmo se o Spock e o Data se encontrassem no Google Wave?




Data: Fiz uma medição surpreendente! Usando os sensores da Enterprise, eu medi os ângulos entre três estrelas distantes, e o resultado é que a soma dos ângulos do triângulo formado pelas estrelas não dá 180 graus!


Spock: Fascinante, mas temo que você tenha cometido um erro. Obviamente a soma dos ângulos de um triângulo é sempre 180 graus. Usando a lógica, eu posso provar que isso é sempre é verdade. Basta considerar uma reta paralela à base do triângulo:



Como você pode ver, os ângulos α e α' são alternos internos, e portanto iguais. O mesmo acontece com β e β', logo, a soma dos três ângulos é um ângulo raso.


Data: Como você sabe, andróides não cometem erros de medida. Os ângulos não somaram 180 graus por culpa da teoria da relatividade geral. Objetos tão massivos quanto as estrelas em questão deformam o espaço, o que explica a minha medida. Eu esperava mais dos vulcanos, certamente não se pode questionar os dados experimentais.


Spock: Eu não estava questionando a medida, estava questionando a conclusão. O que você mediu não era um triângulo de verdade, mas sim um outro tipo de figura geométrica de três vértices. Triângulos precisam necessariamente somar 180 graus: se não somarem, não são triângulos. Eu esperava mais dos andróides, certamente não se pode questionar a lógica.


Ricbit: Vocês dois vão ficar discutindo pra sempre, sem concluir nada, e eu vou mostrar o porquê. Muita gente acha que somente a lógica pode levar à verdade, mas isso não é inteiramente correto. A lógica, por si só, pode apenas produzir obviedades. A função real da lógica não é produzir verdades, mas sim transformar verdades. Para usar a lógica na prática, você precisa de verdades básicas, os axiomas, e a partir deles você deriva as verdades mais complexas.

Cada conjunto de axiomas determina um sistema formal. E é por isso que vocês nunca vão concordar, os dois estão partindo de axiomas diferentes! O Spock, ao dizer que alternos internos são iguais, assumiu implicitamente o Postulado das Paralelas do Euclides. Já o Data, ao dizer que o espaço é curvo, assumiu a negação do Postulado das Paralelas, gerando assim uma geometria não-Euclideana. Como os axiomas são diferentes, vocês dois vão discordar sempre, mesmo que usem a lógica perfeitamente.


Spock: Obviamente, eu sabia disso.


Data: Eu também.


Ricbit: Se sabiam, porque continuaram a discussão?


Spock, Data: ...


Ricbit: Olha, que tal esquecer tudo isso? Vamos jogar uma partida de Guitar Hero que é mais divertido.


Spock: Excelente! Eu fico com a guitarra, prefiro os instrumentos de corda.


Data: Onde que o Guitar Hero é instrumento de corda? Não tem corda nenhuma lá, só botão.


Spock: Ora, quando eu clico no botão ele faz som de instrumento de corda.


Data: Mas você não está excitando nenhuma corda, está só apertando um botão! Não tem como dizer que a guitarra do Guitar Hero é um instrumento de corda só porque ela produz som de corda.


Spock: Se for assim, então o piano também não é um instrumento de corda, afinal, você aciona teclas, e não cordas.


Ricbit: (facepalm)




Uma mesma proposição P pode ser verdadeira ou falsa, dependendo do sistema de axiomas que você adota. Mas como decidir qual sistema formal é o correto? A resposta é que você não decide qual é o correto, você escolhe qual é o correto. O único requisito de um sistema formal é que ele seja internamente consistente, fora isso, você escolhe o que for mais útil na ocasião.

O que vale para sistemas formais também funciona informalmente, mas ao invés de axiomas você tem definições. Qual a definição correta de "instrumento de corda"? É o que produz som de corda, ou é aquele onde você excita uma corda diretamente? Novamente, não existe um correto: você escolhe um deles e arca com as conseqüências da sua definição (se você achar que Guitar Hero não é instrumento de corda, então o piano também não vai ser).

Dito isso, agora já podemos enunciar a Lei de Ricbit, em sua forma original:

Lei de Ricbit: Se dois interlocutores discordam de uma definição, então eles vão discutir pra sempre, sem nunca concordar.

E como usar a Lei de Ricbit na prática? Fique de olho em definições conflitantes, que usualmente são implícitas. Por exemplo, um sinal claro de definição conflitante é a expressão "de verdade". FPGA não é hardware livre de verdade, como o Arduino. Java não é orientada a objeto de verdade, como Smalltalk. Quando a pessoa usa "de verdade", está te dizendo que a definição dele é diferente da sua, e aí é inútil prosseguir. E qual definição é a certa, a sua ou a dele? Tanto faz: desde que você seja consistente, pode adotar qualquer uma como verdadeira.

Marcadores: ,