Como remover uma função ou recurso ao usar TDD

20

Nos textos sobre TDD, frequentemente leio sobre "remover duplicação" ou "melhorar a legibilidade" durante a etapa de refatoração. Mas o que me faz remover uma função não utilizada?

Por exemplo, digamos que exista uma classe Ccom métodos a()e b(). Agora, eu acho que seria bom ter um método f()que seja direcionado C. De fato, f()substitui todas as chamadas para, b()com exceção dos testes de unidade que definiram / descreveram b(). Não é mais necessário - exceto para os testes.

É salvo apenas remover b()e todos os testes que o usaram? Isso faz parte de "melhorar a legibilidade"?

TobiMcNamobi
fonte
3
Basta adicionar um outro teste para verificar se a função não existe, e depois ir para corrigir os casos de teste de falha;)
Filip Haglund
@FilipHaglund Dependendo do idioma, isso pode ser possível. Mas como documentação de código isso pareceria estranho.
TobiMcNamobi
1
Apenas para quem não conhece: o comentário de @ FilipHaglund é obviamente uma piada. Não faça isso.
jhyot

Respostas:

16

Sim, claro. O código mais fácil de ler é aquele que não existe.

Dito isto, refatorar geralmente significa melhorar o código sem alterar seu comportamento. Se você pensa em algo que melhora o código, faça-o. Não há necessidade de encaixá-lo em algum buraco de pombo antes que você possa fazê-lo.

Sebastian Redl
fonte
@ jpmc26 Ágil, cara, ágil! :-)
TobiMcNamobi
1
"O código mais fácil de ler é aquele que não existe". - Eu estou pendurado que a citação na parede: D
winkbrace
27

Remover um método público não é "refatoração" - a refatoração está alterando a implementação enquanto continua a passar nos testes existentes.

No entanto, a remoção de um método desnecessário é uma alteração de design perfeitamente razoável.

O TDD explica isso até certo ponto, porque, ao revisar os testes, você pode observar que está testando um método desnecessário. Os testes estão direcionando seu design, porque você pode ir "Veja, este teste não tem nada a ver com o meu objetivo".

Pode se revelar mais em níveis mais altos de teste, em conjunto com ferramentas de cobertura de código. Se você executar testes de integração com cobertura de código e ver que os métodos não estão sendo chamados, é uma pista que um método não é usado. A análise de código estático também pode indicar que os métodos não são usados.

Existem duas abordagens para remover um método; ambos trabalham em diferentes circunstâncias:

  1. Exclua o método Siga os erros de compilação para excluir qualquer código e teste dependente. Se você achar que os testes afetados são descartáveis, efetue as alterações. Caso contrário, reverta.

  2. Exclua os testes que considera obsoletos. Execute todo o seu conjunto de testes com cobertura de código. Exclua os métodos que não foram exercidos pelo conjunto de testes.

(Isso pressupõe que sua suíte de testes tenha uma boa cobertura para começar).

fino
fonte
10

De fato, f () substitui todas as chamadas para b (), com exceção dos testes de unidade que definiram / descreveram b ()

IMHO o ciclo TDD típico será assim:

  • escreva testes com falha para f () (provavelmente com base nos testes para b ()): os testes ficam vermelhos

  • implementar f () -> testes ficam verdes

  • refatorar : -> remover b () e todos os testes para b ()

Para a última etapa, considere remover b () primeiro e ver o que acontece (ao usar uma linguagem compilada, o compilador deve reclamar apenas dos testes existentes; caso contrário, os testes de unidade antigos para b falharão, por isso claro que você precisa removê-los também).

Doc Brown
fonte
4

Sim.

O melhor, mais livre de erros e mais legível é o código que não existe. Esforce-se para escrever o máximo de código possível, cumprindo seus requisitos.

Kilian Foth
fonte
9
Você perdeu a parte "como fazer".
JeffO 24/05
2

É desejável remover b()uma vez que não seja mais usado, pelo mesmo motivo que é desejável não adicionar funções não usadas em primeiro lugar. Quer você chame de "legibilidade" ou qualquer outra coisa, tudo o mais é igual, é uma melhoria no código que não contém nada para o que não seja útil. Por uma questão de ter pelo menos uma medida específica pela qual é melhor não tê-la, removê-la garante que seu custo futuro de manutenção após essa alteração seja zero!

Não achei necessária nenhuma técnica especial para removê-la com seus testes, pois qualquer pensamento de substituição b()por algo novo deve, é claro, ser acompanhado por uma consideração de todo o código que está sendo chamado atualmente b(), e os testes são um subconjunto de "todo o código "

A linha de raciocínio que geralmente funciona para mim é que, no momento em que percebo que isso f()se tornou b()obsoleto, b()deve ser pelo menos preterido, e estou procurando encontrar todas as chamadas b()com a intenção de substituí-las por chamadas para f(), I considere também o código de teste . Especificamente, se b()não for mais necessário, eu posso e devo remover seus testes de unidade.

Você está certo de que nada me obriga a notar que b()não é mais necessário. É uma questão de habilidade (e, como diz Slim, relatórios de cobertura de código em testes de nível superior). Se apenas testes de unidade e nenhum teste funcional se referirem b(), posso ser cautelosamente otimista de que não faz parte de nenhuma interface publicada e, portanto, removê-la não é uma mudança de quebra para nenhum código que não esteja sob meu controle direto.

O ciclo vermelho / verde / refatorado não menciona explicitamente a remoção de testes. Além disso, a remoção b()viola o princípio de abrir / fechar, pois claramente seu componente está aberto para modificações. Portanto, se você quiser pensar nesta etapa como algo fora do TDD simples, vá em frente. Por exemplo, você pode ter algum processo para declarar um teste como "ruim", que pode ser aplicado neste caso para remover o teste, alegando que ele testa algo que não deveria estar lá (a função desnecessária b()).

Acho que, na prática, a maioria das pessoas provavelmente permite que uma certa quantidade de redesenho seja realizada junto com um ciclo de vermelho / verde / refator, ou eles consideram que a remoção de testes de unidade redundantes é uma parte válida de um "refator", embora seja estritamente falando não é refatoração. Sua equipe pode decidir quanto drama e papelada devem ser envolvidos para justificar essa decisão.

De qualquer forma, se isso b()fosse importante, haveria testes funcionais para isso, e esses não seriam removidos levemente, mas você já disse que existem apenas testes de unidade. Se você não distinguir adequadamente entre testes de unidade (gravados no design interno atual do código, que você alterou) e testes funcionais (gravados em interfaces publicadas, que talvez você não queira alterar), será necessário ter mais cuidado sobre a remoção de testes de unidade.

Steve Jessop
fonte
2

Uma coisa para sempre tentar lembrar é que agora estamos usando REPOSITÓRIOS DE CÓDIGO com CONTROLE DE VERSÃO. Na verdade, esse código excluído não se foi ... ainda está lá em algum lugar de uma iteração anterior. Então estrague tudo! Seja liberal com a tecla delete, porque você sempre pode voltar e recuperar esse método precioso e elegante que você pensou que poderia ser útil algum dia ... se isso acontecer algum dia. Está lá.

Obviamente, isso acompanha a advertência dos males e o perigo de versões não compatíveis com versões anteriores ... aplicativos externos que dependiam da implementação da sua interface, que agora ficam órfãos pelo seu código (repentinamente) obsoleto.

dwoz
fonte
A exclusão do código não é um problema em geral. Eu amo excluir para excluir linhas, funções ou ... bem, módulos inteiros! Se possível. E eu faço isso tudo com ou sem TDD. Mas: existe um ponto no fluxo de trabalho do TDD em que o código (não dupli) é excluído? Caso contrário, ele será excluído de qualquer maneira. Mas existe? Essa foi a minha pergunta.
TobiMcNamobi 25/05