Complejidad inesperada de programas simples

Más de una vez me sorprendió cuando se anunció la evaluación de la complejidad del proyecto: "¿Por qué tanto tiempo?", "Sí, allí mismo, una, dos veces, ¡y listo!", "Puede tomar X y pegarlo en Y ! " Los programadores están acostumbrados a evaluar los plazos como el tiempo dedicado a escribir y depurar código, aunque las tareas grandes implican mucho más.





¿Sabías que, en realidad, los icebergs están ubicados horizontalmente en el agua y no verticalmente, como en la mayoría de las imágenes de archivo?



Pero incluso si se olvida del grupo tradicional de dispositivos empresariales como análisis, compatibilidad con versiones anteriores y pruebas A / B y se centra exclusivamente en el código directamente relacionado con la funcionalidad implementada, puede ver que su complejidad a menudo se sale de control.



En este artículo, les contaré acerca de varias características que mis colegas y yo hemos implementado en Joom en diferentes momentos, desde la declaración del problema hasta los detalles de implementación, y mostraré con qué facilidad las cosas aparentemente simples se convierten en una maraña de lógica compleja que requiere mucho desarrollo. iteraciones.



Buscar por usuarios



Una de las grandes secciones de la aplicación Joom es la red social interna, donde los compradores pueden escribir reseñas de productos, dar me gusta y discutirlas, y suscribirse entre sí. ¡Y qué red social sin búsqueda de usuarios!



Por supuesto, la búsqueda no es una tarea tan sencilla (al menos después de mi artículo anterior ). Pero ya tenía todos los conocimientos necesarios y también teníamos un componente listo para usar en nuestra empresa joom-mongo-connector



que podía transferir datos de una colección a MongoDB a un índice de Elasticsearch, si era necesario, ajustando datos adicionales y haciendo algún otro posprocesamiento. La tarea parecía bastante sencilla.



Una tarea... Crea un backend para búsquedas por usuarios de redes sociales. No se necesitan filtros, ordenar por el número de suscriptores será suficiente para empezar.



Bien, eso suena realmente simple. socialUsers



Configuramos el desbordamiento de la colección a Elasticsearch escribiendo una configuración en YAML. En el backend, agregamos un nuevo punto final con una API similar a la API de búsqueda de productos, pero hasta ahora sin soporte para filtros y clasificaciones (solo quedan el texto de solicitud y la paginación, eso es todo). En el manejador, hacemos una simple solicitud al clúster de Elasticsearch (¡lo principal es no equivocarse con el clúster!), Del resultado obtenemos los ID de los documentos encontrados - son ID de usuario - según los usuarios ellos mismos, luego lo convertimos al cliente JSON, ocultando información privada de miradas indiscretas, y listo. ¿O no?



El primer problema que encontramos fue la transliteración. Los nombres de usuario se tomaron de las redes sociales, donde los usuarios de Rusia (y eran la mayoría en ese momento) a menudo los escribían en latín. Intentas encontrar a Mads, y él está en Facebook de Mads, y eso es todo, no está en los resultados. Del mismo modo, Iván no podrá encontrar a Iván, pero me gustaría mucho hacerlo.



Esta es la primera complicación: al indexar, comenzamos a ir a la API de Microsoft Translator para la transliteración y guardar dos versiones del nombre y apellido, y el componente de indexación general comenzó a depender del cliente del transliterador (y aún depende).



Bueno, el segundo problema, que es fácil de prever si su idioma nativo es el ruso, pero también existe en otros idiomas europeos: formas diminutas y abreviaturas de nombres. Si Iván decidió llamarse a sí mismo Vanya en Facebook, la solicitud de Iván ya no lo encontrará, sin importar cuánto transliteres.



Entonces, la siguiente complicación fue que encontramos un índice de nombres diminutos en Gramota.ru (del diccionario único de nombres rusos de Nikandr Aleksandrovich Petrovsky), lo agregamos a la base del código como una placa codificada (unas dos mil líneas) y se convirtió en índice no solo el nombre y su transliteración, pero también todos encontraron formas diminutivas (dato curioso: en inglés hay un término hipocorismos para ellos). Tomamos cada palabra en el nombre de usuario e hicimos una búsqueda en nuestra humilde hoja de cálculo.





Una captura de pantalla notariada del código base de Joom. Circa 2018.



Pero luego, para no ofender a la otra mitad de nuestros usuarios, distribuidos en una capa desigual en el mundo que no habla ruso, lanzamos un grito a los gerentes de país de Joom y les pedimos que nos encontraran libros de referencia de abreviaturas nombres en sus países. Si no es académico, al menos algunos. Y resultó que en algunos idiomas, además de la tradición de tener un nombre compuesto (Juan Carlos, María Aurora), también hay reducciones de dos, tres o incluso cuatro palabras a una (María de las Nieves → Marinieves).



Esta nueva circunstancia nos privó de la oportunidad de buscar palabra a palabra. Ahora necesitamos dividir la secuencia de palabras en fragmentos de longitud arbitraria y, además, ¡diferentes particiones pueden conducir a diferentes abreviaturas! No queríamos sumergirnos en las profundidades de la lingüística y escribir inteligencia artificial que abreviara un nombre en español de la forma en que un español vivo lo abreviaría, por lo que esbozamos, perdone a Knut, la exageración combinatoria.



Y, como siempre ocurre con las búsquedas combinatorias, estalló en uno de los usuarios y tuvimos que recortar urgentemente un límite en el número máximo de grafías generadas. Esto complicó aún más el código, que resultó tan inesperadamente difícil para esta tarea.



Traducción automática de mercancías



Tarea . Es necesario traducir los nombres y descripciones de los productos proporcionados por los vendedores en inglés al idioma del usuario.



Probablemente todo el mundo haya visto memes sobre la traducción torcida de los nombres de productos chinos. También los vimos, pero el tiempo de comercialización deseado no nos permitió encontrar algo mejor que usar alguna API existente para la traducción.



Es fácil escribir un cliente HTTP, crear una cuenta y, cuando los productos se entregan al usuario, es fácil traducirlos al idioma del dispositivo. Pero las traducciones no son baratas y sería un desperdicio traducir el mismo producto popular al ruso para cada una de las decenas de miles de visitas. Por lo tanto, activamos el almacenamiento en caché: para cada producto, guardamos las traducciones en la base de datos y, si había traducciones allí, ya no íbamos al traductor.



Pero el potencial de ahorro todavía estaba ahí. Decidimos que un compromiso razonable entre la calidad de la traducción y el precio sería superar las descripciones de las oraciones y almacenarlas en caché; después de todo, las mismas frases de plantilla se encuentran a menudo en los productos y es un desperdicio traducirlas siempre. Así es como apareció otra capa de abstracción en nuestro traductor: una capa entre el cliente HTTP y la caché que almacena productos completos en diferentes idiomas, que se dedica a dividir el texto en fragmentos.



Después del lanzamiento, la calidad de las traducciones, por supuesto, nos persiguió y pensamos: ¿y si usamos un traductor más caro? ¿Pero será bueno para nuestras letras específicas? No puedes compararlos a simple vista, necesitas hacer una prueba A / B. Entonces, en nuestra caché de traducción, además del ID del producto, apareció el ID del traductor y comenzamos a solicitar una traducción del ID del traductor, dependiendo del grupo de prueba A / B en el que se encontraba el usuario.



El querido traductor se desempeñó bien, pero aún era un desperdicio ejecutarlo en todos los productos. Pero fuimos a países cuyos idiomas nacionales nuestro traductor principal se las arregló tan mal que estábamos listos para desembolsar el dinero para un lanzamiento exitoso; por lo que la lógica de elegir un traductor se volvió más complicada.



Luego decidimos que algunas tiendas en la plataforma son tan buenas y la plataforma está tan alentada por su éxito que siempre está lista para traducir sus productos con un traductor más caro. Entonces, la lógica de elegir un traductor comenzó a depender del usuario, el país y la identificación de la tienda.



Y finalmente, decidimos que a lo largo de los varios años de existencia de Joom, nuestro traductor principal podría mejorar, y tal vez tenga sentido actualizar la caché de traducción en algunos intervalos. Pero, ¿qué pasa sin una prueba A / B? Entonces, el campo de frescura apareció en nuestro caché y las cosas se complicaron nuevamente. Como resultado, nuestro componente de traducción es increíblemente complejo, y esto a pesar del hecho de que ni siquiera hemos metido en él ninguna lingüística computacional casera. Por ahora.



Conversión de tallas de ropa



Quizás uno de los problemas más dolorosos a la hora de comprar ropa y zapatos en línea es elegir la talla correcta. Y si, cuando se entregan desde los almacenes locales, los jugadores como Lamoda pueden simplemente traer varios tamaños a la vez y recuperar el inadecuado con la misma facilidad, esto no funcionará en un cruce de fronteras. Los paquetes toman mucho tiempo, el costo de cada kilogramo adicional es alto y sus remitentes no esperan un gran flujo de correo entrante.



Además, el problema se agrava por el hecho de que los vendedores de diferentes países pueden tener ideas completamente diferentes sobre los tamaños. La M china fácilmente podría convertirse en la XS rusa, y la aterradora 9XL puede no ser tan diferente de la XXL. Los usuarios cosidos deben confiar en las medidas, pero incluso esas no siempre son correctas: por ejemplo, el usuario espera que se indique la circunferencia del pecho de una persona, y el vendedor indica las medidas de la ropa en sí: difieren entre un cinco y un diez por ciento. . ¡No queremos que el usuario tenga que preocuparse tanto por comprar en Joom!



Tarea . En lugar de los tamaños proporcionados por los vendedores, muestre a los usuarios los tamaños que calculamos a partir de una tabla única basada en las circunferencias.



Bueno. Tomamos una tabla de tamaños, que analizamos a partir de la descripción del producto (esto lo hace una nave espacial separada para 5k líneas) y se almacena en un campo separado, y reemplazamos los tamaños en ella con los calculados. Codifique la tabla para convertir la circunferencia en tamaño, que se encuentra en Internet, y disfrute de la vida.



Pero si no hay una tabla o no hay suficientes filas en ella, esto no funciona. La función está deshabilitada en el producto implícitamente varias veces.



Mmmm, en la mesa las circunferencias del cuerpo humano, y la mayoría de los vendedores las indican midiendo las cosas mismas. Coser el coeficiente de diferencia. El gerente de producto Rodion, el feliz propietario del M-ki perfecto, va al centro comercial, mide un montón de cosas diferentes sobre sí mismo y viene con coeficientes: son similares, pero difieren significativamente para las diferentes categorías de productos. Para un jersey de cuello alto envolvente, la diferencia es casi del 0%, y para un suéter, todo el 10%. Además, la ropa de abrigo varía en ajuste: ajuste delgado, ajuste normal, ajuste holgado, y esto da una oscilación de ± 5%. Ahora nuestro coeficiente (inmortalizado por mí en el código como coeficiente de Rodion ) consta de dos factores.



Para determinar el aterrizaje, hacemos otro analizador que intenta extraerlo del nombre o descripción del producto. Si el producto no entra en una de las categorías marcadas por Rodion, la función está implícitamente desactivada como número dos.



El toque final: muchos productos enumeran el busto desde la axila hasta la axila, es decir, solo la mitad de la circunferencia, lo que da como resultado tallas ridículamente pequeñas. Agregamos la lógica de que si la circunferencia es menor que X, entonces bueno, esto no puede ser, esto es claramente la mitad de la circunferencia, y lo multiplicamos por dos. Es bueno que los adultos generalmente no se diferencien entre sí en dos veces la circunferencia del pecho.



Ahora todo es tan complicado que al probar una función por tipo de producto en el panel de administración, es imposible entender por qué no se enciende o no funciona de una forma u otra. Agregamos una gran capa de lógica al código, registrando en detalle las razones para desactivar la conversión. Para poder rastrear completamente la causa del apagado en un producto específico, debe reenviar los mensajes de error hacia arriba, enriqueciendo con detalles, varias veces. El código se vuelve aterrador.



Y todo funciona de forma diferente dependiendo del grupo de la prueba A / B, por supuesto.



Conclusión



Tenga cuidado con los Danaans, desarrolladores de donaciones que son optimistas sobre los plazos. Es muy difícil estimar el tiempo de desarrollo, por muy simple que parezca la tarea, ¡y nos esperan sorpresas a cada paso!



All Articles