quinta-feira, 2 de outubro de 2008

Passos de bebê

Na lista de discussão do núcleo de pesquisa de que faço parte, surgiu uma polêmica a respeito do uso da técnica de passos de bebê. Resumindo a história, alguém estranhou os passos minúsculos preconizados pela literatura de TDD. Por exemplo, em se tendo apenas o teste (em Java):

Template template = new Template("#{tecnica-agil} é legal!");
template.set("tecnica-agil", "TDD");
assertEquals("TDD é legal!", template.parse());


a implementação seria necessariamente

...
public String parse()
{
return "TDD é legal!";
}
...


Bom, vamos à minha visão da coisa toda: a técnica de "passos de bebê" serve para evitar complexidade desnecessária, como generalização prematura e overengineering. O tamanho do passo - o que parece ser o centro da polêmica - deve ser baseado no bom senso e no quanto é confortável para o desenvolvedor. Se um desenvolvedor ou par se sente confortável em utilizar a técnica de modo mais ortodoxo, ótimo. Se não, é uma questão de ajuste fino. Eu, por exemplo, normalmente utilizo passos um pouco (mas não muito) maiores, exceto quando o domínio é pouco conhecido.

Os passos de bebê estão diretamente ligados a Test-Driven Development. Em TDD, a implementação deve ser unicamente aquela suficiente para passar nos testes, e nada mais. Deste modo, os passos de bebê não estão na implementação, mas na elaboração dos testes. Caso se queira que os passos sejam maiores, basta aumentar a granularidade dos testes unitários. Escrever um teste apenas para, por exemplo, uma entrada "N" e implementar uma solução genérica para o alfabeto inteiro é algo que contraria diretamente os princípios do agilismo (ou seja, da engenharia de software moderna), é implementar a solução para um problema ainda não formulado. Do ponto de vista da programação, os testes unitários são a especificação de requisitos. Assim, uma solução geral para o alfabeto inteiro com um teste só para "N" recairia em uma das duas situações: (1) ou há implementação desnecessária, ou seja, desperdício e complexidade inútil ou (2) o conjunto de testes é deficiente, o que mostra requisitos mal formulados e pode ter consequências ruins como bugs de regressão.

A questão é que a implementação tem que ser a coisa mais simples (sem ser simplória, já dizia Einstein) que possa passar nos testes (e uma cascata de if-elses é tosqueira, não simplicidade). Se você consegue ter uma função que passa nos testes com um mero return "n", o problema está em um conjunto fraco de testes. A implementação está correta, pois cumpre os requisitos (testes unitários) de modo claro, simples, rápido e manutenível.

Toda e qualquer prática ágil deve estar ancorada em princípios e valores. No caso em questão, o valor é a já citada simplicidade, traduzida nos lemas KISS e YAGNI. Os passos de bebê asseguram que o software terá apenas a complexidade necessária e nada mais.

Um comentário:

Colonese disse...

Show de bola, Rodrigo! Apesar de haver razoável quantidade de informação acerca de práticas ágeis e TDD, o momento de colocar a mão na massa é sempre complicado. Olhar um exemplo feito e concordar com ele é simples...O problema é partir do zero, ou seja:
1) Definir o que se quer de certa funcionalidade;
2) Escrever um teste que cheque tal execução
3) Escrever o método tão bastante para que o teste passe
4) Redigir outro teste que consiga quebrar o método
5) Reescrever ou mesmo refatorar o método para que volte a passar no teste
6) encerrar quando não for mais possível pensar em novos testes que quebrem o método
É realmente fantástica a filosofia de se poder especificar o software de forma executável, ou seja, por meio dos testes, mas começar a executar as coisas desta maneira é complicado de início e exige certa dose de persistência e bom-senso.
Valeu por compartilhar as experiências!!
Abraços e parabéns pela iniciativa!