Qual é a melhor maneira de lidar com a refatoração de um arquivo grande?

41

Atualmente, estou trabalhando em um projeto maior que, infelizmente, possui alguns arquivos em que nem sempre as diretrizes de qualidade de software são seguidas. Isso inclui arquivos grandes (leia 2000-4000 linhas) que claramente contêm várias funcionalidades distintas.

Agora, quero refatorar esses arquivos grandes em vários pequenos. O problema é que, como são grandes, várias pessoas (inclusive eu) em diferentes ramificações estão trabalhando nesses arquivos. Portanto, não posso realmente partir do desenvolvimento e refatorar, pois a fusão dessas refatorações com as mudanças de outras pessoas se tornará difícil.

É claro que poderíamos exigir que todos voltem a desenvolver, "congelem" os arquivos (ou seja, não deixem que ninguém os edite mais), refatore e depois "descongele". Mas isso também não é muito bom, pois isso exigiria que todos parassem seu trabalho nesses arquivos até a refatoração ser concluída.

Portanto, existe uma maneira de refatorar, não exigir que mais ninguém pare de trabalhar (por muito tempo) ou junte novamente seus ramos de recursos para desenvolver?

Hoff
fonte
6
Eu acho que isso também depende da linguagem de programação usada.
Robert Andrzejuk 28/03
8
Eu gosto de checkins "pequenos incrementais". A menos que alguém não mantenha a cópia do repositório atualizada, essa prática minimizará os conflitos de mesclagem para todos.
Matt Raffel 28/03
5
Como são seus testes? Se você for refatorar um grande pedaço de código (e provavelmente importante!), Verifique se o seu conjunto de testes está em boas condições antes de refatorar. Isso tornará muito mais fácil verificar se você acertou nos arquivos menores.
corsiKa 29/03
1
Existem inúmeras abordagens que você pode adotar e a melhor abordagem dependerá da sua situação.
Stephen
3
Entrei para o projeto, onde o maior arquivo tem 10k linhas, contendo, entre outras, uma classe com 6k linhas e todo mundo tem medo de tocá-lo. O que quero dizer é que sua pergunta é ótima. Até inventamos uma piada de que essa classe única é uma boa razão para destravar a roda de rolagem em nossos mouses.
ElmoVanKielmo 29/03

Respostas:

41

Você entendeu corretamente que isso não é tanto um problema técnico como social: se você deseja evitar conflitos excessivos de mesclagem, a equipe precisa colaborar de uma maneira que evite esses conflitos.

Isso faz parte de um problema maior com o Git, pois a ramificação é muito fácil, mas a fusão ainda pode exigir muito esforço. As equipes de desenvolvimento tendem a lançar muitos ramos e, em seguida, ficam surpresos com a dificuldade de mesclá-los, possivelmente porque estão tentando imitar o Git Flow sem entender seu contexto.

A regra geral para mesclagens rápidas e fáceis é impedir que grandes diferenças se acumulem, em particular que as ramificações de recursos devem ter vida útil muito curta (horas ou dias, não meses). Uma equipe de desenvolvimento capaz de integrar rapidamente suas alterações verá menos conflitos de mesclagem. Se algum código ainda não estiver pronto para produção, pode ser possível integrá-lo, mas desativá-lo por meio de um sinalizador de recurso. Assim que o código for integrado à sua ramificação principal, ele se torna acessível ao tipo de refatoração que você está tentando fazer.

Isso pode ser demais para o seu problema imediato. Mas pode ser possível pedir aos colegas para mesclar suas alterações que afetam esse arquivo até o final da semana, para que você possa executar a refatoração. Se eles esperarem mais, terão que lidar com os conflitos de mesclagem. Isso não é impossível, é apenas um trabalho evitável.

Você também pode impedir a quebra de grandes extensões de código dependente e fazer apenas alterações compatíveis com a API. Por exemplo, se você deseja extrair algumas funcionalidades em um módulo separado:

  1. Extraia a funcionalidade em um módulo separado.
  2. Altere as funções antigas para encaminhar suas chamadas para a nova API.
  3. Com o tempo, código dependente da porta para a nova API.
  4. Finalmente, você pode excluir as funções antigas.
  5. (Repita para o próximo monte de funcionalidades)

Esse processo de várias etapas pode evitar muitos conflitos de mesclagem. Em particular, só haverá conflitos se alguém também estiver alterando a funcionalidade que você extraiu. O custo dessa abordagem é que é muito mais lento do que alterar tudo de uma vez e que você tem temporariamente duas APIs duplicadas. Isso não é tão ruim até que algo urgente interrompa essa refatoração, a duplicação seja esquecida ou desvalorizada e você acabe com um monte de dívidas de tecnologia.

Mas, no final, qualquer solução exigirá que você coordene com sua equipe.

amon
fonte
1
Infelizmente, esse é um conselho extremamente geral, mas algumas idéias fora do espaço ágil como a Integração Contínua claramente têm seus méritos. As equipes que trabalham juntas (e integram seu trabalho com frequência) terão mais facilidade em fazer grandes mudanças transversais do que as equipes que trabalham apenas juntas. Isso não é necessariamente sobre o SDLC em geral, mais sobre a colaboração dentro da equipe. Algumas abordagens tornam o trabalho mais viável (pense em Princípio Aberto / Fechado, microsserviços), mas a equipe da OP ainda não está lá.
amon
22
Eu não chegaria ao ponto de dizer que um ramo de recursos precisa ter uma vida útil curta - apenas que ele não deve divergir de seu ramo pai por longos períodos de tempo. A fusão regular de alterações da ramificação pai na ramificação de recurso funciona nos casos em que a ramificação de característica precisa permanecer por mais tempo. Ainda assim, é uma boa idéia manter as ramificações de recursos por mais tempo do que o necessário.
Dan Lyons
1
@Laiv Na minha experiência, faz sentido discutir previamente um projeto pós-refatoração com a equipe, mas geralmente é mais fácil se uma única pessoa fizer as alterações no código. Caso contrário, você voltará ao problema de que precisa mesclar coisas. As linhas de 4k parecem muito, mas realmente não são para refatorações direcionadas, como extrair classe . (Eu consideraria o livro de Refatoração de Martin Fowler tão difícil aqui se o tivesse lido.) Mas linhas de 4k é muito apenas para refatorações não direcionadas como "vamos ver como posso melhorar isso".
amon
1
@ DanLyons Em princípio, você está certo: isso pode espalhar parte do esforço de fusão. Na prática, a mesclagem do Git depende muito do último commit ancestral comum das ramificações que estão sendo mescladas. O recurso mesclar mestre → não nos fornece um novo ancestral comum no mestre, mas o recurso mesclar → mestre fornece. Com as repetidas combinações de recursos mestre →, pode acontecer que tenhamos que resolver os mesmos conflitos repetidamente (mas veja git rerere para automatizar isso). O rebasing é estritamente superior aqui, porque a dica do mestre se torna o novo ancestral comum, mas a reescrita da história tem outros problemas.
amon
1
A resposta é boa para mim, exceto pelo discurso retórico sobre o git, tornando muito fácil a ramificação e, portanto, os desenvolvedores ramificam com muita frequência. Lembro-me bem dos tempos do SVN e até do CVS em que a ramificação era difícil (ou pelo menos complicada) o suficiente para que as pessoas geralmente o evitassem, se possível, com todos os problemas relacionados. No git, ser um sistema distribuído , ter muitas ramificações não é nada diferente de ter muitos repositórios separados (isto é, em cada desenvolvedor). A solução está em outro lugar, ser fácil ramificar não é o problema. (E sim, vejo que isso é apenas um aparte ... mas ainda assim).
AnoE 29/03
30

Faça a refatoração em etapas menores. Digamos que seu arquivo grande tenha o nome Foo:

  1. Adicione um novo arquivo vazio Bare confirme com "tronco".

  2. Encontre uma pequena parte do código na Fooqual possa ser movida para Bar. Aplique a movimentação, atualize a partir do tronco, construa e teste o código e confirme com "tronco".

  3. Repita a etapa 2 até Fooe Bartenha o mesmo tamanho (ou o tamanho que você preferir)

Dessa forma, na próxima vez em que seus colegas de equipe atualizarem suas ramificações do tronco, eles receberão suas alterações em "pequenas porções" e poderão mesclá-las uma a uma, o que é muito mais fácil do que ter que mesclar uma divisão completa em uma única etapa. O mesmo ocorre quando na etapa 2 você obtém um conflito de mesclagem porque outra pessoa atualizou o tronco no meio.

Isso não elimina conflitos de mesclagem ou a necessidade de resolvê-los manualmente, mas restringe cada conflito a uma pequena área de código, que é bem mais gerenciável.

E, é claro - comunique a refatoração na equipe. Informe seus parceiros o que você está fazendo, para que eles saibam por que precisam esperar conflitos de mesclagem para o arquivo específico.

Doc Brown
fonte
2
Isso é especialmente útil com a rerereopção gits ativada
D. Ben Knoble
@ D.BenKnoble: obrigado por essa adição. Devo admitir que não sou especialista em git (mas o problema descrito não é específico para o git, ele se aplica a qualquer VCS que permita ramificação e minha resposta deve se encaixar na maioria desses sistemas).
Doc Brown
Imaginei com base na terminologia; de fato, com o git, esse tipo de mesclagem ainda é feito apenas uma vez (se alguém apenas puxar e mesclar). Mas sempre é possível extrair e escolher, mesclar commits individuais ou rebase, dependendo da preferência do desenvolvedor. Leva mais tempo, mas certamente é possível se a fusão automática parecer falhar.
D. Ben Knoble 29/03
18

Você está pensando em dividir o arquivo como uma operação atômica, mas há alterações intermediárias que você pode fazer. O arquivo gradualmente se tornou enorme com o tempo, e gradualmente se tornou pequeno com o tempo.

Escolha uma peça que não precise ser alterada há muito tempo ( git blamepode ajudar com isso) e divida-a primeiro. Faça com que essa alteração seja mesclada nas ramificações de todos e escolha a próxima parte mais fácil de dividir. Talvez até dividir uma parte seja um passo muito grande e você deva apenas reorganizar o arquivo grande primeiro.

Se as pessoas não estiverem voltando a se desenvolver com frequência, incentive-o a aproveitar a oportunidade para separar as partes que acabaram de mudar. Ou peça que eles façam a divisão como parte da revisão da solicitação de recebimento.

A idéia é avançar lentamente em direção ao seu objetivo. Parece que o progresso é lento, mas, de repente, você perceberá que seu código é muito melhor. Demora muito tempo para virar um transatlântico.

Karl Bielefeldt
fonte
O arquivo pode ter começado grande. Arquivos desse tamanho podem ser criados rapidamente. Conheço pessoas que podem escrever milhares de LoC em um dia ou semana. E o OP não mencionou testes automatizados, o que indica para mim que eles estão faltando.
ChuckCottrill 29/03
9

Vou sugerir uma solução diferente do normal para esse problema.

Use isso como um evento de código de equipe. Peça a todos que façam o check-in do código que puderem e ajude outras pessoas que ainda estão trabalhando com o arquivo. Quando todos os códigos relevantes tiverem seu código registrado, encontre uma sala de conferências com um projetor e trabalhe em conjunto para começar a mover as coisas para novos arquivos.

Você pode definir uma quantidade específica de tempo para isso, para que não acabe sendo uma semana em argumentos sem fim à vista. Em vez disso, pode até ser um evento semanal de 1 a 2 horas, até que todos entendam como precisa ser. Talvez você precise apenas de uma a duas horas para refatorar o arquivo. Você não saberá até tentar, provavelmente.

Isso tem o benefício de todos estarem na mesma página (sem trocadilhos) com a refatoração, mas também pode ajudar a evitar erros e obter informações de outras pessoas sobre possíveis agrupamentos de métodos a serem mantidos, se necessário.

Fazer dessa maneira pode ser considerado como uma revisão de código interna, se você fizer esse tipo de coisa. Isso permite que a quantidade apropriada de desenvolvedores assine seu código assim que você o fizer check-in e pronto para a revisão. Você ainda pode querer que eles verifiquem o código em busca de algo que perdeu, mas isso ajuda bastante a garantir que o processo de revisão seja mais curto.

Isso pode não funcionar em todas as situações, equipes ou empresas, pois o trabalho não é distribuído de uma maneira que facilita isso. Também pode ser (incorretamente) interpretado como um uso indevido do tempo de desenvolvimento. Esse código de grupo precisa de adesão do gerente, bem como do próprio refator.

Para ajudar a vender essa ideia ao seu gerente, mencione o bit de revisão de código e todos que sabem onde estão as coisas desde o início. Impedir que os desenvolvedores percam tempo pesquisando em uma série de novos arquivos pode valer a pena evitar. Além disso, impedir que os desenvolvedores sejam enviados para onde as coisas acabaram ou "desapareceu completamente" geralmente é uma coisa boa. (Quanto menos colapsos, melhor, IMO.)

Depois de obter um arquivo refatorado dessa maneira, você poderá obter mais facilmente a aprovação de mais refatores, se bem-sucedido e útil.

No entanto, você decide fazer o seu refator, boa sorte!

computercarguy
fonte
Essa é uma sugestão fantástica que captura uma maneira realmente boa de obter a coordenação da equipe que será essencial para fazê-la funcionar. Além disso, se algumas das ramificações não puderem ser mescladas novamente master, você terá pelo menos todos na sala para ajudar a lidar com as mesclagens nessas ramificações.
Colin Young
+1 por sugerir o código mob
Jon Raynor
1
Isso aborda exatamente o aspecto social do problema.
ChuckCottrill 29/03
4

Corrigir esse problema requer adesão de outras equipes, porque você está tentando alterar um recurso compartilhado (o próprio código). Dito isto, acho que há uma maneira de "migrar para longe" de ter enormes arquivos monolíticos sem atrapalhar as pessoas.

Também recomendo não direcionar todos os arquivos enormes de uma só vez , a menos que o número de arquivos enormes esteja crescendo incontrolavelmente, além do tamanho dos arquivos individuais.

A refatoração de arquivos grandes como esse frequentemente causa problemas inesperados. O primeiro passo é impedir que os arquivos grandes acumulem funcionalidades adicionais além do que está atualmente no mestre ou nas ramificações de desenvolvimento .

Eu acho que a melhor maneira de fazer isso é com ganchos de confirmação que bloqueiam certas adições aos arquivos grandes por padrão, mas podem ser substituídos por um comentário mágico na mensagem de confirmação, como @bigfileokalgo assim. É importante poder anular a política de uma maneira indolor, mas rastreável. Idealmente, você deve poder executar o gancho de confirmação localmente e deve informar como substituir esse erro específico na própria mensagem de erro . Além disso, essa é apenas a minha preferência, mas comentários mágicos não reconhecidos ou comentários mágicos que suprimem erros que não foram acionados na mensagem de confirmação devem ser um aviso ou erro em tempo de confirmação, para que você não treine inadvertidamente pessoas para suprimir os ganchos, independentemente de se eles precisam ou não.

O gancho de confirmação pode procurar novas classes ou fazer outra análise estática (ad hoc ou não). Você também pode escolher uma linha ou contagem de caracteres 10% maior que o arquivo atualmente e dizer que o arquivo grande não pode crescer além do novo limite. Você também pode rejeitar confirmações individuais que aumentam o arquivo grande com muitas linhas ou caracteres ou w / e.

Depois que o arquivo grande parar de acumular novas funcionalidades, você poderá refatorá-lo um por vez (e reduzir os limites impostos pelos ganchos de confirmação ao mesmo tempo para impedir que ele cresça novamente).

Eventualmente, os arquivos grandes serão pequenos o suficiente para que os ganchos de confirmação possam ser completamente removidos.

Gregory Nisbet
fonte
-3

Aguarde até a hora do lar. Dividir o arquivo, confirmar e mesclar para dominar.

Outras pessoas terão que inserir as alterações em seus ramos de recursos pela manhã, como qualquer outra alteração.

Ewan
fonte
3
Ainda significaria que eles teriam que mesclar minhas refatorações com suas alterações ...
Hoff
um pouco relacionado: sugestão sobre estrutura de arquivos organizada
Nick Alexeev
1
Bem, eles realmente precisam lidar com fusões de qualquer maneira, se todos estiverem alterando esses arquivos.
Laiv 28/03
9
Isso tem o problema de "Surpresa, eu quebrei todas as suas coisas". O OP precisa obter aprovação e aprovação antes de fazer isso, e fazê-lo em um horário programado para que ninguém mais tenha o arquivo "em andamento" ajudaria.
computercarguy
6
Pelo amor de cthulhu, não faça isso. É a pior maneira de trabalhar em equipe.
Lightness Races com Monica em