No Test Driven Development (TDD), você inicia com uma solução subótima e, em seguida, produz melhores soluções adicionando casos de teste e refatorando. As etapas devem ser pequenas, o que significa que cada nova solução estará de alguma forma na vizinhança da anterior.
Isso se parece com os métodos matemáticos de otimização local, como descida por gradiente ou pesquisa local. Uma limitação bem conhecida de tais métodos é que eles não garantem encontrar o ideal global ou mesmo o ideal local aceitável. Se o seu ponto de partida estiver separado de todas as soluções aceitáveis por uma grande região de soluções ruins, é impossível chegar lá e o método falhará.
Para ser mais específico: estou pensando em um cenário em que você implementou vários casos de teste e depois descobri que o próximo caso de teste exigiria uma abordagem diferente e competitiva. Você terá que jogar fora seu trabalho anterior e começar de novo.
Esse pensamento pode realmente ser aplicado a todos os métodos ágeis que prosseguem em pequenas etapas, não apenas ao TDD. Essa analogia proposta entre TDD e otimização local tem sérias falhas?
fonte
Respostas:
Para tornar sua comparação mais adequada: para alguns tipos de problemas, é muito provável que os algoritmos de otimização iterativa produzam ótimas ótimas locais; em algumas outras situações, elas podem falhar.
Posso imaginar uma situação em que isso pode acontecer na realidade: quando você escolhe a arquitetura errada de uma maneira, precisa recriar todos os seus testes existentes novamente do zero. Digamos que você comece a implementar seus primeiros 20 casos de teste na linguagem de programação X no sistema operacional A. Infelizmente, o requisito 21 inclui que todo o programa precise ser executado no sistema operacional B, onde X não está disponível. Portanto, você precisa jogar fora a maior parte do seu trabalho e reimplementá-lo na linguagem Y. (É claro que você não jogaria fora o código completamente, mas o portaria para o novo idioma e sistema).
Isso nos ensina que, mesmo ao usar o TDD, é uma boa ideia fazer algumas análises e projetos gerais com antecedência. Isso, no entanto, também é válido para qualquer outro tipo de abordagem, portanto não vejo isso como um problema inerente ao TDD. E, para a maioria das tarefas de programação do mundo real, você pode simplesmente escolher uma arquitetura padrão (como linguagem de programação X, sistema operacional Y, sistema de banco de dados Z no hardware XYZ) e pode ter certeza de que uma metodologia iterativa ou ágil como TDD não o levará a um beco sem saída.
Citando Robert Harvey: "Você não pode desenvolver uma arquitetura a partir de testes de unidade". Ou pdr: "TDD não só me ajuda a obter o melhor design final, mas também a chegar lá em menos tentativas".
Então, na verdade, o que você escreveu
pode se tornar realidade - quando você escolhe uma arquitetura errada, é provável que não alcance a solução necessária a partir daí.
Por outro lado, quando você faz um planejamento geral de antemão e escolhe a arquitetura certa, o uso do TDD deve ser como iniciar um algoritmo de pesquisa iterativo em uma área em que você pode esperar atingir o "máximo global" (ou pelo menos um máximo suficientemente bom) ) em alguns ciclos.
fonte
Não acho que o TDD tenha um problema de máximos locais. O código que você escreve pode, como você notou corretamente, mas é por isso que a refatoração (reescrevendo o código sem alterar a funcionalidade) está em vigor. Basicamente, à medida que seus testes aumentam, você pode reescrever partes significativas do seu modelo de objeto, se precisar, mantendo o comportamento inalterado graças aos testes. Os testes declaram verdades invariáveis sobre o seu sistema que, portanto, precisam ser válidas tanto no máximo local quanto no absoluto.
Se você está interessado em problemas relacionados ao TDD, posso mencionar três diferentes nos quais penso frequentemente:
O problema da completude : quantos testes são necessários para descrever completamente um sistema? A "codificação por exemplos de casos" é uma maneira completa de descrever um sistema?
O problema de proteção : qualquer que seja a interface de teste, precisa ter uma interface imutável. Os testes representam verdades invariantes , lembre-se. Infelizmente, essas verdades não são conhecidas na maior parte do código que escrevemos, na melhor das hipóteses apenas para objetos externos.
O problema do dano no teste : para tornar as afirmações testáveis, podemos precisar escrever código abaixo do ideal (menos desempenho, por exemplo). Como escrevemos testes para que o código seja o melhor possível?
Editado para endereçar um comentário: veja um exemplo de como exceder um máximo local para uma função "dupla" via refatoração
Teste 1: quando a entrada for 0, retorne zero
Implementação:
Refatoração: não necessário
Teste 2: quando a entrada for 1, retorne 2
Implementação:
Refatoração: não necessário
Teste 3: quando a entrada for 2, retorne 4
Implementação:
Refatoração:
fonte
O que você está descrevendo em termos matemáticos é o que chamamos de se pintar em um canto. Esta ocorrência dificilmente é exclusiva do TDD. Em cascata, você pode reunir e despejar os requisitos por meses, esperando ver o máximo global apenas para chegar lá e perceber que há uma idéia melhor apenas na próxima colina.
A diferença está em um ambiente ágil que você nunca esperou ser perfeito neste momento, então você está mais do que pronto para lançar a velha idéia e passar para a nova.
Mais específico para o TDD, existe uma técnica para impedir que isso aconteça ao adicionar recursos no TDD. É a premissa de prioridade de transformação . Onde o TDD tem uma maneira formal de refatorar, essa é uma maneira formal de adicionar recursos.
fonte
Em sua resposta , @Sklivvz argumentou de forma convincente que o problema não existe.
Quero argumentar que isso não importa: a premissa fundamental (e a razão de ser) das metodologias iterativas em geral e do Agile e, especialmente, do TDD em particular, é que não apenas o ideal global, mas também os ideais locais não são ' não sabia. Portanto, em outras palavras: mesmo que isso tenha sido um problema, não há maneira de fazê-lo da maneira iterativa de qualquer maneira. Supondo que você aceite a premissa básica.
fonte
As práticas de TDD e Agile podem prometer produzir uma solução ideal? (Ou mesmo uma solução "boa"?)
Não exatamente. Mas esse não é o objetivo deles.
Esses métodos simplesmente fornecem "passagem segura" de um estado para outro, reconhecendo que as mudanças são demoradas, difíceis e arriscadas. E o objetivo de ambas as práticas é garantir que o aplicativo e o código sejam viáveis e comprovados para atender aos requisitos de maneira mais rápida e regular.
O TDD se concentra em garantir que cada "pedaço" de código atenda aos requisitos. Em particular, ajuda a garantir que o código atenda aos requisitos pré-existentes, em vez de permitir que os requisitos sejam conduzidos por uma codificação ruim. Mas, não promete que a implementação seja "ideal" de forma alguma.
Quanto aos processos ágeis:
A agilidade não está procurando uma solução ideal ; apenas uma solução funcional - com a intenção de otimizar o ROI . Ele promete uma solução de trabalho mais cedo ou mais tarde ; não é o "ideal".
Mas tudo bem, porque a pergunta está errada.
Os ótimos no desenvolvimento de software são alvos difusos e móveis. Os requisitos geralmente estão em fluxo e estão repletos de segredos que só surgem, para sua vergonha, em uma sala de conferências cheia dos chefes de seu chefe. E a "bondade intrínseca" da arquitetura e codificação de uma solução é classificada pelas opiniões subjetivas e divididas de seus colegas e do seu senhorio gerencial - nenhum dos quais pode realmente saber algo sobre um bom software.
No mínimo, as práticas de TDD e Ágil reconhecem as dificuldades e tentam otimizar para duas coisas que são objetivas e mensuráveis: Trabalhar v. Não trabalhar e mais cedo v. Mais tarde.
E, mesmo se tivermos "trabalhando" e "mais cedo" como métricas objetivas, sua capacidade de otimizar para elas depende principalmente da habilidade e experiência de uma equipe.
Coisas que você pode interpretar como esforços produzem soluções ideais incluem:
etc ..
Se cada uma dessas coisas realmente produz soluções ótimas seria outra ótima pergunta!
fonte
Uma coisa que ninguém adicionou até agora é que o "Desenvolvimento TDD" que você está descrevendo é muito abstrato e irreal. Pode ser assim em um aplicativo matemático em que você está otimizando um algoritmo, mas isso não acontece muito nos aplicativos de negócios nos quais os codificadores trabalham.
No mundo real, seus testes estão basicamente exercitando e validando Regras de Negócios:
Por exemplo - se um cliente é um não fumante de 30 anos com uma esposa e dois filhos, a categoria premium é "x" etc.
Você não alterará iterativamente o mecanismo de cálculo premium até que ele esteja correto por muito tempo - e quase certamente não enquanto o aplicativo estiver ativo;).
O que você realmente criou é uma rede de segurança, para que, quando um novo método de cálculo for adicionado para uma categoria específica de cliente, todas as regras antigas não subitamente quebrem e dêem a resposta errada. A rede de segurança é ainda mais útil se a primeira etapa da depuração for criar um teste (ou uma série de testes) que reproduza o erro antes de escrever o código para corrigir o bug. Então, um ano depois, se alguém acidentalmente recriar o bug original, o teste de unidade será interrompido antes que o código seja verificado. Sim, uma coisa que o TDD permite é que agora você pode fazer uma grande refatoração e arrumar coisas com confiança mas não deve ser uma parte enorme do seu trabalho.
fonte
Eu não acho que isso atrapalha. A maioria das equipes não tem ninguém capaz de encontrar uma solução ideal, mesmo que você a escreva no quadro branco. TDD / Agile não atrapalhará.
Muitos projetos não exigem soluções ótimas e, se o fizer, serão necessários tempo, energia e foco nesta área. Como tudo o mais, tendemos a construir, primeiro, fazê-lo funcionar. Então faça isso rápido. Você poderia fazer isso com algum tipo de protótipo se o desempenho fosse tão importante e depois reconstruir tudo com a sabedoria adquirida em muitas iterações.
Isso pode acontecer, mas o mais provável é que haja medo de alterar partes complexas do aplicativo. Não ter nenhum teste pode criar uma sensação maior de medo nessa área. Um benefício do TDD e ter um conjunto de testes é que você construiu este sistema com a noção de que ele precisará ser alterado. Quando você cria essa solução otimizada monolítica desde o início, pode ser muito difícil mudar.
Além disso, coloque isso no contexto de sua preocupação de subotimização, e você não pode deixar de gastar tempo otimizando coisas que não deveria ter e criando soluções inflexíveis porque estava muito concentrado no desempenho delas.
fonte
Pode ser enganador aplicar conceito matemático como "ótimo local" ao design de software. O uso desses termos faz com que o desenvolvimento de software pareça muito mais quantificável e científico do que realmente é. Mesmo que existisse "ideal" para o código, não temos como medi-lo e, portanto, não sabemos como o alcançamos.
O movimento ágil foi realmente uma reação contra a crença de que o desenvolvimento de software poderia ser planejado e previsto com métodos matemáticos. Para o bem ou para o mal, o desenvolvimento de software é mais uma arte do que uma ciência.
fonte
fibonacci
, que já vi usado como exemplo / tutorial de TDD, é mais ou menos uma mentira. Estou disposto a apostar que ninguém nunca "descobriu" fibonacci ou qualquer outra série semelhante ao TDD. Todo mundo começa a conhecer fibonacci, o que é trapaça. Se você tentar descobrir isso com TDD, provavelmente chegará ao beco sem saída com o qual o OP estava perguntando: você nunca será capaz de generalizar a série simplesmente escrevendo mais testes e refatorando - você deve aplicar a matemática raciocínio!