JS: Funciones

Teoría: Funciones puras

Características importantes de las funciones

Las funciones en programación tienen una serie de características importantes. Conociéndolas, podemos determinar con mayor precisión cómo dividir el código en funciones y cuándo es conveniente utilizarlas.

Determinismo

La función incorporada en JavaScript Math.random() devuelve un número aleatorio entre 0 y 1:

Math.random(); // 0.9337432365797949
Math.random(); // 0.5550694016887598

La función es útil y conveniente, pero difícil de depurar y probar. Esto se debe a que para los mismos argumentos de entrada (la falta de argumentos también se considera en este concepto), puede devolver diferentes valores. Las funciones con este comportamiento se llaman no deterministas.

Por ejemplo, las funciones que operan con el tiempo del sistema son no deterministas. Por ejemplo, la función Date.now() devuelve un nuevo valor cada vez:

// Devuelve la hora actual en milisegundos
Date.now(); // 1571909874844
Date.now(); // 1571909876648

Aquí hay un ejemplo con argumentos. Imagina una función getAge(), que toma como entrada el año de nacimiento y devuelve la edad:

getAge(2000); // ?

Aunque en este momento la ejecución repetida devolverá el mismo valor, después de un año será diferente. Es decir, una función se considera no determinista si se comporta así al menos una vez.

Las funciones deterministas, por otro lado, se comportan de manera predecible. Para los mismos datos de entrada, siempre devuelven el mismo resultado. Esto es lo que ocurre con las funciones en matemáticas.

Es interesante que, por ejemplo, la función console.log() sea determinista. Esto se debe a que siempre devuelve el mismo valor para cualquier dato de entrada. Ese valor es undefined, no lo que se imprime en la pantalla, como se podría pensar. La impresión en la pantalla es un efecto secundario, del cual hablaremos más adelante.

console.log('Códica – Big Bang');

La llamada console.log('Códica - Big Bang') realizó dos acciones:

  • Mostró el mensaje Códica - Big Bang en la terminal (o en la consola del navegador, dependiendo del entorno de ejecución).
  • Devolvió el valor undefined. No importa qué mensaje imprimamos, el valor devuelto siempre será el mismo: undefined.

Una función se vuelve no determinista incluso si no solo accede a sus argumentos, sino también a algunos datos externos, como variables globales, variables de entorno, etc. Esto sucede porque los datos externos pueden cambiar y la función comenzará a devolver un resultado diferente, incluso si se le pasan los mismos argumentos.

const getCurrentShell = () => process.env.SHELL;

getCurrentShell(); // /bin/bash

La función getCurrentShell() accede a la variable de entorno SHELL. Pero en diferentes momentos y en diferentes entornos, el valor de esta variable puede ser diferente.

En general, no se puede decir que la falta de determinismo sea un mal absoluto. Muchos programas y sitios web necesitan una función que devuelva un número aleatorio o calcule la fecha actual. Por otro lado, está en nuestro poder dividir el código de manera que contenga la mayor cantidad posible de partes deterministas. La recomendación general al trabajar con la determinación es la siguiente: si es posible escribir una función de manera que sea determinista, entonces hazlo. No uses variables globales, crea funciones que dependan solo de sus propios argumentos.

El concepto de "Determinismo" no se limita a la programación o las matemáticas. Se puede aplicar a casi cualquier proceso. Por ejemplo, lanzar una moneda es un proceso no determinista, su resultado es aleatorio.

Efectos secundarios

La segunda característica clave de las funciones es la presencia de efectos secundarios. Los efectos secundarios se refieren a cualquier interacción con el entorno externo. Esto incluye operaciones de archivos, como escribir en un archivo, leer un archivo, enviar o recibir datos a través de la red e incluso imprimir en la consola.

const someFunction = () => {
  // La función fetch realiza una solicitud HTTP
  // Una solicitud HTTP es un efecto secundario
  fetch('https://app.codica.la/courses');
};

Además, los efectos secundarios incluyen cambios en variables externas (por ejemplo, globales) y en los parámetros de entrada cuando se pasan por referencia.

const someFunction = (obj) => {
  // Alguna lógica
  // Efecto secundario. Cambio en el argumento de entrada.
  obj.key = 'value';
};

Por otro lado, los cálculos (lógica) no tienen efectos secundarios. Por ejemplo, una función que suma dos números pasados como argumentos.

const sum = (num1, num2) => num1 + num2;

Los efectos secundarios representan una de las mayores dificultades en el desarrollo. Su presencia dificulta en gran medida la lógica del código y las pruebas. Conduce a la aparición de un gran número de errores. Solo al trabajar con archivos, la cantidad de errores posibles se mide en cientos, desde quedarse sin espacio en el disco hasta intentar leer datos de un archivo inexistente. Para prevenirlos, el código se llena de numerosas comprobaciones y mecanismos de protección.

No se puede escribir ningún programa útil sin efectos secundarios. No importa qué cálculos importantes realice, su resultado debe ser de alguna manera demostrado. En el caso más simple, debe mostrarse en la pantalla, lo que automáticamente nos lleva a los efectos secundarios:

console.log(sum(4, 11)); // => 15

En las aplicaciones reales, por lo general, todo se reduce a la interacción con una base de datos o al envío de solicitudes a través de la red.

No existe una forma de eliminar completamente los efectos secundarios, pero se puede minimizar su impacto en el programa. Por lo general, en un programa típico, hay pocos efectos secundarios en comparación con el resto del código, y solo ocurren al comienzo y al final. Por ejemplo, un programa que convierte un archivo de formato de texto a PDF idealmente realiza exactamente dos efectos secundarios:

  1. Lee el archivo al comienzo del programa.
  2. Escribe el resultado del programa en un nuevo archivo.

El trabajo principal, que contiene la parte algorítmica pura, se realiza entre estos dos puntos. Los efectos secundarios en este caso estarán solo en la capa superior de la aplicación, y el núcleo que realiza el trabajo principal permanecerá libre de ellos.

Los operadores de incremento y decremento son las únicas operaciones aritméticas básicas en JS que tienen efectos secundarios (cambian el valor de la variable en sí). Es por eso que es difícil trabajar con ellos en expresiones compuestas. Pueden llevar a errores difíciles de detectar, tanto que en muchos lenguajes se han eliminado por completo (no existen en Ruby y Python). En JS, las guías de estilo de codificación recomiendan no usarlos.

Funciones puras

Funciones puras en JavaScript

Una función ideal desde el punto de vista de la facilidad de trabajo con ella se llama función pura (pure). Una función pura es una función determinista que no produce efectos secundarios. Esta función depende solo de sus argumentos de entrada y siempre se comporta de manera predecible.

Las funciones puras tienen una serie de ventajas clave:

  • Son fáciles de probar. Basta con pasar los parámetros necesarios a la función y observar la salida esperada.
  • Se pueden ejecutar de forma segura varias veces, lo cual es especialmente relevante en código asíncrono o en código multihilo.
  • Se pueden combinar fácilmente para obtener un nuevo comportamiento sin tener que reescribir el programa (más detalles más adelante en el curso).

En programas bien diseñados, los efectos secundarios se intentan aislar en una pequeña parte de la aplicación para que la mayor parte del código sea pura.

En este momento, lo que se ha dicho anteriormente puede sonar bastante abstracto. Comprender este tema requiere no solo una comprensión general de lo leído, sino también experiencia práctica relacionada con la complejidad de trabajar en la mezcla de efectos secundarios. El tema de la pureza se abordará regularmente. Es especialmente importante en proyectos con código en vivo.

Completado

0 / 16