
Cuando leo otro artículo sobre características poco conocidas del lenguaje JavaScript y orino en silencio algunas soluciones locas en la consola del navegador, a menudo digo en mi cabeza que, bueno, ¡ciertamente no es así en la producción! Después de todo, el lenguaje ha adquirido durante mucho tiempo una gran comunidad y tiene una cobertura sorprendentemente amplia de desarrollo industrial. Si es así, ¿por qué a menudo nos olvidamos de su capacidad de ser entendido por todos y literalmente propagandizamos todas estas construcciones específicas y "memorables"? ¡Solo hazlo obvio!
Razonamiento sobre el tema
Puedes saltarte esta grafomanía.
Si hablamos de desarrollo industrial, en la gran mayoría de los casos, el requisito de que se admita un código es aún más importante que resolver una tarea planteada por una empresa. Para muchos, esto es obvio, para algunos, en parte (por supuesto, también se encuentran D'Artagnans raros). Cuanto más claro sea nuestro código, menor será el riesgo de llegar al estante polvoriento y que nosotros y nuestros sucesores ganemos problemas con el sistema nervioso.
No es ningún secreto que JavaScript es sorprendente en su flexibilidad, que es tanto su mayor virtud como una maldición molesta. El camino del desarrollador de JavaScript es largo y extremadamente interesante: absorbemos libro por libro, artículo por artículo y adquirimos una experiencia única, pero a veces es realmente específico del idioma. La distribución más amplia de la lengua y, al mismo tiempo, una gran cantidad de no-evidencias acumuladas y alimentadas contribuyen a la formación de dos frentes: los que casi idolatran este idioma y los que lo ven como un pato de derechos torpe e influyente.
Y todo estaría bien, pero a menudo representantes de ambos frentes están trabajando en el mismo proyecto. Y lo habitual, toda práctica aceptada es malentendido (falta de voluntad para comprender e incluso ignorar) el código del otro. Y de hecho, "¡Tengo un desarrollador de Java, y no es tuyo!" . Los propios seguidores de Javascript agregan combustible al fuego, diciendo "¡nadie realmente sabe JavaScript!" sí "¡Puedo escribirlo en una línea en js!" . Confieso que yo mismo estoy abusando de la programación anormal en mi tiempo libre ...
Empiezas a sentir este problema cuando tomas el lugar de un marginal y ganas algo de experiencia trabajando con personas y su código en ambos lados de las barricadas. La planificación y otras reuniones son más productivas cuando todos los desarrolladores se entienden entre sí, no solo a nivel de líneas de negocio, sino al menos un poco a nivel de su implementación. El notorio factor de bajo tiene un impacto menor en el proyecto, cuando en el caso de una sola enfermedad de front-ender, el resto del equipo no duda en corregir alguna línea del archivo .js . El proceso de compartir conocimiento dentro del equipo y más allá se vuelve más transparente para todos cuando todos tienen una imagen más detallada. Bueno y todo en la misma línea.
No insto a nadie a "vaso lleno" o "forma de T" (¿cómo decirlo en este momento?), Pero ¿por qué no levantamos un poco esta cortina incluso de la comunidad JavaScript? Para hacer esto, es suficiente traer un poco de claridad a nuestro código, usando la flexibilidad del lenguaje no para presumir, sino para entendernos.
Crecer y tomar responsabilidad
Por su parte, JavaScript se ha dado cuenta de su papel durante mucho tiempo, no como un lenguaje para la interactividad de las páginas de Internet y para "pegar" sus recursos, sino como una herramienta poderosa y suficiente para crear aplicaciones multiplataforma completas y, a menudo, muy escalables.
Originalmente diseñado para diseñadores web, este "lenguaje de programación más incomprendido" ha estado pisando agua durante mucho tiempo, a pesar de la creciente popularidad y relevancia. Durante 13-14 años anteriores a la edición de ECMAScript 5.1, es difícil recordar cambios importantes en el estándar o comprender el vector de su desarrollo. En ese momento, su comunidad hizo una gran contribución a la formación del ecosistema del lenguaje: Prototype, jQuery, MooTools, etc. Después de recibir esta retroalimentación de los desarrolladores, JavaScript hizo un trabajo significativo en los errores: el fuerte lanzamiento de 6 años de ES6 en 2015 y ahora los lanzamientos anuales de ECMAScript, gracias al proceso de rediseño del comité TC39 para introducir nuevas características a la especificación.
Bueno, cuando nuestras aplicaciones se volvieron lo suficientemente grandes, el modelo prototipo de OOP para describir los tipos de usuarios ya no estaba justificado debido a un enfoque inusual. Bueno en serio, ¿qué es?
function Animal() { } function Rabbit() {} Rabbit.prototype = Object.create(Animal.prototype); Rabbit.prototype.constructor = Rabbit;
Las clases no aparecían en el idioma, pero sí su sintaxis. Y el código está disponible para los seguidores del paradigma tradicional orientado a clases:
class Animal { constructor() { } } class Rabbit extends Animal {}
Ahora en la etapa del candidato para la liberación se encuentran los campos privados de la clase. Es difícil creer que tarde o temprano dejaremos de reírnos mutuamente con un acuerdo para nombrar propiedades privadas a través de guiones bajos.
Al mismo tiempo, en un lenguaje donde una función es un objeto de primer orden y tiene lugar un evento constante, es bastante común:
let that = this; setTimeout(function() { that.n += 1; }, 1000);
Y luego comienzan las explicaciones sobre estos contextos y el cierre en JavaScript, que asusta a cada segundo desarrollador externo. Pero en muchos casos, el lenguaje evita sorpresas innecesarias al usar explícitamente Function.prototype.bind o incluso así:
setTimeout(() => this.n += 1, 1000);
También tenemos funciones de flecha, y estas son realmente funciones, no interfaces funcionales (¿sí, Java?). Junto con un conjunto ampliado de métodos para trabajar con una matriz, también ayudan a escribir la línea de pago declarativa habitual de los cálculos:
[-1, 2, -3, 4] .filter(x => x > 0) .map(x => Math.pow(2, x)) .reduce((s, x) => s + x, 0);
El lenguaje se considera acertadamente multi-paradigmático. Pero aquí hay un ejemplo simple sobre la firma de alguna función:
function ping(host, count) { count = count || 5; }
Primero, una persona que pase hará una pregunta diciendo que probablemente una función puede tomar solo el primer argumento, y luego dirá qué demonios en este caso, ¡el conteo se convierte en booleano! De hecho, la función tiene dos usos: con conteo y sin él. Pero esto es completamente obvio: hay que mirar la implementación y comprender. Usar JSDoc puede ayudar, pero esta no es una práctica común. Y aquí JavaScript avanzó, agregando soporte no para la sobrecarga, sino al menos para los parámetros predeterminados:
function ping(host, count = 5) { }
Resumiendo, JavaScript obtuvo una gran cantidad de cosas familiares: generadores, iteradores, conjuntos de colecciones y diccionarios de mapas , matrices escritas e incluso expresiones regulares comenzaron a deleitar el soporte detrás . El lenguaje hace todo lo posible para ser adecuado para muchas cosas y para ser amigable con todos.
Camino favorable a lo obvio
¡El lenguaje en sí está ciertamente bien hecho, y es difícil discutir con eso! ¿Pero qué nos pasa? ¿Por qué recordamos constantemente al mundo entero que JavaScript es de alguna manera diferente? Veamos ejemplos de algunas técnicas ampliamente utilizadas y preguntemos por su idoneidad.
Tipo de fundición
Sí, JavaScript tiene un sistema de tipo dinámico y débil y le permite realizar operaciones en cualquier cosa, realizando transformaciones implícitamente para nosotros. Pero a menudo, todavía es necesario un casting explícito, y se puede observar lo siguiente:
let bool = !!(expr); let numb = +(expr); let str = ''+(expr);
Estos trucos son conocidos por todos los desarrolladores de JavaScript y están motivados por el hecho de que dicen que puedes "convertir rápidamente" algo en algo: por velocidad aquí se entiende un registro corto. ¿Puede también escribir falso inmediatamente como ! 1 ? Si el desarrollador está tan preocupado por los caracteres imprimibles, en su IDE favorito puede configurar fácilmente la plantilla en vivo necesaria o completar automáticamente. Y si, por el tamaño del código publicado, siempre lo ejecutamos a través del ofuscador, que sabe mejor que el nuestro cómo deshumanizar todo esto. Por qué no
let bool = Boolean(expr); let numb = Number(expr); let str = String(expr);
El resultado es el mismo, solo claro para todos.
Para las conversiones de cadenas, tenemos toString , pero para las numéricas hay un valor interesante de, que también se puede anular. Un ejemplo clásico que introduce a los "no iniciados" en un estupor:
let timestamp = +new Date;
Pero Date tiene un método getTime conocido, usémoslo :
let timestamp = (new Date()).getTime();
o función confeccionada:
let timestamp = Date.now();
No hay absolutamente ninguna necesidad de explotar la conversión de tipo implícita.
Operadores lógicos
Se presta especial atención a los operadores lógicos AND (&&) y OR (||), que no son del todo lógicos en JavaScript: aceptan y devuelven valores de cualquier tipo. No entraremos en detalles sobre el funcionamiento de la calculadora de expresión lógica ; consideraremos ejemplos. La opción presentada anteriormente con la función:
function ping(host, count) { count = count || 5; }
Bien podría verse así:
function ping(host, count) {
Dicha verificación es más familiar y, en algunos casos, puede ayudar a evitar errores.
Más bien, parece un salvajismo para el desarrollador que inicialmente eligió la ruta de JavaScript. Pero para la mayoría de los demás, este código es realmente salvaje:
var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global);
Sí, es compacto, y sí, las bibliotecas populares pueden permitírselo. Pero, por favor, no abusemos de él, porque nuestro código no será leído por los contribuyentes en JavaScript, sino por los desarrolladores que resuelven los problemas comerciales en el marco de tiempo.
Tal patrón puede ocurrir en absoluto:
let count = typeof opts == 'object' && opts.count || 5;
Esto es definitivamente más corto que el operador ternario habitual, pero al leer dicho código, lo primero que recuerda son las prioridades de las operaciones utilizadas.
Si escribimos una función de predicado, que pasamos al mismo Array.prototype.filter , entonces envolver el valor de retorno en booleano es un buen tono. El propósito de esta función se hace evidente de inmediato y no hay disonancia entre los desarrolladores cuyos lenguajes tienen los operadores lógicos "correctos".
Operaciones bit a bit
Un ejemplo común de verificar la presencia de un elemento en una matriz o subcadena en una cadena usando NOT (NOT) bit a bit, que se ofrece incluso en algunos tutoriales:
if (~[1, 2, 3].indexOf(1)) { console.log('yes'); }
¿Qué problema resuelve esto? no tenemos que verificar ! == -1 , ya que indexOf obtendrá el índice del elemento o -1, y la tilde agregará 1 y cambiará el signo. Por lo tanto, la expresión se convertirá en un "falso" en el caso del índice -1.
Pero la duplicación de código se puede evitar de otra manera: poniendo un cheque en una función separada de algunos objetos de utilidad, como todos, que usar operaciones bit a bit para otros fines. Hay una función de inclusión en lodash para esto, y no funciona a través de enculada tilde Puede alegrarse, porque en ECMAScript 2016, el método Array.prototype.includes se ha solucionado (las líneas también tienen uno).
Pero ahí estaba! Otra tilde (junto con XOR) se usa para redondear números, descartando la parte decimal:
console.log(~~3.14);
Pero hay parseInt o Math.floor para estos fines. Las operaciones bit a bit aquí son convenientes para escribir código rápidamente en la consola, ya que también tienen una prioridad baja sobre el resto de la aritmética. Pero en una revisión de código, es mejor no perderse.
Construcciones de sintaxis y lenguaje
Algunas prácticas extrañas son difíciles de atribuir a cualquier sección en particular. Por ejemplo, dicen que los corchetes son opcionales cuando se llama al constructor, y las siguientes dos expresiones son idénticas:
let rabbit = new Rabbit(); let rabbit = new Rabbit;
¡Y realmente lo es! pero ¿por qué crear una pregunta desde cero? No todos los idiomas pueden presumir de tal "característica". Y si aún así lo desea, deje que sea un acuerdo sobre todo el proyecto. De lo contrario, existe la falsa sensación de que hay alguna diferencia.
Una situación similar con la declaración de un conjunto de variables. La sintaxis de las directivas var y let le permite declarar (y definir) varias variables a la vez, separadas por comas:
let count = 5, host, retry = true;
Alguien usa saltos de línea para facilitar la lectura, pero en cualquier caso, esta sintaxis no es común en los idiomas populares. Nadie te echará una mano y te preguntará si escribes así:
let count = 5; let retry = true; let host;
Nuevamente, si hay un acuerdo sobre buen estilo a nivel de proyecto / empresa, entonces no hay preguntas. Es solo que no tienes que combinar demasiada sintaxis para tu estado de ánimo.
Hay construcciones específicas en el lenguaje, como IIFE, que le permite llamar a una función inmediatamente en el lugar de su definición. El truco es que el analizador reconozca una expresión funcional, no una declaración de función. Y esto se puede hacer de muchas maneras diferentes: envolviendo clásicamente entre paréntesis, a través de vacío o cualquier otro operador unario. ¡Y no hay nada maravilloso al respecto! Es necesario elegir la única opción y no dejarla sin la necesidad:
(function() { }());
No es necesario utilizar operadores para hackear el analizador. Cuando un recién llegado llega al proyecto, quiero sumergirlo en la lógica empresarial de la aplicación, y no alimentarlo con explicaciones desde donde se espían todos estos signos de exclamación y vacíos. También hay una segunda entrada entre corchetes clásica y un comentario interesante de Crockford sobre este tema.
La aparición de la sintaxis de clase en ES6 no estuvo acompañada de los modificadores de acceso habituales. Y a veces el desarrollador quiere orinar en las clases y observar la privacidad. Lo que lleva a este código de Frankenstein:
class Person { constructor(name) { let _name = name; this.getName = function() { return _name; } } toString() { return `Hello, ${this.getName()}`; } }
Es decir, los accesos se crean para la instancia en el constructor, y la privacidad se logra mediante su acceso a las variables de propiedad local a través del cierre. Este ejemplo se ve bastante parejo a Lacconcino, pero este es un enfoque completamente escalable, a menos que cree una solución marco documentada a su alrededor. Señores, usemos las clases disponibles (y esperemos la estandarización de los campos privados) o el popular módulo de patrones. Crear una especie de solución de mezcla intermedia aquí es algo para usted, ya que las clases dejan de ser clases y el código es inteligible.
En resumen, compartirá su sentido común con la guía de estilo adoptada en el proyecto, la configuración para el linter, o simplemente fragmentos de código con colegas que contribuyen con su componente no JavaScript al proyecto. El lenguaje ofrece varias opciones para literalmente cada tarea típica, por lo que no es difícil (o casi) mejorar la comprensión de los demás y caer dentro de un denominador común.
Desventura
Este tema es ciertamente holístico y hay muchos más ejemplos, pero el mensaje principal del artículo es que no debe abusar de la falta de evidencia en JavaScript, donde se puede evitar. La naturaleza del lenguaje es única: le permite escribir soluciones elegantes y expresivas (moderadamente "puntiagudas"), así como comprensibles y accesibles para todos. Estoy totalmente en desacuerdo con la creencia convencional de que JavaScript "se castigó a sí mismo" o "se enterró bajo un montón de buenas intenciones y errores". Porque ahora la mayor parte de la extrañeza se demuestra no por el lenguaje, sino por la cultura de los desarrolladores y (no) indiferente formados a su alrededor.