guru - refactoring translate
¿Cuándo es bueno(si es que lo es) eliminar el código de producción y comenzar de nuevo? (29)
Me pidieron que hiciera una revisión del código e informara sobre la posibilidad de agregar una nueva función a uno de nuestros nuevos productos, uno en el que no he trabajado hasta ahora. Sé que es fácil criticar el código de otra persona, pero diría que está en mal estado (al tratar de ser lo más objetivo posible). Algunos aspectos destacados de mi revisión de código:
Abuso de subprocesos:
QueueUserWorkItem
y subprocesos en general se usan mucho , y los delegados de grupo de subprocesos tienen nombres pocoPoolStart
comoPoolStart
yPoolStart2
. También hay una falta de sincronización adecuada entre los subprocesos, en particular el acceso a los objetos de la interfaz de usuario en hilos que no sean el subproceso de interfaz de usuario.Números mágicos y cadenas mágicas : algunos
Const
yEnum
se definen en el código, pero gran parte del código se basa en valores literales.Variables globales : muchas variables se declaran globales y pueden o no inicializarse dependiendo de qué rutas de código se siguen y en qué orden ocurren las cosas. Esto se vuelve muy confuso cuando el código también se salta entre subprocesos.
Advertencias del compilador : el archivo de la solución principal contiene más de 500 advertencias, y el número total es desconocido para mí. Recibí una advertencia de Visual Studio de que no podía mostrar más advertencias.
Clases a medio terminar : el código fue trabajado y agregado aquí y allá, y creo que esto llevó a que las personas olvidaran lo que habían hecho antes, por lo que hay algunas clases a medio terminar y talones vacíos.
No inventado aquí : el producto duplica la funcionalidad que ya existe en las bibliotecas comunes utilizadas por otros productos, como los ayudantes de acceso a datos, los ayudantes de registro de errores y los ayudantes de interfaz de usuario.
Separación de inquietudes : creo que alguien estaba sosteniendo el libro al revés cuando leyó acerca de la típica arquitectura de 3 niveles de "UI -> business layer -> data access layer". En esta base de código, la capa de IU accede directamente a la base de datos, porque la capa de negocio se implementa parcialmente, pero se ignora en su mayoría debido a que no se ha desarrollado lo suficiente, y la capa de acceso a datos controla la capa de IU. La mayoría de los métodos de redes y bases de datos de bajo nivel operan en una referencia global al formulario principal, y muestran, ocultan y modifican directamente el formulario. Cuando la capa empresarial más bien delgada se utiliza realmente, también tiende a controlar la interfaz de usuario directamente. La mayoría de este código de nivel inferior también usa
MessageBox.Show
mostrar mensajes de error cuando se produce una excepción y la mayoría se traga la excepción original. Esto, por supuesto, hace que sea un poco más complicado comenzar a escribir pruebas de unidades para verificar la funcionalidad del programa antes de intentar refactorizarlo.
Estoy solo arañando la superficie aquí, pero mi pregunta es bastante simple: ¿Tendría más sentido tomarse el tiempo para refactorizar la base de código existente, centrándose en un problema a la vez, o consideraría reescribir todo desde cero?
EDITAR : para aclarar un poco, tenemos los requisitos originales para el proyecto, por lo que comenzar de nuevo podría ser una opción. Otra forma de expresar mi pregunta es: ¿puede el código llegar a un punto en el que el costo de mantenerlo sea mayor que el costo de eliminarlo y comenzar de nuevo?
Creo que la línea en la arena es cuando el mantenimiento básico está tomando un 25% - 50% más de lo que debería. Llega un momento en que mantener el código heredado se vuelve demasiado costoso. Una cantidad de factores contribuyen a la decisión final. El tiempo y el costo son los factores más importantes, creo.
Dos hilos de pensamiento en este: ¿tiene los requisitos originales? ¿Confía en que los requisitos originales son precisos? ¿Qué pasa con los planes de prueba o las pruebas unitarias? Si tiene esas cosas en su lugar, podría ser más fácil.
Poniéndome el sombrero de mi cliente, ¿el sistema funciona o es inestable? Si tienes algo que es inestable, tienes un argumento para cambiar; de lo contrario, será mejor que lo refactorice poco a poco.
Estoy de acuerdo con Martin. Realmente necesita ponderar el esfuerzo que supondrá escribir la aplicación desde cero contra el estado actual de la aplicación y cuántas personas la usan, les gusta, etc. A menudo es posible que deseemos comenzar completamente desde cero, pero el el costo supera con creces el beneficio. Me encuentro con códigos de aspecto feo todo el tiempo, pero pronto me doy cuenta de que algunas de estas áreas "feas" son realmente correcciones de errores y hacen que el programa funcione correctamente.
Solo puedes dar un sí definitivo a la reescritura en caso de que sepas completamente cómo funciona tu aplicación (y por completo lo digo en serio, no solo tengo una idea general de cómo debería funcionar) y sabes más o menos exactamente cómo hacerlo mejor. En cualquier otro caso y es un tiro en la oscuridad, depende de muchas cosas. Quizás la refactorización gradual sería más segura si es posible.
Una regla práctica que he encontrado útil es que si se me da una base de código, si tengo que volver a escribir más del 25% del código para hacerlo funcionar o modificarlo según los nuevos requisitos, también puede volver a escribir desde cero.
El razonamiento es que solo puedes parchar un cuerpo de código hasta el momento; más allá de cierto punto, es más rápido hacer otra cosa.
Hay una suposición subyacente de que tiene un mecanismo (como pruebas exhaustivas de unidades y / o sistemas) que le dirá si su versión reescrita es funcionalmente equivalente (donde debe ser) como el original.
Creo que hay una serie de problemas que dependen en gran medida de dónde se encuentre.
¿Funciona bien el software desde la perspectiva del cliente? (Si es así, tenga mucho cuidado con los cambios). Pensaría que sería poco conveniente volver a intentarlo a menos que estuviera expandiendo el conjunto de características si el sistema estaba funcionando. ¿Y planea expandir las características y la base de clientes del software? Si es así, entonces tienes muchas más razones para cambiar.
Por mucho que intente comprender el código de algún otro, incluso si está bien escrito, puede ser difícil, cuando esté mal escrito me imaginaría casi imposible. Lo que describes suena como algo que sería muy difícil de expandir.
Creo que la regla fue ...
- La primera versión es siempre un tiro lejos
Entonces, si aprendiste tu (s) lección (s), o sus lecciones, entonces puedes continuar y escribirlas ahora que entiendes mejor el dominio de tu problema.
No es que no haya partes que puedan / deban mantenerse. El código probado es el código más valioso, por lo que si no es deficiente de ninguna otra forma que no sea el estilo, no hay razón para tirarlo todo .
Deseche el código viejo temprano y con frecuencia. En caso de duda, tírelo. La parte difícil es convencer a personas no técnicas del costo de mantenimiento.
Siempre que el valor derivado parezca ser mayor que el costo de operación y mantenimiento, aún hay un valor positivo que fluye del software. La pregunta que rodea a reescribir esto: "¿obtendremos aún más valor de una reescritura?" O alternativamente, "¿cuánto más valor obtendremos de una reescritura?" ¿Cuántas horas-persona de mantenimiento ahorrará?
Recuerde, la inversión de reescritura es única. El retorno de la inversión de reescritura dura para siempre. Para siempre
Enfoque la pregunta de valor hacia problemas específicos. Usted enumeró un grupo de ellos arriba. Quédate con eso
"¿Obtendremos más valor al reducir costos al eliminar la basura que no usamos pero que aún tenemos que pasar?"
"¿Tendremos más valor al eliminar la basura que no es confiable y se rompe?"
"¿Obtendremos más valor si lo entendemos, no documentando, sino reemplazando con algo que construimos como equipo?"
Has tu tarea. Tendrás que enfrentar los siguientes stop-stoppers. Estos se originarán en algún lugar de la cadena alimentaria ejecutiva de alguien que responderá de la siguiente manera:
"¿Esta roto?" Y cuando dices "No se estrelló como tal", dirán "No está roto, no lo arregles".
"Has hecho el análisis del código, lo entiendes, ya no necesitas arreglarlo".
¿Cuál es tu respuesta para ellos?
Ese es solo el primer obstáculo. Aquí está la peor situación posible. Esto no siempre sucede, pero sucede con una frecuencia alarmante.
Alguien en su cadena de comida ejecutiva tendrá este pensamiento:
- "Una reescritura no crea suficiente valor. En lugar de simplemente reescribir, vamos a expandirlo". La justificación es que al crear valor suficiente , es más probable que los usuarios compren para reescribir.
Un proyecto donde el alcance se expande, artificialmente, para agregar valor generalmente está condenado.
En cambio, haz la reescritura más pequeña que puedas para reemplazar la maldita cosa. Luego amplíese para que se ajuste a las necesidades reales y agregue valor.
El código con una complejidad ciclomática excesivamente alta (como más de 100 en una gran cantidad de módulos) es una buena pista. Además, ¿cuántos errores tiene / KLOC? ¿Cuán críticos son los errores? ¿Con qué frecuencia se introducen los errores cuando se realizan correcciones de errores? Si tu respuesta es mucha (no puedo recordar las normas en este momento), entonces se justifica una reescritura.
El código de producción siempre tiene algún valor. El único caso en el que realmente lo arrojaría todo y comenzaría de nuevo es si determinamos que la propiedad intelectual está irrevocablemente contaminada. Por ejemplo, si alguien trajo grandes cantidades de código de un empleador anterior, o un gran porcentaje del código fue copiado de una base de código GPLd.
Intentaría considerar la arquitectura del sistema y ver si es posible eliminar y reescribir componentes específicos bien definidos sin comenzar todo desde cero.
Lo que normalmente sucedería es que puede hacer eso (y luego venderlo al cliente / administración), o que descubre que el código es un desastre tan horrible y enredado que se convence aún más de que necesita una reescritura y tiene argumentos más convincentes (incluyendo: "si lo diseñamos bien, nunca deberíamos desechar todo y hacer una tercera reescritura).
Un mantenimiento lento eventualmente causaría una deriva arquitectónica que haría que una reescritura fuera más costosa más adelante.
Lo antes posible. Cada vez que tengas una premonición de que tu código se está convirtiendo lentamente en una bestia fea que es muy probable que consuma tu alma y te dé dolores de cabeza, y sabes que el problema está en la estructura subyacente del código (entonces cualquier corrección sería un truco, por ejemplo, introducir una variable global), entonces es hora de comenzar de nuevo.
Por alguna razón, a la gente no le gusta tirar un código precioso, pero si sientes que estás mejor empezando de nuevo, probablemente tengas razón. Confíe en su instinto y recuerde que no fue una pérdida de tiempo, sino que le enseñó una forma más de NO abordar el problema. Podrías (deberías) usar siempre un sistema de control de versiones para que tu bebé nunca se pierda realmente.
No tengo ninguna experiencia con el uso de métricas para esto, pero el artículo "Modelos de métricas de mantenimiento del software en la práctica" analiza más o menos la misma pregunta que aquí se hizo para dos estudios de casos que hicieron. Comienza con la siguiente nota del editor:
En el pasado, cuando un mantenedor recibía un código nuevo para mantener, la regla general era "Si tiene que cambiar más del 40 por ciento del código de otra persona, tírelo y comience nuevamente". El Índice de Mantenibilidad [IM] aquí tratado proporciona un método mucho más cuantificable para determinar cuándo "tirarlo y empezar de nuevo". Este trabajo fue patrocinado por el Centro de Guerra de Información de la Fuerza Aérea de los EE. UU. Y el Departamento de Energía de los EE. UU. [DOE], Oficina de Campo de Idaho, Contrato DOE No. DE-AC07-94ID13223.)
Nunca he descartado completamente el código. Incluso cuando se pasa de un sistema foxpro a un sistema ac #.
Si el viejo sistema funcionó, ¿por qué solo tirarlo?
Me he encontrado con un sistema realmente malo. Hilos que se utilizan donde no son necesarios. Herencia horrible y abuso de interfaces.
Lo mejor es entender qué está haciendo el código anterior y por qué lo está haciendo. Luego cámbialo para que no sea confuso.
Por supuesto, si el código anterior no funciona. Quiero decir que ni siquiera puedo compilar. Entonces tal vez esté justificado comenzar de nuevo. Pero, ¿con qué frecuencia sucede eso realmente?
Para realmente chatarra y comenzar de nuevo?
Cuando el código actual no hace lo que le gustaría que hiciera, y su costo sería prohibitivo de cambiar.
Estoy seguro de que alguien ahora va a vincular el artículo de Joel sobre Netscape arrojando su código y cómo es tan terrible y un gran error. No quiero hablar de ello en detalle, pero si vincula ese artículo, antes de hacerlo, considere esto: el motor de IE, el motor que permitió a MS liberar IE 4, 5, 5.5 y 6 de forma rápida sucesión, el motor de IE que destruyó totalmente Netscape ... era nuevo. Trident era un nuevo motor después de tirar el motor IE 3 porque no proporcionaba una base adecuada para su futuro trabajo de desarrollo . MS hizo lo que Joel dice que nunca debes hacer, y es porque MS lo hizo así que tenían un navegador que les permitía eclipsar completamente a Netscape. Así que por favor ... solo medite en ese pensamiento por un momento antes de vincular a Joel y decir "ah, nunca deberías hacerlo, es una idea terrible".
Sí, puede suceder totalmente. He visto que se ahorra dinero haciéndolo.
Esta no es una decisión tecnológica, es una decisión comercial. Las reescrituras de código son ganancias a largo plazo, mientras que "si no está totalmente roto ..." es una ganancia a corto plazo. Si está en un primer año de inicio que está enfocado en sacar un producto de la puerta, la respuesta suele ser simplemente vivir con eso. Si está en una empresa establecida, o si los errores con los sistemas actuales están causando más carga de trabajo, por lo tanto, más dinero de la empresa ... entonces podrían buscarlo.
Presente el problema lo mejor que pueda a su gerente general, use los valores en dólares donde pueda. "No me gusta lidiar con eso" no significa nada. "Tomará el doble de tiempo para hacer todo hasta que esto se arregle" significa mucho.
Si es posible, normalmente preferiría reescribir partes más pequeñas del código a lo largo del tiempo cuando necesite refactorizar una línea base. Normalmente hay muchos problemas más pequeños, como el número mágico, comentarios pobres, etc. que tienden a hacer que el código parezca peor de lo que realmente es. Entonces, a menos que la línea de base sea horrible, conserve el código y simplemente realice mejoras al mismo tiempo que mantiene el código.
Si la refacturación requiere mucho trabajo, recomiendo diseñar un pequeño plan de rediseño / lista de tareas pendientes que le proporcione una lista de las cosas en las que trabajar para que pueda mejorar la línea base. Empezar de cero es siempre un movimiento arriesgado y no se garantiza que el código sea mejor cuando haya terminado. Al usar esta técnica, siempre tendrá un sistema funcional que mejore con el tiempo.
Si hay interfaces limpias y puede delinear claramente los límites del módulo, entonces puede valer la pena refactorizarlo módulo por módulo o capa por capa para permitirle migrar clientes existentes hacia bases de código más estables y más limpias, y con el tiempo, después de que '' Si refactorizamos cada módulo, habrá reescrito todo.
Pero, según la revisión del código, no parece que haya límites claros.
Si requiere más tiempo para leer y comprender el código (si es posible) de lo que sería reescribir toda la aplicación, digo descartarlo y comenzar de nuevo.
Sin intención de ofender, la decisión de reescribir una base de código desde cero es un error de administración común y serio que los desarrolladores de software nuevos cometen.
Hay muchas desventajas que desconfiar.
- Reescribe para evitar que las nuevas características se desarrollen en frío durante meses / años. Pocos, si es que algunas empresas pueden permitirse quedarse quietas por tanto tiempo.
- La mayoría de los cronogramas de desarrollo son difíciles de identificar. Esta reescritura no será una excepción. Amplifique el punto anterior, ahora, un retraso en el desarrollo.
- Se reintroducirán los errores que se corrigieron en la base de código existente a través de una experiencia dolorosa. Joel Spolsky tiene más ejemplos en este artículo .
- Peligro de ser víctima del efecto del Segundo sistema - en resumen, `` las personas que han diseñado algo solo una vez antes intentan hacer todas las cosas que "no lograron hacer la última vez", cargando el proyecto con todo cosas que postergaron al crear la versión uno, incluso si la mayoría de ellas también se postergaran en la versión dos ".
- Una vez que se complete esta reescritura costosa y onerosa, es probable que el próximo equipo en heredar la nueva base de código use las mismas excusas para hacer otra reescritura. Los programadores odian aprender el código de otra persona. Nadie escribe el código perfecto porque la perfección es muy subjetiva. Encuéntrame cualquier aplicación en el mundo real y te puedo dar una acusación condenatoria y un razonamiento para hacer una reescritura desde cero.
Si finalmente reescribe desde cero o no, comenzar una fase de refactorización ahora es una buena manera de sentarse y comprender el problema para que la reescritura sea más fluida si es realmente necesario, y darle una apariencia honesta al código existente. para ver realmente si se necesita una reescritura.
Tendría en cuenta que si la aplicación hace lo que se pretende, es necesario que realice alguna modificación, y está seguro de que la aplicación se ha probado exhaustivamente en todos los escenarios en los que se utilizará.
No invierta tiempo si la aplicación no necesita modificaciones. Sin embargo, si no funciona como necesita y necesita controlar las horas y el tiempo invertido para realizar correcciones, elimine y vuelva a escribir según los estándares que su equipo pueda admitir. No hay nada peor que un código terrible que tienes que soportar / descifrar pero que aún tienes que vivir. Recuerda, la Ley de Murphy dice que serán las 10 de la noche cuando tendrás que hacer que las cosas funcionen, y eso nunca es productivo.
Vi una aplicación reorganizada dentro de los 2 años de su introducción en producción, y otras reescritas en diferentes tecnologías (una era C ++, ahora Java). Ambos esfuerzos no fueron, en mi opinión, exitosos.
Prefiero un enfoque más evolutivo al mal software. Si puede "componentizar" su aplicación anterior de manera que pueda introducir sus nuevos requisitos e interactuar con el código anterior, puede introducirse en el nuevo entorno sin tener que "vender" la inversión de valor cero (desde una perspectiva de negocio) en reescribiendo
Enfoque sugerido: escriba pruebas de unidad para la funcionalidad con la que desea interactuar: 1) asegúrese de que el código se comporte como espera y 2) proporcione una red de seguridad para cualquier refactorización que desee realizar en la base anterior.
El código malo es la norma. Creo que TI tiene una mala reputación en los negocios por favorecer la reescritura / rearchitecting / etc. Pagan el dinero y "confían" en nosotros (como industria) para ofrecer un código sólido y extensible. Lamentablemente, las presiones comerciales a menudo resultan en atajos que hacen que el código no se pueda mantener. A veces son malos programadores ... a veces malas situaciones.
Para responder a su pregunta reformulada ... ¿pueden los costos de mantenimiento del código exceder los costos de reescritura ... la respuesta es claramente sí? Sin embargo, no veo nada en sus ejemplos que me lleve a creer que este es su caso. Creo que esos problemas se pueden abordar con pruebas y refactorización.
Si requiere más tiempo para leer y comprender el código (si es posible) de lo que sería reescribir toda la aplicación, digo descartarlo y comenzar de nuevo.
Ten mucho cuidado con esto:
- ¿Estás seguro de que no solo eres perezoso y no te molestas en leer el código?
- ¿Eres arrogante con respecto al gran código que escribirás en comparación con la basura que produjo cualquier otra persona?
- Recuerde que el código de trabajo probado vale mucho más que el código imaginario aún por escribir
En palabras de nuestro estimado anfitrión y señor supremo, Joel, cosas que nunca debes hacer ,
no siempre es malo abandonar el código de trabajo, pero debes estar seguro de la razón.
Me pregunto si las personas que votan por el desguace y el reingreso han reconfigurado con éxito un gran proyecto, o al menos visto un gran proyecto en malas condiciones que creen que podría utilizar una refactorización.
En todo caso, me equivoco en el lado opuesto: he visto 4 proyectos grandes que eran un desastre, que defendía la refactorización en lugar de la reescritura. En una pareja, apenas quedaba una línea de código original, y las principales interfaces cambiaban significativamente, pero el proceso nunca implicó que todo el proyecto dejara de funcionar tan bien como originalmente, durante más de una semana. (Y la parte superior del tronco nunca se rompió).
Tal vez exista un proyecto tan severamente roto que tratar de refactorizarlo esté condenado al fracaso, o quizás uno de los proyectos previos que refactoricé hubiera sido mejor atendido por una "nueva escritura limpia", pero no estoy seguro. Sabría cómo reconocerlo.
Voy a publicar este libro cada vez que vea una discusión sobre Refactorización. Todos deberían leer "Trabajar eficazmente con código heredado" de Michael Feathers. Descubrí que es un libro excelente, si no más, es divertido y motivador.
Cuando el código ha alcanzado un punto que ya no es mantenible o extensible. Está lleno de soluciones piratas a corto plazo. Tiene mucho acoplamiento. Tiene métodos largos (100 + líneas). Tiene acceso a la base de datos en la interfaz de usuario. Genera muchos errores aleatorios e imposibles de depurar.
En pocas palabras: cuando mantenerlo es más caro (es decir, lleva más tiempo) que reescribirlo.
¿Cuándo es bueno (si es que lo es) eliminar el código de producción y comenzar de nuevo?
Nunca tuve que hacer esto, pero la lógica dictaría (a mí, de todos modos) que una vez que pasas el punto de inflexión en el que pasas más tiempo rehaciendo y corrigiendo errores en la base de códigos existente que estás agregando nueva funcionalidad, es hora de tirar basura las cosas viejas y comenzar de nuevo.
Solía creer en tan sólo volver a escribir desde cero, pero es un error.
http://www.joelonsoftware.com/articles/fog0000000069.html
Cambié de idea.
Lo que me sugiere es encontrar una forma de refactorizar el código correctamente. Mantenga toda la funcionalidad y prueba existente a medida que avanza. Todos hemos visto las bases de código horribles, pero es importante tener el conocimiento con el tiempo que tiene la aplicación.
En términos de valor comercial, creo que es extremadamente raro que se pueda presentar un caso real para una reescritura debido únicamente al estado interno del código. Si el producto está orientado al cliente y actualmente está activo y trayendo dinero (es decir, no es un producto desmovilizado o inédito), entonces considere que:
- Ya tienes clientes que lo usan. Están familiarizados con esto y podrían haber construido algunos de sus propios activos a su alrededor. (Otros sistemas que interactúan con él, productos basados en él, procesos que deberían cambiar, personal que tal vez tendrían que volver a entrenar). Todo esto le cuesta dinero al cliente.
- Reescribirlo podría costar menos a largo plazo que hacer cambios y correcciones difíciles. Pero aún no puedes cuantificar eso, a menos que tu aplicación no sea más compleja que Hello World. Y una re-escritura significa una nueva prueba y una redistribución, y probablemente una ruta de actualización para sus clientes.
- ¿Quién dice que la reescritura será mejor? ¿Puede decir honestamente que su empresa está escribiendo un código brillante ahora? ¿Se corrigieron las prácticas que convirtieron el código original en espagueti? (Incluso si el principal culpable fue un solo desarrollador, ¿dónde estaban sus compañeros y la gerencia, asegurando la calidad a través de revisiones, pruebas, etc.?)
En términos de razones técnicas, sugeriría que podría ser el momento de una reescritura importante si el original tiene algunas dependencias técnicas que se han vuelto problemáticas. por ejemplo, una dependencia de terceros que ahora está fuera de soporte, etc.
En general, creo que el movimiento más sensato es refactorizar pieza por pieza (piezas muy pequeñas si es realmente tan malo), y mejorar la arquitectura interna de forma incremental en lugar de en una gran caída.