it-swarm-es.com

¿Son aceptables las funciones largas si tienen una estructura interna?

Cuando trato con algoritmos complicados en lenguajes con soporte para funciones anidadas (como Python y D) a menudo escribo funciones enormes (porque el algoritmo es complicado) pero mitigo esto usando funciones anidadas para estructurar el código complicado. ¿Las funciones enormes (más de 100 líneas) todavía se consideran malvadas incluso si están bien estructuradas internamente mediante el uso de funciones anidadas?

Editar: Para aquellos de ustedes que no están familiarizados con Python o D, las funciones anidadas en estos lenguajes también permiten el acceso al alcance de la función externa. En D este acceso permite la mutación de variables en el alcance externo. En Python solo permite la lectura. En D puedes deshabilitar explícitamente el acceso al ámbito externo en una función anidada declarándolo static.

24
dsimcha

¡Siempre recuerda la regla, una función hace una cosa y lo hace bien! Si puede hacerlo, evite las funciones anidadas.

Se dificulta la legibilidad y las pruebas.

21
Bryan Harrington

Algunos han argumentado que las funciones cortas pueden ser más propensas a errores que las funciones largas .

Card y Glass (1990) señalan que la complejidad del diseño realmente involucra dos aspectos: la complejidad dentro de cada componente y la complejidad de las relaciones entre los componentes.

Personalmente, descubrí que el código de línea recta bien comentado es más fácil de seguir (especialmente cuando no fue usted quien lo escribió originalmente) que cuando se divide en múltiples funciones que nunca se usan en otro lugar. Pero realmente depende de la situación.

Creo que la conclusión principal es que cuando divide un bloque de código, está intercambiando un tipo de complejidad por otro. Probablemente hay un punto dulce en algún lugar en el medio.

12
Jonathan Tran

Idealmente, toda la función debería ser visible sin tener que desplazarse. A veces, esto no es posible. Pero si puede dividirlo en pedazos, hará que leer el código sea mucho más fácil.

Sé que tan pronto como empujo la página hacia arriba/abajo o me muevo a una sección diferente del código, solo puedo recordar 7 +/- 2 cosas de la página anterior. Y desafortunadamente, algunas de esas ubicaciones se utilizarán al leer el nuevo código.

Siempre me gusta pensar en mi memoria a corto plazo como los registros de una computadora (CISC, no RISC). Si tiene la función completa en la misma página, puede ir al caché para obtener la información requerida de otra sección del programa. Si la función completa no puede caber en una página, eso sería el equivalente de siempre empujar cualquier memoria al disco después de cada operación.

9
jsternberg

¿Por qué usar funciones anidadas, en lugar de funciones externas normales?

Incluso si las funciones externas solo se usan alguna vez en una función que antes era grande, todavía hace que todo el desastre sea más fácil de leer:

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}
4
Fishtoaster

No tengo el libro frente a mí en este momento (para citar), pero según Code Complete, el "punto dulce" para la longitud de la función era de alrededor de 25-50 líneas de código según su investigación.

Hay momentos en los que está bien tener funciones largas:

  • Cuando la complejidad ciclomática de la función es baja. Sus colegas desarrolladores pueden sentirse un poco frustrados si tienen que mirar una función que contiene una declaración if gigante y la declaración else para ese if no está en la pantalla al mismo tiempo.

Los momentos en que no está bien tener funciones largas:

  • Tiene una función con condicionales profundamente anidados. Hazle un favor a tus compañeros lectores de código, mejora la legibilidad al romper la función. Una función le indica al lector que "este es un bloque de código que hace una cosa". También pregúntese si la longitud de la función indica que está haciendo demasiado y necesita ser factorizada a otra clase.

La conclusión es que la capacidad de mantenimiento debe ser una de las principales prioridades de su lista. Si otro desarrollador no puede ver su código y obtener una "idea general" de lo que está haciendo el código en menos de 5 segundos, su código no proporciona suficientes "metadatos" para decir lo que está haciendo. Otros desarrolladores deberían poder decir qué está haciendo su clase con solo mirar el navegador de objetos en el IDE elegido) en lugar de leer más de 100 líneas de código.

Las funciones más pequeñas tienen las siguientes ventajas:

  • Portabilidad: es mucho más fácil mover la funcionalidad (ya sea dentro de la clase al refactorizar a otra diferente)
  • Depuración: cuando observas el stacktrace, es mucho más rápido detectar un error si estás buscando una función con 25 líneas de código en lugar de 100.
  • Legibilidad: el nombre de la función indica qué está haciendo un bloque de código completo. Es posible que un desarrollador de tu equipo no quiera leer ese bloque si no está trabajando con él. Además, en la mayoría de los IDE modernos, otro desarrollador puede comprender mejor lo que está haciendo su clase leyendo los nombres de las funciones en un navegador de objetos.
  • Navegación: la mayoría de los IDE le permitirán buscar el nombre de las funciones. Además, la mayoría de los IDE modernos tienen la capacidad de ver el origen de una función en otra ventana, esto les da a otros desarrolladores que pueden ver su función larga en 2 pantallas (si los monitores múltiples) en lugar de hacer que se desplace.

La lista continua.....

3
Korbin

La respuesta es que depende, sin embargo, probablemente debería convertir eso en una clase.

2
Lie Ryan

No me gustan la mayoría de las funciones anidadas. Las lambdas caen en esa categoría, pero generalmente no me marcan a menos que tengan más de 30-40 caracteres.

La razón básica es que se convierte en una función altamente localmente densa con recursividad semántica interna, significado que es difícil para mí asimilar mi cerebro, y es más fácil llevar algunas cosas a una función auxiliar que no abarrota el espacio de código.

Considero que una función debe hacer lo suyo. Hacer otras cosas es lo que hacen otras funciones. Entonces, si tiene una función de 200 líneas Doing Its Thing y todo fluye, eso está bien.

2
Paul Nathan

No, las funciones de varias páginas no son deseables y no deberían pasar la revisión del código. Solía ​​escribir funciones largas también, pero después de leer Martin Fowler Refactoring, me detuve. Las funciones largas son difíciles de escribir correctamente, difíciles de entender y de probar. Nunca he visto una función de incluso 50 líneas que no serían más fáciles de entender y probar si se dividiera en un conjunto de funciones más pequeñas. En una función de varias páginas, es casi seguro que se tengan en cuenta las clases completas. Es difícil ser más específico. Tal vez debería publicar una de sus funciones largas en Revisión de código y alguien (tal vez yo) puede mostrarle cómo mejorarla.

1
kevin cline

Es aceptable? Esa es realmente una pregunta que solo tú puedes responder. ¿La función logra lo que necesita? ¿Es mantenible? ¿Es 'aceptable' para los otros miembros de su equipo? Si es así, eso es lo que realmente importa.

Editar: no vi lo que pasa con las funciones anidadas. Personalmente, no los usaría. En su lugar, usaría funciones regulares.

1
GrandmasterB

Una función larga de "línea recta" puede ser una forma clara de especificar una secuencia larga de pasos que siempre ocurren en una secuencia particular.

Sin embargo, como otros han mencionado, esta forma es propensa a tener tramos locales de complejidad en los que el flujo general es menos evidente. Colocar esa complejidad local en una función anidada (es decir, definida en otra parte dentro de la función larga, quizás en la parte superior o inferior) puede restaurar la claridad del flujo de la línea principal.

Una segunda consideración importante es controlar el alcance de las variables que están destinadas a ser utilizadas solo en un tramo local de una función larga. Se requiere vigilancia para evitar tener una variable que se introdujo en una sección del código referenciada involuntariamente en otro lugar (por ejemplo, después de ciclos de edición), ya que este tipo de error no se mostrará como un error de compilación o tiempo de ejecución.

En algunos idiomas, este problema se puede evitar fácilmente: un tramo de código local se puede incluir en su propio bloque, como con "{...}", dentro del cual las variables recién introducidas solo son visibles para ese bloque. Algunos lenguajes, como Python, carecen de esta característica, en cuyo caso las funciones locales pueden ser útiles para imponer regiones de menor alcance.

1
gwideman

Cuando programo en Python, me gusta dar un paso atrás después de haber escrito una función y preguntarme si se adhiere al "Zen de Python" (escriba 'import this' en su python = intérprete):

Hermoso es mejor que feo.
Explícito es mejor que implícito.
Simple es mejor que complejo.
Complejo es mejor que complicado.
Flat es mejor que anidado.
Escaso es mejor que denso.
La legibilidad cuenta.
Los casos especiales no son lo suficientemente especiales como para romper las reglas.
Aunque la practicidad supera la pureza.
Los errores nunca deben pasar en silencio.
A menos que sea silenciado explícitamente.
Ante la ambigüedad, rechaza la tentación de adivinar.
Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.
Aunque ese camino puede no ser obvio al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es mejor que ahora ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Los espacios de nombres son una gran idea para tocar la bocina: ¡hagamos más de esos!

0
user17721

Póngalos en un módulo separado.

Asumiendo que su solución no está más hinchada que la necesidad, no tiene demasiadas opciones. Ya ha dividido las funciones en diferentes subfunciones, por lo que la pregunta es dónde debe colocarlo:

  1. Póngalos en un módulo con una sola función "pública".
  2. Póngalos en una clase con una sola función "pública" (estática).
  3. Anidéalos en una función (como has descrito).

Ahora, tanto la segunda como la tercera alternativa anidan en algún sentido, pero la segunda alternativa para algunos programadores no parece estar mal. Si no descarta la segunda alternativa, no veo demasiadas razones para descartar la tercera.

0
skyking