Como o Yandex me ensinou como entrevistar programadores

Depois de apresentar minha história de "emprego" no Yandex no comentário da sensacional nota "Como trabalhei por 3 meses no Y. Market e me demiti", seria injusto ocultar o benefício que tirei da minha experiência no Yandex.Message.

Minhas responsabilidades no trabalho incluem entrevistas técnicas de candidatos ao cargo de desenvolvedor Fullstack JavaScript / TypeScript, participei ativamente desse negócio (vale a pena dizer que estou um pouco cansado?) Por mais de um ano, tenho mais de 30 entrevistas técnicas.

Anteriormente, em uma entrevista técnica, perguntei ao candidato perguntas bastante estúpidas, como “o que é um fechamento”, “como a herança é implementada em JavaScript”, “existe uma tabela no banco de dados com esses índices, por favor, diga-nos como você pode acelerar tal e tal request ", que, apesar de terem ajudado a identificar as habilidades de engenharia do candidato, não permitiu concluir como uma pessoa pode resolver problemas e com que rapidez ele pode descobrir um código existente. O que não poderia deixar de levar a conseqüências tristes ...

Mas tudo mudou depois que passei por quatro rodadas de entrevistas técnicas na Yandex.

Normalmente, as pedras voam para o jardim dos entrevistadores da Yandex para:

1. Tarefas que não têm valor prático;
2. A necessidade de resolver esses problemas em pedaços de papel com um lápis ou em um quadro negro.

Já é 2019 e é hora de lançar uma linha de produção separada para fundir caldeiras no inferno para aqueles que forçam as pessoas a escreverem texto à mão, sem mencionar o código. Cada pessoa escreve de maneiras diferentes e, ao preparar o texto para esta nota, tive que reescrever, por exemplo, este parágrafo em particular seis vezes - se escrevesse notas para Habr no papel, não escreveria notas para Habr.

Mas não concordo com a tese sobre a futilidade prática das tarefas Yandex. Mesmo o desenvolvimento rotineiro é não, não, mas ele definirá uma tarefa que possui várias soluções. Você não precisa pensar em um por um longo tempo, mas não é o ideal em termos de tamanho, desempenho ou expressividade do código. O outro é exatamente o oposto, mas requer ao programador alguma experiência na construção de algoritmos eficientes e compreensíveis. Aqui está um exemplo de uma entrevista:

/*    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" 

Sobre esse problema, fiquei seriamente embotado e decidi que não é o caminho mais bonito. Infelizmente, a solução não foi preservada, portanto darei uma solução a um de nossos candidatos:

 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('-') } 

Das desvantagens: acessar a matriz em um índice inexistente e manipulação feia de strings: join-split-join. E essa solução também está errada, porque com o exemplo getRanges ([1, 2, 3, 5, 6, 8]) "1-3,5-6,8" é retornado e, para "matar" a vírgula no final, você precisa aumentar ainda mais as condições, complicando a lógica e reduzindo a legibilidade.

Aqui está uma solução no estilo 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(',') 

O Google ajudará a escrever soluções tão elegantes? Para produzir esse código, você precisa de dois componentes: experiência com muitos algoritmos e excelente conhecimento da linguagem. E é exatamente isso que os recrutadores da Yandex alertam: eles perguntam sobre algoritmos e linguagem. A Yandex prefere contratar desenvolvedores que possam escrever códigos interessantes. Esses programadores são eficazes, mas, mais importante, intercambiáveis: eles escreverão sobre as mesmas soluções. Desenvolvedores menos teóricos em uma tarefa são capazes de fornecer dezenas de soluções diversas, às vezes simplesmente surpreendentes: um dos candidatos à nossa vaga embrulhou uma muleta que meus olhos encontraram na testa.

UPD: como o usuário MaxVetrov observou , minha solução está incorreta:

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

Portanto, eu mesmo não consegui resolver adequadamente esse problema até agora.

UPD2: Em geral, os comentários me convenceram de que esse código era ruim, mesmo que funcionasse corretamente.

Não foi em vão que passei algum tempo traduzindo artigos no escritório da Yandex, porque durante esta lição eu entendi como me tornar um entrevistador eficaz. Tomei a ideia e o formato de suas tarefas como base, mas:

  • Não me ofereci para escrever código em papel, mas pedi para escrever em code.yandex-team.ru . Este é um editor de código online para vários usuários, sem a possibilidade de sua execução. Talvez exista outra opção mais conveniente, mas houve uma busca e restou preguiça;
  • Ele não exigiu uma solução ideal, mas pediu para resolvê-la de alguma forma;
  • Ele não exigia o conhecimento da língua de cor, a função ou o método desejado poderia ser solicitado;
  • Ele reduziu o tempo para uma entrevista técnica para 30 minutos.

Um dos objetivos da nossa entrevista técnica:

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

Eu acho que este é um teste muito significativo para o desenvolvedor JavaScript. E o ponto aqui não é o fechamento e o entendimento das diferenças entre pré-incremento e pós-incremento, mas que, por alguma razão inexplicável, um quarto dos entrevistados acredita que o console.log será executado antes do término do ciclo. Eu não estou exagerando. Essas pessoas têm um currículo e experiência de trabalho de pelo menos dois anos e resolveram com êxito outras tarefas que não estavam vinculadas a retornos de chamada. Esta é uma nova geração de desenvolvedores de JavaScript que cresceram em assíncrono / aguardam, que ouviram algo mais sobre o Promise, mas os retornos de chamada para eles - como um telefone de disco para um adolescente moderno - discarão o número, mesmo que não seja a primeira vez, mas não entenderão. como funciona e por quê.

Esta tarefa tem uma continuação: você precisa adicionar o código para que o console.log também execute dentro de setTimeout, mas os valores 1, 2, 3, 4. são exibidos no console.O ditado "viver, aprender" é apropriado, porque um dos entrevistados uma vez propôs tal solução:

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

E então eu descobri que setTimeout e setInterval passam o terceiro e os argumentos subsequentes para o retorno de chamada. É uma pena, sim. A propósito, o conhecimento acabou sendo útil: usei esse recurso mais de uma vez.

Mas peguei emprestada essa tarefa do Yandex, pois é:

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

Aqui estão as habilidades testadas com o Promise. Normalmente, peço para resolver esse problema com promessas puras e, em seguida, usando async / waitit. Com async / waitit, a solução é intuitivamente simples:

 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') } 

Você também pode aplicar o ditado "viva, aprenda" a essa decisão, mas com relação ao meu entrevistador do Yandex: ele não especificou que o assíncrono / espera pode / não pode ser usado e, quando escrevi essa solução, ele ficou surpreso: "Eu não trabalhei com async / waitit, não achei que pudesse ser resolvido com tanta facilidade. " Ele provavelmente esperava ver algo assim:

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

Este exemplo é capaz de mostrar quão bem uma pessoa entende promessas, isso é especialmente importante nas costas. Depois que vi o código JavaScript de um desenvolvedor que não entendeu completamente as promessas, preparei uma promessa para a transação sequencial da seguinte maneira:

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

E imaginando por que apenas um usuário aparece em sua transação. Pode-se usar Promise.all, mas pode-se saber que Promise.prototype.then não adiciona outro retorno de chamada, mas cria uma nova promessa e será assim:

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

Esse caso me fez pensar em complicar a tarefa de entender promessas, mas o candidato me ajudou a formular uma nova tarefa. no código fonte de um de nossos projetos, deu a ele o código real:

 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) }) }) } 

E pediu para se livrar das palavras-chave async / wait. Desde então, essa tarefa foi a primeira e, na metade dos casos, a última na entrevista - ele costuma ficar muito impressionado.

Eu mesmo nunca resolvi esse problema antes de escrever esta nota e o faço pela primeira vez pela terceira vez (a primeira é muito longa e, na segunda , não percebi que ainda havia uma espera):



Que conclusão pode ser tirada disso tudo? As entrevistas são interessantes e úteis ... Claro, se você não está procurando trabalho com urgência.

No final, darei a você outra tarefa da história com a Yandex, ainda não a mostrei a ninguém * , cuidei do que é chamado de caso especial. Há um conjunto de banners, cada banner tem um "peso", que indica com que frequência o banner será exibido em relação a outros banners:

 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 }, ] 

Por exemplo, se houver três faixas com pesos 1, 1, 2, o peso combinado será 4 e o peso do terceiro será 2/4 do peso total, portanto, ele deve ser exibido em 50% dos casos. É necessário implementar a função getBanner, que aleatoriamente, mas levando em consideração os pesos, retorna um banner para exibição. A solução pode ser verificada neste snippet , onde a distribuição esperada e real é exibida.

UPD: eles começaram não apenas a menos o artigo em si, mas também a queimar o carma aos trancos e barrancos e eu o escondi em material oculto, o que é feio em relação aos comentaristas. Corrijo esse mudacismo da minha parte.

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


All Articles