니드포 스피드: 자바스크립트 코딩 챌린지 회고전

게시 됨: 2022-03-11

Toptal은 사람들을 컨퍼런스 부스로 끌어들이기 위한 방법으로 JavaScript 코딩 챌린지 웹 앱을 시작했습니다. 그것이 얼마나 성공적인지 확인한 우리는 웹에서 파일럿을 만들기로 결정했고 커뮤니티와 네트워크의 모든 사람에게 공개되었습니다.

첫 번째 질문 중 하나를 보여주는 Toptal의 JavaScript Speed ​​Coding Challenge 스크린샷. "double"이라는 함수가 제공되고 해당 본문은 "return x doubled"라는 주석으로 구성됩니다.

출시 당시 우리는 동기 부여된 개발자들이 전반적인 JavaScript 코딩 과제에서 높은 점수를 받을 수 있는 창의적인 방법을 생각해내도록 독려했습니다. 훌륭한 독립 프리랜서의 주요 성공 요인 중 일부는 틀에서 벗어나 생각하고 일련의 제약 조건 내에서 작업하는 창의적인 방법을 찾는 능력입니다.

JavaScript 코딩 챌린지 질문

Gauntlet은 인터뷰 질문으로 사용될 수 있는 것과 유사한 여러 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) };

우리는 초보자와 고급 개발자 모두가 재미를 느끼고 친구와 동료를 초대하여 최고 점수를 놓고 경쟁할 수 있기를 원했습니다. 질문은 점수별로 정렬되어 주니어 개발자도 점수를 얻을 수 있었습니다. 같은 양의 점수를 가진 질문의 순서는 임의적이어서 매번 경험이 약간 달랐지만 전체 질문 세트는 일주일 내내 동일하게 유지되었습니다.

3분이라는 제한 시간 내에 최대한 많은 작업을 완료하는 것이 목표였습니다. 누군가 JavaScript 코딩 챌린지에서 모든 작업을 완료하는 방법을 찾은 경우 남은 1초당 10점이 부여됩니다. 챌린지를 완료하기 위해 여러 번 시도할 수 있었습니다.

우리가 예상한 첫 번째 일은 사람들이 시간을 들여 여유롭게 질문을 풀고 웹 애플리케이션에 답을 복사하여 붙여넣는 것입니다.

챌린지를 시작한 지 1시간 40분이 지난 후 첫 번째 사람은 이 접근 방식을 따랐고 애플리케이션에서 허용한 최대 점수 1445점과 남은 1초마다 추가 점수를 받았습니다.

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)

한 가지 주목해야 할 점은 이번 버전의 스피드코딩 챌린지에서는 클라이언트 측에서만 코드가 테스트되었다는 것입니다. 이 때문에 코드 대신 테스트 케이스에 대한 답을 간단히 보낼 수 있었다. 이를 통해 최적화를 수행하고 클라이언트 측에서 몇 밀리초를 줄일 수 있었습니다.

3일 후, 경쟁이 본격적으로 가열되기 시작했으며 3시간 버킷당 시도 횟수가 갑자기 1,000개 미만에서 거의 100,000개로 줄어들었습니다.

3일 동안 점수는 동일하게 유지되었습니다. 어떤 사람들은 코드에 대한 미세 최적화를 작성하고 있었고 여러 사람들은 루프에서 솔루션을 제출하고 서버가 덜 혼잡해져서 몇 점 앞서 나갈 수 있기를 바랐습니다. 우리는 큰 놀라움을 받을 예정이었습니다.

JavaScript 코딩 챌린지 최대 점수 깨기

먼저, 간단한 계산을 해보겠습니다. 모든 작업을 완료하면 총 1445점이 주어지고 180초의 시간이 주어집니다. 지원서에 초당 10점을 부여하면 이론적으로 달성할 수 있는 최대 점수는 3245점이 됩니다(모든 답변을 즉시 제출하는 경우).

사용자 중 한 명이 6000점 이상의 점수를 얻었으며 시간이 지남에 따라 꾸준히 증가했습니다.

사람이 어떻게 그렇게 높은 점수를 얻을 수 있었습니까?

간단한 검토 후에 우리는 무슨 일이 일어났는지 알아냈습니다. 15년 이상의 경쟁력 있는 프로그래밍 경험을 가진 전문 풀스택 개발자이자 Toptaler인 Toptaler는 코딩 챌린지 설정에서 허점을 발견했습니다. 그는 서버를 느리게 만드는 여러 봇을 생성했습니다. 한편, 그는 가능한 한 여러 번 동일한 작업(가장 많은 점수를 받은 작업)을 완료하고 단일 항목에 점수를 할당하여 해당 항목의 점수에 지속적으로 추가할 수 있습니다.

이것은 우리가 창의적인 솔루션을 허용했기 때문에 규칙에 위배되지 않았습니다. 그러나 그가 사용하고 있는 특정 방법으로 인해 서버가 상당히 바쁘게 되어 다른 모든 사람의 네트워크 요청이 느려졌습니다. 우리가 가장 먼저 한 일은 서버 파워를 높이는 것이었습니다. 그 결과 그는 56,000점에서 70,000점으로 올라가 1위를 유지했습니다.

우리는 사람들이 챌린지와 상호 작용하는 방식에 개입하고 싶지 않았지만 이러한 시도로 인해 다른 사용자가 챌린지를 사용하기 어려울 정도로 서버 속도가 느려져 허점을 수정하기로 결정했습니다.

수정 사항으로 인해 JavaScript 코딩 챌린지 마지막 날 다른 사람들이 동일한 점수를 얻을 수 없었습니다. 이 때문에 우리는 상위 참가자에게 주어지는 상 수를 확장하기로 결정했습니다. 원래 1등 상품인 AirPods 한 쌍은 1등 참가자에게만 주어지는 것이었습니다. 결국 에어팟은 1위 6위를 차지한 자들에게 주어졌다.

겸손한 시작과 사나운 끝: 일부 JavaScript 코딩 도전 통계

우리의 최고 득점자조차도 시작하는 데 약간의 어려움을 겪었습니다. 실제로 5번 이상 시도한 모든 사람 중 첫 번째 시도의 최고 점수는 645점이었고 그 그룹의 첫 번째 시도 평균 점수는 25점에 불과했습니다.

대회가 끝나면 총 시도 횟수에서 시도 중인 전략을 추측할 수 있습니다. 일부는 다른 것보다 더 효율적이었지만 멀리 떨어져 있는 상위 참가자가 가장 높은 시도 횟수를 보였습니다.

상위 20명의 참가자의 최고 점수와 총 시도 횟수를 보여주는 결합된 로그 막대 그래프. 최고 시도 횟수는 최고 득점자에게 돌아가고, 2위, 3위, 4위 시도 횟수는 각각 13위, 9위 및 2위 참가자에게 돌아갔습니다. 4위와 6위를 포함한 상위 20위 중 정확히 절반이 1,000회 미만의 시도를 했습니다. 2명의 참가자는 100회 미만을 시도했고 다른 참가자는 10회 미만을 시도했습니다.

앞으로 나아가 다

미래는 무엇을 가지고 있습니까?

이것은 최초의 JS 코딩 챌린지 반복이었습니다. 우리는 앞으로 더 많은 JavaScript 프로그래밍 과제를 수행하고 이전 실행에서 교훈을 배우고 더 흥미롭게 만들고 싶습니다. 우리가 하고 싶은 첫 번째 일은 제출 수를 제한하기 위해 시도 조절을 구현하는 것입니다. 또한 JavaScript의 코딩 문제를 넘어 다양한 프로그래밍 언어에서 사용할 수 있도록 확장하고자 합니다.

마지막으로 JavaScript 코딩 챌린지를 보다 안전하게 만들기 위한 조치를 취하는 동안 향후 챌린지에 대해 봇 및 기타 창의적인 접근 방식의 사용을 계속 허용할 계획입니다.


챌린지와 이 기사에 기여한 Pavel Vydra, Anton Andriievskyi, Tiago Chilanti, Matei Copot, 그리고 콘테스트 앱의 기반이 된 오픈 소스 프로젝트에 대해 @Zirak에게 특별한 감사를 전합니다. 마찬가지로 콘테스트에 참여하고 실행한 모든 분들께 감사드립니다.