¿Los métodos largos siempre son malos?

64

Así que al mirar antes, noté algunos comentarios acerca de que los métodos largos son una mala práctica.

No estoy seguro de estar siempre de acuerdo en que los métodos largos son malos (y me gustaría recibir opiniones de otros).

Por ejemplo, tengo algunas vistas de Django que procesan un poco los objetos antes de enviarlos a la vista, un método largo son 350 líneas de código. Tengo mi código escrito para que se ocupe de los parámetros: ordenar / filtrar el conjunto de consultas, luego, poco a poco, se procesa algo en los objetos que ha devuelto mi consulta.

Por lo tanto, el procesamiento es principalmente una agregación condicional, que tiene reglas suficientemente complejas que no se pueden hacer fácilmente en la base de datos, así que tengo algunas variables declaradas fuera del bucle principal y luego las modifico durante el bucle.

variable_1 = 0
variable_2 = 0
for object in queryset :
     if object.condition_condition_a and variable_2 > 0 :
     variable 1+= 1
     .....
     ...    
     . 
      more conditions to alter the variables

return queryset, and context 

Entonces, de acuerdo con la teoría, debería factorizar todo el código en métodos más pequeños, de modo que tenga el método de visualización con una longitud máxima de una página.

Sin embargo, después de haber trabajado en varias bases de código en el pasado, a veces encuentro que el código es menos legible, cuando necesita saltar constantemente de un método a otro para descubrir todas las partes del mismo, mientras mantiene el método más externo. tu cabeza.

Encuentro que al tener un método largo que está bien formateado, puedes ver la lógica más fácilmente, ya que no se esconde en los métodos internos.

Podría dividir el código en métodos más pequeños, pero a menudo hay un bucle interno que se usa para dos o tres cosas, por lo que resultaría en un código más complejo, o métodos que no hacen una cosa sino dos o tres (alternativamente, podría repetir bucles internos para cada tarea, pero luego habrá un impacto de rendimiento).

Entonces, ¿existe el caso de que los métodos largos no siempre sean malos? ¿Hay siempre un caso para los métodos de escritura, cuando solo se usarán en un lugar?

ACTUALIZACIÓN: Parece que hice esta pregunta hace más de un año.

Así que he refactorizado el código después de la respuesta (mixta) aquí, lo dividí en métodos. Es una aplicación de Django que recupera conjuntos complejos de objetos relacionados de la base de datos, por lo que el argumento de prueba está fuera (probablemente habría tomado la mayor parte del año crear objetos relevantes para los casos de prueba. Tengo un tipo de "esto se hizo ayer") ambiente de trabajo antes de que alguien se queje). Arreglar errores en esa parte del código es un poco más fácil ahora, pero no de forma masiva.

antes:

#comment 1 
bit of (uncomplicated) code 1a  
bit of code 2a

#comment 2 
bit of code 2a
bit of code 2b
bit of code 2c

#comment 3
bit of code 3

ahora:

method_call_1
method_call_2
method_call_3

def method_1 
    bit of (uncomplicated) code 1a  
    bit of code 2a

def method_2 
    bit of code 2a
    bit of code 2b
    bit of code 2c

def method_3
    bit of code 3
    
pregunta wobbily_col 18.10.2013 - 20:37

18 respuestas

78

No, los métodos largos no siempre son malos.

En el libro Code Complete , se mide que los métodos largos a veces son más rápidos y fáciles de escribir, y no dan lugar a problemas de mantenimiento.

De hecho, lo que es realmente importante es mantenerse SECO y respetar la separación de preocupaciones. En algún momento, el cálculo es demasiado largo para escribir, pero realmente no causará problemas en el futuro.

Sin embargo, desde mi experiencia personal, la mayoría de los métodos largos tienden a carecer de separación de preocupación. De hecho, los métodos largos son una forma fácil de detectar que algo PUEDE estar mal en el código, y que se requiere un cuidado especial al realizar la revisión del código.

EDITAR: A medida que se hacen comentarios, agrego un punto interesante a la respuesta. De hecho, también verificaría las métricas de complejidad para la función (NPATH, complejidad ciclomática o incluso mejor CRAP).

De hecho, recomiendo no verificar tales métricas en funciones largas, sino incluir alertas en ellas con herramientas automatizadas (como el estilo de verificación para java, por ejemplo) EN CADA FUNCIÓN.

    
respondido por el deadalnix 17.10.2012 - 23:33
54

La mayor parte del enfoque aquí parece estar alrededor de la palabra siempre . Sí, los absolutos son malos, y la ingeniería de software es casi tanto arte como ciencia, y todo eso ... pero voy a tener que decir que, para el ejemplo que dio, el método sería mejor si se dividiera. arriba. Estos son los argumentos que normalmente usaría para justificar la división de su método:

Legibilidad: no estoy seguro de los demás, pero no puedo leer 350 líneas de código rápidamente. Sí, si es mi código propio , y puedo hacer muchas suposiciones al respecto, podría hojearlo muy rápidamente, pero ese no es el punto. Considere cuánto más fácil sería leer ese método, si consistiera en 10 llamadas a otros métodos (cada uno con un nombre descriptivo). Al hacer esto, introdujo una capa en el código, y el método de alto nivel le da al lector un breve y dulce resumen sobre lo que está sucediendo.

Editar: para poner esto bajo una luz diferente, piénsalo de esta manera: ¿cómo explicarías ese método a un nuevo miembro del equipo? Seguramente tiene alguna estructura que puedes resumir en las líneas de "bueno, comienza haciendo A, luego B, luego a veces C, etc.". Tener un método corto de "vista general" que llame a otros métodos hace que esta estructura sea obvia. Es extremadamente raro encontrar 350 líneas de código que no se benefician; El cerebro humano no está destinado a tratar con listas de cientos de elementos, los agrupamos.

Reutilización: Los métodos largos tienden a tener poca cohesión: a menudo hacen más de una cosa. La baja cohesión es el enemigo de la reutilización; Si combina varias tareas en un solo método, terminará reutilizándose en menos lugares de lo que debería haber sido.

Probabilidad y cohesión: mencioné complejidad ciclomática en un comentario anterior. Es un bonito buena medida de lo complejo que es tu método. Representa el límite inferior del número de rutas únicas a través de su código, dependiendo de las entradas (edición: corregida según el comentario de MichaelT). También significa que para probar adecuadamente su método, debería tener al menos tantos casos de prueba como su número de complejidad ciclomática. Desafortunadamente, cuando se juntan piezas de código que no dependen realmente entre sí, no hay forma de estar seguros de esta falta de dependencia y la complejidad tiende a multiplicarse. Puede pensar en esta medida como una indicación de la cantidad de cosas diferentes que está tratando de hacer. Si es demasiado alto, es hora de dividir y conquistar.

Refactorización y estructura: Los métodos largos a menudo son signos de que falta estructura en el código. A menudo, el desarrollador no pudo averiguar cuáles son los puntos en común entre las diferentes partes de ese método, y dónde se podría trazar una línea entre ellos. Darse cuenta de que un método largo es un problema, y tratar de dividirlo en métodos más pequeños es el primer paso en un camino más largo para identificar realmente una mejor estructura para todo. Quizás necesites crear una clase o dos; ¡Al final no será necesariamente más complejo!

También creo que en este caso, la excusa para tener un método largo es "... algunas variables declaradas fuera del bucle principal se alteran durante el bucle". No soy un experto en Python, pero estoy bastante seguro de que este problema podría solucionarse de forma trivial mediante alguna forma de pasar por referencia.

    
respondido por el Daniel B 15.10.2012 - 16:58
28

Los métodos largos siempre son malos, pero en ocasiones son mejores que las alternativas.

    
respondido por el Telastyn 15.10.2012 - 13:39
8

Los métodos largos son un olor de código . Por lo general, indican que algo está mal, pero no es una regla estricta. Por lo general, los casos en los que están justificados implican una gran cantidad de reglas comerciales estatales y bastante complejas (como ha encontrado).

En cuanto a su otra pregunta, con frecuencia es útil separar fragmentos de lógica en métodos separados, incluso si solo se los llama una vez. Hace que sea más fácil ver la lógica de alto nivel y puede hacer que la gestión de excepciones sea un poco más limpia. ¡Siempre y cuando no tenga que pasar veinte parámetros para representar el estado de procesamiento!

    
respondido por el TMN 15.10.2012 - 14:11
7

Los métodos largos no siempre son malos. Por lo general, son una señal de que puede haber un problema.

En el sistema en el que estoy trabajando, tenemos alrededor de media docena de métodos que tienen más de 10,000 líneas. Una es actualmente 54.830 líneas de largo. Y está bien.

Estas funciones ridículamente largas son muy simples y se generan automáticamente. Ese gran monstruo de 54,830 líneas contiene los datos del movimiento polar diario desde el 1 de enero de 1962 hasta el 10 de enero de 2012 (nuestro último lanzamiento). También lanzamos un procedimiento mediante el cual nuestros usuarios pueden actualizar ese archivo generado automáticamente. Ese archivo fuente contiene los datos de movimiento polar de enlace , traducido automáticamente a C ++ .

La lectura de ese sitio web sobre la marcha no es posible en una instalación segura. No hay conexión con el mundo exterior. Descargar el sitio web como una copia local y analizarlo en C ++ tampoco es una opción; el análisis es lento , y esto tiene que ser rápido. Descargar, traducir automáticamente a C ++ y compilar: ahora tienes algo que es rápido. (Simplemente no lo compile optimizado. Es asombroso el tiempo que tarda un compilador optimizador en compilar 50,000 líneas de código de línea recta extremadamente simple. En mi computadora se demora más de media hora en compilar ese archivo optimizado. Y la optimización no logra absolutamente nada. . No hay nada que optimizar. Es un código de línea recta simple, una declaración de asignación tras otra.)

    
respondido por el David Hammen 15.10.2012 - 14:32
7

Digamos que hay maneras buenas y malas de romper un método largo. Tener que "[mantener] el método más externo en tu cabeza" es una señal de que no lo estás rompiendo de la manera más óptima, o de que tus sub-métodos están mal nombrados. En teoría, hay casos en que un método largo es mejor. En la práctica, es extremadamente raro. Si no puede descubrir cómo hacer legible un método más corto, pídale a alguien que revise su código y pídales ideas específicas para acortar los métodos.

En cuanto a los múltiples bucles que causan un supuesto impacto de rendimiento, no hay forma de saberlo sin medir. Múltiples bucles más pequeños pueden ser significativamente más rápidos si eso significa que todo lo que necesita puede permanecer en el caché. Incluso si hay un impacto en el rendimiento, por lo general es insignificante en favor de la legibilidad.

Diré que a menudo los métodos largos son más fáciles de escribir , aunque son más difíciles de leer . Es por eso que proliferan aunque a nadie les gusten. No hay nada de malo en planificar desde el principio hasta el refactor antes de registrarlo.

    
respondido por el Karl Bielefeldt 15.10.2012 - 15:58
4

Los métodos largos pueden ser más computacionales y eficientes en el espacio, pueden ser más fáciles de ver la lógica y más fáciles de depurar. Sin embargo, estas reglas solo se aplican cuando un solo programador toca ese código. El código será difícil de extender si no es atómico, esencialmente la siguiente persona tendrá que comenzar desde cero y luego depurar y probar esto tomará para siempre ya que no está usando ningún código bueno conocido.

    
respondido por el Inverted Llama 15.10.2012 - 14:20
3

Hay algo que llamamos Descomposición funcional que implica dividir tus métodos más largos en métodos más pequeños siempre que sea posible. Como mencionó que su método consiste en ordenar / filtrar, es mejor que tenga métodos o funciones independientes para estas tareas.

Precisamente, su método debe centrarse en realizar solo 1 tarea.

Y si necesita llamar a otro método por algún motivo, hágalo de otra manera, continúe con el que ya está escribiendo. También para los principios de legibilidad, puede agregar comentarios. Convencionalmente, los programadores usan comentarios de varias líneas (/ ** / en C, C ++, C # y Java) para las descripciones de los métodos y usan comentarios de una sola línea (// en C, C ++, C # y Java). También hay buenas herramientas de documentación disponibles para una mayor legibilidad del código (por ejemplo, JavaDoc ). También puede consultar comentarios basados en XML si es un desarrollador de .Net.

Los bucles afectan el rendimiento del programa y pueden causar una sobrecarga de la aplicación si no se utilizan correctamente. La idea es diseñar su algoritmo de tal manera que utilice los bucles anidados lo menos posible.

    
respondido por el Maxood 15.10.2012 - 14:36
3

Está perfectamente bien escribir funciones largas. Pero varía en el contexto si usted realmente necesita o no. Por ejemplo Algunos de los mejores algoritmos se expresan mejor cuando se trata de una pieza. Por otro lado, un gran porcentaje de rutinas en programas orientados a objetos serán rutinas de acceso, que serán muy cortas. Algunas de las rutinas de procesamiento largas que tienen casos de conmutación prolongados, si las condiciones se pueden optimizar mediante métodos controlados por tablas.

Hay una excelente discusión corta en Code Complete 2 sobre la duración de las rutinas.

  

La mejor longitud máxima teórica de 497 a menudo se describe como una o dos páginas del listado de programas, de 66 a 132 líneas. Los programas modernos tienden a tener volúmenes de rutinas extremadamente cortas combinadas con unas pocas rutinas más largas.

     

Décadas de evidencia dicen que las rutinas de tal longitud no son más propensas a errores que las rutinas más cortas. Los problemas como la profundidad de anidamiento, el número de variables y otras consideraciones relacionadas con la complejidad dictan 535 la duración de la rutina en lugar de imponer una longitud

     

Si desea escribir rutinas de más de 200 líneas, tenga cuidado. Ninguno de los estudios que informaron costos reducidos, tasas de error reducidas o ambas con rutinas más grandes distinguen entre tamaños de más de 200 líneas, y está obligado a toparse con un límite superior de comprensión a medida que pasa 200 líneas de código.   536 restricciones per se.

    
respondido por el sarat 16.10.2012 - 08:12
2

Otra votación que casi siempre es incorrecta. Sin embargo, encuentro dos casos básicos en los que es la respuesta correcta:

1) Un método que, básicamente, simplemente llama a un montón de otros métodos y no realiza ningún trabajo real. Tiene un proceso que toma 50 pasos para lograrlo, obtiene un método con 50 llamadas. Por lo general, no se gana nada al tratar de romper eso.

2) Despachadores. El diseño de OOP se ha deshecho de la mayoría de estos métodos, pero las fuentes de datos entrantes son, por su propia naturaleza, solo datos y, por lo tanto, no pueden seguir los principios de OOP. No es exactamente inusual tener algún tipo de rutina de despachador en el código que maneja los datos.

También diría que uno no debería siquiera considerar la cuestión cuando se trata de cosas generadas automáticamente. Nadie está tratando de entender lo que hace el código generado automáticamente, no importa en absoluto si es fácil para un humano entenderlo.

    
respondido por el Loren Pechtel 16.10.2012 - 00:52
2

Quería abordar el ejemplo que dio:

  

Por ejemplo, tengo algunas vistas de Django que procesan un poco los objetos antes de enviarlos a la vista, un método largo son 350 líneas de código. Tengo mi código escrito para que se ocupe de los parámetros: ordenar / filtrar el conjunto de consultas, luego, poco a poco, se procesa algo en los objetos que ha devuelto mi consulta.

En mi empresa, nuestro proyecto más grande se basa en Django y también tenemos funciones de visión larga (muchas son más de 350 líneas). Yo diría que el nuestro no necesita ser tan largo, y nos están haciendo daño.

Estas funciones de vista están realizando una gran cantidad de trabajos relacionados que deben extraerse al modelo, las clases de ayuda o las funciones de ayuda. Además, terminamos reutilizando vistas para hacer cosas diferentes, que en su lugar deberían dividirse en vistas más cohesivas.

Sospecho que sus puntos de vista tienen características similares. En mi caso, sé que causa problemas y estoy trabajando para hacer cambios. Si no está de acuerdo en que está causando problemas, no necesita arreglarlo.

    
respondido por el phasetwenty 17.10.2012 - 20:37
2

No sé si alguien ya lo ha mencionado, pero una de las razones por las que los métodos largos son malos es porque generalmente involucran varios niveles diferentes de abstracción. Tienes variables de bucle y todo tipo de cosas que suceden. Considera la función ficticia:

function nextSlide() {
  var content = getNextSlideContent();
  hideCurrentSlide();
  var newSlide = createSlide(content);
  setNextSlide(newSlide);
  showNextSlide();
}

Si estuvieras haciendo toda la animación, el cálculo, el acceso a datos, etc. en esa función, habría sido un desastre. La función nextSlide () mantiene una capa de abstracción consistente (el sistema de estado de diapositivas) e ignora otras. Esto hace que el código sea legible.

Si tiene que entrar constantemente en métodos más pequeños para ver qué hacen, entonces el ejercicio de dividir la función ha fallado. El hecho de que el código que está leyendo no haga cosas obvias en los métodos secundarios no significa que los métodos secundarios sean una mala idea, simplemente que se hizo de forma incorrecta.

Cuando creo métodos, generalmente termino dividiéndolos en métodos más pequeños como una especie de estrategia de dividir y conquistar. Un método como

   if (hasMoreRecords()) { ... }

es ciertamente más legible que

if (file.isOpen() && i < recordLimit && currentRecord != null) { ... } 

¿verdad?

Estoy de acuerdo con que las afirmaciones absolutas son malas y también estoy de acuerdo en que generalmente un método largo es malo.

    
respondido por el Tjaart 14.11.2012 - 08:28
1

Historia verdadera. Una vez encontré un método que tenía más de dos mil líneas de largo. El método tenía regiones que describían lo que estaba haciendo en esas regiones. Después de leer a través de una región, decidí hacer un método de extracción automatizado, nombrándolo según el nombre de la región. cuando terminé, el método no era más que 40 llamadas de método de aproximadamente cincuenta líneas cada una y todo funcionaba igual.

Lo que es demasiado grande es subjetivo. A veces, un método no puede desglosarse más allá de lo que es actualmente. Es como escribir un libro. La mayoría de la gente está de acuerdo en que los párrafos largos generalmente deben dividirse. Pero a veces, solo hay una idea y dividirla causa más confusión de la que causaría la longitud de ese párrafo.

    
respondido por el Michael Brown 15.10.2012 - 20:12
0

El objetivo de un método es ayudar a reducir la regurgitación del código. Un método debe tener una función específica de la que es responsable. Si terminas repasando el código en varios lugares, corres el riesgo de que el código tenga resultados inesperados si se modifican las especificaciones para las que está diseñado el software.

Para que un método tenga 350 líneas, se sugiere que muchas de las tareas que está llevando a cabo se replican en otros lugares, ya que es inusual que se requiera una cantidad tan grande de código para realizar una tarea especializada.

    
respondido por el Peter 15.10.2012 - 13:35
0

En realidad, no son los métodos largos los que son una mala práctica, es más dejarlos así, lo que es malo.

Quiero decir que el acto real de refactorizar su muestra desde:

varaible_1 = 0
variable_2 = 0
for object in queryset :
     if object.condition_condition_a and variable_2 > 0 :
     variable 1+= 1
     .....
     ...    
     . 
      more conditions to alter the variables

return queryset, and context 

a

Status status = new Status();
status.variable1 = 0;
status.variable2 = 0;
for object in queryset :
     if object.condition_condition_a and status.variable2 > 0 :
     status.variable1 += 1
     .....
     ...    
     . 
      more conditions to alter the variables (status)

return queryset, and context 

y luego a

class Status {
    variable1 = 0;
    variable2 = 0;

    void update(object) {
        if object.condition_condition_a and variable2 > 0 {
            variable1 += 1
        }
    }
};

Status status = new Status();
for object in queryset :
     status.update(object);
     .....
     ...    
     . 
      more conditions to alter the variables (status)

return queryset, and context 

ahora estás en el buen camino para no solo un método mucho más corto, sino también uno mucho más fácil de usar y comprensible.

    
respondido por el OldCurmudgeon 15.10.2012 - 16:28
0

Creo que el hecho de que el método sea muy largo es algo que se debe verificar, pero definitivamente no es un anti-patrón instantáneo. La gran cosa a buscar en grandes métodos es un montón de anidación. Si tienes

foreach(var x in y)
{
    //do ALL the things
    //....
}

y el cuerpo del bucle no está extremadamente localizado (es decir, podría enviar menos de 4 parámetros), entonces es mejor convertirlo en:

foreach(var x in y)
{
    DoAllTheThings(x);
}
...
void DoAllTheThings(object x)
{
    //do ALL the things
    //....
}

A su vez, esto puede reducir enormemente la duración de una función. Además, asegúrese de buscar un código duplicado en la función y moverlo a una función separada

Finalmente, algunos métodos son largos y complicados y no hay nada que puedas hacer. Algunos problemas necesitan soluciones que no son fáciles de codificar. Por ejemplo, un análisis en contra de una gramática de mucha complejidad puede hacer métodos realmente largos que realmente no se pueden hacer sin empeorar las cosas.

    
respondido por el Earlz 15.10.2012 - 17:59
0

La verdad es, depende. Como se mencionó, si el código no separa las preocupaciones y trata de hacer todo en un solo método, entonces es un problema. La separación del código en varios módulos facilita la lectura del código, así como la escritura de código (por parte de múltiples programadores). Adherirse a un módulo (clase) por archivo fuente es una buena idea para comenzar.

En segundo lugar, cuando se trata de funciones / procedimientos:

void setDataValueAndCheckForRange(Data *data) {/*code*/} 

es un buen método si comprueba el rango de solo "Datos". Es un método MALO cuando se aplica el mismo rango para múltiples funciones (ejemplo de código incorrecto):

void setDataValueAndCheckForRange(Data *data){ /*code */}
void addDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void subDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void mulDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}

Esto debe ser refactorizado para:

bool isWithinRange(Data *d){ /*code*/ }
void setDataValue(Data *d) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void addDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void subDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 
void mulDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/} 

REUSA el código tanto como sea posible. Y eso es posible cuando cada función de su programa es lo suficientemente SIMPLE (no necesariamente fácil).

CITA: La MONTAÑA está compuesta de diminutos granos de tierra. El océano está formado por pequeñas gotas de agua ... (- Sivananda)

    
respondido por el Aniket Inge 16.10.2012 - 13:41
0

Los métodos largos tienden a ser "malos" en los lenguajes imperativos que favorecen las declaraciones, los efectos secundarios y la mutabilidad, precisamente porque esas características aumentan la complejidad y, por lo tanto, los errores.

En los lenguajes de programación funcionales, que favorecen las expresiones, la pureza y la inmutabilidad, hay menos razones para preocuparse.

Tanto en los lenguajes funcionales como en los imperativos, siempre es mejor dividir los fragmentos de código reutilizables en rutinas comunes de nivel superior, pero en los lenguajes funcionales que admiten el alcance léxico con funciones anidadas, etc. dentro de la función (método) de nivel superior que para dividirlos en otras funciones de nivel superior.

    
respondido por el Stephen Swensen 16.10.2012 - 19:29

Lea otras preguntas en las etiquetas