Control de versiones de refactorización de software

¿Cuál es la mejor manera de hacer un control de versiones de refactorización a gran escala?

Mi estilo de progtwigción típico (en realidad también de escribir documentos) es get algo lo más rápido posible y luego refactorizarlo. Por lo general, la refactorización se lleva a cabo al mismo time que se agrega otra funcionalidad. Además de la refactorización estándar de classs y funciones, las funciones pueden moverse de un file a otro, los files se dividen y se fusionan o simplemente se reorderan.

Por el momento, estoy usando el control de versiones como usuario solitario, por lo que no hay problemas de interacción con otros desarrolladores en esta etapa. Aún así, el control de versiones me da dos aspectos:

  1. Respaldo y posibilidad de volver a una buena versión "en caso".
  2. Mirar la historia me dice cómo progresó el proyecto y el flujo de ideas.

Estoy usando mercurial en Windows usando TortoiseHg que permite la selección de trozos para comprometer. La razón por la que menciono esto es porque me gustaría recibir consejos sobre la granularidad de un compromiso en la refactorización. ¿Debo dividir la refactorización de la funcionalidad agregada siempre en el compromiso?

He visto las respuestas de Refactoring y Source Control: ¿Cómo? pero no responde mi pregunta. Esa pregunta se centra en la queueboración con un equipo. Éste se concentra en tener una historia comprensible en el futuro (suponiendo que no reescriba la historia como algunos VCS parecen permitir).

Supongo que no hay una respuesta única para todos ustedes 🙂
Personalmente, prefiero mantener la granularidad sensible más fina en mis compromisos: en su caso, dividiría la acción en dos fases: cada una independiente:

  1. refactorización (y después de confirmar)
  2. nuevas funcionalidades (y compromiso).

Lo mejor que puede hacer es agregar y comprometer cada elemento por sí mismo: dividir la refactorización en cambios localizados, y asignarlos uno a uno, y agregar las funcionalidades una por una, comprometiéndolos en el path.

Hay un poco más de sobrecarga, pero de esta manera, cuando vuelves buscando diferencias, queda claro qué se cambió para la refactorización y para qué agregar nuevas funcionalidades. También es más fácil deshacer solo una adición problemática en particular.

¿Debo dividir la refactorización de la funcionalidad agregada siempre en el compromiso?

Tiendo a hacer el check-in con frecuencia; y cada check-in es refactorización o nueva funcionalidad. Es un ciclo de:

  1. Refactorizar código existente (sin cambiar su funcionalidad) para prepararlo para aceptar el nuevo código
  2. Agregue un nuevo código (que implementa funcionalidad adicional).

Yo recomendaría separar la refactorización de la funcionalidad añadida. Tal vez alternando controles. Esto es de mis experiencias después de que descubrí que no era necesario y reformatear los files fuente al mismo time que hacía cambios de código. Se hizo muy difícil descubrir un cambio real de solo un reformateado. Ahora uncrustify obtiene sus propios commits dedicados.

Habiendo lidiado con el desennetworkingo de los efectos / errores / efectos secundarios de la refactorización MUY complicada combinada con cambios bastante extensos, puedo aconsejar encarecidamente que siempre trates de separar los dos en cuanto a tus versiones, tanto como sea posible.

Si hay algún problema, puede rebuild MUY fácilmente el código de las tags / tags / versiones correspondientes a cada etapa y verificar cuál de las dos introdujo el problema.

Además, intente realizar una refactorización lo más pequeña posible, lógicamente, complete los fragments y comprométalos como puntos de control separados. De nuevo, esto simplifica las investigaciones sobre lo que se rompió por qué / cuándo.

Hasta el momento, todas las respuestas le han aconsejado que separe la refactorización de la funcionalidad agregada, y que I + 1 los haya usado todos. Debería hacer esto, independientemente del control de la fuente. Martin Fowler escribió un libro completo sobre el concepto de que no se puede refactorizar simultáneamente con el cambio de funcionalidad. Desea saber, para cualquier cambio, si el código debe funcionar y funciona igual antes del cambio que después. Y como señala @Amardeep, es mucho más difícil ver qué cambio funcional ha realizado si está oculto al formatear o refactorizar los cambios, por lo tanto, es mucho más difícil rastrear los errores que introdujeron los cambios funcionales. No me refiero a esto para desanimarte de refactorizar, o para posponerlo. Hazlo, por supuesto, con frecuencia. Pero hágalo por separado de los cambios funcionales. Los microcompromisos son el path a seguir.

  • Toma pasos de bebé . Realice el menor cambio útil, pruébelo, envíe y repita.

  • Un tipo de cambio a la vez . No refactorices y cambies el comportamiento al mismo time.

  • Presentar a menudo Los pequeños cambios con descripciones claras y detalladas son invaluables.

  • Asegúrese de que sus testings automatizadas sean confiables y útiles . Si puede confiar en sus testings, puede hacer lo anterior de manera fácil y rápida.

  • Asegúrate de que tus exámenes siempre pasen .

A menudo, comenzaré a trabajar en nuevas funcionalidades o una corrección de errores o lo que sea, para descubrir que si refacciono las cosas solo así, la nueva funcionalidad será mucho más fácil de agregar. Por lo general, descartaré (o saveé en otro lugar) mis cambios hasta el momento, refactorizaré / probará / enviar, luego volveré a trabajar en la nueva funcionalidad. Lo ideal sería pasar el 90% de mi time refactorizando, y cada característica nueva, corrección de errores, mejora de performance, etc. es un cambio simple de una sola línea.