Prefácio
Estou não está procurando uma maneira de refatorar uma grande classe código espaguete, o assunto foi abordado em outras questões.
Questão
Estou procurando técnicas para começar a entender um arquivo de classe escrito por outro colega de trabalho que abrange mais de 4000 linhas e tem um único método de atualização enorme que tem mais de 2000 linhas.
Eventualmente, espero criar testes de unidade para esta classe e refatorá-la em muitas classes menores que seguem o DRY e o Princípio de Responsabilidade Única.
Como posso gerenciar e abordar esta tarefa? Eu gostaria de poder eventualmente desenhar um diagrama dos eventos que ocorrem dentro da classe e depois passar para a funcionalidade de abstração, mas estou lutando para obter uma visão de cima para baixo de quais são as responsabilidades e dependências da classe.
Editar: nas respostas as pessoas já abordaram tópicos relacionados aos parâmetros de entrada, essas informações são para iniciantes:
Nesse caso, o método principal da classe não possui parâmetros de entrada e executa um loop while até que seja instruído a parar. O construtor também não aceita parâmetros.
Isso aumenta a confusão da classe, seus requisitos e dependências. A classe obtém referências a outras classes através de métodos estáticos, singletons e alcançando através de classes às quais já tem referência.
Respostas:
Curiosamente, a refatoração é a maneira mais eficiente que encontrei para entender códigos como este. Você não precisa fazer isso de forma limpa no início. Você pode fazer uma passagem de refatoração de análise rápida e suja, depois reverter e fazê-lo com mais cuidado com testes de unidade.
O motivo disso funcionar é porque a refatoração feita corretamente é uma série de pequenas mudanças quase mecânicas que você pode fazer sem realmente entender o código. Por exemplo, eu posso ver um blob de código que está sendo repetido em todos os lugares e fatorá-lo em um método, sem realmente precisar saber como ele funciona. Eu poderia dar a ele algum nome esfarrapado no começo,
step1
ou algo assim. Então percebo issostep1
estep7
pareço frequentemente aparecer juntos, e fatoro isso. Então, de repente, a fumaça se dissipou o suficiente para que eu pudesse realmente ver o quadro geral e criar alguns nomes significativos.Para encontrar a estrutura geral, descobri que a criação de um gráfico de dependência geralmente ajuda. Faço isso manualmente usando o graphviz, pois descobri que o trabalho manual me ajuda a aprendê-lo, mas provavelmente existem ferramentas automatizadas disponíveis. Aqui está um exemplo recente de um recurso em que estou trabalhando. Descobri que essa também é uma maneira eficaz de comunicar essas informações aos meus colegas.
fonte
Primeiro, uma palavra de cautela (mesmo que não seja o que você está perguntando) antes de responder à sua pergunta: Uma pergunta tremendamente importante a ser feita é por quevocê está querendo refatorar alguma coisa? O fato de você mencionar "colega de trabalho" levanta a questão: existe uma razão comercial para a mudança? Especialmente em um ambiente corporativo, sempre haverá códigos herdados e até mesmo recém-confirmados com os quais você não ficará satisfeito. O fato é que dois engenheiros não resolverão um determinado problema com a mesma solução. Se funciona e não tem implicações horríveis no desempenho, por que não deixar? O trabalho de recursos geralmente é muito mais benéfico do que a refatoração, porque você não gosta particularmente de uma implementação. Dito isto, há algo a ser dito em relação à dívida técnica e à limpeza de código não sustentável. Minha única sugestão aqui é que você já fez a distinção e se comprometeu a fazer as alterações, caso contrário você provavelmente será solicitado "depois de ter afundado muito esforço e provavelmente terá muito mais (e compreensível) reação.
Antes de abordar um refator, sempre quero ter uma compreensão firme do que estou mudando. Geralmente, existem dois métodos que utilizo para lidar com esse tipo de coisa: registro e depuração. Este último é facilitado pela existência de testes de unidade (e testes de sistema, se fizerem sentido). Como você não tem nenhum, eu fortemente exortá-lo a escrever testes com cobertura de ramo completo para garantir que as alterações não criam mudanças comportamentais inesperados. Os testes de unidade e o detalhamento do código ajudam a entender o comportamento de baixo nível em um ambiente controlado. O registro ajuda a entender melhor o comportamento na prática (isso é especialmente útil em situações multithread).
Uma vez que eu obter uma melhor compreensão de tudo o que está acontecendo através de depuração e exploração madeireira, então eu vou começar a ler código. Neste ponto, geralmente tenho uma compreensão muito melhor do que está acontecendo, e sentar e ler 2k LoC deve fazer muito mais sentido nesse momento. Minha intenção aqui é mais obter uma visão de ponta a ponta e garantir que não haja nenhum tipo de casos extremos que os testes de unidade que escrevi não estejam cobrindo.
Então vou pensar no design e elaborar um novo. Se a compatibilidade com versões anteriores tiver alguma importância, assegurarei que a interface pública não seja alterada.
Com um entendimento firme do que está acontecendo, testes e um novo design em mãos, eu o colocarei para revisão. Depois que o design tiver sido testado por pelo menos duas pessoas (espero familiarizado com o código), começarei a implementar as alterações.
Espero que isso não seja dolorosamente óbvio e ajude.
fonte
Não existe uma receita geral, mas algumas regras práticas ( supondo uma linguagem de tipo estaticamente, mas isso não deveria realmente importar):
1) Dê uma olhada na assinatura do método. Diz-lhe o que se passa em e espero que vem para fora . Do total estado deste deus de classe, eu acho que este é um primeiro ponto de dor . Suponho que mais de um parâmetro seja usado.
2) Use a função de pesquisa do seu editor / EDI para determinar Exit-Pontos (geralmente um retorno statement_ é usado)
Pelo que você sabe, o que a função precisa para testar e o que você espera em troca .
Assim, um primeiro teste simples seria chamar a função com os parâmetros necessários e esperar que o resultado é não nulo . Isso não é muito, mas um ponto de partida.
A partir daí, você pode entrar em um círculo hermenêutico (um termo cunhado por HG Gadamer - um filósofo alemão). O ponto é: você agora tem um entendimento rudimentar da classe e atualiza esse entendimento com novos conhecimentos detalhados e tem um novo entendimento de toda a classe.
Isso combinado com o método científico : faça suposições e veja se elas se sustentam.
3) Pegue um parâmetro e veja onde na classe ele é transformado de alguma forma:
Por exemplo, você está fazendo Java como eu, geralmente existem getter e setter para os quais você pode procurar. Searchpattern
$objectname
. (ou$objectname\.(get|set)
se você estiver executando Java)Agora você pode fazer outras suposições sobre o que o método faz.
Rastreie apenas os parâmetros de entrada ( primeiro ), cada um através do método Se necessário, faça alguns diagramas ou tabelas , onde você anota todas as alterações em cada uma das variáveis.
A partir disso, você pode escrever outros testes, descrevendo o comportamento do método. Se você tiver um entendimento aproximado de como cada parâmetro de entrada é transformado ao longo do método, comece a experimentar : passe nulo para um parâmetro ou entrada estranha . Faça suposições, verifique o resultado e varie entradas e suposições.
Se você fizer isso uma vez, terá uma "tonelada" de testes descrevendo o comportamento do seu método.
4) Em uma próxima etapa, eu procuraria dependências : o que o método precisa além de sua entrada para funcionar corretamente ? Existem possibilidades para reduzir ou reestruturar essas? Quanto menos dependências você tiver, mais claramente você verá os pontos onde realizar as primeiras divisões.
5) A partir daí, você pode percorrer toda a estrada de refatoração com padrões de refatoração e refatoração para padrões.
Aqui está um bom vídeo : GoGaRuCo 2014- O método científico de solução de problemas Trata-se de solução de problemas, mas útil para uma metodologia geral de entendimento de como algo funciona .
Você menciona que a função chamada não possui parâmetros de entrada : nesse caso especial, eu tentaria primeiro identificar as dependências e refatorá-las para parâmetros, para que você pudesse trocá-las como quiser.
fonte
Você mencionou um
update
método de 2000 linhas .Eu começaria a ler esse método de cima para baixo. Quando você encontrar um conjunto de instruções que pertencem uma à outra, ou seja, compartilhar uma responsabilidade (por exemplo, criar um usuário), extraia essas instruções em uma função.
Dessa forma, você não muda como o código funciona, mas você
Repita essas etapas para outros métodos.
fonte