O que ajudaria ao refatorar um método grande para garantir que eu não quebre nada?

10

No momento, estou refatorando parte de uma grande base de código sem nenhum teste de unidade. Tentei refatorar o código da maneira bruta, ou seja, tentando adivinhar o que o código está fazendo e quais mudanças não o mudariam, mas sem sucesso: ele quebra aleatoriamente os recursos em toda a base de código.

Observe que a refatoração inclui mover o código C # herdado para um estilo mais funcional (o código herdado não usa nenhum dos recursos do .NET Framework 3 e posterior, incluindo o LINQ), adicionando genéricos nos quais o código pode se beneficiar deles, etc.

Não posso usar métodos formais , considerando quanto eles custariam.

Por outro lado, presumo que pelo menos a regra "Qualquer código legado refatorado venha com testes de unidade" deve ser rigorosamente seguida, não importa quanto custaria. O problema é que, quando refatoro uma parte minúscula de um método privado 500 LOC, adicionar testes de unidade parece ser uma tarefa difícil.

O que pode me ajudar a saber quais testes de unidade são relevantes para um determinado pedaço de código? Suponho que a análise estática do código seja útil de alguma forma, mas quais são as ferramentas e técnicas que posso usar para:

  • Saiba exatamente quais testes de unidade devo criar,

  • E / ou sei se a alteração que fiz afetou o código original de uma maneira que está sendo executada de maneira diferente a partir de agora?

Arseni Mourzenko
fonte
Qual é o seu raciocínio de que escrever testes de unidade aumentará o tempo para este projeto? Muitos proponentes discordariam, mas também depende da sua capacidade de escrevê-los.
Jeffo
Não digo que aumentará o tempo total do projeto. O que eu queria dizer é que aumentará o tempo a curto prazo (ou seja, o tempo imediato que gasto agora ao refatorar o código).
Arseni Mourzenko
11
Você não gostaria de usá- formal methods in software developmentlo de qualquer maneira, porque é usado para provar a correção de um programa usando lógica de predicados e não teria aplicabilidade para refatorar uma grande base de código. Métodos formais normalmente usados ​​para provar o código funcionam corretamente em áreas como aplicações médicas. Você está certo, é caro fazer, e é por isso que não é usado com frequência.
Mushy
Uma boa ferramenta, como as opções de refatoração no ReSharper , facilita muito essa tarefa. Em situações como esta, vale a pena o dinheiro gasto.
precisa saber é o seguinte
11
Não é uma resposta completa, mas uma técnica idiota que me surpreende ser eficaz quando todas as outras técnicas de refatoração estão falhando comigo: crie uma nova classe, divida a função em funções separadas com exatamente o código já existente, apenas quebre a cada 50 linhas, promova qualquer locais que são compartilhados entre as funções dos membros, então as funções individuais se encaixam melhor na minha cabeça e me dão a capacidade de ver nos membros quais partes estão enfileiradas em toda a lógica. Esse não é um objetivo final, é apenas uma maneira segura de deixar uma bagunça herdada pronta para refatorar com segurança.
Jimmy Hoffa

Respostas:

12

Eu tive desafios semelhantes. O livro Trabalhando com Código Legado é um ótimo recurso, mas há uma suposição de que você pode usar a buzina em testes de unidade para apoiar seu trabalho. Às vezes isso simplesmente não é possível.

No meu trabalho de arqueologia (meu termo para manutenção em código legado como esse), sigo uma abordagem semelhante ao que você descreveu.

  • Comece com uma sólida compreensão do que a rotina está fazendo atualmente.
  • Ao mesmo tempo, identifique o que a rotina deveria estar fazendo. Muitos acham que essa bala e a anterior são iguais, mas há uma diferença sutil. Muitas vezes, se a rotina estivesse fazendo o que deveria estar fazendo, você não aplicaria alterações de manutenção.
  • Execute algumas amostras na rotina e verifique os casos de limite, os caminhos de erro relevantes e o caminho da linha principal. Minha experiência é que o dano colateral (quebra de recurso) vem de condições de contorno que não estão sendo implementadas exatamente da mesma maneira.
  • Após esses casos de amostra, identifique o que está sendo persistido que não precisa necessariamente ser persistido. Mais uma vez, descobri que são efeitos colaterais como esses que levam a danos colaterais em outros lugares.

Nesse ponto, você deve ter uma lista de candidatos do que foi exposto e / ou manipulado por essa rotina. Algumas dessas manipulações provavelmente são inadvertidas. Agora eu uso findstro IDE para entender quais outras áreas podem referenciar os itens na lista de candidatos. Passarei algum tempo entendendo como essas referências estão funcionando e qual é a sua natureza.

Finalmente, depois de me iludir pensando que entendo os impactos da rotina original, farei minhas alterações uma por vez e executarei novamente as etapas de análise descritas acima para verificar se as alterações estão funcionando conforme o esperado para funcionar. Eu tento especificamente evitar alterar várias coisas ao mesmo tempo, pois descobri que isso explode quando tento verificar o impacto. Às vezes, você pode se safar de várias alterações, mas se eu puder seguir uma rota de cada vez, essa é a minha preferência.

Em resumo, minha abordagem é semelhante à que você expôs. É muito trabalho de preparação; depois faça mudanças individuais e cautelosas; e verifique, verifique, verifique.


fonte
2
+1 para o uso da "arqueologia" sozinho. Isso é o mesmo termo que eu uso para descrever esta atividade e eu acho que é uma ótima maneira de colocá-lo (também pensou que a resposta foi bom - eu não sou realmente tão superficial)
Erik Dietrich
10

O que ajudaria ao refatorar um método grande para garantir que eu não quebre nada?

Resposta curta: pequenos passos.

O problema é que, quando refatoro uma parte minúscula de um método privado 500 LOC, adicionar testes de unidade parece ser uma tarefa difícil.

Considere estas etapas:

  1. Mova a implementação para uma função diferente (privada) e delegue a chamada.

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. Adicione o código de registro (verifique se o registro não falha) em sua função original, para todas as entradas e saídas.

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

    Execute seu aplicativo e faça o que puder com ele (uso válido, uso inválido, uso típico, uso atípico, etc.).

  3. Agora você tem max(call_count)conjuntos de entradas e saídas para escrever seus testes; Você pode escrever um único teste que repete todos os seus parâmetros / conjuntos de resultados que você possui e os executa em um loop. Você também pode escrever um teste adicional que executa uma combinação específica (a ser usada para verificar rapidamente a passagem de um conjunto de E / S específico).

  4. Mover // 500 LOC herede volta para sua ugly500locfunção (e remover a funcionalidade do log).

  5. Comece a extrair funções da grande função (não faça mais nada, apenas extraia funções) e execute testes. Após isso, você deverá ter mais poucas funções para refatorar, em vez da 500LOC.

  6. Viva feliz para sempre.

utnapistim
fonte
3

Normalmente, os testes de unidade são o caminho a percorrer.

Faça os testes necessários que provam que a corrente funciona conforme o esperado. Não se apresse e o último teste deve deixá-lo confiante na saída.

O que pode me ajudar a saber quais testes de unidade são relevantes para um determinado pedaço de código?

Você está refatorando um pedaço de código, precisa saber exatamente o que ele faz e o que afeta. Então, basicamente, você precisa testar todas as zonas impactadas. Isso levará muito tempo ... mas esse é o resultado esperado de qualquer processo de refatoração.

Então você pode separar tudo sem problemas.

AFAIK, não existe uma técnica à prova de balas para isso ... você só precisa ser metódico (em qualquer método que se sinta confortável), muito tempo e muita paciência! :)

Felicidades e boa sorte!

Alex

AlexCode
fonte
As ferramentas de cobertura de código são essenciais aqui. É difícil confirmar que você percorreu todos os caminhos através de um método complexo e amplo por meio de inspeção. Uma ferramenta que mostra que KitchenSinkMethodTest01 () ... KitchenSinkMethodTest17 () abrange as linhas 1-45, 48-220, 245-399 e 488-500, mas não toca no código entre; fará com que descobrir quais testes adicionais você precise escrever seja muito mais simples.
Dan Is Fiddling Por Firelight