Essa limitação do desenvolvimento orientado a testes (e ágil em geral) é praticamente relevante?


30

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?


Você está se referindo à sub-técnica TDD chamada triangulação ? Por "solução aceitável", você quer dizer uma solução correta ou uma que pode ser mantida / elegante / legível?
precisa saber é o seguinte

6
Eu acho que isso é um problema real. Como é apenas minha opinião, não vou escrever uma resposta. Mas sim, como o TDD é apresentado como uma prática de design , é uma falha que ele possa levar ao máximo local ou a nenhuma solução. Eu diria que, em geral, o TDD NÃO é adequado para o design algorítmico. Veja a discussão relacionada sobre as limitações do TDD: Resolvendo o Sudoku com o TDD , em que Ron Jeffries se assusta enquanto corre em círculos e "pratica o TDD", enquanto Peter Norvig fornece a solução real, conhecendo realmente o assunto,
Andres F.

5
Em outras palavras, eu ofereceria a afirmação (espero) incontroversa de que o TDD é bom para minimizar a quantidade de classes que você escreve em problemas "conhecidos", produzindo assim um código mais limpo e simples, mas não é adequado para problemas algorítmicos ou para problemas complexos onde observar o panorama geral e ter conhecimentos específicos de domínio é mais útil do que escrever testes fragmentados e "descobrir" o código que você deve escrever.
Andrés F.

2
O problema existe, mas não está limitado a TDD ou mesmo ágil. A mudança de requisitos que significa que o design de software escrito anteriormente precisa mudar o tempo todo.
RemcoGerlich

@ guillaume31: Não é necessariamente triangulação, mas qualquer técnica que utiliza iterações no nível do código-fonte. Por solução aceitável Quero dizer que passa todos os testes e pode ser mantido razoavelmente bem ..
Frank soprador

Respostas:


8

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.

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.

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. Você terá que jogar fora seu trabalho anterior e começar de novo.

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

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á.

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.


20

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:

  1. 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?

  2. 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.

  3. 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:

function double(x) {
  return 0; // simplest possible code that passes tests
}

Refatoração: não necessário

Teste 2: quando a entrada for 1, retorne 2

Implementação:

function double(x) {
  return x==0?0:2; // local maximum
}

Refatoração: não necessário

Teste 3: quando a entrada for 2, retorne 4

Implementação:

function double(x) {
  return x==0?0:x==2?4:2; // needs refactoring
}

Refatoração:

function double(x) {
  return x*2; // new maximum
}

11
O que experimentei, porém, é que meu primeiro design funcionou apenas em alguns casos simples e, posteriormente, percebi que precisava de uma solução mais geral. O desenvolvimento da solução mais geral exigiu mais testes, enquanto os testes originais para casos especiais não funcionarão novamente. Achei aceitável remover (temporariamente) esses testes enquanto desenvolvo a solução mais geral, adicionando-os novamente quando o tempo estiver pronto.
precisa saber é o seguinte

3
Não estou convencido de que a refatoração seja uma maneira de generalizar o código (fora do espaço artificial dos "padrões de design", é claro) ou de escapar ao máximo local. A refatoração organiza o código, mas não ajuda a descobrir uma solução melhor.
Andrés F.

2
@ Sklivvz Entendido, mas acho que não funciona assim fora dos exemplos de brinquedos como os que você postou. Além disso, ajudou você a nomear sua função "double"; de uma maneira que você já sabia a resposta. O TDD definitivamente ajuda quando você sabe mais ou menos a resposta, mas deseja escrevê-la "de maneira limpa". Não ajudaria a descobrir algoritmos ou escrever código realmente complexo. É por isso que Ron Jeffries não conseguiu resolver o Sudoku dessa maneira; você não pode implementar um algoritmo que você não está familiarizado com TDD, deixando-o obscuro.
Andres F.

11
@VaughnCato Ok, agora estou na posição de confiar em você ou ser cético (o que seria rude, então não vamos fazer isso). Digamos que, na minha experiência, não funciona como você diz. Eu nunca vi um algoritmo razoavelmente complexo evoluir do TDD. Talvez minha experiência seja muito limitada :)
Andres F.

2
@ Sklivvz "Contanto que você possa escrever os testes apropriados" é precisamente o ponto: parece que está me implorando a pergunta. O que estou dizendo é que muitas vezes você não pode . Pensar em um algoritmo ou em um solucionador não é mais fácil escrevendo primeiro os testes . Você deve olhar para a foto inteira primeiro . É claro que tentar cenários é necessário, mas observe que o TDD não é sobre escrever cenários: o TDD é sobre testar o design ! Você não pode conduzir o design de um solucionador de Sudoku (ou um novo solucionador para um jogo diferente) escrevendo primeiro os testes. Como evidência anedótica (o que não é suficiente): Jeffries não pôde.
Andres F.

13

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.


13

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.


8

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.

... [TDD] se opõe ao desenvolvimento de software que permite a adição de software que não é comprovadamente atendido aos requisitos ... Kent Beck, que é creditado por ter desenvolvido ou 'redescoberto' a técnica, afirmou em 2003 que o TDD incentiva o simples projeta e inspira confiança. ( Wikipedia )

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:

O software de trabalho é a principal medida de progresso ... No final de cada iteração, as partes interessadas e o representante do cliente analisam o progresso e reavaliam as prioridades com o objetivo de otimizar o retorno do investimento ( Wikipedia )

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!


11
É verdade, mas não escrevi que o objetivo do TDD ou de qualquer outro método de desenvolvimento de software seja uma solução ideal no sentido de um ótimo global. Minha única preocupação é que as metodologias baseadas em pequenas iterações a nível de código-fonte pode não encontrar qualquer solução aceitável (boa o suficiente) em tudo, em muitos casos
Frank soprador

@Frank Minha resposta tem como objetivo cobrir os ideais locais e globais. E a resposta de qualquer maneira é "Não, não é para isso que essas estratégias são projetadas - elas são projetadas para melhorar o ROI e mitigar os riscos". ... ou algo assim. E isso se deve em parte à resposta de Jörg: os "ótimos" são alvos móveis. ... Eu daria um passo adiante; não apenas eles estão movendo alvos, mas também não são inteiramente objetivos ou mensuráveis.
svidgen

@FrankPuffer Talvez valha um adendo. Mas, o ponto básico é que você está perguntando se essas duas coisas alcançam algo que não foram projetadas nem pretendem alcançar. Além disso, você está perguntando se eles conseguem algo que nem sequer pode ser medido ou verificado.
precisa saber é

@FrankPuffer Bah. Tentei atualizar minha resposta para dizer melhor. Não tenho certeza se o fiz melhor ou pior! ... Mas preciso sair do SE.SE e voltar ao trabalho.
precisa saber é

Essa resposta é boa, mas o problema que tenho com ela (como em algumas das outras respostas) é que "mitigar riscos e melhorar o ROI" nem sempre são os melhores objetivos. Na verdade, eles não são objetivos por si mesmos. Quando você precisa de algo para trabalhar, mitigar o risco não é suficiente. Às vezes, pequenos passos relativamente não direcionados, como no TDD, não funcionam - você minimiza os riscos, mas não alcança nenhum lugar útil no final.
Andres F.

4

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.


11
Primeiro, quando li sua resposta, pensei "sim, esse é o ponto principal". Mas, depois de repensar a questão novamente, pensei que não era necessariamente tão abstrata ou irrealista. Se você escolher cegamente a arquitetura completamente errada, o TDD não resolverá isso, depois de 1000 iterações.
Doc Brown

@ Doc Brown Concordou, não vai resolver esse problema. Mas ele fornecerá um conjunto de testes que exercitam todas as suposições e regras de negócios para que você possa melhorar iterativamente a arquitetura. Uma arquitetura tão ruim que precisa de uma reescrita básica para corrigir é muito rara (eu espero) e, mesmo nesse caso extremo, os testes de unidade de regra de negócios seriam um bom ponto de partida.
Mcottle

Quando digo "arquitetura incorreta", tenho em mente casos em que é preciso jogar fora o conjunto de testes existente. Você leu minha resposta?
Doc Brown

@DocBrown - Sim, eu fiz. Se você quis dizer "arquitetura incorreta" significa "alterar todo o conjunto de testes", talvez devesse ter dito isso. Alterar a arquitetura não significa que você precisará descartar todos os testes se eles forem baseados em regras de negócios. Você provavelmente precisará alterar todos eles para dar suporte a quaisquer novas interfaces criadas e até mesmo reescrever algumas, mas as Regras de Negócios não serão substituídas por uma alteração técnica, para que os testes permaneçam. Certamente investmenting em testes de unidade não deve ser invalidado pela possibilidade improvável de completamente effing-se a arquitetura
mcottle

Certamente, mesmo que seja necessário reescrever todos os testes em uma nova linguagem de programação, não é preciso jogar tudo fora, pelo menos é possível portar a lógica existente. E eu concordo com você 100% para os principais projetos do mundo real, as suposições da pergunta são bastante irrealistas.
Doc Brown

3

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.

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. Você terá que jogar fora seu trabalho anterior e começar de novo.

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.


0

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.


Mas foi uma reação muito forte? Certamente ajuda em muitos casos em que o planejamento inicial rigoroso se mostrou pesado e caro. No entanto, alguns problemas de software devem ser tratados como um problema matemático e com design inicial. Você não pode TDD-los. Você pode TDD a interface do usuário e o design geral do Photoshop, mas não pode TDD seus algoritmos. Eles não são exemplos triviais como derivar "soma" ou "duplo" ou "pow" em exemplos típicos de TDD [1]). Você provavelmente não pode provocar um novo filtro de imagem escrevendo alguns cenários de teste; você absolutamente deve se sentar, escrever e entender fórmulas.
Andres F.

2
[1] De fato, tenho certeza 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!
Andres F.

Duas observações: (1) Você está certo de que isso pode enganar. Mas não escrevi que TDD é o mesmo que otimização matemática. Eu apenas o usei como analogia ou modelo. Eu acredito que a matemática pode (e deve) ser aplicada a quase tudo, desde que você esteja ciente das diferenças entre o modelo e a coisa real. (2) A ciência (trabalho científico) geralmente é ainda menos previsível que o desenvolvimento de software. E eu diria até que a engenharia de software é mais um trabalho científico do que um ofício. O artesanato geralmente exige muito mais trabalho de rotina.
Frank Puffer

@AndresF .: TDD não significa que você não precisa pensar ou projetar. Significa apenas que você escreve o teste antes de escrever a implementação. Você pode fazer isso com algoritmos.
precisa saber é o seguinte

@FrankPuffer: OK, então qual é o valor mensurável que possui um "ótimo local" no desenvolvimento de software?
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.