¿Cuándo es una función demasiado larga? [cerrado]

130

35 líneas, 55 líneas, 100 líneas, 300 líneas? ¿Cuándo deberías comenzar a romperlo? Lo pregunto porque tengo una función con 60 líneas (incluidos los comentarios) y estaba pensando en separarla.

long_function(){ ... }

dentro:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

Las funciones no se utilizarán fuera de la función long_function, hacer funciones más pequeñas significa más llamadas de función, etc.

¿Cuándo separarías una función en otras más pequeñas? ¿Por qué?

  1. Los métodos deben hacer solo una cosa lógica (piense en la funcionalidad)
  2. Deberías poder explicar el método en una sola oración
  3. Debe caber en la altura de su pantalla
  4. Evite gastos innecesarios (comentarios que señalan lo obvio ...)
  5. Las pruebas unitarias son más fáciles para pequeñas funciones lógicas.
  6. Compruebe si parte de la función puede ser reutilizada por otras clases o métodos
  7. Evite el acoplamiento excesivo entre clases
  8. Evite estructuras de control profundamente anidadas

Gracias a todos por las respuestas , edite la lista y vote por la respuesta correcta. Elegiré esa;)

Estoy refactorizando ahora con esas ideas en mente :)

usuario58163
fuente
Hay un error tipográfico en su pregunta, creo que quiso decir "¿Cuándo es una función demasiado larga?".
Tom
1
Estás equivocando la pregunta al plantearla en términos de líneas de código. Los factores determinantes no se miden en líneas de código.
dkretz
Esta pregunta puede complicarse dependiendo del código y el idioma. tal vez puedas publicarlo.
Ray Tayek el
Si se cumple con el principio de responsabilidad única, simplemente hazlo. Por lo general, siento la necesidad de hacer un encabezado o por cada 20 líneas de código, lo que me marca para abstraerlo y nombrar a este fragmento como una función con un nombre significativo en lugar de hacer un encabezado de capítulo.
Yevgeniy Afanasyev

Respuestas:

75

No hay reglas realmente duras y rápidas para ello. En general, me gustan mis métodos para "hacer una cosa". Entonces, si se trata de capturar datos, luego hacer algo con esos datos, luego escribirlos en el disco y luego dividir el capturar y escribir en métodos separados para que mi método "principal" solo contenga el "hacer algo".

Sin embargo, ese "hacer algo" podría ser un buen número de líneas, así que no estoy seguro de que varias líneas sean la métrica correcta para usar :)

Editar: Esta es una sola línea de código que envié por correo la semana pasada (para probar un punto ... no es algo de lo que me acostumbre :)) - Ciertamente no querría 50-60 de estos chicos malos en mi método :RE

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();
Steven Robbins
fuente
1
LOL Bueno, podría eliminar todo el espacio en blanco en mi método y solo sería una línea muy larga y no una función larga. Haciendo una cosa, esa es probablemente la respuesta que, gracias
@Movaxes Ese fragmento de código que publiqué es una declaración única, no solo muchas líneas en una línea ... no hay punto y coma allí :) Podría haber expandido GetResources () cada vez para hacerlo aún más malvado: P
Steven Robbins
Sí, eso tiene sentido. ¿Por qué no simplemente tomar todo el archivo fuente y ponerlo en una línea? Me refiero entonces realmente llega a ser una Web 2.0 "ninja" :)
BobbyShaftoe
Recuerdo que en revistas antiguas (estoy hablando de BBC Micro old) solían tener "programas de 10 líneas" que solo tenían varias declaraciones en cada línea, hasta la longitud máxima que la BBC podía manejar ... siempre fueron un dolor correcto para escribir: D
Steven Robbins
66
Me gusta el concepto de la función haciendo solo una cosa ... pero. Si tiene una función que hace 10 cosas, y mueve 9 de esas cosas a funciones separadas, que todavía son llamadas por la función restante, ¡no es esa función restante en esencia haciendo 10 cosas! Creo que dividir la función de esta manera hace que sea mucho más fácil probarlo.
mtnpaul
214

Aquí hay una lista de banderas rojas (sin ningún orden en particular) que podrían indicar que una función es demasiado larga:

  1. Estructuras de control profundamente anidadas : por ejemplo, for-loops de 3 niveles de profundidad o incluso solo 2 niveles de profundidad con sentencias if anidadas que tienen condiciones complejas.

  2. Demasiados parámetros que definen el estado : por parámetro que define el estado , me refiero a un parámetro de función que garantiza una ruta de ejecución particular a través de la función. Obtenga demasiados de estos tipos de parámetros y tendrá una explosión combinatoria de rutas de ejecución (esto generalmente ocurre en conjunto con el n. ° 1).

  3. Lógica que se duplica en otros métodos : la reutilización de código deficiente es un gran contribuyente al código de procedimiento monolítico. Gran parte de esa duplicación lógica puede ser muy sutil, pero una vez refactorizado, el resultado final puede ser un diseño mucho más elegante.

  4. Acoplamiento entre clases excesivo : esta falta de encapsulación adecuada da como resultado funciones relacionadas con las características íntimas de otras clases, por lo tanto, las alarga.

  5. Sobrecarga innecesaria : los comentarios que señalan las clases obvias, profundamente anidadas, captadores y establecedores superfluos para variables de clase anidadas privadas y nombres de funciones / variables inusualmente largos pueden crear ruido sintáctico dentro de funciones relacionadas que finalmente aumentarán su longitud.

  6. Su pantalla masiva de grado de desarrollador no es lo suficientemente grande como para mostrarla : en realidad, las pantallas de hoy en día son lo suficientemente grandes como para que una función que esté cerca de su altura sea probablemente demasiado larga. Pero, si es más grande , es una pistola humeante que algo está mal.

  7. No se puede determinar de inmediato el propósito de la función : Por otra parte, una vez que realmente haces determinar su propósito, si no se puede resumir este propósito en una sola frase o sucede que tiene un tremendo dolor de cabeza, esto debería ser una pista.

En conclusión, las funciones monolíticas pueden tener consecuencias de largo alcance y, a menudo, son un síntoma de importantes deficiencias de diseño. Cada vez que encuentro un código que es una alegría absoluta para leer, su elegancia es evidente de inmediato. Y adivina qué: las funciones son a menudo muy cortas.

Ryan Delucchi
fuente
1
¡Buen post! Muy determinista
Chuck Conway el
2
@PedroMorteRolo Exactamente. La API estándar no siempre es un modelo de elegancia. Además, gran parte de la API de Java se desarrolló con un conocimiento profundo del compilador de Java y JVM, por lo tanto, tiene consideraciones de rendimiento que pueden explicarlo. Admito que las secciones críticas del código que no pueden desperdiciar un solo milisegundo pueden tener que romper algunas de estas reglas, pero eso siempre debe considerarse un caso especial. Gastar tiempo de desarrollo adicional por adelantado es una inversión inicial que puede evitar una deuda tecnológica futura (potencialmente paralizante).
Ryan Delucchi
2
Por cierto ... Estoy a favor de la opinión de que los métodos largos-son-heurísticos malos también se aplican a las clases. En mi humilde opinión, las clases largas son malas, porque tienden a violar el principio de responsabilidad única. Sería divertido tener compiladores emitiendo advertencias tanto para clases largas como para métodos ...
Pedro Rolo
3
@PedroMorteRolo Definitivamente estoy de acuerdo con esto. Además, es probable que las clases grandes tengan un estado más mutable: lo que conduce a un código que es muy difícil de mantener.
Ryan Delucchi
1
La mejor respuesta. Otra buena pista es: ¿cómo son los comentarios en el código? El número de veces que he tropezó con el código de otra con una línea como: // fetch Foo's credentials where Bar is "uncomplete". Es casi seguro que es un nombre de función allí mismo y debe desacoplarse. Probablemente quiera ser refactorizado en algo como: Foo.fetchCredentialWhereBarUncomplete()
Jay Edwards
28

Creo que hay una gran advertencia para el mantra "haz una sola cosa" en esta página. A veces, hacer una cosa hace malabares con muchas variables. No divida una función larga en un grupo de funciones más pequeñas si las funciones más pequeñas terminan teniendo largas listas de parámetros. Hacer eso solo convierte una sola función en un conjunto de funciones altamente acopladas sin ningún valor individual real.

jmucchiello
fuente
18

Una función debe hacer solo una cosa. Si está haciendo muchas cosas pequeñas en una función, haga que cada cosa pequeña sea una función y llame a esas funciones desde la función larga.

Lo que realmente no desea hacer es copiar y pegar cada 10 líneas de su función larga en funciones cortas (como sugiere su ejemplo).

Jeremy Ruten
fuente
Sí hacer un montón de pequeñas funciones con la copia y pegar patrón no es una gran idea, estoy de acuerdo en función debe tratar siempre de hacer sólo una cosa
"hacer una cosa" puede o no ser correcto, dependiendo de la granularidad. Si una función multiplica una matriz, está bien. Si una función construye un automóvil virtual, eso es "una cosa", pero también es algo muy importante. Se pueden usar múltiples funciones para construir un automóvil, componente por componente.
void.pointer
16

Estoy de acuerdo en que una función solo debe hacer una cosa, pero a qué nivel es esa única cosa.

Si sus 60 líneas logran una cosa (desde la perspectiva de sus programas) y las piezas que conforman esas 60 líneas no serán utilizadas por otra cosa, entonces 60 líneas está bien.

No hay un beneficio real en dividirlo, a menos que pueda dividirlo en piezas de concreto que se mantengan solas. La métrica a usar es la funcionalidad y no las líneas de código.

He trabajado en muchos programas en los que los autores llevaron la única cosa a un nivel extremo y todo lo que terminó haciendo fue que pareciera que alguien llevó una granada a una función / método y la explotó en docenas de piezas desconectadas que son difícil de seguir.

Al extraer partes de esa función, también debe tener en cuenta si agregará una sobrecarga innecesaria y evitar pasar grandes cantidades de datos.

Creo que el punto clave es buscar la reutilización en esa función larga y extraer esas partes. Lo que le queda es la función, ya sea de 10, 20 o 60 líneas de largo.

bruceatk
fuente
2
+1 "La métrica para usar es la funcionalidad y no las líneas de código"
Cody Piersall
Otra métrica importante es el número de niveles de anidación de bloques. Mantener al mínimo. Romper una función en partes más pequeñas a menudo ayuda. También pueden ayudar otras cosas, como los retornos múltiples.
user2367418
10

60 líneas es grande pero no demasiado larga para una función. Si cabe en una pantalla en un editor, puede verlo todo de una vez. Realmente depende de lo que esté haciendo las funciones.

Por qué puedo romper una función:

  • Es muy largo
  • Hace que el código sea más fácil de mantener al dividirlo y usar nombres significativos para la nueva función
  • La función no es cohesiva.
  • Partes de la función son útiles en sí mismas.
  • Cuando es difícil encontrar un nombre significativo para la función (probablemente está haciendo demasiado)
dajorad
fuente
3
Buenos puntos, estoy de acuerdo, también si tienes que nombrar la función DoThisAndThisAndAlsoThis probablemente hace demasiado. gracias :)
2
Estás fuera de lugar con este compañero. 60 líneas siempre serán demasiado. Yo diría que si te estás acercando a 10 líneas, probablemente estés cerca del límite.
willcodejavaforfood
Pero otra función sigue llamando a estas funciones y es esencialmente la misma DoThisAndThisAndAlsoThisfunción, pero con mucha abstracción que todavía tiene que nombrar de alguna manera
Timo Huovinen
6

Mi heurística personal es que es demasiado largo si no puedo ver todo sin desplazarme.

Ferruccio
fuente
44
... mientras ha establecido el tamaño de fuente en 5?
EricSchaefer
5

Tamaño aproximado de su tamaño de pantalla (así que obtenga una gran pantalla panorámica giratoria y gírela) ... :-)

Broma aparte, una cosa lógica por función.

Y lo positivo es que las pruebas unitarias son mucho más fáciles de hacer con pequeñas funciones lógicas que hacen 1 cosa. ¡Las funciones grandes que hacen muchas cosas son más difíciles de verificar!

/ Johan

Johan
fuente
Buen punto sobre la prueba de la unidad :)
5

Regla general: si una función contiene bloques de código que hacen algo, que está algo separado del resto del código, colóquelo en una función separada. Ejemplo:

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

mucho más bonito:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

Este enfoque tiene dos ventajas:

  1. Siempre que necesite buscar direcciones para un determinado código postal, puede usar la función fácilmente disponible.

  2. Cuando necesite volver a leer la función build_address_list_for_zip(), sabrá lo que hará el primer bloque de código (obtiene direcciones para un determinado código postal, al menos eso es lo que puede derivar del nombre de la función). Si hubiera dejado el código de consulta en línea, primero necesitaría analizar ese código.

[Por otro lado (negaré que te dije esto, incluso bajo tortura): si lees mucho sobre la optimización de PHP, podrías tener la idea de mantener el número de funciones lo más pequeño posible, porque las llamadas a funciones son muy, Muy caro en PHP. No sé sobre eso ya que nunca hice ningún punto de referencia. Si ese fuera el caso, probablemente sería mejor no seguir ninguna de las respuestas a su pregunta si su aplicación es muy "sensible al rendimiento" ;-)]

EricSchaefer
fuente
ejemplo agradable gracias :) Voy a Google por los puntos de referencia de función en php
5

Eche un vistazo al ciclomático de McCabe, en el que divide su código en un gráfico donde, "Cada nodo en el gráfico corresponde a un bloque de código en el programa donde el flujo es secuencial y los arcos corresponden a las ramas tomadas en el programa. "

Ahora imagine que su código no tiene funciones / métodos; es solo una enorme extensión de código en forma de gráfico.

Desea dividir esta expansión en métodos. Tenga en cuenta que, cuando lo haga, habrá un cierto número de bloques en cada método. Solo un bloque de cada método será visible para todos los demás métodos: el primer bloque (suponemos que podrá saltar a un método en un solo punto: el primer bloque). Todos los demás bloques en cada método serán información oculta dentro de ese método, pero cada bloque dentro de un método puede saltar potencialmente a cualquier otro bloque dentro de ese método.

Para determinar qué tamaño deben tener sus métodos en términos de número de bloques por método, una pregunta que puede hacerse es: ¿cuántos métodos debo tener para minimizar el número máximo potencial de dependencias (MPE) entre todos los bloques?

Esa respuesta está dada por una ecuación. Si r es el número de métodos que minimiza el MPE del sistema, yn es el número de bloques en el sistema, entonces la ecuación es: r = sqrt (n)

Y se puede demostrar que esto da el número de bloques por método para ser, también, sqrt (n).

Ed Kirwan
fuente
4

Tenga en cuenta que puede terminar refactorizando solo por refactorizar, lo que puede hacer que el código sea más ilegible de lo que era en primer lugar.

¡Un antiguo colega mío tenía una extraña regla de que una función / método solo debe contener 4 líneas de código! Intentó apegarse a esto de manera tan rígida que los nombres de sus métodos a menudo se volvieron repetitivos y sin sentido, además las llamadas se volvieron profundamente anidadas y confusas.

Entonces, mi propio mantra se ha convertido: si no puede pensar en un nombre de función / método decente para la porción de código que está re-factorizando, no se moleste.

Saltmeister
fuente
2

La razón principal por la que generalmente rompo una función es porque partes de ella también son ingredientes en otra función cercana que estoy escribiendo, por lo que las partes comunes se tienen en cuenta. Además, si está utilizando muchos campos o propiedades de alguna otra clase, hay una buena posibilidad de que la porción relevante se pueda retirar al por mayor y, si es posible, trasladarse a la otra clase.

Si tiene un bloque de código con un comentario en la parte superior, considere convertirlo en una función, con los nombres de función y argumento que ilustran su propósito, y reservando el comentario para la justificación del código.

¿Estás seguro de que no hay piezas allí que puedan ser útiles en otro lugar? ¿Qué tipo de función es?

Jeffrey Hantin
fuente
La función hace que un archivo de caché partir de una plantilla, basado en la url, como post_2009_01_01.html de la url / post / 2009/01/01 gracias por su respuesta
2

En mi opinión, la respuesta es: cuando hace demasiadas cosas. Su función debe realizar solo las acciones que espera del nombre de la función misma. Otra cosa a tener en cuenta es si desea reutilizar algunas partes de sus funciones en otras; en este caso podría ser útil dividirlo.

Pierpaolo
fuente
2

Por lo general, rompo las funciones por la necesidad de colocar comentarios que describan el siguiente bloque de código. Lo que antes entraba en los comentarios ahora va al nuevo nombre de la función. Esta no es una regla difícil, pero (para mí) es una buena regla general. Me gusta que el código hable por sí mismo mejor que uno que necesita comentarios (como he aprendido, los comentarios generalmente mienten)

Olaf Kock
fuente
Me gusta comentar mi código, principalmente no para mí, sino para otros, que elimina muchas preguntas sobre dónde se definió $ variable, pero también me gusta que el código se explique por sí mismo. ¿Los comentarios mienten?
sí, porque la mayoría de las veces no se mantienen. En el momento de la escritura, podrían ser correctos, pero una vez que se introduce una corrección de errores o una nueva característica, nadie obliga a modificar los comentarios de acuerdo con la nueva situación. Los nombres de los métodos tienden a mentir con mucha menos frecuencia que los comentarios en mi humilde opinión
Olaf Kock el
Acabo de encontrar esta respuesta: stackoverflow.com/questions/406760/… afirmando que "La mayoría de los comentarios en código son de hecho una forma perniciosa de duplicación de código". También - Larga línea de comentarios allí.
Olaf Kock
1

Esto es en parte una cuestión de gustos, pero la forma en que lo determine es que trato de mantener mis funciones aproximadamente solo el tiempo que quepa en mi pantalla al mismo tiempo (como máximo). La razón es que es más fácil entender lo que está sucediendo si puedes ver todo de una vez.

Cuando codifico, es una mezcla de escribir funciones largas, luego refactorizar para extraer bits que podrían ser reutilizados por otras funciones, y escribir pequeñas funciones que realizan tareas discretas a medida que avanzo.

No sé si hay una respuesta correcta o incorrecta a esto (por ejemplo, puede conformarse con 67 líneas como su máximo, pero puede haber ocasiones en que tenga sentido agregar algunas más).

Andrew Hedges
fuente
Bueno, también me gusta ver mi función completa en la pantalla :) a veces eso significa una fuente Monospace 9 y una gran resolución en un fondo negro, estoy de acuerdo en que es más fácil de entender de esa manera.
1

Se ha realizado una investigación exhaustiva sobre este mismo tema, si desea la menor cantidad de errores, su código no debería ser demasiado largo. Pero tampoco debería ser demasiado corto.

No estoy de acuerdo con que un método debe caber en su pantalla en uno, pero si se desplaza hacia abajo en más de una página, entonces el método es demasiado largo.

Consulte El tamaño de clase óptimo para el software orientado a objetos para obtener más información.

Ian Hickman
fuente
gracias por el enlace, leyendo :)
1

He escrito 500 funciones de línea antes, sin embargo, estas fueron solo grandes declaraciones para decodificar y responder mensajes. Cuando el código para un solo mensaje se volvió más complejo que un solo si-entonces-otro, lo extraje.

En esencia, aunque la función era de 500 líneas, las regiones mantenidas independientemente promediaron 5 líneas.

Joshua
fuente
1

Normalmente uso un enfoque basado en pruebas para escribir código. En este enfoque, el tamaño de la función a menudo está relacionado con la granularidad de sus pruebas.

Si su prueba está lo suficientemente enfocada, entonces lo llevará a escribir una pequeña función enfocada para hacer que la prueba pase.

Esto también funciona en la otra dirección. Las funciones deben ser lo suficientemente pequeñas como para probar de manera efectiva. Entonces, cuando trabajo con código heredado, a menudo encuentro que desgloso funciones más grandes para probar las diferentes partes de ellas.

Usualmente me pregunto "cuál es la responsabilidad de esta función" y si no puedo expresar la responsabilidad en una oración clara y concisa, y luego la traduzco en una pequeña prueba enfocada, me pregunto si la función es demasiado grande.

telescopio léxico
fuente
1

Si tiene más de tres ramas, generalmente esto significa que una función o método debe separarse para encapsular la lógica de ramificación en diferentes métodos.

Cada bucle for, instrucción if, etc., no se ve como una rama en el método de llamada.

Cobertura para el código Java (y estoy seguro de que hay otras herramientas para otros idiomas) calcula el número de if, etc. en una función para cada función y lo suma para la "complejidad ciclomática promedio".

Si una función / método solo tiene tres ramas, obtendrá un tres en esa métrica, lo cual es muy bueno.

A veces es difícil seguir esta directriz, a saber, para validar la entrada del usuario. Sin embargo, poner ramas en diferentes métodos ayuda no solo al desarrollo y el mantenimiento, sino también a las pruebas, ya que las entradas a los métodos que realizan la ramificación se pueden analizar fácilmente para ver qué entradas deben agregarse a los casos de prueba para cubrir las ramas que No estaban cubiertos.

Si todas las ramas estuvieran dentro de un solo método, las entradas tendrían que ser rastreadas desde el inicio del método, lo que dificulta la capacidad de prueba.

Martin54
fuente
0

Sospecho que encontrarás muchas respuestas sobre esto.

Probablemente lo dividiría en función de las tareas lógicas que se realizaban dentro de la función. Si le parece que su cuento se está convirtiendo en una novela, sugeriría encontrar y extraer pasos distintos.

Por ejemplo, si tiene una función que maneja algún tipo de entrada de cadena y devuelve un resultado de cadena, puede dividir la función en función de la lógica para dividir su cadena en partes, la lógica para agregar caracteres adicionales y la lógica para ponerla todos juntos de nuevo como resultado formateado.

En resumen, lo que sea que haga que su código sea limpio y fácil de leer (ya sea simplemente asegurando que su función tenga buenos comentarios o dividiéndolo) es el mejor enfoque.

Phil.Wheeler
fuente
0

suponiendo que está haciendo una cosa, la duración dependerá de:

  • qué estás haciendo
  • que idioma estas usando
  • con cuántos niveles de abstracción necesita lidiar en el código

60 líneas pueden ser demasiado largas o estar bien. Sin embargo, sospecho que puede ser demasiado largo.

Ray Tayek
fuente
Estoy haciendo algo de almacenamiento en caché en PHP, sí, probablemente 60 líneas es demasiado, refactorizando ...
0

Una cosa (y esa cosa debería ser obvia por el nombre de la función), pero no más que una pantalla llena de código, independientemente. Y siéntase libre de aumentar el tamaño de su fuente. Y si tiene dudas, refactorícelo en dos o más funciones.

gkrogers
fuente
0

Extendiendo el espíritu de un tweet del tío Bob hace un tiempo, sabes que una función se está alargando demasiado cuando sientes la necesidad de poner una línea en blanco entre dos líneas de código. La idea es que si necesita una línea en blanco para separar el código, su responsabilidad y alcance se están separando en ese punto.

Mark Bostleman
fuente
0

Mi idea es que si tengo que preguntarme si es demasiado largo, probablemente sea demasiado largo. Ayuda a hacer funciones más pequeñas, en esta área, porque podría ayudar más adelante en el ciclo de vida de la aplicación.

sokket
fuente