Hallo! Mein Name ist Dmitry Andriyanov, ich arbeite als Schnittstellenentwickler in Yandex. Letztes Jahr habe ich an der Vorbereitung unseres Online-Frontend-Wettbewerbs teilgenommen.

Vor ein paar Tagen erhielt ich einen Brief von den Organisatoren, in dem ich gefragt wurde, ob ich wieder teilnehmen möchte - um Frontend-Aufgaben für die zweite Programmiermeisterschaft zu entwickeln . Ich stimmte zu - und fand es ein interessantes Thema für den Artikel. Kaffee einfüllen, zurücklehnen. Ich werde Ihnen erzählen, wie wir die Aufgaben vor einem Jahr vorbereitet haben.
Wir waren ungefähr zehn, fast alle waren Front-End-Entwickler aus verschiedenen Yandex-Diensten. Wir mussten eine Auswahl von Aufgaben treffen, die durch Autotests überprüft wurden.
Für Programmierwettbewerbe gibt es einen speziellen Service - Yandex.Contest . Dort können Sie Aufgaben veröffentlichen und die Teilnehmer registrieren und lösen. Das Testen von Aufgaben erfolgt automatisch, die Ergebnisse der Teilnehmer werden in einer speziellen Tabelle veröffentlicht. Damit war die Infrastruktur bereits fertig. Alles, was benötigt wurde, war, Aufgaben zu entwickeln. Es stellte sich jedoch heraus, dass es eine Einschränkung gibt. Zuvor veranstaltete Yandex Wettbewerbe zu Algorithmen, maschinellem Lernen und anderen Themen, jedoch nie zu Front-End-Wettbewerben. Niemand hatte ein Verständnis dafür, woraus der Wettbewerb bestehen sollte und wie die Überprüfung automatisiert werden kann.

Wir haben entschieden, dass für Front-End-Entwickler Aufgaben geeignet sind, die Layout, JavaScript und Kenntnisse der Browser-API erfordern. Das Layout kann durch Vergleichen von Screenshots überprüft werden. Algorithmische Aufgaben können in Node.js ausgeführt und durch Vergleichen des Ergebnisses mit der richtigen Antwort überprüft werden. Programme, die mit der Browser-API arbeiten, können über Puppeteer gestartet werden, und das Skript kann den Status der Seite nach der Ausführung überprüfen.
Die Wettbewerbe bestehen aus zwei Runden - Qualifikation und Finale, mit 6 Aufgaben in jeder Runde. Qualifikationsaufgaben müssen variiert werden, damit verschiedene Teilnehmer unterschiedliche Optionen erhalten. Wir haben die Anzahl und Art der Aufgaben für jede Runde ausgewählt, in Teams von zwei Personen aufgeteilt und Aufgaben auf Teams verteilt. Jede Gruppe musste sich zwei Variationsprobleme für die Qualifikation und zwei nicht-Variationsaufgaben für das Finale einfallen lassen.

Klicken wir auf die DOM-Elemente ...
Die Idee kam auf - ein Browsergame, in dem Sie auf die DOM-Elemente klicken müssen, als eine der variablen Aufgaben zu geben. Die Aufgabe des Teilnehmers bestand darin, ein Programm zu schreiben, das dieses Spiel spielt und gewinnt. Erfunden 4 Optionen:
Wenn Sie möchten, können Sie den Links folgen und spielen. Wenn Sie "Telefon" oder "Klavier" spielen, vergessen Sie nicht, den Ton einzuschalten.
Schrieb einen gemeinsamen Teil für alle Optionen. Es enthielt die Logik zum Anzeigen anklickbarer Elemente sowie Elemente mit Informationen zum Klicken (Notizen, handgeschriebene Zahlen, Karten mit Bildern und Farben). Informationssätze und anklickbare Elemente werden über Parameter festgelegt.
Das Aussehen wurde durch CSS kontrolliert. Es stellte sich heraus, dass es csszengarden.com sehr ähnlich war - ein Layout mit unterschiedlichen Stilen sieht anders aus.




Das Ergebnis des Teilnehmerprogramms ist ein Protokoll der Klicks auf Elemente. Es wurde ein Handler hinzugefügt, der Informationen zu angeklickten Elementen in eine globale Variable schreibt. Damit der Teilnehmer anstelle ehrlicher Klicks das Ergebnis nicht sofort in diese Variable schreiben konnte, geben wir seinen Namen außen weiter.
function initGame(targetClasses, keyClasses, resultName) {
Das Skript zum Ausführen des Teilnehmerprogramms war ungefähr so:
Sound hinzufügen
Wir haben beschlossen, das Spiel mit dem Telefon ein wenig wiederzubeleben und den Klang von Tastenanschlägen hinzuzufügen. Solche Sounds werden DTMF-Töne genannt . Ich habe einen Artikel darüber gefunden, wie man sie generiert. Kurz gesagt, es ist notwendig, zwei Sounds gleichzeitig mit unterschiedlichen Frequenzen abzuspielen. Sounds einer bestimmten Frequenz können mit der Web Audio API abgespielt werden. Das Ergebnis ist ungefähr so wie dieser Code:
function playSound(num) {
Zum Klavierspielen wurden auch Sounds hinzugefügt. Wenn einer der Teilnehmer versucht hätte, die auf der Seite geschriebenen Noten zu spielen, hätte er den imperialen Marsch von Star Wars gehört.

Lassen Sie uns die Aufgabe komplizieren
Wir freuten uns über die coole Aufgabe mit den Sounds, die wir gemacht haben, aber die Freude hielt nicht lange an. Während des Testens des Spiels stellte sich heraus, dass das Programm sehr schnell auf die Schaltflächen klickt und alle unsere coolen Sounds zu einem gemeinsamen Durcheinander verschmelzen. Wir haben beschlossen, zwischen den Tastenanschlägen eine Verzögerung von 50 ms hinzuzufügen, damit die Sounds nacheinander abgespielt werden. Gleichzeitig erschwerte dies die Aufgabe ein wenig.
function initGame(targetClasses, keyClasses, resultName) {
Das ist aber noch nicht alles. Wir dachten, dass die Teilnehmer den Quellcode leicht sehen und die Verzögerung sofort sehen könnten. Um ihre Aufgabe zu verkomplizieren, haben wir den gesamten JS-Code auf der Seite mit UglifyJS minimiert . Diese Bibliothek ändert jedoch nicht die öffentliche API von Klassen. Daher haben wir die Teile, die UglifyJS gleich gelassen hat (nämlich die Namen der Methoden und Klassenfelder), durch replace
.
Das Skript zur Verschleierung des Spiels sah ungefähr so aus:
const minified = uglifyjs.minify(lines.join('\n')); const replaced = minified.code .replaceAll('this.window', 'this.') .replaceAll('this.document', 'this.') .replaceAll('this.log', 'this.') .replaceAll('this.lastClick', 'this.') .replaceAll('this.target', 'this.') .replaceAll('this.resName', 'this.') .replaceAll('this.audioContext', 'this.') .replaceAll('this.keyCount', 'this.') .replaceAll('this.classMap', 'this.') .replaceAll('_createDiv', '_') .replaceAll('_renderTarget', '_') .replaceAll('_renderKeys', '_') .replaceAll('_updateLog', '_') .replaceAll('_generateAnswer', '') .replaceAll('_createKeyElement', '') .replaceAll('_getMessage', '') .replaceAll('_next', '_____') .replaceAll('_pos', '__') .replaceAll('PhoneGame', '') .replaceAll('MusicGame', '') .replaceAll('BaseGame', 'xyz');
Schreiben wir eine kreative Bedingung
Wir haben den technischen Teil des Spiels vorbereitet, aber wir brauchten einen kreativen Text der Bedingung - nicht nur mit den Anforderungen, die erfüllt werden müssen, sondern mit einer Art Geschichte.
Meine Lieblingsart von Humor ist Absurdität. Dies ist, wenn Sie mit einem ernsten Blick einen lächerlichen Unsinn sagen. Unsinn klingt normalerweise unerwartet und verursacht Lachen. Ich wollte die Bedingungen der Aufgaben absurd machen, um den Teilnehmern zu gefallen. Es gab also eine Geschichte über Adolfs Pferd, das keinen Freund anrufen kann, weil er seine großen Hufe nicht auf die Tasten des Telefons bekommt.

Dann gab es eine Geschichte über ein Mädchen, das sich mit Klavier beschäftigt und es automatisieren möchte, so dass sie anstelle des Unterrichts spazieren geht. Es gab den Satz "Wenn ein Mädchen aufhört zu spielen, kommt Mama aus dem Raum und gibt einen Schlag ins Gesicht." Uns wurde gesagt, dass dies Propaganda des Kindesmissbrauchs ist und wir einen weiteren Text schreiben müssen. Dann kamen wir auf eine Geschichte über ein Orchester, in dem ein Pianist vor einem Konzert krank wurde, und einer der Musiker schrieb ein Programm über JS, das seine Rolle spielen würde.
Im Allgemeinen ist es uns gelungen, die gewünschte Wirkung der Texte zu erzielen. Wenn Sie möchten, können Sie sie hier lesen .
Aufgaben im Wettbewerb festlegen
Wir hatten also Aufgabenbedingungen, Skripte zum Überprüfen von Lösungen und Referenzlösungen parat. Dann war es notwendig, all dies im Wettbewerb zu konfigurieren. Für jede Aufgabe gibt es mehrere Tests, von denen jeder einen Satz von Eingabedaten und die richtige Antwort enthält. Das folgende Diagramm zeigt die Phasen des Wettbewerbs. Die erste Stufe ist die Ausführung des Programms, die zweite die Überprüfung des Ergebnisses:

Bei der Eingabe der ersten Stufe werden ein Satz von Testdaten und ein Teilnehmerprogramm empfangen. Im Inneren funktioniert das Skript run.js, dessen Code wir oben geschrieben haben. Er ist dafür verantwortlich, das Programm des Teilnehmers auszuführen, das Ergebnis seiner Arbeit zu empfangen und in eine Datei zu schreiben. Das Programm wird in einer separaten virtuellen Maschine ausgeführt, die sich vor der Ausführung aus dem Docker-Image erhebt. Diese virtuelle Maschine ist in ihren Ressourcen begrenzt und hat keinen Zugriff auf das Netzwerk.
Die zweite Stufe (Überprüfung des Ergebnisses) wird in einer anderen virtuellen Maschine durchgeführt. Somit hat das Programm des Teilnehmers keinen physischen Zugriff auf die Umgebung, in der die Überprüfung stattfindet. Die Eingabe der zweiten Stufe ist das Ergebnis der Arbeit des Teilnehmerprogramms (erhalten in der ersten Stufe) und der Datei mit der richtigen Antwort. Die Ausgabe ist der Exit-Code des Verifizierungsskripts, nach dem der Wettbewerb versteht, wie die Überprüfung beendet wurde:
- OK
= 0,
- PE
(Präsentationsfehler - falsches Ergebnisformat) = 4
- WA
(falsche Antwort) = 5
- CF
(Fehler bei der Überprüfung) = 6
Der Wettbewerb war schlecht an Aufgaben im Frontend angepasst, einschließlich Node.js. Wir haben das Problem gelöst, indem wir die Validierungsskripte mit pkg zusammen mit Node.js und node_modules in eine Binärdatei gepackt haben. Jetzt haben wir geheimes Wissen über den Wettbewerb und haben viel weniger Schwierigkeiten bei der Vorbereitung der aktuellen Meisterschaft.
Also haben wir die Aufgaben vorbereitet. Danach gab es noch viel mehr: öffentliche Tests zur Kalibrierung der Komplexität, Veröffentlichung von Aufgaben, technische Unterstützung während des Wettbewerbs und Vergabe der Gewinner im Yandex-Büro. Aber das sind ganz andere Geschichten.
Anstatt in bestimmten Bereichen zu konkurrieren, veranstalten wir jetzt einheitliche Programmiermeisterschaften, bei denen es einfach parallele Strecken gibt, einschließlich des Frontends.
Ich bereue kein bisschen die Zeit, die für die Vorbereitung von Aufgaben aufgewendet wurde. Es war interessant und lustig, unkonventionell. In einem Kommentar zu Habré schrieb er, dass die Bedingungen von Enthusiasten des Geschäfts ausgedacht wurden. Während des Wettbewerbs war es cool zu erkennen, dass die Teilnehmer die Aufgaben lösen, die Sie sich ausgedacht haben.
Referenzen:
- Analyse der Frontend-Zuweisung des letzten Jahres, die wir vorbereitet haben
- Analyse der Strecke am Frontend in der ersten Meisterschaft dieses Jahres