JavaScript como la encarnación del mal

Los desarrolladores de JavaScript a menudo se quejan de que su lenguaje de programación es injustamente culpable de tener demasiadas funciones demasiado complicadas y confusas. Muchos luchan con esta actitud hacia JS, hablando de por qué criticar este lenguaje por lo que es incorrecto. El autor del material, cuya traducción publicamos hoy, decidió no defender a JS, volviéndose al lado oscuro del lenguaje. Sin embargo, aquí no quiere hablar, por ejemplo, sobre las trampas que JavaScript establece para los programadores inexpertos. Le interesa la pregunta de qué sucede si intenta confirmar la mala reputación del idioma con un código que podría escribir alguien a quien no le importan los demás.



En los ejemplos de este material, se utilizarán muchos mecanismos de lenguaje. Gran parte de lo que ves aquí, por cierto, funciona en otros idiomas, por lo que, con la debida diligencia, también puedes encontrar sus lados oscuros. Pero JavaScript, ciertamente, tiene un verdadero don para todo tipo de bullying, y es muy difícil competir con otros idiomas en esta área. Si escribe un código con el que otras personas necesitarán trabajar, JS le brinda una cantidad inagotable de oportunidades para molestar, confundir, acosar y engañar a estas personas. De hecho, aquí consideraremos solo una pequeña parte de tales técnicas.

Modificadores Getter


JavaScript admite getters, funciones que le permiten trabajar con lo que devuelven como con una propiedad normal. Bajo uso normal, se ve así:

let greeter = {  name: 'Bob',  get hello() { return `Hello ${this.name}`} } console.log(greeter.hello) // Hello Bob greeter.name = 'World'; console.log(greeter.hello) // Hello World 

Si usa captadores, tramando el mal, entonces, por ejemplo, puede crear objetos autodestructivos:

 let obj = {  foo: 1,  bar: 2,  baz: 3,  get evil() {     let keys = Object.keys(this);     if(keys) {        delete this[keys[0]]     }     return 'Nothing to see here';  } } 

Aquí, con cada llamada a obj.evil , se obj.evil una de las otras propiedades del objeto. Al mismo tiempo, el código que funciona con obj.evil no sabrá que algo muy extraño está sucediendo justo debajo de su nariz. Sin embargo, esto es solo el comienzo de la conversación sobre los efectos secundarios nocivos que se pueden lograr con los mecanismos de JavaScript.

Proxies inesperados


Los captadores son geniales, pero han existido durante muchos años, muchos desarrolladores los conocen. Ahora, gracias al proxy, tenemos a nuestra disposición una herramienta mucho más poderosa para entretener con objetos. Los proxies son una característica de ES6 que le permite crear envoltorios alrededor de los objetos. Con su ayuda, puede controlar lo que sucede cuando un usuario intenta leer o escribir propiedades de objetos proxy. Esto permite, por ejemplo, crear un objeto que, en un tercio de los intentos de acceder a una determinada clave de dicho objeto, devolverá un valor mediante una clave seleccionada al azar.

 let obj = {a: 1, b: 2, c: 3}; let handler = {   get: function(obj, prop) {     if (Math.random() > 0.33) {       return obj[prop];     } else {       let keys = Object.keys(obj);       let key = keys[Math.floor(Math.random()*keys.length)]       return obj[key];     }   } }; let evilObj = new Proxy(obj, handler); //          console.log(evilObj.a); // 1 console.log(evilObj.b); // 1 console.log(evilObj.c); // 3 console.log(evilObj.a); // 2 console.log(evilObj.b); // 2 console.log(evilObj.c); // 3 

Desafortunadamente, nuestra maldad es parcialmente revelada por herramientas de desarrollo que identifican evilObj como un objeto de tipo Proxy . Sin embargo, la construcción descrita anteriormente, antes de que se revele su baja esencia, es capaz de brindar muchos minutos agradables a quienes trabajarán con ella.

Funciones contagiosas


Hasta ahora, hemos hablado sobre cómo los objetos pueden modificarse a sí mismos. Pero, además, podemos crear funciones de aspecto inocente que infectan los objetos que se les pasan, cambiando su comportamiento. Por ejemplo, supongamos que tenemos una función get() simple que le permite buscar de forma segura una propiedad en el objeto que se le pasa, teniendo en cuenta el hecho de que dicho objeto puede no existir:

 let get = (obj, property, default) => {  if(!obj) {     return default;  }  return obj[property]; } 

Es fácil reescribir tal función para que infecte los objetos transferidos a ella, cambiándolos ligeramente. Por ejemplo, puede asegurarse de que la propiedad a la que ayudó a acceder ya no se muestre cuando intente iterar sobre las claves de un objeto:

 let get = (obj, property, defaultValue) => {  if(!obj || !property in obj) {     return defaultValue;  }  let value = obj[property];  delete obj[property];  Object.defineProperty(obj, property, {     value,     enumerable: false  })  return obj[property]; } let x = {a: 1, b:2 }; console.log(Object.keys(x)); // ['a', 'b'] console.log(get(x, 'a')); console.log(Object.keys(x)); // ['b'] 

Este es un ejemplo de una intervención muy sutil en el comportamiento de un objeto. Enumerar las claves de un objeto no es la operación más notable, ya que no es muy raro, pero no se usa con demasiada frecuencia. Dado que los errores que puede provocar dicha modificación de objetos no pueden vincularse a su código, pueden existir en un determinado proyecto durante bastante tiempo.

Prototipo de desorden


Discutimos varias características de JS arriba, incluidas algunas bastante recientes. Sin embargo, a veces no hay nada mejor que la tecnología antigua y probada. Una de las características de JS, por lo que es más criticado, es la capacidad de modificar los prototipos incorporados. Esta característica se utilizó en los primeros años de JS para extender objetos incrustados, como matrices. Aquí se explica cómo extender las capacidades de la matriz estándar, por ejemplo, agregando el método contains a un prototipo de objeto Array :

 Array.prototype.contains = function(item) { return this.indexOf(item) !== -1; } 

Como resultado, si hace algo como esto en una biblioteca realmente usada, puede interrumpir el trabajo con los mecanismos básicos del lenguaje en toda la aplicación que usa esta biblioteca. Por lo tanto, la inclusión de métodos útiles adicionales en prototipos de objetos estándar puede considerarse un movimiento muy exitoso para los desarrolladores de pacientes que desean hacer otras cosas desagradables. Sin embargo, si hablamos de sociópatas impacientes, se les puede ofrecer algo rápido, pero no menos interesante. La modificación de prototipos tiene una propiedad muy útil, que consiste en el hecho de que la modificación afecta a todo el código que se ejecuta en un determinado entorno, incluso uno que se carga desde módulos o está en cierres. Como resultado, si diseña el siguiente código en forma de secuencia de comandos de un tercero (por ejemplo, podría ser una secuencia de comandos de una red publicitaria o un servicio analítico), todo el sitio que use esta secuencia de comandos será propenso a pequeños errores.

 Array.prototype.map = function(fn) {  let arr = this;  let arr2 = arr.reduce((acc, val, idx) => {     if (Math.random() > 0.95) {        idx = idx + 1     }     let index = acc.length - 1 === idx ? (idx - 1 ) : idx     acc[index] = fn(val, index, arr);     return acc;  },[]);  return arr2; } 

Aquí redefinimos el método estándar Array.prototype.map para que, en general, funcione bien, pero en el 5% de los casos intercambia dos elementos de la matriz. Esto es lo que puede obtener después de varias llamadas a este método:

 let arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; let square = x => x * x; console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,100,81,121,144,169,196,225 console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] 

Aquí lo lanzamos tres veces. Lo que sucedió cuando lo usó por primera vez es ligeramente diferente de los siguientes dos resultados de llamarlo. Este es un cambio menor, no siempre causa algún tipo de falla. Y la mejor parte es que es imposible comprender la causa de los errores que rara vez ocurren debido a este método sin leer su código fuente, que es la causa de estos errores. Nuestra función no llama la atención cuando se trabaja con herramientas de desarrollador, no produce errores cuando se trabaja en modo estricto. En general, con la ayuda de algo como esto, es completamente posible volver loco a alguien.

Nombres complicados


Nombrar entidades, como saben, es una de las dos tareas más difíciles de la informática. Por lo tanto, los malos nombres son inventados no solo por aquellos que buscan dañar a otros conscientemente. Por supuesto, puede ser difícil creer en usuarios experimentados de Linux. Tenían años a su disposición para asociar al intruso informático con el peor nombre (Microsoft) con las formas más profundas del mal. Pero los nombres fallidos no perjudican directamente a los programas. No hablaremos de cosas pequeñas como nombres engañosos y comentarios que han perdido relevancia. Por ejemplo, sobre tales:

 //   let arrayOfNumbers = { userid: 1, name: 'Darth Vader'}; 

Para profundizar en esto y comprender que hay algo mal con el comentario y con el nombre de la variable, aquellos que leen el código en el que esto ocurre tendrán que reducir la velocidad y pensar un poco. Pero esto no tiene sentido. Hablemos de cosas realmente interesantes. ¿Sabía que la mayoría de los caracteres Unicode se pueden usar para nombrar variables en JavaScript? Si usted, en el asunto de asignar nombres de variables, es positivo, le gustará la idea de usar nombres en forma de iconos ( Habr cortó emoji, aunque en el original aquí después dejémoslo era emoji kakahi ):

 let = { postid: 123, postName: 'Evil JavaScript'} 

Sin embargo, estamos hablando de cosas realmente desagradables aquí, por lo que es mejor que recurramos a caracteres que son similares a los que generalmente se usan para nombrar variables, pero no lo son. Por ejemplo, hagámoslo así:

 let obj = {}; console.log(obj); // Error! 

La letra en el nombre obj puede parecer casi normal, pero no es una letra latina minúscula b. Esta es la llamada letra latina minúscula de ancho completo b. Los símbolos son diferentes, por lo que cualquiera que intente ingresar el nombre de una variable de forma manual probablemente estará muy confundido.

Resumen


A pesar de la historia de varias cosas desagradables que se pueden hacer usando JavaScript, este material está dirigido a advertir a los programadores que usen trucos como los descritos, y transmitirles el hecho de que esto puede causar un daño real. El autor del material dice que siempre es útil saber qué problemas pueden aparecer en un código mal escrito. Él cree que se puede encontrar algo similar en proyectos reales, pero espera que exista en una forma menos destructiva. Sin embargo, el hecho de que el programador que escribió dicho código no haya tratado de dañar a otros no hace que sea más fácil trabajar con dicho código y depurarlo. Al mismo tiempo, el conocimiento de los intentos intencionales de dañar puede ampliar los horizontes de un programador y ayudarlo a encontrar una fuente de errores similares. Nadie puede estar completamente seguro de que no hay ningún error en el código con el que funciona. Quizás alguien, sabiendo sobre su tendencia a sospechar demasiado, trate de tranquilizarse de que la ansiedad por tales errores es solo un producto de su imaginación. Sin embargo, esto no evitará que tales errores, posiblemente introducidos en algún código intencionalmente, se prueben una vez.

Estimados lectores! ¿Has encontrado en la práctica algo similar a lo que se discutió en este artículo?

Source: https://habr.com/ru/post/es417263/


All Articles