En este capítulo, vamos a explorar los estilos de programación de JavaScript y cómo los desarrolladores trabajaron con tipos en JavaScript (en lugar de JS++). Este capítulo lo ayudará a comprender los siguientes capítulos que explican en detalle el sistema de tipos JS++.
En este tutorial, utilizaremos el navegador web Google Chrome. Haga clic aquí para descargar Google Chrome si aún no lo tiene.
Para ejecutar el código JavaScript, usaremos la consola de Chrome Developer Tools. Abra Chrome y presione la combinación de teclas Ctrl + Shift + J y elija la pestaña «Consola».
Copie y pegue el siguiente código en su consola y presione enter para ejecutarlo:
var message; message = "This is a test."; if (Math.random() > 0.5) { message = 123; } console.log(message);
Presione su «flecha hacia arriba» y presione «enter» para evaluar el código más de una vez. Intente evaluar el código varias veces.
Observe cómo el tipo de datos en el código anterior cambia de una string a un número. Sin embargo, solo cambia a un número si un número generado aleatoriamente es mayor que 0,5. Por lo tanto, el tipo de datos de la variable ‘mensaje’ puede ser diferente cada vez que se ejecuta el script. Este fue un problema importante en JavaScript. Por ejemplo, el siguiente código JavaScript no es seguro:
function lowercaseCompare(a, b) { return a.toLowerCase() == b.toLowerCase(); }
El motivo es que toLowerCase() es un método que solo está disponible para strings de JavaScript. Ejecutemos el siguiente código JavaScript en la consola de Chrome:
function lowercaseCompare(a, b) { return a.toLowerCase() == b.toLowerCase(); } console.log("First message."); lowercaseCompare("10", 10); // Crashes with 'TypeError' console.log("Second message."); // Never executes.
Observe cómo la secuencia de comandos se bloqueará con un TypeError. El segundo mensaje nunca se registra. La conclusión clave es que el código falló porque toLowerCase() no es un método disponible para números, pero la función se llamó con una string («10») y un número (10). El argumento numérico no era un argumento válido para la función ‘comparar en minúsculas’. Si cambia la llamada a la función, observará que el programa ya no falla:
// Change this: // lowercaseCompare("10", 10); // Crashes with 'TypeError' // to: lowercaseCompare("10", "10");
Los desarrolladores solucionaron estos problemas en JavaScript comprobando primero los tipos. Esta es la forma más segura de reescribir la función ‘comparar en minúsculas’ anterior en JavaScript:
function lowercaseCompare(a, b) { if (typeof a != "string" || typeof b != "string") { return false; } return a.toLowerCase() == b.toLowerCase(); }
Verificamos los tipos usando ‘typeof’ y, si recibimos tipos de argumentos no válidos, devolvemos un valor predeterminado. Sin embargo, para programas más grandes, esto puede generar una gran cantidad de código adicional y es posible que no siempre haya un valor predeterminado aplicable.
Errores implacables en JavaScript
En el ejemplo anterior, exploramos un tipo de error implacable en JavaScript: un TypeError que hace que finalice la ejecución del script. Hay muchos otros tipos de errores que JS++ previene, pero, por ahora, solo veremos otra categoría de errores: ReferenceErrors. ¿Qué pasa con el siguiente bit de código JavaScript?
var message = "This is a test."; console.log(message);
Intente ejecutar el código anterior en su consola. Una vez más, no se registra nada. En su lugar, obtienes un ReferenceError. Esto se debe a que hay un error tipográfico en el código anterior. Si arreglamos el error tipográfico, el código tiene éxito:
var message = "This is a test."; console.log(message);
¡JavaScript puede fallar en errores tipográficos! TypeErrors y ReferenceErrors no ocurren en JS++. Clasificamos TypeErrors y ReferenceErrors como errores «implacables» porque pueden hacer que se detenga la ejecución del script de JavaScript. Sin embargo, hay otro tipo de error en JavaScript que es un poco más peligroso porque son «silenciosos».
Perdonar errores «silenciosos» en JavaScript
Hay una clase de errores «silenciosos» en JavaScript que pueden continuar propagándose silenciosamente a través de su programa. Llamamos a estos errores «perdonadores» porque no detienen la ejecución del script, pero, a pesar del nombre inocuo, podemos considerarlos más peligrosos que los errores implacables porque continúan propagándose.
Considere la siguiente función de JavaScript:
function subtract(a, b) { return a - b; }
Esta función puede parecer sencilla en la superficie, pero, a medida que la secuencia de comandos se vuelve más compleja, cuando las variables cambian y dependen de otros valores que abarcan miles de líneas de código, es posible que termine restando accidentalmente una variable que termina siendo un número de una variable que termina siendo una string. Si intenta realizar una llamada de este tipo, obtendrá NaN (No es un número).
Evalúa el siguiente código en tu consola:
function subtract(a, b) { return a - b; } subtract("a", 1);
Observe el valor NaN (No es un número) resultante. No bloquea su aplicación, por lo que lo llamamos un error indulgente, pero el valor del error se propagará por el resto de su programa para que su programa continúe ejecutándose silenciosamente con errores. Por ejemplo, los cálculos posteriores pueden depender del valor devuelto por la función ‘restar’. Intentemos operaciones aritméticas adicionales para observar:
function subtract(a, b) { return a - b; } var result = subtract("a", 1); // NaN console.log(result); result += 10; // Add 10 to NaN console.log(result);
Sin fallos ni informes de errores. Simplemente continúa ejecutándose silenciosamente con el valor de error.
No podrá ejecutar el siguiente código, pero aquí hay una ilustración de cómo dichos valores de error podrían propagarse a través de su aplicación en un escenario potencial del mundo real, un backend de carrito de compras:
var total = 0; total += totalCartItems(); while ((removedPrice = removedFromCart()) != null) { total = subtract(total, removedPrice); } total += tax(); total += shipping();
En el ejemplo anterior, nuestro carrito de compras puede terminar con un valor NaN (No es un número), lo que resulta en una pérdida de ventas para la empresa que puede ser difícil de detectar porque no hubo errores explícitos.
Intuición JavaScript
JS ++ se diseñó en base a una amplia experiencia en el desarrollo de JavaScript, no solo para aplicaciones grandes y complejas, sino en cualquier lugar donde se pueda usar JavaScript: scripts y macros para Windows Script Host hasta programas heredados basados en ActiveX y similares que aún prevalecen en algunos entornos corporativos. En resumen, JS ++ funcionará en cualquier lugar donde se espere JavaScript, desde lo básico hasta lo complejo y lo arcano.
Una observación importante relevante para JS++ es que la mayoría de los programas de JavaScript ya están bien escritos (pero no «perfectamente»). Recuerde las versiones «no seguras» y «seguras» de la función ‘comparar en minúsculas’ de JavaScript:
// Unsafe: function lowercaseCompare(a, b) { return a.toLowerCase() == b.toLowerCase(); } // Safe: function lowercaseCompare(a, b) { if (typeof a != "string" || typeof b != "string") { return false; } return a.toLowerCase() == b.toLowerCase(); }
La versión segura es mucho más tediosa y, en la práctica, la mayoría de los desarrolladores de JavaScript escribirán la mayoría de sus funciones de forma no segura. El motivo es que, al observar el cuerpo de la función, sabemos que los tipos de parámetros esperados son strings porque ambos parámetros usan el método ‘toLowerCase’ solo disponible para strings. En otras palabras, en JavaScript, tenemos una intuición sobre los tipos con solo mirar el código.
Considere las siguientes variables y adivine sus tipos:
var employeeAge; var employeeName; var isEmployed;
employeeAge tiene sentido como un número, employeeName tiene sentido como una string y isEmployed tiene sentido como un valor booleano.
Ahora intente adivinar los tipos de parámetros esperados para las siguientes funciones:
function multiply(a, b) { return a * b; } function log(message) { console.log("MESSAGE: " + message); }
La función ‘multiplicar’ tiene más sentido si proporciona argumentos numéricos a los parámetros ‘a’ y ‘b’. Además, la función ‘log’ es más correcta con strings.
Conversiones forzadas de JavaScript («Coerción de tipo»)
A veces, en lugar de verificar el tipo usando ‘typeof’, los programadores de JavaScript forzarán una conversión del argumento al tipo de datos que necesitan (especialmente si la intuición puede fallar). Esta técnica es una instancia de coerción de tipo y da como resultado un código que es más tolerante a fallas porque no saldrá con una excepción si el tipo de datos del argumento proporcionado es incorrecto.
Una vez más, veamos cómo podemos cambiar nuestro ejemplo de ‘comparación en minúsculas’ usando esta idea:
// Unsafe: function lowercaseCompare(a, b) { return a.toLowerCase() == b.toLowerCase(); } // Safer: function lowercaseCompare(a, b) { a = a.toString(); b = b.toString(); return a.toLowerCase() == b.toLowerCase(); }
En la versión reescrita de la función ‘lowercaseCompare’, estamos «forzando» a que los argumentos ‘a’ y ‘b’ se conviertan en una string. Esto nos permite llamar de manera segura al método ‘toLowerCase’ sin fallar. Ahora, si se llama a la función ‘comparar en minúsculas’, obtenemos los siguientes resultados:
lowercaseCompare("abc", "abc") // true lowercaseCompare("abc", 10) // false lowercaseCompare("10", "10") // true lowercaseCompare("10", 10) // true
Sin embargo, el observador astuto notará que la nueva versión de ‘lowercaseCompare’ está marcada como «más segura» en lugar de «segura».
¿Por qué?
toString no es la forma más correcta de forzar una conversión a una string. (Tampoco es el más rápido debido a las búsquedas de métodos en tiempo de ejecución, pero ¿imagina tener que considerar todos estos detalles al escribir una línea de código? Así es como solía ser la programación para la web antes de JS++).
Un ejemplo es si tratamos de llamar a ‘lowercaseCompare’ con una variable que olvidamos inicializar, se bloqueará nuevamente si usamos ‘toString’. Vamos a intentarlo:
function lowercaseCompare(a, b) { a = a.toString(); b = b.toString(); return a.toLowerCase() == b.toLowerCase(); } var a, b; // uninitialized variables var result = lowercaseCompare(a, b); console.log(result); // Never executes
No, en cambio, la forma más correcta de realizar la coerción de tipos a una string sería así:
// Finally safe: function lowercaseCompare(a, b) { a += ""; // correct type coercion b += ""; // correct type coercion return a.toLowerCase() == b.toLowerCase(); } var a, b; var result = lowercaseCompare(a, b); console.log(result);
Solo queda un problema con el código correcto: se vuelve ilegible. ¿Cómo se vería su código si tuviera que insertar += «» en todas partes donde desee expresar la intención de que desee datos de string?
‘comparar minúsculas’ en JS++
¡Eso era mucho para digerir! Escribir un buen código en JavaScript es difícil. Imagine tener que tener en cuenta todas estas consideraciones al escribir un pequeño código en JavaScript: seguridad, rendimiento, legibilidad del código, errores implacables, errores silenciosos, corrección y más. En realidad, esto solo rasca la superficie de los casos de esquina de JavaScript, pero nos brinda suficiente información para comenzar a comprender los tipos en JS ++.
Sin embargo, si escribimos nuestro código en JS++, JS++ realmente maneja todas estas consideraciones por nosotros . Esto significa que puede escribir código que sea legible, pero el compilador JS++ manejará la generación de código que sea rápido, seguro y correcto.
Antes de pasar al siguiente capítulo, que explica en detalle el sistema de tipos JS++, intentemos reescribir el código ‘lowercaseCompare’ en JS++. Comenzaremos con el código que es intencionalmente incorrecto para mostrarle cómo JS++ detecta dichos errores antes y le muestra cómo corregirlos. Cree un archivo ‘test.jspp’ y escriba el siguiente código:
import System; function lowercaseCompare(string a, string b) { return a.toLowerCase() == b.toLowerCase(); } Console.log("First message."); lowercaseCompare("10", 10); Console.log("Second message.");
Intente compilar el archivo. No funcionará. JS++ encontró el error temprano:
[ ERROR ] JSPPE5024: No overload for `lowercaseCompare' matching signature `lowercaseCompare(string, int)' at line 8 char 0 at test.jspp
Le dice exactamente la línea donde ocurrió el error para que pueda solucionarlo, antes de que sus usuarios, visitantes o clientes tengan la oportunidad de encontrarlo. Arreglemos la línea infractora, que JS++ nos dijo que estaba en la Línea 8:
// lowercaseCompare("10", 10); // becomes: lowercaseCompare("10", "10");
Ejecute el código después de corregir la línea infractora. En Windows, haga clic con el botón derecho en el archivo y seleccione «Ejecutar con JS++». En Mac o Linux, ejecute el siguiente comando en su terminal:
js++ --execute test.jspp
Verá que ambos mensajes se registraron correctamente.
En el próximo capítulo, exploraremos el sistema de tipos JS++ y las «garantías de tipos» por ejemplo.