Krypton Solid

Terribles errores de JavaScript que se deben evitar con un analizador de código estático

Terribles errores de JavaScript que se deben evitar con un analizador de código estático

Casi ninguna línea de mi código sale perfecta la primera vez que la escribo. Bueno, la mayor parte del tiempo … Algunas veces … Um, casi nunca. La verdad es que paso más tiempo persiguiendo mis propios errores de programación estúpidos de lo que me gustaría admitir. Por eso utilizo analizadores estáticos en cada archivo JavaScript que escribo. Los analizadores estáticos miran el código y encuentran problemas antes de ejecutarlo. Realizan verificaciones simples, como hacer cumplir la sintaxis (por ejemplo, tabulaciones en lugar de espacios) y verificaciones más integrales, como asegurarse de que sus funciones no sean demasiado complejas. Analizadores estáticos también encontrar errores que no puede encontrar con las pruebas, como instancias de == cuando quisiste decir ===.

Casi ninguna línea de mi código sale perfecta la primera vez que la escribo. Bueno, la mayor parte del tiempo … Algunas veces … Um, casi nunca. La verdad es que paso más tiempo persiguiendo mis propios errores de programación estúpidos de lo que me gustaría admitir. Por eso utilizo analizadores estáticos en cada archivo JavaScript que escribo.

Los analizadores estáticos miran el código y encuentran problemas antes de ejecutarlo. Realizan verificaciones simples, como hacer cumplir la sintaxis (por ejemplo, tabulaciones en lugar de espacios) y verificaciones más integrales, como asegurarse de que sus funciones no sean demasiado complejas. Analizadores estáticos también encontrar errores que no puede encontrar con las pruebas, como instancias de == cuando quisiste decir ===.

Otras lecturas en SmashingMag:

En proyectos grandes y en equipos grandes, estará feliz de tener un poco de ayuda para encontrar esos errores «simples» que resultan ser mucho menos simples de lo que parecían.

Compilador JSLint, JSHint y Closure

Tiene tres opciones principales para analizadores estáticos en el mundo de JavaScript: JSLint, JSHint y Compilador de cierre.

JSLint

JSLint fue el primer analizador estático de JavaScript. Puedes ejecutarlo en el sitio web oficial o usar uno de los envoltorios para ejecutarlo en sus archivos locales. JSLint encuentra muchos errores útiles, pero es muy rígido. He aquí un buen ejemplo:


var s="mystring";
for (var i = 0; i < s.length; i++) {
  console.log(s.charAt(i));
}

JSLint mostrará dos errores para este código:

Unexpected '++'.
Move 'var' declarations to the top of the function.

El primer problema es la declaración de la variable. i en la parte superior del bucle. A JSLint tampoco le gusta el ++ operador al final de la declaración del bucle. Quiere que el código se vea así:


var s="mystring";
var i;
for (i = 0; i < s.length; i = i + 1) {
  console.log(s.charAt(i));
}

Aprecio de dónde viene JSLint, pero es demasiado estricto para mí. Era demasiado rígido para Anton Kovalyov también, por lo que creó JSHint.

JSHint

JSHint funciona de manera similar a JSLint, pero está escrito sobre Node.js y es mucho más flexible. JSHint tiene un larga lista de opciones, lo que permite crear controles personalizados mediante escribiendo tu propio reportero.

Puede ejecutar JSHint desde el sitio web, pero la mayoría de las veces lo harías instalar JSHint como una herramienta de línea de comandos local usando Node.js. Una vez que JSHint está instalado, puede ejecutarlo en sus archivos con un comando como este:


jshint test.js

JSHint también tiene complementos para editores de texto populares, por lo que puede ejecutar JSHint mientras codifica.

Compilador de cierre

Closure Compiler, de Google, es una raza diferente. Como sugiere el nombre, es tanto un compilador como un verificador. Está escrito en Java y se basa en Rinoceronte analizador de Mozilla. Closure Compiler tiene un modo simple para realizar comprobaciones básicas de código, pero también tiene modos más avanzados para realizar comprobaciones adicionales y hacer cumplir declaraciones de tipos especiales.

Closure Compiler informa errores en el código JavaScript, pero también crea versiones minimizadas de JavaScript. El compilador elimina los espacios en blanco, los comentarios y las variables no utilizadas y simplifica las declaraciones largas para hacer que un script sea lo más pequeño posible.

Google hace una versión simple de su compilador disponible en la web, pero la mayor parte del tiempo querrás descargar Closure Compiler y ejecutarlo localmente.

Closure Compiler generará una lista de archivos en un solo archivo minimizado después de verificar su código. Puede ejecutarlo así después de haber descargado el compiler.jar expediente.


java -jar compiler.jar --js_output_file compress.js --js test1.js --js test2.js

Elegir el corrector correcto

En mis proyectos, combino Closure Compiler con JSHint. Closure Compiler realiza la minimización y la verificación básica, mientras que JSHint maneja el análisis de código más complejo. Los dos funcionan bien juntos y cada uno cubre algunas áreas que el otro no cubre. Además, puedo usar las capacidades de extensión de JSHint para escribir verificadores personalizados. Un verificador común escribo comprobaciones para funciones particulares que no quiero, como llamar a funciones que no quiero permitir en mi proyecto.

Ahora que hemos visto algunas fichas, veamos un código incorrecto. Todos estos seis ejemplos son códigos que nunca debe escribir y son lugares donde los verificadores de código lo mantendrían fuera de problemas.

Este artículo usa JSHint para la mayoría de los ejemplos, pero Closure Compiler generaría advertencias similares.

== Versus ===

JavaScript es un lenguaje escrito dinámicamente. No tiene que declarar tipos cuando está codificando, pero existen en tiempo de ejecución. JavaScript ofrece dos operadores de comparación para manejar estos tipos dinámicos: == y ===. Veamos un ejemplo.


var n = 123;
var s="123";

if (n == s) {
  alert('The variables were equal');
}

if (n === s) {
  alert('The variables were identical');
}

La == El operador compara los valores de los dos objetos. Convierte los objetos y los compara por separado de sus tipos. La === El operador compara los tipos de objetos y los valores. En este caso, el primer if bloque aparecerá una alerta, y el segundo if el bloque no lo hará, porque n y s tienen el mismo valor pero no el mismo tipo.

La == comparator es una reliquia de las raíces del lenguaje C de JavaScript. Usarlo casi siempre es un error: comparar valores separados de los tipos rara vez es lo que el desarrollador piensa hacer. En realidad, el número «ciento veintitrés» es diferente de la cadena «uno dos tres». Estos operadores son fáciles de escribir mal e incluso más fáciles de leer mal.

Verifique este código con JSHint y obtendrá esto:

test.js: line 9, col 12, Expected '===' and instead saw '=='.

Variables indefinidas y definiciones tardías

Comencemos con un código simple:


function test() {
  var myVar="Hello, World";
  console.log(myvar);
}

¿Ves el error? Cometo este error todo el tiempo. Ejecute este código y obtendrá un error:

ReferenceError: myvar is not defined

Hagamos que el problema sea un poco más difícil de detectar:


function test() {
  myVar="Hello, World";
  console.log(myVar);
}

Ejecute esto y obtendrá:

Hello, World

Este segundo ejemplo funciona, pero tiene algunos efectos secundarios muy inesperados. Las reglas para declarar variables de JavaScript y los ámbitos en los que terminan son, en el mejor de los casos, confusas.

En el primer caso, JSHint le dirá esto:


test.js: line 3, col 17, 'myvar' is not defined.

En el segundo caso, te dirá esto:


test.js: line 2, col 5, 'myVar' is not defined.
test.js: line 3, col 17, 'myVar' is not defined.

El primer caso lo salva de un error en tiempo de ejecución. No tiene que probar su aplicación, JSHint encontrará el error por usted. El segundo caso es peor porque las pruebas no encontrarán el error.

El problema del segundo caso es insidiosamente sutil y complejo. La variable myVar ahora ha escapado de su ámbito de función y ha sido izado en el ámbito global de toda la página. Esto significa que existirá y tendrá un valor de Hello, World después de la test se ha ejecutado la función. A esto se le llama «contaminación de alcance global».

La myVar La variable existirá para cualquier otra función que se ejecute después de test función. Ejecute el siguiente código después de haber ejecutado el test función:


console.log('myVar: ' + myVar);

Todavía obtendrás Hello, World. La myVar la variable colgará alrededor de su código como un molde, causando errores complicados que no encontrará hasta las 3:00 am la noche anterior a su lanzamiento, todo porque olvidó escribir var.

Reutilización variable

La redefinición de variables está permitida en JavaScript, pero casi siempre es un accidente. Echar un vistazo:


function incrementCount(counter) {
  if (counter.count) {
    counter.count++;
  } else {
    var counter = 1;
    counter.count = counter;
  }
}

En esta función estamos incrementando el count propiedad en el objeto que se pasó, pero necesitamos agregar la propiedad si aún no existe. ¿Ves el error?

Esta función nunca agregará ni incrementará un contador en nada. La else La declaración siempre será llamada y redefinirá el argumento de la función. counter. Básicamente, esta función crea un nuevo objeto, le asigna una propiedad y luego pierde el objeto cuando la función regresa. Nunca cambiará el objeto que se pasó.

Este simple error tipográfico hará que el código se ejecute sin errores, pero producirá un resultado muy extraño.

JSHint te dirá esto:


test.js: line 21, col 21, 'counter' is already defined.

Tirantes rizados en bloques, bucles y condicionales


if (false)
  doSomethingElse();
  doSomething();

¿Este código doSomething o doSomethingElse? A primera vista, siempre pienso que no doSomething o doSomethingElse. Así funciona en Python, pero no en JavaScript. JavaScript tratará la línea después de la if declaración simplemente como parte del bloque; la sangría no importa.

Este problema se trata simplemente de la legibilidad del código. Si no puede entender lo que hará el código, entonces escribirá errores.

A Python y CoffeeScript les gusta saltarse las llaves. Eso podría funcionar bien en lenguajes que garantizan formatear bien los espacios en blanco, pero JavaScript es más flexible que eso. JavaScript permite una gran cantidad de sintaxis extraña y las llaves te mantendrán fuera de problemas.


if (false) {
  doSomethingElse();
  doSomething();
}

Agregue las llaves y siempre hará que el código sea más legible. Sáltelos y JSHint le dirá esto:


test.js: line 27, col 5, Expected '{' and instead saw 'doSomething'.

Cotizaciones simples y dobles


console.log("This is a string. It's OK.");
console.log('This string is OK too.');
console.log("This string " + 'is legal, but' + "really not OK.");

JavaScript le permite definir una cadena con comillas simples o dobles. Es bueno tener la flexibilidad, como cuando está definiendo HTML, pero la flexibilidad adicional puede llevar a un código muy inconsistente.

Google tiene una guía de estilo de código que siempre usa comillas simples para las cadenas, para que no tengan que escapar de las comillas dobles en HTML. No puedo argumentar que las comillas simples son mejores que las comillas dobles, pero puedo defender la coherencia. Mantener todo coherente hace que el código sea más legible.

JSHint le advertirá sobre citas mixtas como esta:


test.js: line 31, col 27, Mixed double and single quotes.

Copiar y pegar o escribir mal una cita es fácil. Una vez que tenga una cita incorrecta, le seguirán otras, especialmente si muchas personas están editando el archivo. Los analizadores estáticos ayudarán a mantener la coherencia de las cotizaciones y evitarán una gran limpieza en el futuro.

Complejidad ciclomática

Complejidad ciclomática es la medida de la complejidad de un determinado bloque de código. Mire el código y cuente el número de caminos que posiblemente podrían correr: ese número es su complejidad ciclomática.

Por ejemplo, este código tiene una complejidad ciclomática de 1:


function main() {
  return 'Hello, World!';
}

Puede seguir solo una ruta a través de este código.

Agreguemos un poco de lógica condicional:


function main() {
  if (true) {
    return 'Hello, World!';
  } else {
    return 'Hello, unWorld!';
  }
}

La complejidad ciclomática ha aumentado a 2.

El código ideal es fácil de leer y comprender. Cuanto mayor sea la complejidad ciclomática, más difícil será la comprensión del código. Todo el mundo está de acuerdo en que una alta complejidad ciclomática es mala, pero nadie está de acuerdo en un límite; 5 está bien y 100 es demasiado alto, pero hay una gran cantidad de área gris en el medio.

Si la complejidad ciclomática llega al límite predefinido, JSHint se lo informará.


test.js: line 35, col 24, This function's cyclomatic complexity is too high. (17)

JSHint es el único de los tres correctores que analiza la complejidad ciclomática. También le permite establecer el límite. Ve por encima del maxcomplexity número que ha establecido y JSHint le advertirá. Me gusta establecer el límite en 14, pero iré un poco más alto en proyectos en los que hago mucho análisis o cuando tengo otras razones para necesitar muchas rutas de código.

La verdadera razón por la que el número de complejidad es importante es que le indica cuándo refactorizar su código. La primera vez que escribe una función larga, siempre tiene sentido. Pero si espera seis meses y luego regresa para corregir los errores, se alegrará de haberse tomado el tiempo para facilitar la lectura.

La complejidad ciclomática generalmente se rompe con las listas de lavandería. Por ejemplo, creé un calendario y quería obtener el primer día de la semana correcto para cada país. Tenía una función que se parecía a esto:


function getFirstDay(country) {
  if (country === 'USA') {
    return 'Sunday';
  } else if (country === 'France') {
    return 'Monday';
  } else if…
}

Apoyé a muchos países, por lo que la complejidad ciclomática creció rápidamente a más de 50. Aunque el código era muy fácil de leer, la cantidad de rutas era alta, por lo que mi analizador de código se quejó. Al final, dividí la función para obtener la complejidad por debajo de mi máximo. Fue un truco para este caso particular, pero es un pequeño precio a pagar por un código más limpio en general.

Verifique todo lo que alguna vez editará más de una vez

Los verificadores estáticos encuentran los errores que no encontraría con una prueba simple. También encuentran errores en tiempo de compilación, a diferencia del tiempo de ejecución, esos errores de medianoche que solo se infiltran cuando una docena de personas intentan hacer lo mismo. Encontrar todos esos errores sutiles es un proceso largo y doloroso sin verificación de código.

Comencé este artículo afirmando que siempre uso un analizador de código, pero no lo hago en un caso: con código desechable. Me gusta usar prototipos rápidos para mostrar ideas interactivas y ayudar a que mi equipo se una sobre cómo debería funcionar algo. Esos prototipos son código de una sola escritura; Nunca necesito corregir errores en ellos porque tiraré los prototipos unas semanas más tarde. Este código desechable existe únicamente para las demostraciones rápidas, y no me importa si tiene errores sutiles. Todo lo que me importa, sin embargo, es analizado.

Corregir este tipo de errores al comienzo de un proyecto es fácil; encontrarlos la noche anterior al lanzamiento te volverá loco. Los analizadores de código me han salvado el trasero muchas veces y también salvarán el tuyo.

Imagen en portada creada por Ruiwen Chua.

Deja un comentario