كيف علمتني ياندكس كيفية مقابلة المبرمجين

بعد أن أوضحت قصتي عن "التوظيف" على Yandex في التعليق على الملاحظة المثيرة "كيف عملت لمدة 3 أشهر في سوق Y. وإنهيت" ، سيكون من الظلم إخفاء المزايا التي استفدت من تجربتي في Yandex.Message.

تتضمن مسؤولياتي الوظيفية إجراء مقابلات تقنية مع المرشحين لشغل منصب Fullstack JavaScript / TypeScript ، لقد شاركت بنشاط في هذا العمل (هل من الجدير القول إنني سئمت قليلاً؟) منذ أكثر من عام ، لدي أكثر من 30 مقابلة فنية.

في وقت سابق في مقابلة فنية ، سألت المرشح أسئلة غبية إلى حد ما مثل "ما هو الإغلاق" ، "كيف يتم تطبيق الميراث في JavaScript" ، "هنا جدول في قاعدة بيانات به مثل هذه المؤشرات ، يرجى إخبارنا كيف يمكنك تسريع مثل هذا request "، على الرغم من أنها ساعدت في تحديد القدرات الهندسية للمرشح ، إلا أنها لم تسمح لهم باستنتاج مدى قدرة أي شخص على حل المشكلات ومدى السرعة التي يمكنه بها اكتشاف رمز موجود. ما لا يمكن إلا أن يؤدي إلى عواقب حزينة ...

لكن كل شيء تغير بعد أن مرت بأربع جولات من المقابلات الفنية في ياندكس.

عادة ، تطير الحجارة إلى حديقة المقابلات في Yandex من أجل:

1. المهام التي ليس لها قيمة عملية ؛
2. الحاجة إلى حل هذه المشكلات على قطع من الورق بقلم رصاص أو على سبورة.

إنه بالفعل 2019 وقد حان الوقت لإطلاق خط إنتاج منفصل لصب الغلايات في الجحيم لأولئك الذين يجبرون الناس على كتابة نص باليد ، ناهيك عن الكود. كل شخص يكتب بطرق مختلفة ، وفي إعداد النص لهذه المذكرة ، كان علي أن أعيد كتابة ، على سبيل المثال ، هذه الفقرة بالذات ست مرات - إذا كتبت ملاحظات عن حبر على الورق ، فلن أكتب ملاحظات عن حبر.

لكنني لا أتفق مع أطروحة حول الجدوى العملية لمهام ياندكس. حتى التطوير الروتيني لا ، لا ، ولكنه سيحدد لك مهمة لها عدة حلول. لا تحتاج إلى التفكير في واحدة لفترة طويلة ، لكنها ليست مثالية من حيث حجم الرمز أو الأداء أو التعبير. الآخر هو عكس ذلك تمامًا ، لكنه يتطلب للمبرمج بعض الخبرة في بناء خوارزميات فعالة ومفهومة. فيما يلي مثال من المقابلة:

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

حول هذه المشكلة ، أضعفت على محمل الجد ، وقررت أنها ليست الطريقة الأكثر جمالا. لسوء الحظ ، لم يتم الحفاظ على الحل ، لذلك سأقدم حلاً لأحد المرشحين:

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

من السلبيات: الوصول إلى الصفيف في فهرس غير موجود ومعالجة سلسلة بشعة: join-split-join. وهذا الحل خاطئ أيضًا ، لأنه مع المثال getRanges ([1 ، 2 ، 3 ، 5 ، 6 ، 8]) يتم إرجاع "1-3،5-6،8" ، و "قتل" الفاصلة في النهاية ، تحتاج لزيادة الظروف عن طريق تعقيد المنطق وتقليل قابلية القراءة.

هنا حل على غرار ياندكس:
 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 في كتابة مثل هذه الحلول الأنيقة؟ لإنتاج مثل هذا الرمز ، فأنت بحاجة إلى مكونين: التجربة مع العديد من الخوارزميات والمعرفة الممتازة باللغة. وهذا بالضبط ما يحذره مجندو ياندكس: سوف يسألك عن الخوارزميات واللغة. تفضل ياندكس توظيف مطورين يمكنهم كتابة كود رائع. هؤلاء المبرمجون فعالون ، لكن الأهم من ذلك أنهم قابلون للتبادل: سوف يكتبون عن نفس الحلول. يمكن للمطورين الأقل ذكاءً في مهمة واحدة أن يقدموا العشرات من الحلول المتنوعة ، والمدهشة في بعض الأحيان: أحد المرشحين لشغور الوظيفة اختتم عكازًا جعلت عيناي على جبهتي.

محدث: كما لاحظ MaxVetrov المستخدم ، حل بلدي غير صحيح:

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

وبالتالي ، أنا نفسي لم أتمكن من حل هذه المشكلة بشكل صحيح حتى الآن.

UPD2: بشكل عام ، أقنعتني التعليقات أن هذا الرمز تبين أنه سيئ ، حتى لو كان يعمل بشكل صحيح.

لم يكن عبثًا أن قضيت وقتًا في ترجمة الورق في مكتب ياندكس ، لأنني خلال هذا الدرس فهمت كيف أصبح مجريًا فعالًا في المقابلة. أخذت فكرة وشكل مهامهم كأساس ، ولكن:

  • لم أقم بتقديم رمز على الورق ، لكنني طلبت الكتابة في code.yandex-team.ru . هذا هو محرر التعليمات البرمجية عبر الإنترنت متعدد المستخدمين دون إمكانية تنفيذه. ربما كان هناك خيار آخر أكثر ملاءمة ، ولكن كان هناك بحث وترك الكسل ؛
  • لم يكن بحاجة إلى حل مثالي ، لكنه طلب حلها بطريقة ما ؛
  • لم يكن بحاجة إلى معرفة اللغة عن ظهر قلب ، يمكن طلب الوظيفة أو الطريقة المطلوبة ؛
  • قام بتقليل الوقت لإجراء مقابلة فنية إلى 30 دقيقة.

أحد أهداف المقابلة الفنية لدينا:

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

أعتقد أن هذا اختبار مهم جدًا لمطور JavaScript. والنقطة هنا ليست إغلاق وفهم الاختلافات بين ما قبل الدمج وما بعده ، ولكن ذلك لسبب لا يمكن تفسيره ، يعتقد ربع الذين أجريت معهم مقابلات أن console.log سينفذ قبل انتهاء الدورة. أنا لا أبالغ. يتمتع هؤلاء الأشخاص بالسيرة الذاتية والخبرة العملية لمدة عامين على الأقل ، وقد نجحوا في حل المهام الأخرى التي لم تكن مرتبطة بردود الفعل. إما أن يكون هذا جيلًا جديدًا من مطوري JavaScript الذين نشأوا على التزامن / الانتظار ، والذين سمعوا شيئًا آخر عن Promise ، ولكن عمليات الاستدعاء الخاصة بهم - مثل هاتف قرص لمراهق حديث - ستطلب الرقم ، حتى لو لم تكن المرة الأولى ، لكنهم لن يفهموا كيف يعمل ولماذا.

هذه المهمة لها استمرارية: تحتاج إلى إضافة الكود بحيث يتم تشغيل console.log أيضًا داخل setTimeout ، لكن يتم عرض القيم 1 و 2 و 3 و 4 في وحدة التحكم ، حيث أن عبارة "live، Learn" مناسبة هنا ، لأنه بمجرد إجراء أحد المقابلات اقترح مثل هذا الحل:

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

ثم اكتشفت أن setTimeout و setInterval يمرران الوسيطتين الثالثة واللاحقة إلى رد الاتصال. إنه عار ، نعم. بالمناسبة ، تبين أن المعرفة مفيدة: لقد استخدمت هذه الميزة أكثر من مرة.

لكنني استعارت هذه المهمة من Yandex كما هي:

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

هنا يتم اختبار المهارات مع وعد. عادةً أطلب حل هذه المشكلة بناءً على وعود صافية ، ثم استخدام المزامنة / الانتظار. مع المزامنة / الانتظار ، يكون الحل بسيطًا:

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

يمكنك أيضًا تطبيق مقولة "عيش ، تعلّم" على هذا القرار ، لكن فيما يتعلق بمقابلة Yandex التي أجريتها: لم يحدد / لا يمكن استخدام المزامنة / الانتظار ، وعندما كتبت هذا الحل ، فوجئت: "لم أكن أعمل مع عدم التزامن / الانتظار ، لم أكن أعتقد أنه يمكن حلها بهذه السهولة. " ربما توقع أن يرى شيئًا كهذا:

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

هذا المثال قادر على إظهار مدى فهم الشخص للوعود ، وهذا مهم بشكل خاص على ظهره. بمجرد أن رأيت شفرة JavaScript لمطور لم يفهم الوعود بالكامل ، أعد وعدًا للمعاملة المتعاقبة كما يلي:

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

ويتساءل لماذا يظهر مستخدم واحد فقط في معاملته. يمكن للمرء استخدام Promise.all ، ولكن يمكن للمرء أن يعرف أن Promise.prototype.then لا يضيف رد اتصال آخر ، ولكنه ينشئ وعدًا جديدًا وسيكون مثل هذا:

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

دفعتني هذه القضية إلى تعقيد مهمة فهم الوعود ، لكن المرشح ساعدني في صياغة مهمة جديدة. في شفرة المصدر لأحد مشاريعنا ، أعطاه الكود الحقيقي:

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

وطلب التخلص من الكلمات المتزامنة / الانتظار. منذ ذلك الحين ، كانت هذه المهمة الأولى ، وفي نصف الحالات ، كانت المهمة الأخيرة في المقابلة - غالبًا ما يكون غارقًا جدًا.

أنا شخصياً لم أحل هذه المشكلة أبداً قبل كتابة هذه المذكرة وأفعلها للمرة الأولى للمرة الثالثة ( الأولى طويلة جدًا ، وفي الثانية لم ألاحظ انتظارًا واحدًا):



ما النتيجة التي يمكن استخلاصها من كل هذا؟ المقابلات ممتعة ومفيدة ... بالطبع ، إذا كنت لا تبحث عن عمل بشكل عاجل.

في النهاية ، سأعطيك مهمة أخرى من القصة مع Yandex ، لم أقم بعرضها على أي شخص * حتى الآن ، لقد اهتمت بما يسمى حالة خاصة. هناك مجموعة من الشعارات ، لكل لافتة "وزن" ، مما يشير إلى عدد المرات التي سيتم فيها عرض البانر بالنسبة للرايات الأخرى:

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

على سبيل المثال ، إذا كان هناك ثلاثة شعارات ذات أوزان 1 ، 1 ، 2 ، يكون وزنها المجمع 4 ، ويبلغ وزن الثلث 2/4 من الوزن الكلي ، ثم يجب عرضه في 50٪ من الحالات. من الضروري تطبيق وظيفة getBanner ، التي تقوم بشكل عشوائي ، ولكن مع مراعاة الأوزان ، بإرجاع شعار واحد للعرض. يمكن التحقق من الحل في هذا المقتطف ، حيث يتم عرض التوزيع الفعلي المتوقع.

محدث: لقد بدأوا ليس فقط في ناقص المقالة نفسها ، ولكن أيضًا لحرق الكرمة بسرعة فائقة وقمت بإخفائها في إخفاء المادة ، وهي قبيحة بالنسبة للمعلقين. أنا أصحح هذا mudachism من جانبي.

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


All Articles