JavaScript robusto: persiguiendo un mito

JavaScript a menudo se llama el "lenguaje más popular", pero parece que nadie habla del desarrollo de JS como "el más seguro", y la cantidad de problemas al acecho en el ecosistema es grande. ¿Cómo sortearlos de manera efectiva?



Ilya Klimov pensó en esto cuando el error fue muy costoso (literalmente), y como resultado hizo una presentación en HolyJS. Y dado que las opiniones de la audiencia resultaron ser excelentes, ahora hemos preparado una versión de texto de este informe para Habr. Debajo del corte, tanto texto como video.



Hola a todos Mi nombre es Ilya Klimov, soy de Jarkov, Ucrania. Tengo mi propia empresa de outsourcing pequeña, de hasta diez personas. Hacemos todo por lo que se paga dinero ... en el sentido, programamos en JavaScript en todos los sectores. Hoy, hablando de JavaScript confiable, quiero compartir mis mejores prácticas en algún lugar durante el año pasado, ya que este tema comenzó a molestarme bastante en serio.

Quienes trabajan en outsourcing comprenderán el contenido de la siguiente diapositiva:



Todo lo que hablaremos, por supuesto, no tiene nada que ver con la realidad. Como dicen en South Park, todos los personajes están mimados y son miserables. Naturalmente, aquellos lugares donde hay una sospecha de violación de la NDA se acordaron con representantes de los clientes.

Nada afectó mis pensamientos sobre la fiabilidad y similares como la base de mi propia empresa. Cuando comienzas tu propia compañía, de repente resulta que puedes ser un programador genial, puedes tener muchachos geniales, pero a veces suceden cosas increíbles, un par de cosas imposibles y completamente locas.

Tengo un proyecto educativo Ninja JavaScript. A veces hago promesas. A veces incluso los sigo. Prometí en 2017, como parte de un proyecto educativo, grabar un video sobre Kubernetes. Me di cuenta de que había hecho esta promesa y sería bueno cumplirla el 31 de diciembre. Y me senté a grabar ( aquí está el resultado ).

Como me gusta grabar videos lo más cerca posible de la realidad, aproveché ejemplos de un proyecto real. Como resultado, en el clúster de demostración, implementé una cosa que tomó órdenes reales de producción real y las puse en una base de datos de Kubernetes separada en mi clúster de demostración.

Como era el 31 de diciembre, parte de las órdenes desaparecieron. Desestimado por estacionalidad: todos fueron a tomar el té. Cuando el cliente se despertó alrededor del 12 al 13 de enero, el costo total del video fue de aproximadamente $ 500,000. Todavía no he tenido una producción tan costosa.



Ejemplo número dos: otro clúster de Kubernetes. Infraestructura del ecosistema de nueva moda como un código: todo lo que pueda se describe mediante código y configuraciones, Kubernetes se contrae programáticamente desde los shells de JavaScript, etc. Genial, a todos les gusta. Un pequeño cambio en el procedimiento de implementación, y llega un momento en el que necesita implementar un nuevo clúster. Surge la siguiente situación:

const config = { // … mysql: process.env.MYSQL_URI || 'mysql://localhost:3306/foo' // ... } 

Muchos de ustedes probablemente también tengan esta línea de código en sus configuraciones. Es decir, tomamos la configuración de la variable mysql o tomamos la base de datos local.

Debido a un error tipográfico en el sistema de implementación, resultó que el sistema se configuró nuevamente como producción, pero la base de datos MySQL utilizó una base de datos de prueba, local, que era para pruebas. Esta vez se gastó menos dinero, solo $ 300,000. Afortunadamente, esta no era mi empresa, sino el lugar donde trabajaba como consultor comprometido.



Puede pensar que todo esto no le concierne como front-end, porque hablé sobre DevOps (por cierto, admiro el nombre de la conferencia DevOops , describe la esencia perfectamente). Pero te contaré sobre otra situación.

Hay un sistema que controla las epidemias veterinarias en Etiopía, desarrollado bajo los auspicios de la ONU. Contiene uno de los elementos de la interfaz cuando se trata de una persona, y él ingresa manualmente las coordenadas: cuándo y dónde hubo brotes de una determinada enfermedad.

Hay un brote de fiebre aftosa regular (no soy fuerte en las enfermedades de las vacas), y el operador presiona accidentalmente el botón "agregar" dos veces, dejando los campos vacíos. Como tenemos JavaScript, y JavaScript y los tipos son muy buenos, los campos vacíos de latitud y longitud con alegría conducen a coordenadas cero.

No, no enviamos al médico lejos al océano, pero resultó que todo esto se calculó previamente desde el punto de vista de la agrupación para mostrar en el mapa, generar informes, análisis, analizar la ubicación de las personas en el backend. Estamos tratando de unir los puntos de estallido.

Como resultado, el sistema se paraliza durante el día, ya que el backend está tratando de calcular el clúster con la inclusión de este punto, todos los datos se vuelven irrelevantes, las órdenes completamente irresponsables de la serie "drive 400 kilometros" llegan a los médicos. 400 kilómetros en Etiopía es un placer dudoso.

La pérdida total estimada es de aproximadamente un millón de dólares. En el informe sobre esta situación estaba escrito "Fuimos víctimas de un conjunto desafortunado de circunstancias". ¡Pero sabemos que el punto es JavaScript!



Y el último ejemplo. Desafortunadamente, aunque esta historia fue hace mucho tiempo, todavía no puedo nombrar la compañía. Pero esta es una compañía que tiene su propia aerolínea, sus propios hoteles, etc. Ella trabaja muy interactivamente con las empresas, es decir, les proporciona una interfaz separada para reservar boletos, etc.

Una vez que ocurrió un accidente absurdo, llega una orden para reservar boletos de Nueva York a Los Ángeles por un monto de 999,999 piezas. El sistema de la compañía compró felizmente todos los vuelos de su propia aerolínea, descubrió que no había suficientes asientos y envió los datos al sistema internacional de reservas para compensar la escasez. El sistema de reserva internacional, al ver una solicitud de aproximadamente 950,000 boletos, desconectó felizmente a esta aerolínea de su sistema.

Dado que el cierre es un evento fuera de lo común, el problema se resolvió en siete minutos. Sin embargo, en estos siete minutos, el costo de las multas que tuvieron que pagar fue de solo $ 100,000.



Afortunadamente, todo esto sucedió en más de un año. Pero estos casos me hicieron pensar en problemas de fiabilidad y hacer dos preguntas originales en ruso: ¿quién tiene la culpa y qué hacer al respecto?

Por qué sucede esto: la juventud del ecosistema


Si analiza muchas historias, encontrará que hay más historias sobre problemas similares con JavaScript que con otro lenguaje de programación. Esta no es mi impresión subjetiva, sino los resultados del análisis inteligente de las noticias en Hacker News. Por un lado, esta es una fuente hipster y subjetiva, pero, por otro lado, es bastante difícil encontrar una fuente sensata por fakap en el campo de la programación.

Además, hace un año realicé una competencia donde tenía que resolver problemas algorítmicos todos los días. Como estaba aburrido, los resolví en JavaScript usando programación funcional. Escribí una función completamente pura, y en el Chrome actual, funcionó correctamente 1197 veces, y 3 veces produjo un resultado diferente. Fue solo un pequeño error en el optimizador TurboFan, que acaba de ingresar al Chrome principal.

Por supuesto, se solucionó, pero usted comprende: esto significa, por ejemplo, que si sus pruebas unitarias pasaron una vez, esto no significa en absoluto que funcionarán en el sistema. Es decir, ejecutamos código aproximadamente 1197 veces, luego vino el optimizador y dijo: “¡Guau! Característica caliente! Optimicémoslo ". Y en el proceso de optimización llevó al resultado incorrecto.

En otras palabras, podemos llamar a la juventud del ecosistema una de las primeras razones por las que esto sucede. JavaScript es una industria bastante joven precisamente en el campo de la programación seria, en los casos en que giran millones, donde el costo de un error se mide entre cinco y seis caracteres.

Durante mucho tiempo, JavaScript fue percibido como un juguete. Debido a esto (no porque no nos lo tomemos en serio), todavía tenemos problemas con la falta de herramientas.

Por lo tanto, para abordar esta razón, que es el principio fundamental fundamental de todo lo que hablaré hoy, traté de formular las reglas de confiabilidad que podría imponer en mi empresa o transferir como consultor a otros. Como dice el refrán, "la regla número uno es no hablar de confiabilidad". Pero más en serio, entonces ...

Regla de confiabilidad n. ° 1: todo lo que se puede automatizar debe automatizarse


Incluyendo, por cierto, y la corrección ortográfica:

Texto oculto
Regla de confiabilidad n. ° 1: todo lo que se puede automatizar debe automatizarse

Todo comienza con las cosas más simples. Parece que todos han estado escribiendo Prettier durante mucho tiempo. Pero solo en 2018, esto que todos usamos, bueno y sólido, aprendió a trabajar con git add -p cuando agregamos parcialmente archivos al repositorio de git, y queremos formatear bien el código, por ejemplo, antes de enviarlo al repositorio principal. La utilidad realinstaged bastante conocida, que le permite verificar solo aquellos archivos que han sido modificados, tiene exactamente el mismo problema.



Continuar jugando Captain Evidence: ESLint. No preguntaré quién lo usa aquí, porque no tiene sentido que toda la audiencia levante la mano (bueno, eso espero y no quiero decepcionarme). Mejor levante la mano, ya que tienen sus propias reglas escritas personalizadas en ESLint.

Dichas reglas son una de las formas muy poderosas de automatizar el desorden que ocurre en los proyectos donde las personas escriben en el nivel junior y similares.

Todos queremos un cierto nivel de aislamiento, pero tarde o temprano surge una situación: “Mira, este ayudante Vasya vendió muy cerca en el directorio de su componente. No lo sacaré en común, entonces lo haré ”. La palabra mágica es "más tarde". Esto lleva al hecho de que no comienzan a aparecer dependencias verticales en el proyecto (cuando los elementos superiores conectan a los inferiores, los inferiores nunca trepan detrás de los superiores), pero el componente A depende del componente B, que se encuentra en una rama completamente diferente. Como resultado, el componente A no se transporta tan fácilmente a otros componentes.

Por cierto, expreso mi respeto por Alfa-Bank, tienen una biblioteca de componentes muy bien escrita en React, es un placer usarla precisamente en términos de diseño de la calidad del código.

La regla banal de ESLint, que realiza un seguimiento de dónde importa las entidades, le permite aumentar significativamente la calidad del código y guardar el modelo mental durante la revisión del código.

Ya estoy desde el punto de vista del mundo de los front-end de edad. Recientemente, en la región de Kharkov, una compañía grande y seria, PricewaterhouseCoopers, ha completado un estudio, y la edad promedio del proveedor es de 24 a 25 años. Ya es difícil para mí pensar en todo esto, quiero centrarme en la lógica empresarial durante una revisión de solicitud de extracción. Por lo tanto, me complace escribir las reglas de ESLint para no pensar en tales cosas.

Parece que puede ajustar las reglas habituales para esto, pero la realidad generalmente molesta mucho más, porque resulta que se necesitan algunos selectores de Redux desde el componente de reacción (desafortunadamente, todavía está vivo). Y estos selectores están en algún lugar en una jerarquía completamente diferente, así que "../../../ ..".

O, peor aún, el alias de paquete web, que rompe aproximadamente el 20% de otras herramientas, porque no todos entienden cómo trabajar con él. Por ejemplo, mi amado Flow.

Por lo tanto, la próxima vez antes de querer gruñir en un junior (y el programador tiene un pasatiempo favorito), piense si de alguna manera puede automatizar esto para no cometer errores en el futuro. En un mundo ideal, por supuesto, escribirás instrucciones que nadie leerá de todos modos. Aquí están los oradores de HolyJS, especialistas talentosos con gran experiencia, pero cuando se propuso elaborar las instrucciones para los oradores en un mitin interno, dijeron "sí, no lo leerán". ¡Y estas son las personas para tomar un ejemplo!

Lo último del banalismo, y pasar al estaño. Estas son herramientas para ejecutar ganchos de precompromiso. Usamos husky , y no pude evitar insertar esta hermosa foto de husky, pero puedes usar otra cosa.



Si crees que todo esto es muy simple, como dicen, espera mi cerveza, pronto descubriremos que todo es más complicado de lo que piensas. Algunos puntos más:

Escribiendo


Si no está escribiendo TypeScript, es posible que desee pensarlo. No me gusta TypeScript, tradicionalmente flujo de alto flujo, pero hablaremos de esto más adelante, pero aquí desde la etapa promocionaré la solución convencional con disgusto.

Por qué El comité del programa TC39 recientemente tuvo una gran discusión sobre hacia dónde va generalmente el idioma. Llegaron a una conclusión muy divertida: en TC39 siempre hay un "cisne, cáncer y lucio", que arrastran la lengua en diferentes direcciones, pero hay una cosa que todos quieren y siempre es el rendimiento.

TC39 extraoficialmente, en una discusión interna, emitió esta diatriba: "Siempre haremos JavaScript para que siga siendo productivo, y aquellos a quienes no les guste tomarán un lenguaje que se compila en JavaScript".

TypeScript es una muy buena alternativa al ecosistema adulto. No puedo dejar de mencionar mi amor por GraphQL. Es realmente bueno, desafortunadamente, nadie permitirá que se implemente en una gran cantidad de proyectos existentes donde ya tenemos que trabajar.



Ya hubo informes sobre GraphQL en la conferencia, por lo tanto, solo hay un golpe específico para la cuestión de la confiabilidad: si usa, por ejemplo, Express GraphQL, cada vez que además de un resolutor específico puede colgar ciertos validadores que le permiten ajustar los requisitos de valor en comparación con tipos estándar de GraphQL.

Por ejemplo, me gustaría que el monto de la transferencia entre dos representantes de algunos bancos sea positivo. Porque a más tardar ayer, la ventana emergente en mi banca por Internet anunció alegremente que tenía -2 mensajes no leídos del banco. Y es como un banco líder en mi país.

En cuanto a estos validadores, que imponen un rigor adicional: usarlos es una buena y buena idea, simplemente no los use como sugiere, por ejemplo, GraphQL. Te encuentras muy apegado a GraphQL como plataforma. Al mismo tiempo, la validación que usted hace es necesaria en dos lugares al mismo tiempo: en el frontend antes de enviar y recibir datos, y en el backend.

Regularmente tengo que explicarle al cliente por qué tomamos JavaScript y no el lenguaje X como back-end. Además, el lenguaje X suele ser algún tipo de PHP, y no hermoso Go y similares. Debo explicar que podemos reutilizar el código de la manera más eficiente posible, incluso entre el cliente y el servidor, debido al hecho de que están escritos en el mismo lenguaje de programación. Desafortunadamente, como muestra la práctica, a menudo esta tesis sigue siendo solo una frase en la conferencia y no encuentra encarnación en la vida real.

Contratos


Ya he hablado sobre la juventud del ecosistema. La programación por contrato ha existido por más de 25 años como un enfoque principal. Si escribe en TypeScript, tome io-ts, si escribe en Flow, como yo, tome el contrato escrito, y obtendrá una cosa muy importante: la capacidad de describir contratos de tiempo de ejecución de los cuales derivar tipos estáticos.



No hay peor para un programador que tener más de una fuente de verdad. Conozco personas que perdieron cantidades de cinco dígitos en dólares simplemente porque su tipo se describe en un lenguaje con tipeo estático (usaron TypeScript; bueno, por supuesto, esto es solo una coincidencia) y el tipo de tiempo de ejecución (parece haber usado tcomb ) Un poco diferente.

Por lo tanto, el error no se detectó en tiempo de compilación, simplemente porque ¿por qué verificarlo? No hubo pruebas unitarias para ello, porque fuimos verificados por un tipificador estático. No tiene sentido probar cosas que han sido verificadas por la capa a continuación, todos recuerdan la jerarquía de prueba.

Debido al hecho de que la sincronización entre estos dos contratos se rompió con el tiempo, un día se realizó una transferencia incorrecta a la dirección que se dirigió a la dirección incorrecta. Como era una criptomoneda, es imposible revertir una transacción un poco más que en principio. Nadie volverá a bifurcar el aire por tu bien. Por lo tanto, los contratos y las interacciones en la programación de contratos son las primeras cosas que debe comenzar a hacer mañana.

Por qué sucede esto: aislamiento


El siguiente problema es el aislamiento. Es multifacético y multifacético. Cuando trabajaba para un hotel y una compañía de viajes aéreos, tenían una aplicación en Angular 1. Eso fue hace mucho tiempo, así que es excusable. Un equipo de 80 personas trabajó en esta aplicación. Todo estaba cubierto de pruebas. todo estuvo bien hasta que un buen día hice mi función, no la congelé y descubrí que, durante las pruebas, rompí lugares absolutamente increíbles en el sistema que ni siquiera toqué.

Resultó que tengo problemas con la creatividad. Resultó que accidentalmente llamé al servicio exactamente igual que otro servicio que existía en el sistema. Como se trataba de Angular 1, y el sistema de servicio no estaba estrictamente escrito, pero se escribía de forma secuencial, Angular comenzó a deslizar mi servicio con bastante calma en lugares completamente diferentes e, irónicamente, coincidieron un par de métodos para nombrar.

Esto, por supuesto, no fue una coincidencia: usted comprende que si dos servicios reciben el mismo nombre, es probable que hagan lo mismo, más o menos. Era un servicio relacionado con el cálculo de descuentos. Solo un módulo estaba ocupado calculando descuentos para clientes corporativos, y el segundo módulo con mi nombre estaba relacionado con el cálculo de descuentos en acciones.

Obviamente, cuando la aplicación es aserrada por 80 personas, esto significa que es grande. La división de código se implementó en la aplicación, y esto significa que la secuencia de conexión de los módulos dependía directamente del viaje del usuario en el sitio. Para hacerlo aún más interesante, sucedió que ni una sola prueba de extremo a extremo que probó el comportamiento y el paso del usuario por el sitio, es decir, un escenario empresarial específico, detectó este error. Porque parece que nadie tendrá que ponerse en contacto con ambos módulos de descuento al mismo tiempo. Es cierto que esto paralizó por completo el trabajo de los administradores del sitio, pero con quienes no sucede.

El problema de aislamiento está muy bien ilustrado por el logotipo de uno de los proyectos que resuelve parcialmente este problema. Esta es Lerna



Lerna es una excelente herramienta para administrar múltiples paquetes npm en un repositorio. Cuando tienes un martillo en tus manos, todo se vuelve sospechosamente como un clavo. Cuando tienes un sistema similar a Unix con la filosofía correcta, todo se vuelve sospechosamente como un archivo. Todo el mundo sabe que en los sistemas Unix todo es un archivo. Hay sistemas donde se lleva al más alto grado (casi dije "hasta el punto de lo absurdo"), como el Plan 9 .

Conozco organizaciones que, después de sintonizarse para garantizar la confiabilidad de una aplicación gigante, se les ocurrió una idea simple: todo es un paquete.



Cuando saca algún elemento de funcionalidad, ya sea un componente u otra cosa, en un paquete separado, automáticamente proporciona una capa de aislamiento. Solo porque normalmente no puede llegar a otro desde un paquete. Y también porque el sistema de trabajar con paquetes que se recopilan en un único repositorio a través de npm-link o Yarn Workspaces es tan horrible e impredecible en términos de cómo está organizado internamente que ni siquiera puede recurrir a un hack y conectar algún tipo de presentar a través de "node_modules algo", simplemente porque diferentes personas tienen todo en una estructura diferente. Esto depende especialmente de la versión de Yarn. Allí, en una de las versiones, cambiaron por completo el mecanismo de cómo Yarn Workspaces organiza el trabajo con paquetes.

El segundo ejemplo de aislamiento para mostrar que el problema es multifacético es el paquete que estoy tratando de usar en todas partes ahora, esto está enganchado a cls . Es posible que conozca otro paquete que implementa lo mismo: este es el almacenamiento local de continuación . Resuelve un problema muy importante que, por ejemplo, los desarrolladores en PHP no enfrentan.

Se trata de aislar cada solicitud específica. En PHP, tenemos, en promedio, en un hospital, todas las solicitudes están aisladas, interactuando entre ellas solo si no usa perversiones como Shared Memory, no podemos, todo está bien, tranquilo, hermoso. En esencia, cls-hook agrega lo mismo, permitiéndole crear contextos de ejecución, poner variables locales en ellos y luego, lo más importante, destruir automáticamente estos contextos para que no continúen consumiendo su memoria.

cls-hooked async_hooks, node.js , , . , .

, , node-.

#2: «»


, , . , JavaScript — , . grep-.

Que es esto vim. , - Language Server, - — , , grep. , , . grep- , grep. , , .

Sequelize . ORM . user.getProjects(). , getProjects? .



, Sequelize, , hasMany, belongsToMany. , , , . , .

code review, , . . — , .

, : «merge request 20 — 30 , merge request 5000 — looks good to me». , .

JavaScript Ninja , , , junior-, . « react redux», 8000 , 10 000 » , , « ». , , « », , merge request .

, , merge request , , Linux. , , -. , git. , . , , . .

- . . - , , - .

, . Microsoft Surface Linux, . , , . - .

:




« », « ». , React. React, Fiber — .

, Fiber ( OCaml) JavaScript, . , . , — , proposal, JavaScript. Scheduler — proposal stage 0.

, React , - . , : , DOM. — . , .



— Vue.js. Vue? , . Vue, , , .

Vue React. , Vue, , React.

. Vue , state, , state , . , . Vue .

. , , Web Components c , , . : , pop-up, , , - . — .

Vue — scoped slots. , - . scoped slot — , . . React Render Proper: , . Vue , -.

-, Vue . Vue : , child-, , scoped slots, forceUpdate. , child . .

React . , , shouldComponentUpdate(), . Vue , , , , . . Vue. , - .



Jest . Facebook. JavaScript: , , . . , .

, ECMAScript 2015 . , if, require. Require , . , , , . Jest Babel Require .

NGS- Node, proposal, . JavaScript : Require, . . , Jest c NGS- , , . , .

, , - . , Inversion of Control - Dependency Injection-.

IoC/DI


, , . Angular IoC/DI. React … , , React Vue? dan.church , evan.church .

, , React Dependency Injection, - . Vue inject provide. .

, , . , NestJS . InversifyJS . TypeScript, , , JavaScript. , .



Typescript, Inversify. , : inject (TYPES.Weapon) katana: Weapon. , , TYPES.Weapon Weapon? — .

, , , TypeScript ( Flow ) , inject dependency injection runtime, .

« »? Weapon , , TypeScript , . TypeScript , first-class citizen JavaScript, . , , runtime , katana Weapon. .

- C++, , , RTTI: run-time type information, , . C# Java reflection, . TypeScript, « », , RTTI.

. . Vue Vue 3 TypeScript, , Vue TypeScript , Microsoft: « , TypeScript, ?» , Vue, : props, this. , props , this, . TypeScript , . .

Microsoft , : Vue , Angular, . , TypeScript. React, , .

, . , Dart, mirrors, , Flutter. mirrors, , , .

Inversify, Babel-, , , , runtime-, , .

: . . , , , . , V8 .

V8 , . , , V8, , , . , V8 , TypeScript.

. , , .

#3: «» , «»


«» «» , , «» — , . , Vue, . - , - props' .

. typed-css-modules? : CSS, CSS Modules, .



typed-css-modules , , css-, .

12 , CSS- , . 11 12 undefined, CSS- , , , , undefined, .



Yeoman , , , . , , . Angular CLI, Blueprints (, Angular, GDG SPB , , ).




Anguar , , . .

, «» , «» — , . , «» — . , , , , , , .

, — . junior', , , , . , , React- .

, , , - ESLInt, - , , .

, , . , NestJS Sails.js .



, , , , , - -. Sails ORM, . Waterline — , . , Sails.js . blueprint , . , , .



Nest, — , . Dependency Injection, , middleweight .

. , , , , , : « - ?»

Angular, — , , dependency injection, , .

- Vue (, ), , Vue . Vue, - , — Nest. , , , , TypeScript.



:


, JavaScript. , , , , , .

— , , , , , : - . - , — .



Grafana, , — . , - , . speech recognition API Microsoft, , , 20 . , , , -.



: CI/CD. GitLab, , . GitLab, , JavaScript-friendly environment , , .

. , , … , -.



Blue/Green deployment: - , , . , , !


  • JavaScript , , . , , , JavaScript GitHub. — , , , .
  • JavaScript , ( , ). « , ». , . DI , « ?»
  • , . TypeScript, Flow, Rizen — , runtime exception, , .

    , — -, .

HolyJS, : HolyJS 24-25 . — , ( MobX) ( Chrome DevTools). — .

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


All Articles