¿Cuál es la mejor manera de manejar la refactorización de un archivo grande?

41

Actualmente estoy trabajando en un proyecto más grande que desafortunadamente tiene algunos archivos donde no siempre se siguen las pautas de calidad del software. Esto incluye archivos grandes (leer líneas 2000-4000) que claramente contienen múltiples funcionalidades distintas.

Ahora quiero refactorizar estos archivos grandes en varios pequeños. El problema es que, dado que son tan grandes, varias personas (incluido yo) en diferentes ramas están trabajando en estos archivos. Por lo tanto, realmente no puedo pasar de desarrollar y refactorizar, ya que fusionar estas refactorizaciones con los cambios de otras personas se volverá difícil.

Por supuesto, podríamos requerir que todos se fusionen para desarrollar, "congelar" los archivos (es decir, no permitir que nadie los edite más), refactorizar y luego "descongelar". Pero esto tampoco es realmente bueno, ya que esto requeriría que todos básicamente detuvieran su trabajo en estos archivos hasta que se complete la refactorización.

Entonces, ¿hay alguna forma de refactorizar, no exigir a nadie que deje de trabajar (por mucho tiempo) o fusionar sus ramas de características para desarrollar?

Hoff
fuente
66
Creo que esto también depende del lenguaje de programación utilizado.
Robert Andrzejuk
8
Me gustan los checkins "incrementales pequeños". A menos que alguien no mantenga fresca su copia del repositorio, esta práctica minimizará los conflictos de fusión para todos.
Matt Raffel
55
¿Cómo son tus exámenes? Si va a refactorizar un código grande (¡y probablemente importante!), Asegúrese de que su conjunto de pruebas esté en muy buenas condiciones antes de refactorizar. Esto hará que sea mucho más fácil asegurarse de acertar en los archivos más pequeños.
corsiKa
1
Existen numerosos enfoques que podría adoptar con esto y el mejor enfoque dependerá de su situación.
Stephen
3
Me uní al proyecto donde el archivo más grande tiene 10k líneas de largo que contiene entre otros una clase que tiene 6k líneas de largo y todos tienen miedo de tocarlo. Lo que quiero decir es que tu pregunta es genial. Incluso inventamos una broma de que esta clase única es una buena razón para desbloquear la rueda de desplazamiento en nuestros ratones.
ElmoVanKielmo

Respuestas:

41

Ha entendido correctamente que esto no es tanto un problema técnico como social: si desea evitar conflictos de fusión excesivos, el equipo debe colaborar de una manera que evite estos conflictos.

Esto es parte de un problema mayor con Git, ya que la ramificación es muy fácil, pero la fusión aún puede requerir mucho esfuerzo. Los equipos de desarrollo tienden a lanzar muchas sucursales y luego se sorprenden de que fusionarlas sea difícil, posiblemente porque están tratando de emular Git Flow sin comprender su contexto.

La regla general para las fusiones rápidas y fáciles es evitar que se acumulen grandes diferencias, en particular que las ramas de características deben tener una vida muy corta (horas o días, no meses). Un equipo de desarrollo que pueda integrar rápidamente sus cambios verá menos conflictos de fusión. Si algún código aún no está listo para la producción, podría ser posible integrarlo pero desactivarlo mediante un indicador de función. Tan pronto como el código se haya integrado en su rama maestra, será accesible para el tipo de refactorización que está tratando de hacer.

Eso podría ser demasiado para su problema inmediato. Pero puede ser factible pedirles a los colegas que combinen sus cambios que afectan este archivo hasta el final de la semana para que pueda realizar la refactorización. Si esperan más tiempo, tendrán que lidiar con los conflictos de fusión ellos mismos. Eso no es imposible, es solo un trabajo evitable.

También es posible que desee evitar romper grandes extensiones de código dependiente y solo realizar cambios compatibles con API. Por ejemplo, si desea extraer alguna funcionalidad en un módulo separado:

  1. Extraiga la funcionalidad en un módulo separado.
  2. Cambie las funciones antiguas para reenviar sus llamadas a la nueva API.
  3. Con el tiempo, el código dependiente del puerto a la nueva API.
  4. Finalmente, puede eliminar las funciones antiguas.
  5. (Repita para el siguiente grupo de funcionalidades)

Este proceso de varios pasos puede evitar muchos conflictos de fusión. En particular, solo habrá conflictos si otra persona también está cambiando la funcionalidad que extrajo. El costo de este enfoque es que es mucho más lento que cambiar todo a la vez, y que tiene temporalmente dos API duplicadas. Esto no es tan malo hasta que algo urgente interrumpe esta refactorización, la duplicación se olvida o se prioriza, y terminas con un montón de deudas tecnológicas.

Pero al final, cualquier solución requerirá que coordines con tu equipo.

amon
fuente
1
@Laiv Desafortunadamente, todo eso es un consejo extremadamente general, pero algunas ideas fuera del espacio ágil como la integración continua claramente tienen sus méritos. Los equipos que trabajan juntos (e integran su trabajo con frecuencia) tendrán más facilidad para realizar grandes cambios transversales que los equipos que solo trabajan juntos. No se trata necesariamente del SDLC en general, sino de la colaboración dentro del equipo. Algunos enfoques hacen que trabajar en conjunto sea más factible (piense en Principio abierto / cerrado, microservicios), pero el equipo de OP aún no está allí.
amon
22
No iría tan lejos como para decir que una rama de características debe tener una vida útil corta, simplemente que no debe divergir de su rama principal durante largos períodos de tiempo. La fusión periódica de los cambios de la rama principal en la rama de la función funciona en aquellos casos en los que la rama de la característica debe permanecer más tiempo. Aún así, es una buena idea mantener las ramas de entidades alrededor no más de lo necesario.
Dan Lyons
1
@Laiv En mi experiencia, tiene sentido discutir un diseño posterior a la refactorización con el equipo de antemano, pero generalmente es más fácil si una sola persona realiza los cambios en el código. De lo contrario, volverá al problema de que tiene que fusionar cosas. Las líneas 4k suenan mucho, pero en realidad no son para refactorizaciones específicas como extract-class . (Me gustaría explicar el libro de Refactorización de Martin Fowler aquí si lo hubiera leído). Pero 4k líneas es mucho solo para refactorizaciones no dirigidas como "veamos cómo puedo mejorar esto".
amon
1
@DanLyons En principio tienes razón: eso puede extender parte del esfuerzo de fusión. En la práctica, la fusión de Git depende mucho del último compromiso de antepasado común de las ramas que se fusionan. La combinación de master → feature no nos da un nuevo ancestro común en master, pero la combinación de característica → master sí. Con fusiones repetidas de características master →, puede suceder que tengamos que resolver los mismos conflictos una y otra vez (pero vea git rerere para automatizar esto). La reformulación es estrictamente superior aquí porque la punta del maestro se convierte en el nuevo antepasado común, pero la reescritura de la historia tiene otros problemas.
amon
1
La respuesta está bien para mí, excepto por la queja sobre git que hace que sea demasiado fácil ramificarse y, por lo tanto, los desarrolladores se ramifican con demasiada frecuencia. Recuerdo bien los tiempos de SVN e incluso CVS cuando la ramificación era lo suficientemente difícil (o al menos engorrosa) como para que las personas generalmente la evitaran si era posible, con todos los problemas relacionados. En git, al ser un sistema distribuido , tener muchas ramas no es realmente diferente de tener muchos repositorios separados (es decir, en cada desarrollador). La solución está en otra parte, no es el problema ser fácil de ramificar. (Y sí, veo que eso es solo un aparte ... pero aún así).
AnoE
30

Realice la refactorización en pasos más pequeños. Digamos que su archivo grande tiene el nombre Foo:

  1. Agregue un nuevo archivo vacío Bar, y confírmelo en "trunk".

  2. Encuentre una pequeña porción del código en la Fooque se pueda mover Bar. Aplique el movimiento, actualice desde troncal, cree y pruebe el código y comprométase a "troncal".

  3. Repita el paso 2 hasta Fooy Bartienen igual tamaño (o cualquier tamaño que prefiera)

De esa manera, la próxima vez que tus compañeros de equipo actualicen sus ramas desde el tronco, obtendrán tus cambios en "porciones pequeñas" y podrán fusionarlos uno por uno, lo cual es mucho más fácil que tener que fusionar una división completa en un solo paso. Lo mismo ocurre cuando en el paso 2 obtienes un conflicto de fusión porque alguien más actualizó el enlace intermedio.

Esto no eliminará los conflictos de fusión ni la necesidad de resolverlos manualmente, pero restringe cada conflicto a un área pequeña de código, que es mucho más manejable.

Y, por supuesto, comunicar la refactorización en el equipo. Informe a sus compañeros lo que está haciendo, para que sepan por qué tienen que esperar conflictos de fusión para el archivo en particular.

Doc Brown
fuente
2
Esto es especialmente útil con la rerereopción gits habilitada
D. Ben Knoble
@ D.BenKnoble: gracias por esa adición. Tengo que admitir que no soy un experto en git (pero el problema descrito no es específicamente para git, se aplica a cualquier VCS que permita la ramificación, y mi respuesta debería ajustarse a la mayoría de esos sistemas).
Doc Brown
Me imaginé basado en la terminología; de hecho, con git, este tipo de fusión todavía se realiza solo una vez (si uno solo tira y combina). Pero siempre se puede tirar y elegir, o fusionar commits individuales, o rebase según la preferencia del desarrollador. Lleva más tiempo, pero es factible si la fusión automática parece fallar.
D. Ben Knoble
18

Está pensando en dividir el archivo como una operación atómica, pero puede realizar cambios intermedios. El archivo gradualmente se volvió enorme con el tiempo, y gradualmente puede volverse pequeño con el tiempo.

Elija una parte que no haya tenido que cambiar en mucho tiempo ( git blamepuede ayudar con esto) y divídala primero. Combina ese cambio en las ramas de todos, luego elige la siguiente parte más fácil de dividir. Tal vez incluso dividir una parte es un paso demasiado grande y primero debe reorganizar el archivo grande.

Si las personas no se fusionan con frecuencia para desarrollarse, debe alentarlo, luego, después de fusionarse, aproveche la oportunidad para separar las partes que acaban de cambiar. O pídales que hagan la división como parte de la revisión de solicitud de extracción.

La idea es avanzar lentamente hacia su objetivo. Sentirá que el progreso es lento, pero de repente te darás cuenta de que tu código es mucho mejor. Lleva mucho tiempo girar un transatlántico.

Karl Bielefeldt
fuente
El archivo puede haber comenzado grande. Los archivos de ese tamaño se pueden crear rápidamente. Conozco personas que pueden escribir miles de LoC en un día o una semana. Y OP no mencionó las pruebas automatizadas, lo que me indica que faltan.
ChuckCottrill
9

Voy a sugerir una solución diferente a la normal para este problema.

Use esto como un evento de código de equipo. Haga que todos revisen su código y puedan ayudar a otros que todavía están trabajando con el archivo. Una vez que todos los relevantes tengan su código registrado, busque una sala de conferencias con un proyector y trabajen juntos para comenzar a mover las cosas y a nuevos archivos.

Es posible que desee establecer una cantidad de tiempo específica para esto, de modo que no termine siendo una semana de argumentos sin un final a la vista. En cambio, esto podría incluso ser un evento semanal de 1-2 horas hasta que todos tengan las cosas como deben ser. Tal vez solo necesite 1-2 horas para refactorizar el archivo. No lo sabrás hasta que lo intentes, probablemente.

Esto tiene el beneficio de que todos estén en la misma página (sin juego de palabras) con la refactorización, pero también puede ayudarlo a evitar errores y a obtener información de otros sobre posibles agrupaciones de métodos para mantener, si es necesario.

Hacerlo de esta manera puede considerarse que tiene una revisión de código incorporada, si hace ese tipo de cosas. Esto permite que la cantidad adecuada de desarrolladores firmen su código tan pronto como lo registren y estén listos para su revisión. Es posible que aún desee que verifiquen el código de cualquier cosa que se haya perdido, pero es muy importante asegurarse de que el proceso de revisión sea más corto.

Es posible que esto no funcione en todas las situaciones, equipos o empresas, ya que el trabajo no se distribuye de manera que esto suceda fácilmente. También se puede interpretar (incorrectamente) como un mal uso del tiempo de desarrollo. Este código de grupo necesita la aceptación del administrador y del refactorizador.

Para ayudar a vender esta idea a su gerente, mencione el bit de revisión de código, así como que todos sepan dónde están las cosas desde el principio. Evitar que los desarrolladores pierdan tiempo buscando en una gran cantidad de archivos nuevos puede valer la pena. Además, evitar que los desarrolladores se pongan a prueba sobre dónde terminaron las cosas o "faltaban por completo" suele ser algo bueno. (Cuantas menos crisis, mejor, OMI).

Una vez que obtenga un archivo refactorizado de esta manera, es posible que pueda obtener más fácilmente la aprobación de más refactores, si fue exitoso y útil.

Como sea que decidas hacer tu refactorización, ¡buena suerte!

computadora portátil
fuente
Esta es una sugerencia fantástica que captura una muy buena manera de lograr la coordinación del equipo que será fundamental para que funcione. Además, si algunas de las ramas no pueden fusionarse de nuevo al masterprincipio, al menos tiene a todos en la sala para ayudar a lidiar con las fusiones en esas ramas.
Colin Young
+1 por sugerir el código mob
Jon Raynor
1
Esto aborda exactamente el aspecto social del problema.
ChuckCottrill
4

La solución de este problema requiere la aceptación de los otros equipos porque está tratando de cambiar un recurso compartido (el código en sí). Dicho esto, creo que hay una manera de "migrar" de tener grandes archivos monolíticos sin interrumpir a las personas.

También recomendaría no apuntar a todos los archivos grandes a la vez, a menos que la cantidad de archivos grandes crezca sin control además del tamaño de los archivos individuales.

Refactorizar archivos grandes como este con frecuencia causa problemas inesperados. El primer paso es evitar que los archivos grandes acumulen funcionalidades adicionales más allá de lo que está actualmente en las ramas maestras o de desarrollo .

Creo que la mejor manera de hacer esto es con enlaces de confirmación que bloquean ciertas adiciones a los archivos grandes de forma predeterminada, pero se pueden anular con un comentario mágico en el mensaje de confirmación, como @bigfileoko algo así. Es importante poder anular la política de una manera que sea indolora pero rastreable. Idealmente, debería poder ejecutar el enlace de confirmación localmente y debería indicarle cómo anular este error particular en el mensaje de error mismo . Además, esto es solo mi preferencia, pero los comentarios mágicos no reconocidos o los comentarios mágicos que suprimen los errores que en realidad no se activaron en el mensaje de confirmación deberían ser una advertencia o un error de tiempo de confirmación, por lo que no capacita inadvertidamente a las personas para suprimir los ganchos independientemente de si lo necesitan o no.

El enlace de confirmación podría buscar nuevas clases o hacer otro análisis estático (ad hoc o no). También puede elegir un recuento de líneas o caracteres que sea un 10% más grande que el archivo actual y decir que el archivo grande no puede crecer más allá del nuevo límite. También puede rechazar confirmaciones individuales que hacen crecer el archivo grande por demasiadas líneas o demasiados caracteres o w / e.

Una vez que el archivo grande deja de acumular nueva funcionalidad, puede refactorizar las cosas de a una por vez (y reducir los límites impuestos por los ganchos de confirmación al mismo tiempo para evitar que vuelva a crecer).

Eventualmente, los archivos grandes serán lo suficientemente pequeños como para que los ganchos de confirmación se puedan eliminar por completo.

Gregory Nisbet
fuente
-3

Espera hasta la hora de casa. Dividir el archivo, confirmar y fusionar con master.

Otras personas tendrán que colocar los cambios en sus ramas de características en la mañana como cualquier otro cambio.

Ewan
fuente
3
Sin embargo, todavía significaría que tendrían que fusionar mis refactorizaciones con sus cambios ...
Hoff
1
Bueno, de todos modos tienen que lidiar con las fusiones si todos están cambiando estos archivos.
Laiv
99
Esto tiene el problema de "Sorpresa, rompí todas tus cosas". El OP necesita obtener la aceptación y la aprobación antes de hacer esto, y hacerlo a una hora programada para que nadie más tenga el archivo "en progreso" ayudaría.
computercarguy
66
Por el amor de cthulhu no hagas esto. Se trata de la peor forma en que puedes trabajar en equipo.
ligereza corre con Mónica el