Funciones en Node.js y JavaScript. Lo realmente importante

Funciones en Node.js y JavaScript. Lo realmente importante

Al igual que Objetos en Node.js y Javascript. Lo realmente importante. “Funciones en Node.js y JavaScript. Lo realmente importante” se refiere a los principios detrás de las funciones, lo que es realmente importante para continuar aprendiendo, mejorar el entendimiento de funciones en programación, Node.js y en Javascript.

Antes de hablar de funciones en Node.js y JavaScript, vale la pena recordar o definir lo que es una función en programación.

¿Qué son las funciones?

Las funciones son un elemento muy importante en cualquier lenguaje de programación. Sabemos que la programación funcional se inventó incluso antes de cualquier lenguaje de programación basándose en el cálculo lambda de Alonzo Church.

Sintaxis calculo lambda, programación funcional
Sintaxis calculo lambda, programación funcional
Sintaxis calculo lambda, programación funcional. Imagen tomada de https://www.slideshare.net/FASTPresentations/introduction-to-lambda-calculus-using-smalltalk-by-facundo-javier-gelatti

En los primeros lenguajes de programación se usaban subrutinas, procedimientos y funciones. Las subrutinas, procedimientos y funciones tienen en común que agrupan un conjunto de operaciones con la finalidad de reutilizarlas muchas veces y solo escribirlas una vez.

Las funciones, a diferencia de los procedimientos y subrutinas, aún se usan en los lenguajes de programación modernos, y son la unida más pequeña de organización de código. Se usan para definir el comportamiento de los objetos, componiéndolos de funcionalidades específicas. Recordemos de otra publicación que los objetos son un grupo de funcionalidades y que contribuyen a la comunicación con otros objetos.

Sin las funciones, un objeto no serviría de mucho. Las funciones definen el comportamiento de objetos. Y también forma funciones más grandes. En conclusión, las funciones nos sirven para.

  • Organizar el código
  • Reusar código
  • Se usa para la composición de objetos, agregando comportamientos.
  • Se usa para la composición de funciones más complicadas

¿Qué son las funciones en Node.js y JavaScript?

En Node.js y JavaScript, y de hecho en cualquier ambiente donde se ejecute JavaScript, las funciones son todo lo descrito en la sección anterior. Y además son objetos.

  • Al ser objetos, pueden tratarse como cualquier otro valor:

    • Pueden ser asignadas a variables y propiedades de otros objetos

    • Crearlas dinámicamente durante la ejecución del código JavaScript

    • Tener sus propiedades y métodos

    • Ser parámetros para otra función

    • Ser el valor de retorno de una función

  • Adicionalmente el cuerpo de una función proporciona ámbito local a las variables y parámetros

Aunque no es el tema de esta publicación, todas las características listadas hacen que JavaScript pueda usarse como un lenguaje de programación funcional.

¿Cómo crear funciones en Node.js y JavaScript?

Las tres formas recomendadas para crear funciones son las siguientes.

  • Declaración normal
  • Función como expresión
  • Funciones flechas

Declaración de función

Este es el método más común, muy similar en otros lenguajes de programación. Se usa la palabra reservada function, seguida del nombre de la función, luego una lista de argumentos entre paréntesis, los cuales se separan por comas. Esta lista de argumentos es opcional.

Por último el cuerpo de la función usando llaves { }. El cuerpo de la función contiene las sentencias que necesites.

function nombre(argumento1, argumento2, ...argumentoN) {
  // sentencias
}

Ejemplo concreto:

function sumar(a, b) {
  return a + b;
}

sumar(1, 2); // 3

Para ejecutar el código de la función es necesario invocarla. La invocación se hace con un par de paréntesis y dentro los argumentos necesarios, separados por comas. Así como el ejemplo anterior.

Una función siempre regresa algún valor, incluso si no se define explícitamente. Si no defines lo que retorna una función, por defecto el valor de retorno será undefined.

function sumarSinReturn(a, b) {
  const resultado = a + b;
}

sumarSinReturn(1, 2); // undefined

Si ejecutamos la función, nos regresa undefined porque no le indicamos explícitamente que regrese algún valor

Función en forma de expresión

Esta forma de crear funciones es mucho más flexible, su definición puede aparecer donde sea que se pueda definir una expresión. Eso le da la habilidad de ser asignada a una variable o a una propiedad de un objeto.

Su sintaxis es igual que la declaración de una función que vimos anteriormente, pero al ser asignada a una variable o propiedad, su nombre es opcional.

function [nombreOpcional](argumento1, argumento2, ...argumentoN) {
  // sentencias
}

A continuación vamos a ver algunos ejemplos.

// Sin nombre, tambie  conocida como funcion anonima
const sumar = function(a, b) {
 return a + b;
};

// Con nombre
const sumar = function sumar(a, b) {
  return a + b;
};

const calculadora = {
  sumar: function(a, b) {
    return a + b;
  }
};

const persona = {
  // como propiedad de un objeto
  comer: function() {
    return 'Comiendo...';
  }
};

Funciones flecha

Las funciones flecha es la forma más nueva de crear funciones, mucho más parecido a las funciones matemáticas del álgebra. Se sienten muy convenientes porque su sintaxis es mucho más reducida. Son una alternativa a las funciones en forma de expresión, es más rápido escribirlas. Sin embargo tiene muchas limitantes en comparación a las otras dos formas de crear funciones. Aunque si las usas para programación funcional son bastante efectivas.

Para ser honesto a mí me parece que si su uso no es enfocado a la programación funcional, sí que le agrega más complejidad al uso de funciones en JavaScript, de por sí las funciones en Node.js y Javascript pueden ser muy diferentes en comparación con otros lenguajes.

Pero bueno, veamos su sintaxis.

argumento => expresión;

// Com mas de un arguento es necesario parentesis
(argumento1, argumentN) => expresión;

// Con varias lineas de sentencias, es necesario las llaves {}
argumento => {
 // sentencias
};

// Con mas de un argumento y con varias lineas se sentencias
(argumento1, argumentoN) => {
  // sentencias
};

// Sin argumentos es necesario los parentesis
() => expresión

Cuando se usan expresiones, no es necesario definir explícitamente el return. El resultado de la expresión es el valor de retorno.

const calcularCuadrado = a => a * a;
const sumar = (a, b) => a + b;
const saludar = () => 'Hola';

// invocaciones
calcularCuadrado(5); // 25
sumar(1, 2); // 3
saludar(); // 'Hola'

Cuando queremos que el cuerpo de la función tenga varias líneas de sentencias, se usan llaves. Además si queremos que la función regrese algún valor, entonces explícitamente usamos la sintaxis del return.

Ejemplos.

const calcularCuadrado = a => {
  const result = a * a;
  
  return result;
};

const sumar = (a, b) = > {
  const result = a + b;
  
  return result;
};

// invocaciones
calcularCuadrado(5); // 25
sumar(1, 2); // 3

Funciones anidadas o internas

Una función se puede definir dentro de otra función, es decir, dinámicamente podemos crear funciones internas dentro de otra función principal e invocarlas.

function saludar() {
  function saludarInterna() {
    return 'Hola';
  }
  
  const saludo = saludarInterna();
  console.log(saludo);
}

saludar(); // 'Hola'

En la siguiente sección veremos otras funciones anidadas.

Ámbito local de funciones

Las funciones en Node.js y JavaScript proporcionan un ámbito de valores, local al cuerpo de la función, es decir, lo que está definido en el cuerpo de la función solo puede ser referenciado dentro de la misma.

function abuelo() {
  const nombre = 'Jaime';
  const apellido = 'Cervantes'

  function padre() {
    const apellido = 'Buendía';

    function hijo() {
      const apellido = 'Pérez';
      const nombreCompleto = `${nombre} ${apellido}`;

      console.log(nombreCompleto); // Jaime Pérez
    }
    hijo();

    const nombreCompleto = `${nombre} ${apellido}`;
    console.log(nombreCompleto); // Jaime Buendía
  }

  padre();

  const nombreCompleto = `${nombre} ${apellido}`;
  console.log(nombreCompleto); // Jaime Cervantes
}

abuelo();

Las funciones internas pueden acceder a las variables de su función padre (por nombrarlas de alguna manera). En el ejemplo anterior se puede notar que la función hijo puede hacer referencia a la constante nombre de la función abuelo. Esto nos produce Jaime Pérez. En la siguiente sección lo explicamos mejor

Closures o cierres

El anidado de funciones permite que las funciones hijas tengan su propio ámbito local, oculto de las funciones padres. Al mismo tiempo estas funciones internas tienen acceso a los valores definidos en las funciones padres. Este encapsulamiento de información y al mismo tiempo acceso a información externa, se le llama closure o cierre.

Continuemos con el ejemplo de la sección anterior, las funciones abuelo, padre e hijo

function abuelo() {
  const nombre = 'Jaime';
  const apellido = 'Cervantes'

  function padre() {
    const apellido = 'Buendía';

    function hijo() {
      const apellido = 'Pérez';
      const nombreCompleto = `${nombre} ${apellido}`;

      console.log(nombreCompleto); // Jaime Pérez
    }
    hijo();

    const nombreCompleto = `${nombre} ${apellido}`;
    console.log(nombreCompleto); // Jaime Buendía
  }

  padre();

  const nombreCompleto = `${nombre} ${apellido}`;
  console.log(nombreCompleto); // Jaime Cervantes
}

abuelo();

El resultado de la invocación de la función abuelo es:

Jaime Pérez --> Desde función hijo
Jaime Buendía --> Desde función padre
Jaime Cervantes --> Desde funcion abuelo

Entre más interna la función, más alcance a todos los ámbitos de las demás funciones “externas padres” tiene. Como la imagen de abajo, es como si los ámbitos de las funciones abuelo y padre estuvieran dentro del ámbito de la función hijo.

Closure, ámbito de funciones en js
Closure, ámbito de funciones en js

Siempre una función tomará el valor de la variable que este más cerca de su propio ámbito local. Las variables dentro de su propio ámbito local son las de mayor relevancia. Esto permite que los nombres de variables y constantes no colisionen entre ámbitos anidados.

La función hijo, tiene acceso a las constantes nombre y apellido de la función abuelo. También tiene acceso a la constante apellido de la función padre. Pero la constante apellido dentro de la propia función hijo está más cerca que lo definido en padre y abuelo, tiene mayor relevancia. Entonces el nombre completo que se imprime en la consola es Jaime Pérez en lugar de Jaime Buendía o Jaime Cervantes.

La función padre si tiene acceso a las constantes nombre y apellido de la función abuelo. En su propio ámbito tiene una constante apellido igual a Buendía. Al estar más cerca este valor, no toma el apellido de la función abuelo que está más lejos. Por eso en la consola el nombre completo que se imprime es Jaime Buendía. Luego la función padre NO tiene acceso a la constante apellido de la función hijo.

Finalmente se imprime en la consola Jaime Cervantes Velasco porque las constantes nombre y apellido están definidas en el propio ámbito local de la función abuelo. La función abuelo NO tiene acceso a las constantes apellido de sus funciones internas padre e hijo.

POO surgio de funciones

Ya que vimos un poco de las funciones anidadas y closures, podemos hablar de como se descubrió la programación orientada a objetos. Esto refleja la importancia de las funciones en los lenguajes de programación.

Ole Johan Dahl y Kristen Nygaard se dieron cuenta de que la pila de llamadas de las funciones en ALGOL, podía ser movida a un Heap. Esto permite que las variables declaradas por una función puedan existir incluso después de que la función termine su ejecución y retorne algún valor.

De esta manera la función se convirtió en el constructor de la clase, las variables locales en propiedades de la instancia de clase y las funciones internas en sus métodos. Y así en 1966 la programación orientada a objetos fue descubierta.

Este comportamiento lo podemos implementar usando funciones en Node.js y JavaScript y aprovechando su habilidad de crear closures.

function crearPersona(nombre, apellido) {
  function saludar() {
    return `Hola soy ${nombre}...`;
  }
  
  function comer() {
    return 'Comiendo...';
  }
  
  function getNombre() {
    return `${nombre} ${apellido}`;
  }
  
  const instancia = {};
  instancia.saludar = saludar;
  instancia.comer = comer;
  instancia.getNombre = getNombre;
  
  return instancia
}

const jaime = crearPersona('Jaime', 'Cervantes');
jaime.comer(); // Comiendo...
jaime.saludar(); // Hola soy Jaime
jaime.getNombre(); // Jaime Cervantes

Los parámetros nombre y apellido, están dentro del ámbito local de la función crearPersona, así que funcionan igual que variables dentro del cuerpo de la función. Las funciones internas continúan teniendo acceso a esos parámetros incluso después de que la función padre regrese su valor, un objeto literal que es la instancia de una persona.

Luego cuando la instancia jaime invoca a su método getNombre, esta propiedad hace referencia a la función interna getNombre de la función crearPersona. Debido al closure de la función interna getNombre, tenemos acceso a los parámetros nombre y apellido incluso mucho después de que la función padre crearPersona haya regresado su valor.

Nombres de funciones en Node.js y Javascript

Tenemos que estar conscientes de que la programación y el desarrollo de software es una actividad social, de mucha comunicación. Y entre más eficiente sea esta comunicación, mayor es el éxito del software. Esto nos permite ahorrar el tiempo y el recurso económico de todos los involucrados. Estoy hablando de programadores y no programadores, inversionistas, clientes y usuarios.

Una de las formas de comunicación entre compañeros programadores y muchas veces uno mismo en el futuro, es a través de un código fácil de comprender, y para contribuir a esta fácil comprensión debemos de escoger con mucho cuidado el nombre de nuestras funciones.

Toma en cuenta las siguientes recomendaciones. Pero teniendo en mente que solo son ejemplos, y a la hora de escribir tus funciones reales y con el contexto adecuado, muy probable puedas idear mejores nombres de funciones a los aquí mostrados.

Dedica el tiempo suficiente para nombrar tu función.

Igual de importante que nombrar las variables, así las funciones. Las funciones son las unidades más pequeñas que nos permiten definir comportamientos en aplicaciones. El tiempo invertido en nombrar tus funciones es mucho menor que el tiempo que tienes que invertir tu mismo en el futuro y tus compañeros al tratar de descifrar lo que una función realmente hace. Es como organizar tu cuarto, entre más ordenado, más rápido encontraras las cosas que necesitas, más rápido te cambiaras, o más rápido encontraras tus calcetines, etcétera.

El nombre debe ser muy semántico, describir su objetivo

El nombre de una función debe describir con la mayor claridad posible lo que hace. Es importante que sea un verbo porque una función siempre realiza una o más operaciones enfocadas a una tarea en concreto.

Por ejemplo, si una funcion regresa el nombre completo de una persona ¿Cual de los siguientes nombres encaja mejor?

fullName()
getName();
getFullName()

El nombre que describe mejor el objetivo de la función es getFullName.

Si la función regresa un booleano, el nombre de la función debe indicar que el resultado puede ser true o false. Tal cual el resultado de una condición lógica. Es como hacer una pregunta cuyas posibles respuestas solo pueden ser si o no.

hasChildren(person) {
  return Boolean(person.childre.length);
}

if (hasChildren(currentPerson)) {
  // Haz algo
}

Si `hasChildren` fuera un método, quedaría de la siguiente forma.

if (currentPerson.hasChildren()) {
  // Haz algo
}

Te das cuenta como la condición se lee como una frase muy entendible. Si currentPerson tiene hijos, entonces...haz algo.

Evitar suposiciones erróneas

Si el nombre describe cosas que en realidad no hace, entonces debemos de renombrar esa función. Por ejemplo, si una función forma el nombre completo de un cliente y regresa ese nombre. ¿Qué función evita mejor las suposiciones erróneas?

function setClientName() {} // Se entiende que el nombre del cliente va a ser modificado

function getFullClientName() {} // Aquí esta claro que solo consigue el nomnbre completo del cliente

setClienteName nos indica que el nombre del cliente será modificado, es un mal nombre. Entonces el nombre que mejor evita las suposiciones erróneas es getFullClientName. No dice que forma el nombre, pero sí que va a regresar un nombre completo, para fines prácticos no nos interesa saber como forma ese nombre completo, solo que no los regrese.

Acuerdos con los programadores

Es muy importante establecer acuerdos para el nombramiento de las funciones. En los ejemplos anteriores he estado utilizando el prefijo get que indica que mando a obtener algo. Pero sería confuso que algunos programadores utilizaran la palabra fetch y otros la palabra retrieve, y otros collect o bring.

Usa el contexto adecuado

Es importante que se entienda el contexto de la función, en anteriores ejemplos utilizamos la funcion getFullClientName, pero dependiendo del contexto de la aplicación, pudiera ser mejor getFullUserName o getFullEmployeeName.

Aunque estos nombres tiene que ver con el contexto del negocio o problema, también hay términos técnicos que los programadores ya están muy acostumbrados y no se deben mezclar con el dominio del problema.

Por ejemplo, el patrón de diseño observador contiene métodos como update, subscribe, publish, notify. Si estamos trabajando con la aplicación de una revista que usa notificaciones nativas del celular, sms, y realiza publicaciones periódicas. Esto también puede crear confusiones, así que se deben de nombrar las funciones con mucho cuidado de tal manera que se distingan entre funciones o métodos del patrón de diseño y las otras relacionadas con el negocio.

El alcance de la función ayuda a la longitud del nombre

El nombre de las funciones pude ser largo o corto dependiendo del alcance que tiene en el software. Por ejemplo, una función que se usa mucho en diferentes archivos, vale la pena que su nombre se a corto. Porque si muchas personas lo utilizan, es importante que sea fácil de escribir y pronunciar.

Por otro lado, si es una función que solo se utiliza en un archivo, su nombre puede ser largo, este tipo de funciones normalmente las utilizan internamente las otras funciones con nombre corto. Entonces las funciones de nombres largos son una forma de explicar lo que hace la función de nombre corto. Esto no quiere decir que no pueda ser corta, pero si necesitas más palabras para describir mejor la función, adelante.

Como ejemplo imaginemos una función que regresa el total de tus ganancias a la fecha actual de todo tu portafolio de inversiones. Donde las ganancias son la suma de los intereses de tus inversiones más las contribuciones que has hecho a la fecha.

// funcion corta, reutilizable en otros archivos o aplicaciones
function getEarnings() {
  return calculateEarnings();
}

// funciones con nombre más largo que describen a la funcion corta
function calculateEarnings() {
 const earnings = calculateCurrentTotalInterest();
 const aditionals = calculateCurrentTotalAdditionalContributions(); 

  return earnings + aditionals;
}

function calculateCurrentTotalInterest() {}

function calculateCurrentAdditionalContributions() {}

No te preocupes tanto por estos ejemplos, el objetivo es que tengas una idea. En una futura publicación haremos una pequeña aplicación donde veremos como aplicar estas recomendaciones.

Conclusiones

Las funciones en Node.js y Javascript es un tema bastante amplio, es una de las cosas mejor hechas en JavaScript que revelan la potencia del lenguaje. Se nota que Javascript está influenciado por LISP y Scheme.

Así mismo no olvidemos de nombrar correctamente nuestras funciones, ellas son las unidades más pequeñas que permiten organizar el software en comportamientos bien definidos.

La programación funcional es el primer paradigma de programación inventado, de ahí la importancia de las funciones, porque es un paradigma del cual la programación orientada a objetos tomó bases.

No olvidemos que las funciones en Node.js y JavaScript son objetos y por lo tanto pueden tratarse como cualquier valor.

Aún nos faltan varios temas importantes sobre las funciones. Estos son abordados en esta publicación. Cualquier duda, no dudes en escribirla en los comentarios, ¡Estaremos contentos de ayudarte!.

Si quieres, como ejercicio, puedes traducir todos los ejemplos a funciones en forma de expresión y funciones flechas. ¡Diviértete!

Podría interesarte

¿Cómo crear un servidor web en 5 minutos con Node.js?

Para crear un servidor web en node.js, primero, ¿Qué es node.js?, tomando la definición del sitio oficial, es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes de Node.js, npm, es el ecosistema más grande de librerías de código abierto en el mundo.