Comment Yandex m'a appris à interviewer des programmeurs

Après avoir exposé mon histoire d '«emploi» sur Yandex dans le commentaire de la note sensationnelle «Comment j'ai travaillé pendant 3 mois dans Y. Market and quit», il serait injuste de cacher l'avantage que j'ai retiré de mon expérience avec Yandex.Message.

Mes responsabilités professionnelles incluent des entretiens techniques avec des candidats pour le poste de développeur Fullstack JavaScript / TypeScript, j'ai été activement impliqué dans cette entreprise (cela vaut-il la peine de dire que j'en ai un peu marre?) Depuis plus d'un an, j'ai plus de 30 entretiens techniques.

Plus tôt dans une interview technique, j'ai posé au candidat des questions plutôt stupides comme «qu'est-ce qu'une fermeture», «comment l'héritage est-il implémenté en JavaScript», «voici un tel tableau dans une base de données avec de tels indices, dites-nous comment vous pouvez accélérer tel ou tel demande », qui, bien qu’elles aient aidé à identifier les capacités d’ingénierie du candidat, ne lui ont pas permis de conclure à quel point une personne peut résoudre des problèmes et à quelle vitesse elle peut comprendre un code existant. Ce qui ne pouvait que conduire à de tristes conséquences ...

Mais tout a changé après avoir subi quatre séries d'entretiens techniques chez Yandex.

En règle générale, les pierres volent dans le jardin des enquêteurs de Yandex pour:

1. Tâches qui n'ont pas de valeur pratique;
2. La nécessité de résoudre ces problèmes sur des morceaux de papier avec un crayon ou sur un tableau noir.

Nous sommes déjà en 2019 et il est temps de lancer une ligne de production distincte pour couler des chaudières en enfer pour ceux qui forcent les gens à écrire du texte à la main, sans parler du code. Chaque personne écrit de différentes manières, et en préparant le texte de cette note, j'ai dû réécrire, par exemple, ce paragraphe en particulier six fois - si j'écrivais des notes pour Habr sur papier, je n'écrirais pas de notes pour Habr.

Mais je ne suis pas d'accord avec la thèse sur la futilité pratique des tâches Yandex. Même le développement de routine est non, non, mais il vous fixera une tâche qui a plusieurs solutions. Vous n'avez pas besoin d'y penser longtemps, mais ce n'est pas optimal en termes de taille de code, de performances ou d'expressivité. L'autre est exactement le contraire, mais il requiert du programmeur une certaine expérience dans la construction d'algorithmes efficaces et compréhensibles. Voici un exemple tiré d'une interview:

/*    getRanges,    : */ getRanges([0, 1, 2, 3, 4, 7, 8, 10]) // "0-4,7-8,10" getRanges([4,7,10]) // "4,7,10" getRanges([2, 3, 8, 9]) // "2-3,8-9" 

Sur ce problème, j'ai sérieusement émoussé, et décidé que ce n'était pas la plus belle façon. La solution, malheureusement, n'a pas été conservée, je vais donc donner une solution à l'un de nos candidats:

 function getRanges(arr: number[]) { return arr.map((v, k) => { if (v - 1 === arr[k - 1]) { if (arr[k + 1] === v + 1) { return '' } else { return `-${v},` } } else { return v + ',' } }).join('').split(',-').join('-') } 

Parmi les inconvénients: accès au tableau à un index inexistant et manipulation de chaîne laide: join-split-join. Et cette solution est également erronée, car avec l'exemple getRanges ([1, 2, 3, 5, 6, 8]) «1-3,5-6,8» est renvoyé, et pour «tuer» la virgule à la fin, vous avez besoin pour augmenter encore les conditions en compliquant la logique et en réduisant la lisibilité.

Voici une solution de style Yandex:
 const getRanges = arr => arr .reduceRight((r, e) => r.length ? (r[0][0] === e + 1 ? r[0].unshift(e) : r.unshift([e])) && r : [[e]], []) .map(a => a.join('-')).join(',') 

Google aidera-t-il à écrire des solutions aussi élégantes? Pour produire un tel code, vous avez besoin de deux composants: une expérience avec de nombreux algorithmes et une excellente connaissance du langage. Et c'est exactement ce que les recruteurs de Yandex mettent en garde: ils vous poseront des questions sur les algorithmes et le langage. Yandex préfère embaucher des développeurs capables d'écrire du code sympa. Ces programmeurs sont efficaces, mais surtout interchangeables: ils écriront sur les mêmes solutions. Les développeurs moins théoriquement avertis sur une tâche sont en mesure de proposer des dizaines de solutions diverses, parfois tout simplement incroyables: l'un des candidats à notre vacance a bouclé une telle béquille que mes yeux ont grimpé.

UPD: comme l'a noté l'utilisateur MaxVetrov , ma solution est incorrecte:

 getRanges([1,2,3,4,6,7]) // 1-2-3-4,6-7 

Ainsi, je n'ai moi-même pas été en mesure de résoudre correctement ce problème jusqu'à présent.

UPD2: En général, les commentaires m'ont convaincu que ce code s'est avéré être mauvais, même s'il fonctionnait correctement.

Ce n'est pas en vain que j'ai passé du temps à traduire du papier dans le bureau Yandex, car pendant cette leçon j'ai compris comment devenir moi-même un intervieweur efficace. J'ai pris l'idée et le format de leurs tâches comme base, mais:

  • Je n'ai pas proposé d'écrire du code sur papier, mais j'ai demandé d'écrire dans code.yandex-team.ru . Il s'agit d'un éditeur de code en ligne multi-utilisateurs sans possibilité d'exécution. Il y a peut-être une autre option, plus pratique, mais il y a eu une perquisition et il y a eu de la paresse;
  • Il n'avait pas besoin d'une solution idéale, mais a demandé de la résoudre d'une manière ou d'une autre;
  • Il n'exigeait pas la connaissance de la langue par cœur, la fonction ou la méthode souhaitée pouvait être demandée;
  • Il a réduit le temps d'un entretien technique à 30 minutes.

Un des objectifs de notre entretien technique:

 let n = 0 while (++n < 5) { setTimeout(() => console.log(n), 10 + n) } //     ? 

Je pense que c'est un test très important pour le développeur JavaScript. Et le point ici n'est pas la fermeture et la compréhension des différences entre pré-incrément et post-incrément, mais que, pour une raison inexplicable, un quart des personnes interrogées pensent que console.log s'exécutera avant la fin du cycle. Je n'exagère pas. Ces personnes ont un curriculum vitae et une expérience de travail d'au moins deux ans et ont réussi à résoudre d'autres tâches qui n'étaient pas liées aux rappels. Soit il s'agit d'une nouvelle génération de développeurs JavaScript qui ont grandi sur async / wait, qui ont entendu autre chose sur Promise, mais des rappels pour eux - comme un téléphone à disque pour un adolescent moderne - composeront le numéro, même si ce n'est pas la première fois, mais ne comprendront pas comment cela fonctionne et pourquoi.

Cette tâche se poursuit: vous devez ajouter le code afin que console.log s'exécute également dans setTimeout, mais les valeurs 1, 2, 3, 4 s'affichent dans la console. Le dicton «live, learn» est approprié ici, car une fois l'une des personnes interrogées a proposé une telle solution:

 setTimeout(n => console.log(n), 10 + n, n) 

Et puis j'ai découvert que setTimeout et setInterval transmettent le troisième argument et les suivants au rappel. C'est dommage, oui. Soit dit en passant, les connaissances se sont avérées utiles: j'ai utilisé cette fonctionnalité plus d'une fois.

Mais j'ai emprunté cette tâche à Yandex telle qu'elle est:

 /*    fetchUrl,     .  fetchUrl     fetch,    Promise      reject */ fetchUrl('https://google/com') .then(...) .catch(...) // atch     5       fetchUrl 

Voici les compétences testées avec Promise. Habituellement, je demande de résoudre ce problème sur de pures promesses, puis en utilisant async / wait. Avec async / wait, la solution est intuitivement simple:

 function async fetchUrl(url) { for (let n = 0; n < 5; n++) { try { return await fetch(url) } catch (err) { } } throw new Error('Fetch failed after 5 attempts') } 

Vous pouvez également appliquer le dicton «vivre, apprendre» à cette décision, mais en ce qui concerne mon intervieweur Yandex: il n'a pas précisé que l'async / attente peut / ne peut pas être utilisé, et quand j'ai écrit cette solution, il a été surpris: «Je n'ai pas travaillé avec async / wait, je ne pensais pas que cela pourrait être résolu aussi facilement. " Il s'attendait probablement à voir quelque chose comme ça:

 function fetchUrl(url, attempt = 5) { return Promise.resolve() .then(() => fetch(url)) .catch(() => attempt-- ? fetchUrl(url, attempt) : Promise.reject('Fetch failed after 5 attempts')) }'error' 

Cet exemple est capable de montrer à quel point une personne comprend les promesses, cela est particulièrement important sur le dos. Une fois que j'ai vu le code JavaScript d'un développeur qui ne comprenait pas pleinement les promesses, j'ai préparé une promesse pour la transaction suivante comme suit:

 const transaction = Promise.resolve() for (const user of users) { transaction.then(() => { return some_action... }) } 

Et se demandant pourquoi un seul utilisateur apparaît dans sa transaction. On pourrait utiliser Promise.all, mais on pourrait savoir que Promise.prototype.then n'ajoute pas d'autre rappel, mais crée une nouvelle promesse et ce sera comme ceci:

 let transaction = Promise.resolve() for (const user of users) { transaction = transaction.then(() => { await perform_some_operation... return some_action... }) } 

Ce cas m'a fait penser à compliquer la tâche de comprendre les promesses, mais le candidat qui a refusé de résoudre les problèmes m'a aidé à formuler une nouvelle tâche, les a appelés littéralement de la merde et a dit qu'il avait l'habitude de travailler avec du vrai code, pour lequel j'ai fouillé pendant quelques minutes dans le code source de l'un de nos projets, lui a donné le vrai code:

 public async addTicket(data: IAddTicketData): Promise<number> { const user = data.fromEmail ? await this.getUserByEmail(data.fromEmail) : undefined let category = data.category if (category === 'INCIDENT' && await this.isCategorizableType(data.type)) { category = 'INC_RFC' } const xml = await this.twig.render('Assyst/Views/add.twig', { from: data.fromEmail, text: data.text, category, user, }) const response = await this.query('events', 'post', xml) return new Promise((resolve, reject) => { xml2js.parseString(response, (err, result) => { if (err) { return reject(new Error(err.message)) } if (result.exception) { return reject(new Error(result.exception.message)) } resolve(result.event.id - 5000000) }) }) } 

Et a demandé de se débarrasser des mots clés asynchrones / attendre. Depuis lors, cette tâche a été la première et, dans la moitié des cas, la dernière lors de l'entretien - il est souvent très débordé.

Je n'ai moi-même jamais résolu ce problème avant d'écrire cette note et de le faire la première fois pour la troisième fois (la première est trop longue, et dans la seconde je n'ai pas remarqué une attente):



Quelle conclusion peut-on tirer de tout cela? Les entretiens sont intéressants et utiles ... Bien sûr, si vous ne cherchez pas d'urgence du travail.

En fin de compte, je vais vous donner une autre tâche de l'histoire avec Yandex, je ne l'ai montré à personne * pour l' instant, j'ai pris soin de ce qu'on appelle un cas spécial. Il y a un ensemble de bannières, chaque bannière a un «poids», qui indique la fréquence à laquelle la bannière sera affichée par rapport aux autres bannières:

 const banners = [ { name: 'banner 1', weight: 1 }, { name: 'banner 2', weight: 1 }, { name: 'banner 3', weight: 1 }, { name: 'banner 4', weight: 1 }, { name: 'banner 5', weight: 3 }, { name: 'banner 6', weight: 2 }, { name: 'banner 7', weight: 2 }, { name: 'banner 8', weight: 2 }, { name: 'banner 9', weight: 4 }, { name: 'banner 10', weight: 1 }, ] 

Par exemple, s'il y a trois bannières avec les poids 1, 1, 2, leur poids combiné est 4 et le poids du troisième est 2/4 du poids total, il devrait donc être affiché dans 50% des cas. Il est nécessaire d'implémenter la fonction getBanner, qui au hasard, mais en tenant compte des poids, renvoie une bannière pour l'affichage. La solution peut être vérifiée dans cet extrait de code , où la distribution attendue et réelle est affichée.

UPD: ils ont commencé non seulement à réduire l'article lui-même, mais aussi à brûler le karma à pas de géant et je l'ai caché dans des documents cachés, ce qui est laid par rapport aux commentateurs. Je corrige ce mudachisme de ma part.

Source: https://habr.com/ru/post/fr470407/


All Articles