Unerwartete Komplexität einfacher Programme

Mehr als einmal war ich überrascht, als die Bewertung der Projektkomplexität angekündigt wurde: "Warum so lange?", "Ja, genau dort, einmal, zweimal, und Sie sind fertig!", "Sie können einfach X nehmen und es in Y stecken ! " Programmierer sind es gewohnt, Fristen als Zeitaufwand für das Schreiben und Debuggen von Code zu bewerten, obwohl große Aufgaben viel mehr erfordern.





Wussten Sie, dass sich Eisberge in Wirklichkeit horizontal im Wasser und nicht vertikal befinden, wie in den meisten Archivbildern?



Aber selbst wenn Sie die traditionellen Unternehmens-Gadgets wie Analytics, Abwärtskompatibilitätsunterstützung und A / B-Tests vergessen und sich nur auf den Code konzentrieren, der direkt mit der implementierten Funktionalität zusammenhängt, können Sie feststellen, dass seine Komplexität häufig außer Kontrolle gerät.



In diesem Artikel werde ich Ihnen einige Funktionen erläutern, die meine Kollegen und ich zu unterschiedlichen Zeiten in Joom implementiert haben, von der Problemstellung bis hin zu Implementierungsdetails, und zeigen, wie leicht sich scheinbar einfache Dinge in ein Gewirr komplexer Logik verwandeln, das viele Entwicklungen erfordert Iterationen.



Suche nach Benutzern



Einer der großen Bereiche der Joom-App ist das interne soziale Netzwerk, in dem Kunden Produktbewertungen schreiben, sie mögen, diskutieren und sich gegenseitig abonnieren können. Und was für ein soziales Netzwerk ohne Benutzersuche!



Natürlich ist das Suchen keine so einfach aussehende Aufgabe (zumindest nach meinem vorherigen Artikel ). Aber ich hatte bereits alle notwendigen Kenntnisse und wir hatten auch eine vorgefertigte Komponente in unserem Unternehmen joom-mongo-connector



, die Daten aus einer Sammlung an MongoDB in einen Elasticsearch-Index übertragen konnte, um bei Bedarf zusätzliche Daten anzupassen und eine andere Nachbearbeitung durchzuführen. Die Aufgabe klang ziemlich einfach.



Eine Aufgabe... Erstellen Sie ein Backend für die Suche nach Benutzern sozialer Netzwerke. Es sind keine Filter erforderlich. Die Sortierung nach der Anzahl der Abonnenten reicht zunächst aus.



Okay, das klingt wirklich einfach. socialUsers



Wir konfigurieren den Überlauf von der Sammlung zu Elasticsearch, indem wir eine Konfiguration in YAML schreiben. Im Backend fügen wir einen neuen Endpunkt mit einer API hinzu, die der API für die Produktsuche ähnelt, jedoch bisher keine Unterstützung für Filter und Sortierungen bietet (nur der Anfragetext und die Paginierung bleiben erhalten, das ist alles). Im Handler stellen wir eine einfache Anfrage an den Elasticsearch-Cluster (die Hauptsache ist, keinen Fehler mit dem Cluster zu machen!). Aus dem Ergebnis erhalten wir die IDs der gefundenen Dokumente - es handelt sich um Benutzer-IDs - gemäß den Benutzern selbst konvertieren wir dann zu Client JSON, verstecken private Informationen vor neugierigen Blicken und sind bereit. Oder nicht?



Das erste Problem, auf das wir stießen, war die Transliteration. Benutzernamen wurden aus sozialen Netzwerken übernommen, in denen Benutzer aus Russland (und sie waren zu dieser Zeit in der Mehrheit) häufig in lateinischer Sprache verfasst wurden. Du versuchst Mads zu finden und er ist auf Mads 'Facebook und das war's - er ist nicht in den Ergebnissen. Ebenso wird Ivan Ivan nicht finden können, aber ich würde es sehr gerne tun.



Dies ist die erste Komplikation. Bei der Indizierung haben wir begonnen, zur Transliteration auf die Microsoft Translator-API zuzugreifen und zwei Versionen des Vor- und Nachnamens zu speichern. Die allgemeine Indizierungskomponente war vom Transliterator-Client abhängig (und hängt immer noch davon ab).



Nun, das zweite Problem, das leicht vorherzusehen ist, wenn Ihre Muttersprache Russisch ist, aber auch in anderen europäischen Sprachen existiert - Verkleinerungsformen und Abkürzungen von Namen. Wenn Ivan sich auf Facebook als Wanja bezeichnet, wird Iwans Bitte ihn nicht mehr finden, egal wie viel Sie transliterieren.



Die nächste Komplikation war, dass wir auf Gramota.ru (aus Nikandr Aleksandrovich Petrovskys einzigartigem Wörterbuch russischer Namen) einen Index mit kleinen Namen fanden, ihn als fest codierte Platte (etwa zweitausend Zeilen) zur Codebasis hinzufügten und nicht nur Index wurden der Name und seine Transliteration, aber auch alle gefundenen Verkleinerungsformen (lustige Tatsache: auf Englisch gibt es einen Begriff Hypokorismen für sie). Wir haben jedes Wort im Benutzernamen genommen und in unserer bescheidenen Tabelle nachgeschlagen.





Ein notariell beglaubigter Screenshot der Joom-Codebasis. Um 2018.



Aber um die andere Hälfte unserer Benutzer, die in einer ungleichmäßigen Schicht über die nicht russischsprachige Welt verteilt ist, nicht zu beleidigen, warfen wir den Joom-Ländermanagern einen Schrei zu und baten sie, uns Nachschlagewerke mit Abkürzungen für National zu suchen Namen in ihren Ländern. Wenn nicht akademisch, dann zumindest einige. Und es stellte sich heraus, dass in einigen Sprachen neben der Tradition eines zusammengesetzten Namens (Juan Carlos, Maria Aurora) auch zwei, drei oder sogar vier Wörter zu einem reduziert werden (María de las Nieves → Marinieves).



Dieser neue Umstand beraubte uns der Möglichkeit, wortweise nachzuschlagen. Jetzt müssen wir die Wortfolge in Fragmente beliebiger Länge aufteilen, und außerdem können unterschiedliche Partitionen zu unterschiedlichen Abkürzungen führen! Wir wollten nicht in die Tiefen der Linguistik eintauchen und künstliche Intelligenz schreiben, die einen spanischen Namen so abkürzt, wie ein lebender Spanier ihn abkürzen würde, also skizzierten wir Knut, einen kombinatorischen Overkill.



Und wie immer bei kombinatorischen Suchvorgängen platzte es bei einem der Benutzer und wir mussten dringend eine Begrenzung der maximalen Anzahl generierter Schreibweisen festlegen. Dies komplizierte den Code weiter, was für diese Aufgabe so unerwartet schwierig war.



Maschinelle Übersetzung von Waren



Aufgabe . Es ist notwendig, die Namen und Beschreibungen der von den Verkäufern bereitgestellten Waren in Englisch in die Sprache des Benutzers zu übersetzen.



Jeder hat wahrscheinlich Meme über die krumme Übersetzung der Namen chinesischer Waren gesehen. Wir haben sie auch gesehen, aber die gewünschte Markteinführungszeit ermöglichte es uns nicht, etwas Besseres zu finden, als eine vorhandene API für die Übersetzung zu verwenden.



Es ist einfach, einen HTTP-Client zu schreiben, ein Konto zu erstellen und die Waren, wenn sie an den Benutzer ausgegeben werden, einfach in die Gerätesprache zu übersetzen. Aber Übersetzungen sind nicht billig, und es wäre verschwenderisch, dasselbe beliebte Produkt für jede von Zehntausenden von Ansichten ins Russische zu übersetzen. Aus diesem Grund haben wir das Caching aktiviert: Für jedes Produkt haben wir Übersetzungen in der Datenbank gespeichert, und wenn dort Übersetzungen vorhanden waren, sind wir nicht mehr zum Übersetzer gegangen.



Das Einsparpotenzial war jedoch noch vorhanden. Wir haben beschlossen, dass ein vernünftiger Kompromiss zwischen Übersetzungsqualität und Preis darin besteht, Beschreibungen für Sätze zu übertreffen und zwischenzuspeichern. Schließlich werden in Produkten häufig dieselben Vorlagenphrasen gefunden, und es ist verschwenderisch, sie jedes Mal zu übersetzen. Auf diese Weise erschien eine weitere Abstraktionsebene in unserem Übersetzer - eine Ebene zwischen dem HTTP-Client und dem Cache, in der ganze Waren in verschiedenen Sprachen gespeichert werden, wodurch der Text in Fragmente zerlegt wird.



Nach dem Start hat uns natürlich die Qualität der Übersetzungen verfolgt, und wir dachten: Was ist, wenn wir einen teureren Übersetzer verwenden? Aber wird es gut für unsere spezifischen Texte sein? Sie können sie nicht mit dem Auge vergleichen, Sie müssen einen A / B-Test durchführen. Daher wurde in unserem Übersetzungscache zusätzlich zur Produkt-ID die Übersetzer-ID angezeigt, und wir haben begonnen, eine Übersetzung von der Übersetzer-ID anzufordern, je nachdem, in welcher A / B-Testgruppe sich der Benutzer befand.



Der liebe Übersetzer hat gute Leistungen erbracht, aber es war immer noch zu verschwenderisch, es auf allen Produkten auszuführen. Aber wir gingen in Länder, deren Landessprachen unser Hauptübersetzer so schlecht beherrschte, dass wir bereit waren, uns auf einen erfolgreichen Start vorzubereiten. Daher wurde die Logik der Auswahl eines Übersetzers komplizierter.



Dann haben wir festgestellt, dass einige Geschäfte auf der Plattform so gut sind und die Plattform für ihren Erfolg so verwurzelt ist, dass sie immer bereit ist, ihre Waren mit einem teureren Übersetzer zu übersetzen. Die Logik bei der Auswahl eines Übersetzers hing also vom Benutzer, dem Land und der Geschäfts-ID ab.



Und schließlich haben wir beschlossen, dass sich unser Hauptübersetzer im Laufe der Jahre, in denen Joom existiert, verbessern könnte. Vielleicht ist es sinnvoll, den Übersetzungscache in bestimmten Abständen zu aktualisieren. Aber was ist ohne einen A / B-Test? Das Frische-Feld erschien also in unserem Cache, und die Dinge wurden wieder kompliziert. Infolgedessen ist unsere Übersetzungskomponente unglaublich komplex, und dies trotz der Tatsache, dass wir noch nicht einmal eine hausgemachte Computerlinguistik in sie hineingeschraubt haben. Eine Weile.



Kleidergrößen umrechnen



Vielleicht ist eines der schmerzhaftesten Probleme beim Online-Kauf von Kleidung und Schuhen die Wahl der richtigen Größe. Und wenn Spieler wie Lamoda bei Lieferung aus lokalen Lagern einfach mehrere Größen gleichzeitig einbringen und das Ungeeignete mit der gleichen Leichtigkeit zurücknehmen können, funktioniert dies nicht grenzüberschreitend. Pakete dauern lange, die Kosten für jedes zusätzliche Kilogramm sind hoch und die Absender erwarten keinen großen Posteingang.



Darüber hinaus wird das Problem durch die Tatsache verschärft, dass Verkäufer aus verschiedenen Ländern möglicherweise völlig unterschiedliche Vorstellungen von Größen haben. Das chinesische M könnte sich leicht als russisches XS herausstellen, und das erschreckende 9XL unterscheidet sich möglicherweise nicht so stark vom XXL. Genähte Benutzer müssen sich auf Maße verlassen, aber auch diese sind nicht immer korrekt: Beispielsweise erwartet der Benutzer, dass der Brustumfang einer Person angezeigt wird, und der Verkäufer gibt die Maße der Kleidung selbst an - sie unterscheiden sich um fünf bis zehn Prozent . Wir möchten nicht, dass sich der Benutzer so sehr um das Einkaufen bei Joom!



Aufgabe . Zeigen Sie den Benutzern anstelle der von den Verkäufern angegebenen Größen die Größen, die wir anhand einer einzelnen Tabelle basierend auf dem Umfang berechnet haben.



Okay. Wir nehmen eine Größentabelle, die wir aus der Beschreibung des Produkts analysieren (dies wird von einem separaten Raumfahrzeug für 5k Linien durchgeführt) und in einem separaten Feld gespeichert werden, und ersetzen die darin enthaltenen Größen durch die berechneten. Codieren Sie die Tabelle für die Umrechnung des Umfangs in Größe im Internet fest und genießen Sie das Leben.



Wenn jedoch keine Tabelle vorhanden ist oder nicht genügend Zeilen enthalten sind, funktioniert dies nicht. Die Funktion ist für das Produkt implizit so oft deaktiviert.



Hmm, in der Tabelle zeigen die Umfänge des menschlichen Körpers und die meisten Verkäufer sie, indem sie an den Dingen selbst messen. Differenzkoeffizient einnähen. Produktmanager Rodion, der glückliche Besitzer des perfekten M-ki, geht ins Einkaufszentrum, misst eine Reihe verschiedener Dinge an sich selbst und kommt mit Koeffizienten - sie sind ähnlich, unterscheiden sich jedoch für verschiedene Warengruppen erheblich. Bei einem umlaufenden Rollkragenpullover beträgt der Unterschied fast 0% und bei einem Pullover alle 10%. Außerdem variiert die Passform der Oberbekleidung: schlanke Passform, normale Passform, lockere Passform, und dies ergibt einen Schwung von ± 5%. Jetzt besteht unser Koeffizient (von mir im Code als Rodion-Koeffizient verewigt ) aus zwei Faktoren.



Um die Landung zu bestimmen, erstellen wir einen weiteren Parser, der versucht, sie aus dem Namen oder der Beschreibung des Produkts zu extrahieren. Wenn das Produkt nicht in eine der von Rodion geprüften Kategorien fällt, ist die Funktion Nummer zwei implizit deaktiviert.



Der letzte Schliff: Viele Produkte listen die Büste von Achsel zu Achsel auf, was nur den halben Umfang bedeutet, was zu lächerlich kleinen Größen führt. Wir fügen die Logik hinzu, dass, wenn der Umfang kleiner als X ist, dies nicht sein kann, dies eindeutig die Hälfte des Umfangs ist, und wir multiplizieren ihn mit zwei. Es ist gut, dass sich Erwachsene normalerweise nicht um das Zweifache des Brustumfangs voneinander unterscheiden.



Jetzt ist alles so kompliziert, dass beim Testen einer Funktion nach Produkttyp im Admin-Bereich nicht nachvollziehbar ist, warum sie nicht eingeschaltet wurde oder auf die eine oder andere Weise funktioniert. Wir fügen dem Code eine große Logikebene hinzu und protokollieren detailliert die Gründe für das Deaktivieren der Konvertierung. Um die Ursache für das Herunterfahren eines bestimmten Produkts vollständig verfolgen zu können, müssen Sie Fehlermeldungen mehrmals nach oben weiterleiten und mit Details anreichern. Der Code wird erschreckend.



Und natürlich funktioniert alles je nach Gruppe des A / B-Tests unterschiedlich.



Fazit



Hüten Sie sich vor Danaans, Spendenentwicklern , die hinsichtlich der Fristen optimistisch sind. Es ist sehr schwierig, die Entwicklungszeit abzuschätzen, egal wie einfach die Aufgabe klingt, und bei jedem Schritt erwarten uns Überraschungen!



All Articles