Cambié la firma de un método y ahora tengo más de 25,000 errores. ¿Ahora que?

166

Comencé un nuevo trabajo recientemente donde estoy trabajando en una aplicación muy grande (15M loc). En mi trabajo anterior teníamos una aplicación similar de gran tamaño pero (para bien o para mal) usamos OSGi, lo que significaba que la aplicación se dividía en muchos microservicios que podían cambiarse, compilarse e implementarse independientemente. La nueva aplicación es solo una base de código grande, con quizás un par de .dlls.

Entonces necesito cambiar la interfaz de esta clase, porque eso es lo que mi jefe me pidió que hiciera. Inicialmente lo escribieron con algunas suposiciones que no se generalizaron demasiado bien, y durante un tiempo han estado evitando el problema de la refactorización porque está muy estrechamente acoplado. Cambié la interfaz y ahora hay más de 25000 errores. Algunos de los errores están en clases con nombres importantes que suenan como "XYZPriceCalculator" que realmenteNo debe romperse. Pero no puedo iniciar la aplicación para verificar si funciona hasta que se resuelvan todos los errores. Y muchas de las pruebas unitarias hacen referencia directa a esa interfaz, o están acopladas a clases base que hacen referencia a esa interfaz, por lo que solo arreglarlas es una tarea bastante grande en sí misma. Además, no sé cómo encajan todas estas piezas, por lo que incluso si pudiera comenzar, no sé cómo se vería si las cosas se rompieran.

Nunca me enfrenté a un problema como este en mi último trabajo. ¿Qué debo hacer?

usuario788497
fuente
77
Tendrá que darnos más información sobre los tipos de errores y qué es "esta clase", no somos lectores de la mente.
cuál es el
137
"más de 25000 errores", tales números mostrados en VS a menudo son incorrectos. Hay algunos errores, pero con la construcción de un dll de uso frecuente roto, otras construcciones también fallan, dando lugar a números de error astronómicos. Siempre comienzo a arreglar con el primer mensaje de error que se encuentra en la salida de compilación, no en la lista de errores.
Bernhard Hiller el
13
Me gustaría ver las firmas de antes y después del método que cambió. Por cierto, los errores de 25k realmente no parecen demasiados para tratar. Tedioso sí, desalentador, sí, inmanejable, no.
jmoreno
46
Una interfaz pública es un contrato. No rompas esos contratos, crea nuevos.
Matthew leyó el
66
¿25000 errores? Houston, tenemos un problema. Definitivamente revierta el cambio y hable con su gerente, explíquele que es posible que deba crear una nueva interfaz por completo.
Code Whisperer el

Respuestas:

349

25000 errores básicamente significa "no tocar eso". Cambiarlo de nuevo. Cree una nueva clase que tenga la interfaz deseada y mueva lentamente a los consumidores de la clase a la nueva. Dependiendo del idioma, puede marcar la clase anterior como obsoleta, lo que puede causar todo tipo de advertencias del compilador, pero en realidad no interrumpirá su compilación.

Desafortunadamente, estas cosas suceden en bases de código antiguas. No hay mucho que pueda hacer al respecto, excepto mejorar lentamente las cosas. Cuando cree las nuevas clases, asegúrese de probarlas adecuadamente y crearlas utilizando principios SÓLIDOS para que sean más fáciles de cambiar en el futuro.

Stephen
fuente
79
No necesariamente tiene que ser una nueva clase . Dependiendo del idioma y las circunstancias, podría ser una función, interfaz, rasgo, etc.
Jan Hudec
33
También ayuda si la interfaz anterior se puede reemplazar por un contenedor de compatibilidad alrededor de la nueva.
Jan Hudec el
79
A veces, 25000 errores en realidad significa que nunca nos atrevimos a tocar eso, pero ahora que ha llegado un recién llegado, vamos a darle esa tarea de limpieza de establos de Augean.
Mouviciel
13
@theDmi: Estoy de acuerdo, 1 cambio y 25k errores probablemente significan 2.5k cambios como máximo, y no me sorprendería descubrir que fue alrededor de 250. De todas formas, mucho trabajo, pero factible.
jmoreno
33
Esos 25000 errores pueden ser corregibles con un solo cambio. Si rompo la clase base de una jerarquía enorme, cada una de las clases derivadas arrojará errores sobre una base no válida, cada uso de esas clases arrojará errores sobre la clase no existente, etc. Imagina que era "getName", y agregué un argumento. La anulación en la clase de implementación "HasAName" ahora no funciona (error), y todo lo que hereda de ella ahora genera errores (las 1000 clases), así como cada vez que creo una instancia de cualquiera de ellos (24 veces por clase en promedio). La solución es ... una línea.
Yakk el
80

Divide y conquista con refactorizaciones

A menudo, dividir el cambio que necesita hacer en pasos más pequeños puede ayudar porque puede realizar la mayoría de los pasos más pequeños de una manera que no rompa el software en absoluto. Las herramientas de refactorización ayudan mucho con tales tareas.

Dividir

Primero, identifique los cambios más pequeños posibles (en términos de cambios lógicos, no en términos de LoC cambiados) que sumen el cambio que desea lograr. Especialmente trate de aislar los pasos que son pura refactorización y que pueden llevarse a cabo con herramientas.

Conquistar

Para casos complicados como el suyo, puede tener sentido realizar una pequeña refactorización a la vez y luego dejar que el problema descanse, de modo que todos los sistemas de integración continua puedan verificar el cambio, y tal vez el equipo de prueba también haya echado un vistazo. Esto valida los pasos que realiza.

Para llevar a cabo una refactorización particular, absolutamente necesita soporte de herramientas para un caso en el que tiene 25,000 sitios de llamadas del método que desea cambiar. Quizás la búsqueda y reemplazo también funcione, pero para un caso tan crítico, me daría miedo.

Ejemplo

En C #, por ejemplo, es posible usar Resharper para cambiar la firma de un método. Si el cambio es lo suficientemente simple, por ejemplo, agregar un nuevo parámetro, puede especificar qué valor se debe usar en los sitios de llamadas que de otro modo tendrían un error de compilación.

Entonces se encuentra en una base de código libre de errores de inmediato y puede ejecutar todas las pruebas unitarias, lo que pasará, porque era solo una refactorización.

Una vez que la firma del método se ve bien, puede ir y reemplazar los valores que Resharper agregó como argumentos al parámetro recién introducido. Esto ya no es una refactorización , pero tiene una base de código libre de errores y puede ejecutar las pruebas después de cada línea que cambie.

A veces eso no funciona

Este es un enfoque muy útil para casos como el suyo, donde tiene muchos sitios de llamadas. Y ahora tiene un software que funciona, por lo que puede ser posible realizar pequeños pasos de refactorización para cambiar la firma solo un poco y luego hacer otro.

Desafortunadamente, no funcionará si el cambio de firma es demasiado complejo y no se puede dividir en cambios más pequeños. Pero esto es raro; dividir el problema en problemas más pequeños generalmente muestra que es posible.

theDmi
fuente
12
Esta. Refactorice si puede (de forma segura), de lo contrario, necesita una migración adecuada.
sleske el
3
Y por amor a lo que sea, utilice las herramientas de refactorización integradas de su IDE, en lugar de simplemente cambiar la (por ejemplo) firma del método localmente y luego solucionar los errores consiguientes.
KlaymenDK
36

Aclare su tarea con su jefe para ayudarlo a comprender el problema y sus necesidades como desarrollador de software profesional.

Si forma parte de un equipo, busque el desarrollador principal y pídale consejo.

Buena suerte.

mmehl
fuente
15
Este es el primer paso que tomaría: "Soy un estudiante de tercer año y mi jefe me dijo que hiciera algo y ahora recibo un gran mensaje de error, pero mi jefe no me contó sobre eso". Paso 1: "Hola jefe, hice lo mismo, pero ahora tengo
25 mil
28

No lo toques. No cometas nada.

En lugar de eso, siéntate en tu silla y grita "¡¡¡Heeeeelp !!!!!" tan fuerte como puedas

Bueno, no exactamente así, pero pida consejo a cualquiera de sus colegas superiores. Si tiene 25,000 errores, no arregla los errores, arregla lo que causó los errores. Y un colega senior debería poder aconsejarle cómo hacer el cambio que su jefe quiere sin los 25,000 errores involucrados. Hay varias formas de hacer esto, pero cuál es una buena manera depende de su situación específica.

Y podría ser que el jefe les dijo a sus colegas superiores que hicieran el mismo cambio, y ellos dijeron "no". Porque sabían lo que sucedería. Por eso te dieron el trabajo.

gnasher729
fuente
2
Esto es definitivamente algo importante a tener en cuenta. Si un nuevo empleado es verdaderamente ambicioso en gran medida, podría ser lo suficientemente trabajador (crédulo) para hacer la tarea realmente grande que solo termina ganando una ventaja de memoria de 5 bytes o ser un poco más lógico para recodificar y mantener más tarde.
El gran pato el
@TheGreatDuck: Dime que nunca has visto una interfaz utilizada en todas partes y mal en todas partes. Claro que sí.
Joshua
@Joshua eso ni siquiera es relevante para lo que acabo de decir. Hay momentos en los que solucionar un error menor no siempre es la mejor idea, ya que significa reescribir más de 10,000 líneas de código. Desafortunadamente, un nuevo empleado puede ser lo suficientemente crédulo como para sacar a 10 personas que no están trabajando fuera del trabajo reescribiendo ese código para impresionar a su jefe y lograrlo. Claro, es una gran cosa que hacer, pero a veces suficiente es suficiente. Solo tienes que aceptar la existencia del error.
The Great Duck
1
@Joshua por cierto, solo me uní a esta comunidad porque esta pregunta apareció en mi feed de preguntas populares. No tengo experiencia con un diseño a gran escala como este. Acabo de estar de acuerdo con la opinión de esta persona.
El gran pato el
22

Las API incorporadas no se pueden cambiar simplemente. Si realmente necesita cambiarlos, anótelos y / o documente como obsoletos (por cualquier medio que el idioma lo permita) y documente qué API debe usarse en su lugar. La antigua API se puede eliminar lentamente ... tal vez muy lentamente dependiendo de su presupuesto de tiempo para la refactorización.

Kevin Krumwiede
fuente
10

Evaluar

Evalúe si este cambio es necesario o si puede agregar un nuevo método y desaprobar el otro.

Adelante

Si es necesario cambiar; entonces es necesario un plan de migración.

El primer paso es introducir el nuevo método y hacer que el método anterior masajee sus argumentos para que pueda llamar al nuevo. Esto puede requerir codificar algunas cosas; esta bien.

Este es un punto de confirmación: verifique que todas las pruebas pasen, se comprometan, empujen.

Emigrar

El trabajo ocupado está migrando todas las personas que llaman del método antiguo al nuevo. Afortunadamente se puede hacer gradualmente gracias al promotor.

Así que adelante; no dude en usar herramientas para ayudar ( sedsiendo las más básicas, hay otras).

Marque el método anterior como obsoleto (con una sugerencia de cambiar al nuevo método); te ayudará a detectar si olvidaste algo y ayudará si un compañero de trabajo introduce una llamada al método anterior mientras estás trabajando en esto.

Este es un punto de compromiso (o tal vez varios puntos de compromiso): verifique que todas las pruebas pasen, se comprometan, empujen.

Eliminar

Después de que haya pasado un tiempo (tal vez tan poco como un día), simplemente elimine el método anterior.

Matthieu M.
fuente
44
sedes probablemente una mala idea ... Algo que realmente "entiende" el lenguaje y no hará cambios drásticos involuntarios es mejor.
wizzwizz4
1
@ wizzwizz4: Desafortunadamente, he encontrado muy pocas herramientas que realmente entiendan el lenguaje lo suficientemente bien para C ++; la mayoría de las herramientas parecen adaptarse al cambio de nombre y eso es todo. Por supuesto, renombrar solo las llamadas de método exactas (y no cualquier sobrecarga o llamadas de método no relacionadas pero con nombres similares) ya es impresionante, pero es insuficiente para algo más complicado. Por lo menos, necesitaría las habilidades para (1) barajar argumentos, (2) aplicar una transformación a un argumento dado (llamar .c_str()por ejemplo) e introducir nuevos argumentos. sedun poco funciona, el compilador detecta sus problemas después.
Matthieu M.
1
sed(o ed) puede ser adecuado para este tipo de cosas, siempre y cuando revise correctamente la diferencia antes de comprometerse.
Toby Speight el
Este es el TCRR de html, ¿sí? :)
Daniel Springer el
8

Si su cambio en la firma del método es simplemente un cambio de nombre, la solución simple es usar herramientas para automatizar el cambio en las 25,000 clases que hacen referencia al método en cuestión.

Supongo que simplemente ha editado el código manualmente, lo que dio lugar a todos los errores. También supongo que está familiarizado con Java (ver su referencia a OSGi), por lo que, por ejemplo, en Eclipse (no sé qué entorno de programación utiliza, pero otros entornos tienen herramientas de refactorización similares) puede usar "Refactoring -> Rename" para actualizar todas las referencias al método, lo que debería dejarlo sin errores.

En caso de que realice otros cambios en la firma del método que simplemente renombre (cambiando el número o los tipos de parámetros), puede usar "Refactorización -> Cambiar firma del método". Sin embargo, es probable que tenga que ser más cuidadoso como lo sugieren las otras respuestas. Además, independientemente del tipo de cambio, aún puede ser una gran tarea confirmar todos esos cambios en una base de código ocupado.

MikkelRJ
fuente
2
OP dijo específicamente que OSGi estaba en su trabajo anterior, por lo que no es realmente relevante aquí.
un CVn
3
@ MichaelKjörling Si el OP fue un programador de Java en su trabajo anterior, existe una gran posibilidad de que también sean un programador de Java en este trabajo.
Rico
@ MichaelKjörling Quería dar una recomendación concreta, usando Eclipse para refactorizar porque OP está familiarizado con Java. Supongo que si OP está utilizando Java para el proyecto actual, es menos importante, pero debo aclarar mi respuesta. Gracias.
MikkelRJ
6

Aquí mi contribución.

Recientemente comencé un nuevo trabajo donde estoy trabajando en una aplicación muy grande (15 millones de líneas de código).

Probablemente no esté familiarizado con el proyecto y sus "características". Antes de escribir una sola línea de código, es importante estar familiarizado con el proyecto. Así que deshaga sus cambios y comience analizando el código . (Al menos el afectado)

Comprender la solución existente le brinda una mejor perspectiva de dónde se está metiendo. Contextualizar la solución y su importancia.

Como señaló @Greg, debería poder probar el código existente para tener una referencia válida para comparar (pruebas de regresión). Su solución debe ser capaz de generar los mismos resultados que la existente. En esta etapa, no le importa si los resultados son correctos o no . El primer objetivo es refactorizar, no corregir errores. Si la solución existente dice "2 + 2 = 42", su solución también debería hacerlo. Si no arroja excepciones, la suya tampoco debería hacerlo. Si devuelve nulos, el suyo también debe devolver nulos. Y así. De lo contrario, comprometerá 25k líneas de código.

Esto es por el bien de la retrocompatibilidad.

¿Por qué? Porque en este momento, es su garantía única de un refactor exitoso.

Y muchas de las pruebas unitarias hacen referencia directa a esa interfaz o están acopladas a clases base que hacen referencia a esa interfaz.

Se necesita con urgencia una forma de garantizar la retrocompatibilidad. Así que aquí está su primer desafío. Aislar el componente para pruebas unitarias.

Tenga en cuenta que esas 25 mil líneas de código se hicieron asumiendo los posibles resultados del código existente. Si no rompe esta parte del contrato, está a medio camino de la solución final. Si lo haces, bueno: que la fuerza te acompañe

Una vez que haya diseñado e implementado el nuevo "contrato", reemplace el anterior. Desaprobarlo o sacarlo.

He sugerido que dejar los errores solos debido a la refactorización y la corrección de errores son tareas diferentes. Si intentas llevarlos hacia adelante juntos, puedes fallar en ambos. Puede pensar que encontró errores, sin embargo, podrían ser "características". Así que déjelos solos (por un minuto).

25 mil líneas de código me parecen suficientes problemas para hacer que me centre en una sola tarea.

Una vez que haya terminado su primera tarea. Exponga esos errores / características a su jefe.

Finalmente, como dijo @Stephen:

No hay mucho que pueda hacer al respecto, excepto mejorar lentamente las cosas. Cuando cree las nuevas clases, asegúrese de probarlas adecuadamente y crearlas utilizando principios SÓLIDOS para que sean más fáciles de cambiar en el futuro

Laiv
fuente
5

Pruébalo.

Todos los demás recomiendan cómo refactorizar para que haya poco impacto. Pero con tantos errores, incluso si logra refactorizar con tan solo 10 líneas de código (probablemente pueda), entonces ha impactado 25,000 flujos de código , incluso si no necesita reescribirlos.

Entonces, lo siguiente que debe hacer es asegurarse de que su conjunto de pruebas de regresión pase con gran éxito. Y si no tienes uno, entonces haz uno que lo haga. Agregar un conjunto completo de pruebas de regresión a su proyecto monolítico parece aburrido, pero es una buena manera de aumentar la confianza en los candidatos de lanzamiento, así como liberarlos más rápido si el conjunto está bien automatizado.

Greg
fuente