2010-08-04 12 views
21

Me encuentro en una situación en la que tenemos muchos métodos muy largos, 1000 líneas o más.¿Los métodos muy largos siempre necesitan refactorización?

Para darle más detalles, tenemos una lista de comandos de alto nivel entrantes, y cada genera resultados en una lista más larga (alguna vez enorme) de comandos de nivel inferior. Hay una fábrica que crea una instancia de una clase para cada comando entrante. Cada clase tiene un método de proceso, donde todos los comandos de nivel inferior son generados añadidos en secuencia. Como dije, estas secuencias de comandos y sus parámetros causan con bastante frecuencia que los métodos de proceso lleguen a miles de líneas.

Hay muchas repeticiones. Muchos patrones de comando se comparten entre diferentes comandos, pero el código se repite una y otra vez. Eso me lleva a pensar que refactorizar sería una muy buena idea.

Por el contrario, las especificaciones hemos llegado exactamente en la misma forma que el código actual. Lista muy larga de comandos para cada uno entrante. Cuando intenté refactorizar algo, empecé a sentirme incómodo con las especificaciones. Echo de menos la analogía obvia entre las especificaciones y el código, y pierdo tiempo cavando en clases comunes recién creadas.

Luego aquí la pregunta: en general, ¿cree que estos métodos tan largos siempre necesitarían refactorización, o en un caso similar sería aceptable? (por desgracia refactorización las especificaciones no es una opción)


edición: He eliminado todas las referencias a "generar" causa realidad era confuso. No es un código generado automáticamente.

class InCmd001 { 

    OutMsg process (InMsg& inMsg) { 

    OutMsg outMsg = OutMsg::Create(); 

    OutCmd001 outCmd001 = OutCmd001::Create(); 
    outCmd001.SetA(param.getA()); 
    outCmd001.SetB(inMsg.getB()); 

    outMsg.addCmd(outCmd001); 

    OutCmd016 outCmd016 = OutCmd016::Create(); 
    outCmd016.SetF(param.getF()); 

    outMsg.addCmd(outCmd016); 

    OutCmd007 outCmd007 = OutCmd007::Create(); 
    outCmd007.SetR(inMsg.getR()); 

    outMsg.addCmd(outCmd007); 

    // ...... 

    return outMsg; 
    } 
} 

aquí el ejemplo de una clase de comando entrante (escrito manualmente en pseudo-C++)

+0

¿Puede darnos algún pseudo código que nos muestre el patrón de estos métodos enormes? –

+1

Parece que podría ser un candidato para la generación de código. –

+0

@Binary Worrier: la muestra es una versión muy simplificada, solo para darle una idea. De todos modos, estoy de acuerdo en que incluso el código real podría ser de alguna manera autogenerado, pero esto no va a suceder en el corto plazo. – Gianluca

Respuesta

11

Entiendo exactamente de dónde vienes, y puedo ver exactamente por qué has estructurado tu código tal como está, pero debe cambiar.

La incertidumbre que siente al intentar refactorizar puede mejorarse mediante pruebas de unidad de escritura. Si tiene pruebas específicas para cada especificación, entonces el código para cada especificación se puede refactorizar hasta que esté azul en la cara, y puede tener confianza en ello.

Una segunda opción, ¿es posible generar automáticamente su código a partir de una estructura de datos? Si tiene un conjunto básico de clases que hacen el trabajo de burro y las cajas de borde, puede generar automáticamente los métodos repetitivos de 1000 líneas tantas veces como desee.

Sin embargo, hay excepciones para cada regla.
Si los métodos son una interpretación literal de la especificación (muy poca lógica adicional), y las especificaciones cambian con poca frecuencia, y las partes "comunes" (es decir, bits que son los mismos en este momento) cambian en diferentes momentos , y no se le pedirá que obtenga una ganancia de rendimiento 10 veces del código en el corto plazo, luego (y solo entonces). . . puedes estar mejor con lo que tienes.

. . . pero en general, refactor.

+1

+1 para generar el código a partir de un dato estructura - Considero que un "lenguaje muy específico para problemas" (VPSL?) solo para llevar la verbosidad a niveles manejables. – peterchen

10

Sí, siempre. 1000 líneas es al menos 10 veces más larga que cualquier función, y estoy tentado de decir 100x, excepto que cuando se trata de análisis y validación de entrada, puede ser natural escribir funciones con 20 o más líneas.

Editar: Simplemente vuelva a leer su pregunta y no tengo claro un punto: ¿está hablando de un código generado por la máquina que nadie tiene que tocar? En ese caso, dejaría las cosas como están.

+1

No, es un código escrito y mantenido todos los días por humanos. – Gianluca

+1

Excelente punto sobre el tamaño de lo que una función DEBERÍA ser. –

6

Los métodos largos necesitan una refactorización si los humanos los mantienen (y por lo tanto deben ser comprendidos).

+2

+1 solo refactoriza los métodos codificados a mano, no los generados. – BoltClock

+3

Suena poco probable que hable sobre el código generado.Si es así, votaría por cerrar debido a "robar nuestro tiempo" –

+0

Definitivamente no es código generado automáticamente. – Gianluca

1

¿Alguna vez tiene que leer o mantener el código generado?

En caso afirmativo, entonces creo que algunas refacciones podrían estar en orden.

Si no, entonces el lenguaje de nivel superior es realmente el idioma con el que está trabajando -el C++ es solo una representación intermedia en el camino hacia el compilador- y puede que no sea necesario refactorizar.

0

He visto casos en los que no es el caso (por ejemplo, crear una hoja de cálculo de Excel en .Net a menudo requiere mucha línea de código para formatear la hoja), pero la mayoría de las veces, la mejor cosa sería refactorizarlo.

Personalmente intento hacer una función lo suficientemente pequeña para que aparezca en mi pantalla (sin afectar la legibilidad, por supuesto).

0

1000 líneas? Definitivamente necesitan ser refactorizados. Además, no es que, por ejemplo, el número máximo predeterminado de declaraciones ejecutables es 30 en Checkstyle, comprobador estándar de codificación bien conocido.

1

Me parece que ha implementado un idioma diferente dentro de su aplicación. ¿Ha pensado en hacerlo de esa manera?

+1

también estaba pensando en esa dirección. Tal vez puedas escribir un sistema que haga todas esas llamadas en función de algunos datos de entrada; entonces usted podría alimentar directamente las especificaciones, en algún formato. Porque lo que describes es básicamente una segunda capa de lógica (especificaciones a llamadas) que, sin embargo, no es ejecutada por un procesador sino por un programador deficiente. Siempre me gusta cuando mi código habla sobre lo que hace: si tienes un método principal que básicamente dice "y aquí tomamos las especificaciones y realizamos las llamadas como se especifica", tu código debería ser mucho más claro y mucho más pequeño. – Nicolas78

+0

De alguna manera sí, pero ¿qué quieres decir con "ir por ese camino"? – Gianluca

+0

@ nicolas78 Definitivamente tienes razón, pero eso no será posible por mucho tiempo. Hay una gran base de código y actualmente las especificaciones vienen como Word Docs :-( – Gianluca

10

Refectar no es lo mismo que escribir desde cero. Si bien nunca debe escribir código como este, antes de refactorizarlo, debe considerar los costos de refactorización en términos de tiempo invertido, los riesgos asociados en términos de romper el código que ya funciona y los beneficios netos en términos de tiempo futuro ahorrado . Refactorice solo si los beneficios netos superan los costos y riesgos asociados.

En ocasiones, el ajuste y la reescritura pueden ser una solución más segura y rentable, incluso si a primera vista parece caro.

+0

Me gusta que esta NO sea la respuesta final. Solo el Abridor conoce todos los factores asociados con la refacturación de este código. –

0

Entonces aquí la cuestión: en general, hacer usted que tales métodos muy largos haría siempre se necesita refactorización,

si preguntas en general, diremos .

o en un caso similar, sería aceptable? (por desgracia refactorización las especificaciones no es una opción)

A veces son aceptable, pero es muy inusual, te daré un par de ejemplos: Hay unos 8 microcontroladores bits llamados Microchip PIC, que solo tiene una pila fija de 8 niveles, por lo que no puede anidar más de 8 llamadas, entonces se debe tener cuidado para evitar el "desbordamiento de la pila", por lo que en este caso especial tener muchas funciones pequeñas (anidadas) no es la mejor opción . Otro ejemplo es cuando se optimiza el código (a un nivel muy bajo), por lo que debe tener en cuenta el costo de salto y el ahorro de contexto. Úselo con cuidado.

EDIT:

Incluso en el código generado, lo que necesita para su refactorize la manera genera, por ejemplo, para el ahorro de memoria, ahorro de energía, generar legible, belleza, quién sabe, etc ..

38

Código nunca necesita refactorización. El código funciona, o no funciona. Y si funciona, el código no necesita nada.

La necesidad de refactorización viene de usted, el programador. La persona que lee, escribe, mantiene y extiende el código.

Si tiene problemas para entender el código, debe volver a cambiarlo. Si sería más productivo limpiando y refactorizando el código, debe ser refactorizado.

En general, yo diría que es una buena idea por su propio bien para refactorizar más de 1000 funciones de línea. Pero no lo estás haciendo porque el código lo necesita. Lo haces porque eso hace que sea más fácil para ti entender el código, probar su corrección y agregar nuevas funcionalidades.

Por otro lado, si el código es generado automáticamente por otra herramienta, nunca necesitará leerlo o editarlo. Entonces, ¿cuál sería el sentido de refactorizarlo?

+0

+1 Bien articulado –

+0

+1 debe ser el mantra para todos los programadores –

+1

, por su propio bien y para el El bien de cualquier otro humano que tenga que leer el código, que a menudo es mucha gente, pero la esencia de lo que dices es verdad. – Aviendha

2

Creo que primero necesita "refactorizar" las especificaciones. Si hay repeticiones en la especificación, será más fácil de leer, si utiliza algunos "bloques de construcción básicos".


Editar: Siempre que no pueda refactorizar las especificaciones, no cambiaría el código. Las guías de estilo de codificación están hechas para facilitar el mantenimiento del código, pero en su caso especial, la facilidad de mantenimiento se logra siguiendo las especificaciones.

Algunas personas aquí preguntaron si se genera el código. En mi opinión, no importa: si el código sigue la especificación "línea por línea", no importa si el código se genera o se escribe a mano.

1

Tengo entendido que se recomienda refactorizar cualquier método de más de 100 líneas de código.

3

Como regla general, codifique primero para humanos. No estoy de acuerdo con la idea común de que las funciones deben ser cortas. Creo que lo que debes apuntar es cuando un humano lee tu código, lo asimilan rápidamente.

Para este efecto, es una buena idea simplificar las cosas tanto como sea posible, pero no más que eso. Es una buena idea delegar aproximadamente una tarea para cada función. No hay una regla en cuanto a lo que significa "aproximadamente una tarea": ​​tendrás que usar tu propio juicio para eso. Pero reconozca que una función dividida en demasiadas funciones reduce la legibilidad. Piensa en el ser humano que lee tu función por primera vez: tendrían que seguir una llamada de función tras otra, cambiar constantemente de contexto y mantener una pila en su mente. Esta es una tarea para máquinas, no para humanos.

Encuentra el saldo.

Aquí ves lo importante que es nombrar las cosas.Verá que no es tan fácil elegir nombres para variables y funciones, lleva tiempo, pero por otro lado puede ahorrar mucha confusión en el lado del lector humano. Nuevamente, encuentre el equilibrio entre salvar su tiempo y el tiempo de los humanos amigables que lo seguirán.

En cuanto a la repetición, es una mala idea. Es algo que debe corregirse, al igual que una pérdida de memoria. Es una bomba de tiempo.

Como han dicho otros antes que yo, cambiar el código puede ser costoso. Debe pensar si valdrá la pena gastar todo este tiempo y esfuerzo, enfrentando los riesgos del cambio, para obtener un código mejor. Posiblemente perderá mucho tiempo y se convertirá en un dolor de cabeza después de otro ahora, con el fin de ahorrar mucho tiempo y dolor de cabeza más adelante.

+1

Mis palabras. Solo para agregar esto: no cambies el código de ejecución sin una buena razón –

0

Si refactoriza, cuando refactorice, agregue algunos comentarios para explicar qué diablos está haciendo.

Si tuviera comentarios, sería mucho menos probable que sea un candidato para la refactorización, porque ya sería más fácil de leer y seguir para alguien que comienza desde cero.

1

Creo que algunas reglas pueden ser un poco diferentes en su época cuando el código se ve más comúnmente en un IDE. Si el código no contiene repetición explotable, de modo que hay 1.000 líneas a las que se hará referencia una vez cada una, y que comparten una cantidad significativa de variables de forma clara, dividiendo el código en rutinas de 100 líneas, cada una de las cuales se llama una vez puede no ser una gran mejora en comparación con tener un bien formateado módulo de 1,000 líneas que incluye etiquetas #region o su equivalente para permitir la visualización de estilo de esquema.

Mi filosofía es que ciertos diseños de código generalmente implican ciertas cosas. En mi opinión, cuando una parte del código se coloca en su propia rutina, eso sugiere que el código se podrá utilizar en más de un contexto (excepción: controladores de devolución de llamada y similares en idiomas que no admiten métodos anónimos). Si el segmento de código # 1 deja un objeto en un estado oscuro que solo es utilizable por el segmento de código n. ° 2, y el segmento de código n. ° 2 solo se puede usar en un objeto de datos que se deja en el estado creado por n. ° 1. para poner los segmentos en diferentes rutinas, deben aparecer en la misma rutina. Si un programa coloca los objetos a través de una cadena de estados oscuros que se extiende a cientos de líneas de código, podría ser bueno volver a trabajar el diseño del código para subdividir la operación en piezas más pequeñas que tienen pre y post "natural" - condiciones, pero a falta de alguna razón convincente para hacerlo, no me gustaría dividir el código sin cambiar el diseño.

3

Eche un vistazo a la pregunta relacionada How many lines of code is too many?. Hay bastantes cositas de sabiduría en todas las respuestas allí.

volver a publicar una cita (aunque voy a intentar hacer comentarios al respecto un poco más aquí) ... Hace un tiempo, leí este passage from Ovid's journal:

Hace poco escribió algo de código para Clase: : Sniff que detectaría "long métodos" e informarlos como un olor de código . Incluso escribí una entrada en el blog sobre cómo lo hice (qué sorpresa, ¿eh?). Fue entonces cuando Ben Tilly hizo una pregunta embarazosamente obvia : ¿cómo sé que los métodos largos son un olor de código ?

Tiré las justificaciones habituales, pero él no se detuvo.Quería información de y citó el excelente código de libro como contraargumento . Bajé mi copia de este libro y comencé a leer "Cómo largo debería ser una rutina" (página 175, segunda edición). El autor, Steve McConnell, argumenta que las rutinas deben ser que no superen las 200 líneas. Holy crud! Eso es muy largo. Si una rutina es más larga que aproximadamente 20 o 30 líneas, creo que es hora de romperla arriba.

Lamentablemente, McConnell tiene la mejilla citar seis estudios separados, todos que encontró que las rutinas más largas eran no sólo no se correlaciona con una mayor tasa de defectos , pero también eran a menudo baratos de desarrollar y fácil de comprender. Como resultado, la última versión de Class :: Sniff en github ahora documenta que las rutinas más largas pueden no ser ser un olor de código después de todo. Ben estaba a la derecha. Estaba equivocado.

(El resto de la entrada, en TDD, vale la pena leer también.)

Viniendo de los "métodos más cortos están mejor" campo, esto me dio mucho que pensar.

Anteriormente, mis métodos grandes generalmente estaban limitados a "Necesito entrar aquí, y el compilador no coopera", o "por una razón u otra, el bloque de conmutadores gigantes realmente se ejecuta más rápido que la tabla de distribución", o "este las cosas solo se llaman exactamente en secuencia y realmente no quiero que la llamada funcione aquí ". Todos los casos relativamente raros.

En su situación, sin embargo, tendría un gran prejuicio hacia no tocar cosas: la refactorización conlleva cierto riesgo inherente, y actualmente puede superar la recompensa. (Descargo de responsabilidad: estoy un poco paranoico; por lo general, soy el tipo que termina arreglando los bloqueos.)

Considere invertir sus esfuerzos en pruebas, afirmaciones o documentación que pueda fortalecer el código existente e inclinar el riesgo/recompensa escala antes de cualquier intento de refactorización: invariant checks, bound function análisis y pre/postcondition tests; cualquier otro concepto útil de DBC; tal vez incluso una implementación paralela en otro idioma (tal vez algo orientado a mensajes como Erlang podría darle una mejor perspectiva, dado el ejemplo de código) o incluso algún tipo de formal logical representation de la especificación que está tratando de seguir si tiene algo de tiempo para grabar.

Cualquiera de estos tipos de esfuerzos generalmente tiene unos pocos resultados, incluso si no tiene la oportunidad de refactorizar el código: aprende algo, aumenta su comprensión (y la de su organización) y la capacidad de utilizar el código y las especificaciones , es posible que encuentre algunos agujeros que realmente deben llenarse ahora, y se vuelve más seguro de su capacidad de realizar cambios con menos posibilidades de consecuencias desastrosas.

A medida que comprenda mejor el dominio del problema, puede encontrar que hay diferentes maneras de refactorizar que no había pensado previamente.

Esto no quiere decir que "tendrá un conjunto de pruebas de cobertura total, y DBC afirma, y ​​una especificación lógica formal". Es solo que se encuentra en una situación típicamente imperfecta, y se está diversificando un poco, buscando formas novedosas de abordar los problemas que encuentra (facilidad de mantenimiento, facilidad de aprendizaje del sistema).): puede brindarle un poco de avance y un aumento de la confianza, después de lo cual puede dar pasos más grandes.

Así que piense menos desde la perspectiva de "demasiadas líneas es un problema" y más de "esto podría ser un olor a código, qué problemas nos causará, y hay algo fácil y/o gratificante que puede hacer al respecto? "

Dejarlo cocinando en la parte posterior por un tiempo - volver y volver a visitarlo cuando el tiempo y las coincidencias lo permitan (por ejemplo, "hoy trabajo cerca del código, tal vez pasearé para ver si no puedo documentar las suposiciones un poco mejor ... ") puede producir buenos resultados. Por otra parte, ser realzado y decidir algo debe ser hecho sobre la situación también es efectivo.

¿He logrado ser bastante flojo aquí? Mi punto, creo, es que el código huele, los patrones/antipatrones, las mejores prácticas, etc., están ahí para servirlo. Experimenta para acostumbrarte a ellos, y luego toma lo que tenga sentido para tu situación actual, y deja el resto.

1

1000 mil líneas de código no es nada. Tenemos funciones de 6 a 12 mil líneas de largo. Por supuesto, esas funciones son tan grandes, que literalmente las cosas se pierden allí, y ninguna herramienta puede ayudarnos siquiera a mirar abstracciones de alto nivel de ellos. el código ahora es desafortunadamente incomprensible. Mi opinión sobre las funciones que son tan grandes, es que no fueron escritas por brillantes programadores sino por hackers incompetentes que no deben dejarse cerca de una computadora, sino que deben ser despedidos y dejaron hamburguesas en McDonald's. Este código hace estragos dejando atrás características que no se pueden agregar o mejorar. (Demasiado malo para el cliente). El código es tan frágil que no puede ser modificado por nadie, ni siquiera por los autores originales.

Y sí, esos métodos deben ser refactorizados o desechados.

0

Ha habido muy buenos consejos generales, aquí una recomendación práctica para su muestra:

patrones comunes se pueden aislar en los métodos de alimentación de civil:

void AddSimpleTransform(OutMsg & msg, InMsg const & inMsg, 
         int rotateBy, int foldBy, int gonkBy = 0) 
{ 
    // create & add up to three messages 
} 

Es posible incluso mejorar que al hacer esto una miembro del outMsg, y utilizando una interfaz fluida, de manera que se puede escribir

OutMsg msg; 
msg.AddSimpleTransform(inMsg, 12, 17) 
    .Staple("print") 
    .AddArtificialRust(0.02); 

que puede ser una mejora adicional de acuerdo con las circunstancias.

+0

De hecho, hacemos algo muy similar. Pero no lo mostré en el ejemplo para mantener las cosas claras. En la práctica, escribimos algo así como: + outCmd001 . SetA (param.getA()) . SetB (inMsg.getB()); – Gianluca

Cuestiones relacionadas