Жажда скорости: ретроспектива лучших вызовов JavaScript-кодирования

Опубликовано: 2022-03-11

Toptal запустила веб-приложение с вызовом JavaScript, чтобы привлечь людей к нашим кабинам для конференций. Увидев, насколько это было успешно, мы решили сделать пилотную версию в Интернете, открытую для всех в нашем сообществе и их сетях.

Скриншот Toptal JavaScript Speed ​​Coding Challenge, показывающий один из первых вопросов. Предоставляется функция с именем "double", а ее тело состоит из комментария "return x doubled".

Во время запуска мы поощряли мотивированных разработчиков придумывать творческие способы достижения высоких результатов в общей задаче кодирования JavaScript. Некоторые из ключевых факторов успеха великих независимых фрилансеров — это способность мыслить нестандартно и находить творческие способы работы в рамках набора ограничений.

Контрольные вопросы по программированию на JavaScript

Перчатка состояла из ряда вопросов по JavaScript — похожих на те, которые можно было бы использовать в качестве вопросов на собеседовании — начиная от действительно простых контрольных вопросов по JavaScript:

 box.double = function double (x) { //return x doubled };

…к более промежуточным:

 box.dateRank = function dateRank (x) { // x is a date in 2019 as string (example: "06/30/2019") // return the rank of the day in 2019 (ie, "09/01/2019" translates to 244) };

Мы хотели, чтобы как начинающие, так и продвинутые разработчики получили удовольствие и пригласили своих друзей и коллег посоревноваться с ними за самые высокие баллы. Вопросы были отсортированы по баллам, так что даже младшие разработчики могли набрать несколько баллов. Порядок вопросов, имеющих одинаковое количество баллов, был случайным, поэтому опыт каждый раз немного отличался, в то время как полный набор вопросов оставался неизменным в течение недели.

Цель состояла в том, чтобы выполнить как можно больше задач в течение трех минут. В случае, если кто-то находил способ выполнить все задачи в конкурсе JavaScript coding, за каждую оставшуюся секунду начислялось 10 баллов. Мы разрешили несколько попыток выполнить задание.

Первое, что мы ожидали, это то, что людям потребуется некоторое время, чтобы решить вопросы в неторопливом темпе, а затем скопировать и вставить ответы в веб-приложение.

Через час и 40 минут после запуска челленджа первый человек последовал этому подходу и получил 1445 максимальных баллов, разрешенных приложением, плюс несколько дополнительных баллов, которые мы начисляли за каждую оставшуюся секунду.

Поворотный момент в вызове JavaScript-кодирования

С подходом «копировать и вставить» лучшие участники ничего не выиграют, сосредоточившись на кодировании ответов самостоятельно. Вместо этого они сосредоточили свое внимание на увеличении скорости своих навыков автоматизации.

Самый простой подход на этом этапе состоял в том, чтобы написать код JavaScript, который решал бы каждую задачу, ожидая в цикле, пока кнопки не будут готовы, и копировать и вставлять его в консоль браузера:

 const solutions = { 'double': 'return x*2', 'numberToString': '...', 'square': '...', 'floatToInt': '...', 'isEven': '...', 'squareroot': '...', 'removeFirstFive': '...', // ... 'dateRank': '...', // ... }; const get_submit_button = () => document.querySelector('.task-buttons > .col > .btn'); const solve = () => { const ace_editor = ace.edit(document.querySelector('.ace_editor')) const submission = ace_editor.getValue() for (const key in solutions) { if (submission.includes('box.' + key + ' ')) { ace_editor.insert(solutions[key]) get_submit_button().click() setTimeout(() => { get_submit_button().click() setTimeout(() => { solve() }, 400) }, 900) return } } } solve()

Эту часть также можно автоматизировать с помощью обычных инструментов автоматизации, таких как Selenium. Но быстрее было бы автоматизировать использование API, отправляя решения задач:

 const request = require('request'); const runTask = (data, entryId, callback) => { const tests = data.nextTask.tests_json; const results = Object.fromEntries( Object.entries(tests).map(([key, value]) => [key, value.result]) ); request.post(`https://speedcoding.toptal.com/webappApi/entry/${entryId}/attemptTask`, { form: { attempt_id: data.attemptId, tests_json: JSON.stringify(results), }, }, (error, res, body) => { if (error) throw error; const next = JSON.parse(body).data if (next.isChallengeEntryFinished) { callback(next) return } runTask(next, entryId, callback) }); } const runEntry = (callback) => { request.post('https://speedcoding.toptal.com/webappApi/entry', { form: { challengeSlug: 'toptal-speedcoding', email: ..., leaderboardName: ..., isConfirmedToBeContacted: ..., dateStop: ... }, }, (error, res, body) => { if (error) throw error; const { data } = JSON.parse(body); const entryId = data.entry.id runTask(data, entryId, callback) }); } runEntry(console.log)

Следует отметить, что в этой версии задания на скоростное кодирование код тестировался только на стороне клиента. Из-за этого можно было просто рассылать ответы на тестовые случаи вместо кода. Это позволило оптимизировать и сократить несколько миллисекунд на стороне клиента.

Через три дня конкуренция действительно начала накаляться, и количество попыток за трехчасовое ведро внезапно увеличилось с менее 1000 до почти 100 000.

В течение трех дней результаты оставались прежними. Некоторые люди писали микрооптимизации для своего кода, а некоторые отправляли свои решения в цикле, надеясь, что сервер станет менее загруженным, и они смогут получить несколько очков вперед. Нас ждал большой сюрприз.

Преодолеть максимальный балл конкурса JavaScript Coding Challenge

Во-первых, давайте быстро посчитаем: у нас было в общей сложности 1445 очков, присужденных за выполнение всех задач, и запас времени 180 секунд. Если мы будем присуждать 10 баллов за каждую секунду, оставшуюся в приложении, максимальное теоретическое достижимое количество баллов будет 3245 — в случае мгновенной отправки всех ответов.

Один из наших пользователей набрал более 6000 баллов, и эта цифра со временем неуклонно росла.

Как кто-то мог получить такой высокий балл?

После краткого обзора мы выяснили, что происходит. Наш лучший участник, профессиональный разработчик полного стека и Toptaler с более чем 15-летним опытом соревновательного программирования, нашел лазейку в настройке задачи кодирования. Он породил множество ботов, которые замедляли работу сервера; тем временем он мог выполнить одно и то же задание (то, за которое было начислено наибольшее количество баллов) столько раз, сколько возможно, и присвоить их баллы одной записи, постоянно добавляя баллы к этой записи.

Это не противоречило правилам, поскольку мы допускали творческие решения; однако конкретный метод, который он использовал, значительно увеличил нагрузку на сервер, что замедлило сетевые запросы для всех остальных. Первое, что мы сделали, это увеличили мощность нашего сервера, в результате чего он поднялся с 56 000 до 70 000 баллов и остался на первом месте.

Хотя мы не хотели вмешиваться в то, как люди взаимодействовали с вызовом, эти попытки замедляли работу сервера до такой степени, что другим пользователям было трудно использовать вызов, поэтому мы решили исправить лазейку.

Это исправление сделало невозможным для других людей набрать такой же балл в последний день испытания по кодированию JavaScript. В связи с этим мы решили увеличить количество наград, присуждаемых лучшим участникам. Изначально главный приз — пара наушников AirPods — должен был достаться только одному лучшему участнику. В итоге AirPods получили те, кто занял первые шесть мест.

Скромное начало и свирепый конец: немного статистики JavaScript-вызовов

Даже нашим лучшим бомбардирам было трудно начинать. На самом деле, из всех людей, которые сделали пять или более попыток, наивысший балл за первую попытку был 645, а средний балл за первые попытки в этой группе составил всего 25 баллов.

К концу конкурса по общему количеству попыток можно было угадать используемую стратегию. В то время как некоторые были более эффективны, чем другие, лучший участник, безусловно, имел наибольшее количество попыток.

Комбинированная логарифмическая гистограмма, показывающая наивысшие баллы 20 лучших участников и общее количество попыток. В то время как наибольшее количество попыток досталось лучшему бомбардиру, второе, третье и четвертое по количеству попыток досталось участникам, занявшим 13, девятое и второе места соответственно. Ровно половина из 20 лучших, включая участников, занявших четвертое и шестое места, сделали менее 1000 попыток. У двух участников было меньше 100 попыток, а у одного даже меньше 10.

Двигаться вперед

Что нас ждет в будущем?

Это была только первая итерация вызова JS-кодирования. Мы хотим решить еще много задач по программированию на JavaScript в будущем, извлечь уроки из предыдущих запусков и сделать их еще более захватывающими. Первое, что мы хотим сделать, это реализовать регулирование количества попыток, чтобы ограничить количество отправлений. Мы также хотим выйти за рамки задач кодирования в JavaScript, сделав их доступными для широкого спектра языков программирования.

Наконец, пока мы принимаем меры, чтобы сделать вызов JavaScript более безопасным, мы планируем разрешить использование ботов и других творческих подходов для будущих задач.


Особая благодарность Павлу Выдре, Антону Андриевскому, Тьяго Чиланти и Матею Копоту за их вклад в задачу и эту статью, а также @Zirak за проект с открытым исходным кодом, который лег в основу конкурсного приложения. Кроме того, спасибо всем, кто участвовал и руководил конкурсом.