¿Debo colocar funciones que solo se usan en otra función, dentro de esa función?
30
Específicamente, estoy escribiendo en JavaScript.
Digamos que mi función principal es la Función A. Si la Función A hace varias llamadas a la Función B, pero la Función B no se usa en ningún otro lugar, ¿debería colocar la Función B dentro de la Función A?
¿Eso es una buena práctica? ¿O aún debería poner la función B en el mismo ámbito que la función A?
Generalmente estoy a favor de las funciones anidadas, especialmente en JavaScript.
En JavaScript, la única forma de limitar la visibilidad de una función es anidándola dentro de otra función.
La función auxiliar es un detalle de implementación privado. Ponerlo en el mismo ámbito es similar a hacer públicas las funciones privadas de una clase.
Si resulta ser de uso más general, es fácil mudarse, porque puede estar seguro de que el ayudante actualmente solo lo utiliza esa función. En otras palabras, hace que su código sea más coherente.
A menudo puede eliminar parámetros, lo que hace que la firma y la implementación de la función sean menos detalladas. Esto no es lo mismo que hacer que las variables sean globales. Es más como usar un miembro de la clase en un método privado.
Puede dar a las funciones auxiliares nombres mejores y más simples, sin preocuparse por los conflictos.
La función auxiliar es más fácil de encontrar. Sí, hay herramientas que pueden ayudarte. Todavía es más fácil mover un poco los ojos hacia arriba en la pantalla.
El código forma naturalmente una especie de árbol de abstracción: algunas funciones generales en la raíz, que se ramifican en varios detalles de implementación. Si utiliza un editor con plegado / colapso de funciones, el anidamiento crea una jerarquía de funciones estrechamente relacionadas en el mismo nivel de abstracción. Eso hace que sea muy fácil estudiar el código en el nivel que necesita y ocultar los detalles.
Creo que mucha oposición proviene del hecho de que la mayoría de los programadores fueron criados en una tradición C / C ++ / Java, o fueron enseñados por otra persona que era. Las funciones anidadas no parecen naturales porque no estábamos expuestos a ellas mucho cuando estábamos aprendiendo a programar. Eso no significa que no sean útiles.
Debería ponerlo a nivel mundial, por varias razones.
Anidar una función auxiliar en la persona que llama aumenta la longitud de la persona que llama. La longitud de la función es casi siempre un indicador negativo; Las funciones cortas son más fáciles de entender, memorizar, depurar y mantener.
Si la función auxiliar tiene un nombre razonable, leer ese nombre es suficiente sin necesidad de ver la definición cercana. Si haces necesidad de ver la definición de ayuda con el fin de comprender la función que llama, entonces esa persona está haciendo demasiado, o está trabajando en demasiados niveles de abstracción simultáneamente.
Tener el ayudante disponible globalmente permite que otras funciones lo llamen si resulta que, en general, es útil después de todo. Si el ayudante no está disponible, tiene la tentación de cortarlo y pegarlo, o de olvidarlo y volver a implementarlo mal, o de hacer que otra función sea más larga de lo necesario.
Anidar la función auxiliar aumenta la tentación de utilizar variables del alcance del llamante sin declaración, por lo que no queda claro cuáles son las entradas y salidas del auxiliar. Si una función no indica claramente en qué datos opera y qué efectos tiene, generalmente es un signo de responsabilidades poco claras. Declarar al ayudante como una función independiente te obliga a saber exactamente lo que realmente hace.
Editar
Esa resultó ser una pregunta más controvertida de lo que pensaba. Para aclarar:
En JavaScript, las grandes funciones de expansión de archivos a menudo cumplen el papel de clases porque el lenguaje no proporciona ningún otro mecanismo de limitación de alcance. Ciertamente, las funciones auxiliares deben ir dentro de tales cuasi clases, no fuera de ellas.
Y el punto sobre la reutilización más fácil presupone que si una subrutina se usa más ampliamente, está dispuesto a moverla completamente fuera de su lugar y colocarla en el lugar apropiado, por ejemplo, una biblioteca de utilidad de cadena o en su registro de configuración global. Si no desea ordenar su código de esa manera, también podría anidar la subrutina, tal como lo haría con un objeto de método normal en un lenguaje más "en bloque".
Gracias, todos muy buenos puntos. En una nota al margen, ¿cómo debo generalmente ordenar mis funciones? Por ejemplo, en este momento, con pequeños programas simplemente los ordeno alfabéticamente. ¿Pero debo agrupar las funciones de ayuda y de llamada? Supongo que al menos debería desglosar mis funciones en función de qué partes de la aplicación se aplican, por ejemplo.
Gary
No me he molestado en ordenar definiciones de métodos en mucho tiempo. Si programa con algún grado de profesionalismo, debe tener un entorno que le permita saltar a cualquier definición con unas pocas teclas de todos modos. Por lo tanto, los métodos cortos son útiles, pero los archivos de clase cortos o incluso bien ordenados agregan poco valor. (Por supuesto, JavaScript utiliza para requerir que todas las definiciones para colocarse por encima de sus usos o el resultado sería técnicamente un comportamiento indefinido -. No puedo recordar si eso es todavía el caso o no)
Kilian Foth
2
Esta es una respuesta genial. El problema de la encapsulación rota es algo que muchos no consideran al usar funciones internas.
sbichenko
2
Los globales son un olor a código. Causan un acoplamiento innecesario que impide la refactorización, provocan conflictos de nombres, exponen detalles de implementación, etc. Esto es especialmente malo en Javascript, ya que todas las variables son mutables, el código generalmente se carga directamente de terceros, no hay (todavía) un sistema de módulos ampliamente disponible, o incluso una implementación ampliamente disponible de "let". En un entorno tan hostil, la única estrategia defensiva que tenemos es la encapsulación dentro de las funciones de un solo disparo.
Warbo
1
"Cumplir el papel de las clases" es intentar escribir JS como si fuera otra cosa. ¿Cuál es "el papel de las clases"? ¿Verificación de tipo? ¿Modularidad? Compilación por etapas? ¿Herencia? La pregunta implica el alcance, por lo que supongo que te refieres a las reglas crudas de alcance de las clases. Eso es solo pasar el dinero: ¿cómo se deben determinar las clases ? PHP los hace globales, lo que no proporciona encapsulación. Las clases de ámbito de Python son léxicamente, pero si estás en un bloque de ámbito léxico, ¿por qué envolver todo en otro bloque de ámbito de clase? JS no tiene tanta redundancia: solo use el alcance léxico que necesite.
Warbo
8
Voy a proponer un tercer camino, para colocar ambas funciones dentro de un cierre. Se vería así:
var functionA =(function(){function functionB(){// do stuff...}function functionA(){// do stuff...
functionB();// do stuff...}return functionA;})();
Creamos el cierre envolviendo la declaración de ambas funciones en un IIFE . El valor de retorno del IIFE es la función pública, almacenada en una variable del nombre de la función. La función pública se puede invocar exactamente de la misma manera que si se declarara como una función global, es decir functionA(). Tenga en cuenta que el valor de retorno es la función , no una llamada a la función, por lo tanto, no hay parens al final.
Al envolver las dos funciones de esta manera, functionBahora es completamente privado y no se puede acceder fuera del cierre, pero solo es visible para él functionA. No está abarrotando el espacio de nombres global, y no está abarrotando la definición de functionA.
La definición de la función B dentro de la función A le da acceso a la función B a las variables internas de A.
Entonces, una función B definida dentro de una función A da la impresión (o al menos la posibilidad) de que B está usando o alterando las variables locales de A. Si bien la definición de B fuera de A deja en claro que B no lo hace.
Por lo tanto, para la claridad del código, definiría B dentro de A solo si B necesita acceder a las variables locales de A. Si B tiene un propósito claro que es independiente de A, definitivamente lo definiría fuera de A.
Dado que la función B no se usa en ningún otro lugar, también podría convertirla en una función interna dentro de la función A porque esa es la única razón por la que existe.
Voy a proponer un tercer camino, para colocar ambas funciones dentro de un cierre. Se vería así:
Creamos el cierre envolviendo la declaración de ambas funciones en un IIFE . El valor de retorno del IIFE es la función pública, almacenada en una variable del nombre de la función. La función pública se puede invocar exactamente de la misma manera que si se declarara como una función global, es decir
functionA()
. Tenga en cuenta que el valor de retorno es la función , no una llamada a la función, por lo tanto, no hay parens al final.Al envolver las dos funciones de esta manera,
functionB
ahora es completamente privado y no se puede acceder fuera del cierre, pero solo es visible para élfunctionA
. No está abarrotando el espacio de nombres global, y no está abarrotando la definición defunctionA
.fuente
La definición de la función B dentro de la función A le da acceso a la función B a las variables internas de A.
Entonces, una función B definida dentro de una función A da la impresión (o al menos la posibilidad) de que B está usando o alterando las variables locales de A. Si bien la definición de B fuera de A deja en claro que B no lo hace.
Por lo tanto, para la claridad del código, definiría B dentro de A solo si B necesita acceder a las variables locales de A. Si B tiene un propósito claro que es independiente de A, definitivamente lo definiría fuera de A.
fuente
No debe anidarlo, porque de lo contrario la función anidada se volverá a crear cada vez que llame a la función externa.
fuente
Dado que la función B no se usa en ningún otro lugar, también podría convertirla en una función interna dentro de la función A porque esa es la única razón por la que existe.
fuente