Desenvolvimento

24 mar, 2017

Quebra de simetria

Publicidade

Imagine que você é um contador. Você é responsável por manipular símbolos arcanos, conceitos e procedimentos para criar modelos financeiros profundamente complicados e detalhados para o seu negócio. As apostas são enormes. Precisão é essencial. Milhões esperam ser perdidos ou ganhos com base em suas habilidades raras e esotéricas.

Como você garante o seu desempenho? De quais disciplinas você depende? Como você vai se certificar de que os modelos que você construiu e que o conselho que eles implicam são fiéis à sua profissão e rentáveis para o seu negócio?

Nos últimos 500 anos, contadores vêm usando a disciplina contabilidade de entrada dupla. A ideia é simples, mas a execução é desafiadora. Cada transação é registrada, simultaneamente dentro de um sistema de contas – uma vez como um débito e, em seguida, novamente como um crédito. Esses débitos e créditos seguem caminhos matemáticos separados, mas complementares, através de um sistema de contas categorizadas, até que convergem no balanço em uma subtração que deve render um zero. Qualquer coisa que não seja um zero implica que um erro foi feito em algum lugar ao longo de um desses caminhos.

Nós, programadores, temos um problema semelhante. Nós manipulamos símbolos arcanos, conceitos e procedimentos para criar modelos profundamente complicados e detalhados de comportamento para nossos negócios. As apostas são enormes. Precisão é essencial. Milhões esperam ser perdidos ou ganhos com base em nossas habilidades raras e esotéricas.

Como nós garantimos o nosso desempenho? De quais disciplinas nós dependemos? Como iremos nos certificar de que os modelos que nós construímos e que o comportamento que eles provocam são fiéis à nossa profissão e rentáveis para os nossos negócios?

Tem sido afirmado que o Test Driven Development (TDD) é o equivalente à contabilidade de entrada dupla. Existem alguns paralelos inegáveis. Sob a disciplina de TDD, cada comportamento desejado é escrito duas vezes – uma vez no código de teste que verifica o comportamento, e uma vez no código de produção que exibe esse comportamento. Os dois fluxos de código são escritos simultaneamente e seguem caminhos de execução complementares, embora separados, até que convergem na contagem de defeitos – uma contagem que deve ser zero.

Outro paralelo é a granularidade das duas disciplinas. A contabilidade de entrada dupla opera com a granularidade extremamente fina de transações individuais. O TDD opera com a granularidade fina e equivalente de comportamentos e afirmações individuais. Em ambos os casos, a divisão entre os grânulos é natural e óbvia. Não há outro grânulo para a contabilidade; e é difícil imaginar um grânulo mais apropriado para software.

Ainda assim, outro paralelo é o feedback imediato das duas abordagens. Erros são detectados em cada grânulo. Contadores são ensinados a verificar os resultados de cada transação. Programadores usando TDD são ensinados a verificar os testes para cada afirmação. Portanto, quando corretamente executado, nenhum erro pode infiltrar-se e, assim, corromper grandes partes dos modelos. O feedback rápido, em ambos os casos, impede longas horas de depuração e retrabalho.

Apesar de essas duas disciplinas parecerem tão semelhantes na superfície, existem algumas profundas diferenças entre elas. Algumas são óbvias, tais como o fato de que uma trata de números e contas, enquanto a outra trata de funções e afirmações. Outras diferenças são menos óbvias e muito mais profundas.

Assimetria

Contabilidade de entrada dupla é simétrica. Os débitos e os créditos não têm prioridade relativa. Cada um deles é derivável do outro. Se você sabe as contas creditadas e as transações, então você pode derivar um conjunto razoável de contas debitadas, e vice-versa. Portanto, não há nenhuma razão que justifique que contadores devam entrar primeiro com crédito ou débito. A escolha é arbitrária. A subtração irá funcionar em ambos os casos.

Isso não é verdade no TDD. Há uma seta óbvia entre os testes e o código de produção. Os testes dependem do código de produção; o código de produção não depende dos testes. Isso é verdade tanto em tempo de compilação quanto em tempo de execução. A seta aponta em uma direção e em apenas uma única direção.

Essa assimetria leva à conclusão inevitável de que a equivalência à contabilidade de entrada dupla só funciona se os testes forem escritos primeiro. Não há maneira de criar uma disciplina equivalente se o código de produção for escrito antes dos testes.

Isso pode ser difícil de ver no início. Então vamos usar o velho truque matemático de exclusão por absurdo. Mas, antes de fazer isso, vamos declarar TDD com a formalidade que pode ser invertida, a formalidade das três leis. Essas leis são:

  1. Você não tem permissão para escrever qualquer código de produção sem primeiro escrever um teste que falhe, porque o código de produção não existe.
  1. Você não tem permissão para escrever mais de um teste do que o suficiente para falhar, incluindo falha de compilação.
  1. Você não tem permissão para escrever mais código de produção do que o suficiente para passar no teste atualmente falhando.

Seguir essas três leis resulta em um procedimento muito ordenado e discreto:

  • Você deve decidir qual função de código de produção você pretende criar.
  • Você deve escrever um teste que falhe porque esse código de produção não existe.
  • Você deve parar de escrever esse teste assim que ele falhar por qualquer motivo, incluindo erros de compilação.
  • Você deve escrever apenas o código de produção que faça o teste passar.
  • Repita indefinidamente.

Observe como a disciplina reforça a granularidade fina de comportamentos e afirmações individuais, incluindo afirmações de tempo de compilação. Observe que há muito pouca ambiguidade sobre quanto código escrever em qualquer ponto, e se esse código deve ser código de produção ou código de teste. Essas três leis te amarram em um comportamento muito apertado.

Deve ficar muito claro que seguir o processo ditado pelas três leis é o equivalente lógico da contabilidade de entrada dupla.

Exclusão por absurdo

Agora, vamos supor que uma disciplina semelhante possa ser definida como aquela que inverte a ordem, de modo que o código de teste é escrito após o código de produção. Como escreveríamos tal disciplina?

Poderíamos começar simplesmente por inverter as três leis. Mas, logo que fazemos isso, nós nos deparamos com problemas:

  • 1) Você não tem permissão para escrever qualquer código de teste sem primeiro escrever código de produção que…

Como você completa essa regra? Na regra não invertida, a sentença é completada exigindo que o teste falhe porque o código de produção ainda não existe. Mas qual é a condição para nossa nova regra invertida? Poderíamos escolher algo arbitrário como “… é uma função completa”. No entanto, isso não é realmente um inverso adequado da primeira lei do TDD.

Na verdade, não há inverso adequado. A primeira lei não pode ser invertida. A razão é que a primeira lei presume que você sabe qual recurso de produção você está prestes a criar – mas assim deve qualquer primeira lei, incluindo qualquer primeira lei invertida.

Por exemplo, podemos tentar inverter a primeira lei da seguinte maneira:

  • 1) Você não tem permissão para escrever qualquer código de teste sem primeiro escrever código de produção que irá falhar o código de teste para o comportamento que você está escrevendo.

Eu acho que você pode ver porque isso não é realmente uma inversão. A fim de seguir esta regra, você teria que escrever o código de teste em sua mente, em primeiro lugar, e depois escrever o código de produção que o fez falhar. Em essência, o teste foi identificado antes de o código de produção ser escrito. O teste ainda está em primeiro lugar.

Você pode objetar observando que é sempre possível escrever testes após o código de produção e que, de fato, os programadores têm feito isso há anos. Isso é verdade, mas nosso objetivo era escrever uma regra que era o inverso da primeira lei de TDD. Uma regra que nos força ao mesmo nível de granularidade de comportamentos e afirmações, mas isso nos fez inventar os testes por último. Essa regra não parece existir.

A segunda regra tem problemas semelhantes.

  • 2) Você não está autorizado a escrever mais código de produção do que o suficiente para…

Como você completa essa frase? Não há limite óbvio para a quantidade de código de produção que você pode escrever. Novamente, se escolhermos um predicado, esse predicado será arbitrário. Por exemplo: …completar uma única função. Mas, claro, essa função poderia ser enorme, ou pequena, ou de qualquer tamanho. Perdemos a granularidade óbvia e natural dos comportamentos e afirmações individuais.

Então, mais uma vez, a regra não é possível de ser invertida.

Observe que essas falhas de invertibilidade são todas sobre granularidade. Quando os testes vêm primeiro, a granularidade é naturalmente limitada. Quando o código de produção vem em primeiro lugar, não há restrição.

Essa granularidade sem restrições implica algo mais profundo. Observe que a terceira lei do TDD obriga-nos a fazer o teste que falha do momento, e apenas o teste que falha do momento passa. Isso significa que o código de produção que estamos prestes a escrever será derivado do teste de falha. Mas, se você inverter a terceira lei, você acaba chegando a uma falta de sentido:

  • 3) Você não tem permissão para escrever mais código de teste do que o suficiente para passar o código de produção atual.

O que isso significa? Eu posso escrever um teste que passa o código de produção atual, escrevendo um teste sem afirmações – ou um teste com nenhum código. Essa regra está nos pedindo para testar todas as afirmações possíveis? Que afirmações são essas? Elas não foram identificadas.

Isso nos leva à conclusão de que os testes com granularidade fina não podem obviamente ser derivados do código de produção.

Vamos afirmar isso com mais precisão. É simples, usando as três leis do TDD, derivar a totalidade do código de produção a partir de uma série de testes de afirmação individuais, mas não é simples derivar a totalidade de um conjunto de testes a partir do código de produção concluído.

Isso não quer dizer que você não pode imputar testes do código de produção. Claro que você pode. O que isso está nos dizendo é: (e quem já tentou escrever testes de código legado sabe disso) é extremamente difícil, se não totalmente impraticável, escrever testes simples e abrangentes a partir do código de produção. Para escrever esses testes a partir do código de produção, você deve primeiro entender a totalidade desse código de produção, porque qualquer parte desse código de produção pode afetar o teste que você está tentando escrever. E, em segundo lugar, o código de produção deve ser desacoplado de forma a permitir a granularidade fina.

Quando os testes são escritos primeiro, granularidade e dissociação são triviais de alcançar. Quando os testes seguem o código de produção, dissociação e granularidade são muito mais difíceis de alcançar.

Irreversibilidade

Isso significa que os testes e o código de produção são irreversíveis. Contadores não têm esse problema. As contas debitadas e creditadas são mutuamente reversíveis. Você pode derivar uma da outra. Mas os testes e o código de produção progridem em uma direção.

Por que razão isso deveria ser assim?

A resposta está em mais uma assimetria entre os testes e o código de produção: sua estrutura. A estrutura do código de produção é muito diferente da estrutura do código de teste.

O código de produção forma um sistema com componentes interativos. Esse sistema funciona como um todo único. Ele é subdividido em componentes, separados por camadas de abstração e organizados com caminhos de comunicação, todos os quais suportam a operação, o rendimento e a manutenção desse sistema.

Os testes, por outro lado, não formam um sistema. Eles são, em vez disso, um conjunto de afirmações não relacionadas. Cada afirmação é independente de todas as outras. Cada pequeno teste no conjunto de teste está sozinho. Cada teste pode ser executado sozinho. De fato, os testes não têm nenhuma ordem de execução preferida, e muitas estruturas de teste aplicam isso executando os testes em uma ordem aleatória.

A disciplina do TDD nos diz para construir o código de produção em um pequeno caso de teste de cada vez. Essa disciplina também nos dá orientação sobre a ordem em que escrever esses testes. Nós escolhemos os testes mais simples no início e só aumentamos a complexidade dos testes quando todos os testes mais simples foram escritos e passados.

Essa ordem é importante. Novatos no TDD muitas vezes obtêm a ordenação errada e descobrem que eles têm escrito um teste que os obriga a implementar código de produção demais. As regras do TDD nos dizem que se um teste não pode ser feito para passar por uma adição trivial ou mudança para o código de produção, então um teste mais simples deve ser escolhido.

Assim, os testes e sua ordenação formam as instruções de montagem para o código de produção. Se esses testes são feitos para passar nessa ordem, então o código de produção será montado através de uma série de passos triviais.

Mas, como todos sabemos, as instruções de montagem não são reversíveis. É difícil olhar para um avião, por exemplo, e derivar o procedimento de montagem. Por outro lado, dado o procedimento de montagem, um avião pode ser construído com uma peça de cada vez.

Assim, a conversão do conjunto de teste em código de produção é uma função trap door, um pouco como a multiplicação de dois grandes números primos. Pode ser trivialmente executada em uma direção, mas é muito difícil, se não completamente impraticável, executar na outra. Os testes podem conduzir o código de produção trivialmente, mas o código de produção não pode conduzir de forma prática o conjunto de testes equivalente.

Conclusão

O que podemos concluir disso é que há uma disciplina bem definida de test-first que é equivalente à contabilidade de dupla entrada, mas não há tal disciplina para test-after. É possível testar depois, é claro, mas não há como definir isso como uma disciplina. A disciplina só funciona em uma direção – test-first.

Como eu disse no início: as apostas são enormes. Milhões estão esperando para serem ganhos ou perdidos. Vidas e fortunas estão em jogo. Nossos negócios e, de fato, toda a nossa sociedade, dependem de nós. Qual disciplina usaremos para garantir que não os decepcionemos?

Se contadores podem fazê-lo, não podemos?

Claro que podemos.

***

Uncle Bob faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://blog.cleancoder.com/uncle-bob/2017/03/07/SymmetryBreaking.html.