
El lenguaje JavaScript es cada vez más utilizado también del lado del servidor gracias a la gran cantidad de librerías de desarrollo disponibles. Esto implica un considerable aumento en la complejidad del código, que debe ser cada vez más abierto a extensiones y cerrado a modificaciones.
En la siguiente parte del artículo se mostrará cómo evitar la duplicación de código aprovechando las funciones de orden superior (high-order functions) en JavaScript.
Problemas de duplicación de código
Primero, expliquemos claramente un problema típico: se proporciona un array de objetos JavaScript que representan usuarios de una web app y que tienen los atributos nombre, apellido y posts. Posts es una lista de strings que contiene el título de cada post publicado.
<code>let p = {
"name": "Mary",
"surname": "Smith",
"posts": ["image.png", "codemotionRome.png", "article.txt"]
}
</code>
Lenguaje del código: JavaScript (javascript)
Se quieren implementar varios algoritmos de ordenación del array, como por ejemplo selectionSort y bubbleSort. También se quiere implementar la posibilidad de ordenar el array por nombre, por apellido o por número de posts publicados.
Para implementar un algoritmo de ordenación es necesario comparar el atributo de interés por el cual se quiere ordenar. Sin embargo, la comparación entre dos nombres se hace de manera diferente a la comparación entre el número de posts publicados:
<code>let people = [p0, p1, p2, ...]
// Comparación de personas por nombre
people[0]["name"] > people[1]["name"]
// Comparación de personas por número de posts publicados
people[0]["posts"].length > people[1]["posts"].length
</code>
Lenguaje del código: JavaScript (javascript)
Entonces, uno podría pensar en desarrollar las siguientes funciones, modificando solo la implementación de la comparación entre objetos:
<code>selectionSortByName(){...};
bubbleSortByName(){...};
selectionSortByPosts(){...};
bubbleSortByPosts(){...};
...
</code>
Lenguaje del código: HTML, XML (xml)
La solución propuesta arriba es realmente mala porque provoca una gran duplicación de código que ejecuta los algoritmos selectionSort y bubbleSort, causando los siguientes problemas:
- Es necesario definir muchas funciones con implementaciones casi idénticas
- Si se detecta un error en el algoritmo de una función, todas las demás funciones de la misma familia deben modificarse manualmente para resolver el mismo problema
Objetos de primera clase y funciones de orden superior
En JavaScript, las funciones son objetos de primera clase (first-class objects), lo que significa que su código (firma e implementación) puede asignarse a una variable.
<code>function foo(){
console.log("Executing foo...");
}
console.log(foo) // Imprime el código de la función
</code>
Lenguaje del código: JavaScript (javascript)
Una función se dice que es de orden superior (high-order function) cuando acepta como argumento otra función o cuando retorna una función como resultado.
<code>foo = function(){
console.log("foo")
}
function execute(fn){
fn()
}
execute(foo)
</code>
Lenguaje del código: JavaScript (javascript)
En el código anterior, la función execute()
es una función de orden superior.
Comparadores y algoritmos de ordenación
Se puede pensar entonces en aprovechar la lógica de las funciones de orden superior para evitar la duplicación de código en el ejemplo anterior o en casos similares. Aquí cómo proceder:
- Definir las funciones de orden superior, es decir, las funciones que implementan el algoritmo de ordenación y que aceptan como parámetros el array a ordenar y una función que será invocada para realizar la comparación entre los distintos elementos del array.
- Definir varias funciones para comparar dos objetos del array, en nuestro caso dos personas, según diferentes parámetros. Estas funciones comparadoras serán pasadas como parámetro a las funciones de orden superior.
Un aspecto fundamental que no se debe olvidar es que las funciones comparadoras deben actuar de forma diferente, pero tener la misma firma y lógica en los valores de retorno.
Cada función comparadora recibe dos elementos del array y retorna los siguientes valores:
-1
si el primer elemento es anterior al segundo0
si los dos elementos son intercambiables+1
si el primer elemento es posterior al segundo
Veamos un ejemplo:
<code>let compareByName = function(personA, personB){
if(personA.name < personB.name){ return -1 }
else if(personA.name > personB.name){ return +1 }
else { return 0 }
}
let p1 = {"name": "John", "surname": "Doe", "posts": []}
let p2 = {"name": "Mary", "surname": "Smith", "posts": []}
compareByName(p1, p2) // Retorna -1 porque John < Mary
compareByName(p2, p1) // Retorna +1 porque Mary > John
</code>
Lenguaje del código: JavaScript (javascript)
La misma lógica puede implementarse para comparar por número de posts publicados:
<code>let compareByPosts = function(personA, personB){
if(personA.posts.length < personB.posts.length){ return -1 }
else if(personA.posts.length > personB.posts.length){ return +1 }
else { return 0 }
}
</code>
Lenguaje del código: JavaScript (javascript)
Ahora solo queda definir la función de orden superior y pasarle el método de comparación deseado:
<code>function selectionSort(list, comparator){
for(let i = 0; i < list.length - 1; i++){
for(let j = i+1; j < list.length; j++){
if(comparator(list[j], list[i]) < 0){
let tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
}
}
}
// Ordenar por nombre
selectionSort(people, compareByName)
// Ordenar por número de posts
selectionSort(people, compareByPosts)
</code>
Lenguaje del código: PHP (php)
Así, hemos logrado aprovechar dos únicas funciones de orden superior que definen el algoritmo de ordenación del array y adoptan métodos diferentes para comparar los distintos elementos. El código está poco duplicado y es fácilmente extensible; de hecho, si se quisiera añadir un nuevo criterio de ordenación, por ejemplo el apellido, solo sería necesario implementar la función compareBySurname()
y pasarla como comparadora a una de las dos funciones de orden superior.