У пошуках оптимального формату технічного інтерв’ю — обговорюємо варіанти

Привіт! Мене звати Дмитро і я відповідаю за команду iOS в компанії Uklon. За останні кілька років я провів чимало технічних інтерв’ю у пошуку найкращих гравців для нашої команди. Чимало з них пройшли, скажемо відверто, субоптимально. Як для нас, так і для наших кандидатів. Але ми завжди намагаємось виносити уроки з помилок і покращувати процеси.

В даній статті я хочу розказати про підхід до проведення технічного інтерв’ю, який зараз мені здається нашою найкращою спробою. Він є результатом багатьох експериментів і помилок, про які мова також піде. Блог є своєрідним закликом спільноти до конструктивного обговорення варіантів проведення співбесід, адже всі ми з ними стикаємось.

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

Це плейграунд, в якому пропонується порефакторити код. Поряд з кодом надані тести, які можна і треба запускати, щоб швидко перевірити, чи код досі працює як треба. Єдине — тести не можна редагувати. Правильного рішення завдання, як такого, не має. Є рішення до якого ми прийшли свого часу своїми силами. Але варіант, який мені здається найцікавішим, засновано на ідеях, що були надані кандидатами у процесі проходження інтерв’ю.

Пропоную, перш ніж переходити до розв’язків, проаналізувати підходи до оцінювання професійного рівня розробника. Методи, які ми пробували раніше і чим обґрунтовуємо поточний вибір наведено нижче.

Методи оцінювання професійного рівня розробника

Екзамен на знання теорії

Інтерв’ю саме такого роду доводилось проходити свого часу мені особисто. І це перше, від чого хотілось би відійти. Коли представник компанії зачитує з листочка підготовлені заздалегідь питання на знання найбільш потаємних нюансів мови програмування чи фреймворків.

Наприклад, яка реалізація методу буде викликана в разі, якщо клас імплементує протокол, протокол має дефолтну реалізацію методу, при тому, що батьківський клас даного класу має свою реалізацію методу з ідентичною сигнатурою.

Питання заздалегідь знає тільки одна сторона, не всі з них очевидні. Така співбесіда дуже стресова для кандидата і рідко залишає по собі приємні враження про компанію. Якщо питання були надто складними, виникає закономірне питання, чи дійсно всі ті знання є обов’язковими для виконання обов’язків. Питання суто теоретичні чи холіварні, типу принципів чи парадигм, можуть спровокувати суперечки чи зайві емоції і залишити сумніви щодо компетентності з обох сторін.

Гірше за все, що такого роду інтерв’ю навряд чи дозволить сформувати повноцінне враження від співрозмовника.

Домашнє завдання

Далеко не всі погоджуються виконувати хоч наскільки-то велике тестове завдання у свій вільний час і я особисто таку позицію поділяю. Такого роду завдання залишає надто великий ризик витратити час даремно, оскільки може залишитись незрозумілим, за якими параметрами воно взагалі буде оцінюватись.

Співвідношення часу, що витрачається на виконання завдання і часу, що витрачається на формування фідбеку за завданням часто є просто зневажливим у відношенні до кандидата. Відсутність можливості виправити ту саму маленьку деталь, через яку завдання загалом вважається виконаним незадовільно — демотивує.

Для компанії за кадром залишаються питання, скільки насправді часу було витрачено на завдання та які форми допомоги були використані при його виконанні. Хоча мені не відомі випадки, коли нам було б надане рішення, виконане зі сторонньою допомогою, але виключати такий ризик також не можна.

Unit-Тест

Була у нас спроба сформулювати тестове завдання у вигляді реалізації певного інтерфейсу. Ідея полягала в тому, що оцінити його можна за допомогою підготовлених заздалегідь unit-тестів. Відповідно, оцінку ми отримували повністю об’єктивну, без жодного простору для образ і суперечок. Але підібрати правильний рівень складності для такого завдання виявилось задачею нетривіальною. Знову ж таки — надто мало таке завдання казало про кандидата.

З іншого боку, піти у зворотному напрямку — дати як завдання покрити інтерфейс unit-тестами, могло б бути цікавіше. Як варіант, для перевірки повноти тест-сьюта можна спробувати запускати тести на заздалегідь підготовленій реалізації інтерфейсу з помилками. У добре покритому варіанті тоді мали б знайтись тести, які не пройдуть.

Недолік такого завдання був би в тому, що воно перевіряє дуже конкретні, хоч і корисні навички — саме покриття коду unit-тестами.

Давати таке завдання в доповнення до іншого — значило б або ускладнити процес, або збільшити об’єм домашнього завдання з очевидними наслідками.

Багофікс

Спостерігав такий формат збоку, мені здався доволі цікавим. Завдання: пропонується розглянути непрацюючий код, знайти і виправити в ньому помилки.

З плюсів: завдання не вимагає багато часу на виконання.

З мінусів: в ньому можна зайти в тупик, особливо, якщо для виправлення помилки треба просто знати якийсь нюанс технологій, що використовуються. При цьому сам факт, що хтось з чимось раніше не стикався, ще нічого не говорить однозначно про здібності до вирішення подібних задач.

Портфоліо

Якщо у резюме ми бачимо посилання на open-source проєкт, що активно підтримується і використовується, питання про будь-які тестові завдання закономірно можна пропустити. На практиці часто не зустрічається і вимагати таке від претендентів на вакансію будь-якого левела ми наразі не бачимо можливим.

Зрозуміло, що контриб’юти у чужі open-source проєкти — також потенційно можуть стати достатньою демонстрацією практичних навичок спеціаліста.

Рекомендації

Також не зустрічається надто часто, коли можна почути відгуки про потенційного співробітника або від авторитетних представників індустрії, або від нинішніх чи колишніх колег.

Аргумент доволі вагомий, але не самодостатній. Кандидат, що приходить по рекомендації, часто виявляється не підготовленим до серйозної співбесіди. З цим варто бути обережним.

Live coding

Варіант має свої очевидні ризики, пов’язані з нехарактерним рівнем стресу для типового процесу кодингу. Завдання, що передбачає конкретні знання без можливості погуглити, здебільшого нагадувало б варіант екзамену. Необхідність використовувати інструменти може спровокувати нікому не цікаві технічні паузи.

Серед переваг: від кандидата не вимагається витрачати багато вільного часу, є можливість коментувати виконання завдання на випадок, якщо очевидно, що в тому є потреба. Також, на мою думку, такий спосіб подивитись на спеціаліста в дії є найбільш інформативним.

Обраний на зараз варіант завдання

Сьогодні технічне інтерв’ю до iOS команди складається з 2-годинного відеодзвінка. Не розбиваючи процес на зайві кілька етапів, ми не витрачаємо цінний час.

Біля години ми спілкуємось про попередній досвід кандидата та відповідаємо на питання щодо нашої команди, а годину відводимо на live-coding. Наше завдання на рефакторинг не передбачає правильної відповіді і не потребує якихось конкретних знань. Ідея полягає в тому, що кожен може прийти до якогось рішення. Таким чином ми намагаємось не залишити від інтерв’ю відчуття, що хтось з чимось не впорався.

Нам зі свого боку це дає можливість провести своєрідний гештальт-аналіз. Поставити питання на кшталт: що ви бачите у такому коді? Чи не здається він вам занадто заплутаним? Що саме в ньому викликає у вас незадоволення? А як можна було б його переписати?

Завдання у плейграунді не вимагає особливих налаштувань і допомагає уникнути технічних затримок. Той факт, що для коду підібрані тести, не тільки спрощує перевірку коректності коду, але й має вказати на те, що ми також витратили час на підготовку завдання.

Також хочу відзначити, що код для завдання ми не обфускували навмисно, натомість використали код зі справжнього мерж-реквеста. Таким чином ми отримуємо очевидну відповідь на запитання про доцільність навичок, які вимагаємо. І, так, ми теж можемо втомитись і спробувати зробити швидко-негарно. На те і є код рев’ю.

За бажання кандидата, ми дозволяємо надіслати нам рішення протягом кількох годин після завершення співбесіди. Цим ми намагаємось знизити тиск, викликаний обмеженістю в часі і дати можливість довести до завершення ідеї, які не вдалось реалізувати на камеру. Ми намагаємось побачити, що людина зробить з типовим для нас завданням. Але екстремальне програмування типу «на камеру, під наглядом трьох осіб» — не є тим, чим ми займаємось буденно.

Варіанти розв’язання завдання

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

Наостанок пропоную подивитись на варіант рішення, який ми більш-менш очікували побачити і варіант, який став для нас цілковитим відкриттям.

Перш ніж в коді щось міняти, корисним може бути розібратись, що він робить. Зрозуміти, що саме робить код, можна або читаючи сам код, або дивлячись на очікування в тестах.

Так чи інакше з’ясовуємо, що задача полягає в тому, щоби порівняти два рядкових масиви і видати результатом словник з рядками у ключах і значеннями типу Bool.

  • Значення true мають рядки, які не входили у перший масив, але входять у другий.
  • Значення false мають рядки, які входили у перший масив, але не входять у другий.
  • Значення, які або входять в обидва масиви або в жодний з них — у результаті відсутні.

Очікуваний розв’язок

Не подобатися в коді може багато що. Так задумано.

У допоміжного приватного метода доволі дивна сигнатура. Одна property класу використовується напряму, інша — передається параметром в метод. Назви змінних довгі і мало відрізняються одна від одного, через що зливаються. Допоміжний метод має три варіанти результату, а отже немає за чим декларувати довільне ціле число як повернуте значення.

Необхідність вказувати кожне окреме значення enum в масиві можна обійти, якщо enum адаптуватиме протокол CaseIterable.

Зауважимо, що згідно з тестами, чи є масив порожнім, чи відсутнім — не впливає на результат. Відповідно, нам здається логічним не бачити в коді рішення конструкції if let чи guard let, оскільки немає сенсу опрацьовувати значення nil якось по-особливому. Більш того, немає сенсу взагалі окремо виділяти кейс з порожніми масивами, оскільки входить елемент у масив чи ні, перевіряється однаково як для порожнього масиву, так і для непорожнього. Чи ні? :)

На мою думку, саме ці останні два моменти роблять код невиправдано складним.

І варіант, який усунув би зайві перевірки мені здається вже достатньо непоганим. В такому коді перевірки дублюють поставлене завдання ледь не слово-в-слово, завдяки чому його принаймні просто зрозуміти. Як тут:

…
private func change(between previousValues: [String], and updatedValues: [String], for item: String) -> Bool? {
        if previousValues.contains(item) && !updatedValues.contains(item) {
            return false
        } else if !previousValues.contains(item) && updatedValues.contains(item) {
            return true
        }
        return nil
    }
    
    public func getDifference() -> [String: Bool] {
        var result = [String: Bool]()
        CityPreferenceItem.allCases.forEach({ item in
            if let change = change(between: self.preselectedConditionsPreferenceOldValue ?? [], and: self.preselectedConditionsPreferenceNewValue ?? [], for: item.rawValue) {
                result[item.rawValue] = change
            }
        })
        return result
    }
…

Посилання на рішення

Неочікуваний розв’язок

Уважні читачі можуть помітити, як і деякі з наших співрозмовників, що питання про належність одній колекції, але не іншій зручно вирішується у термінах алгебри множин. Тож можна перевести наші опціональні масиви у множини, швидко-зручно визначитись, хто якій різниці множин належить.

Далі, залишається з двох масивів зробити результуючий словник. А це можливо зробити доволі просто, відобразивши елементи кожної з множин у відповідний кортеж і скориставшись конструктором словника, який якраз приймає масив кортежів. Виявляється, всю нашу задачу можна вирішити доволі компактно, без жодних допоміжних методів:

…
public func getDifference() -> [String: Bool] {
        let setPrev = Set(self.preselectedConditionsPreferenceOldValue ?? [String]())
        let setUpdated = Set(self.preselectedConditionsPreferenceNewValue ?? [String]())
        let added = setUpdated.subtracting(setPrev)
        let removed = setPrev.subtracting(setUpdated)
        let result = Dictionary.init(uniqueKeysWithValues: added.map({ ($0, true) }) + removed.map({ ($0, false) }))
        return result
    }
…

Посилання на рішення

Варто зазначити, що наведені три варіанти розв’язку можуть відрізнятись у поведінці, якщо почати додавати значення до enum або мати у вхідних даних рядкові значення, що не є валідними значеннями enum’а. Проте такі кейси тестами в завданні не покриті, а отже в рамках завдання не є цікаві. Але зазначити таку різницю — це проявити уважність, що стане у пригоді на грумінгах ;)

Висновок

Я цілком розумію, що наш нинішній формат технічного інтерв’ю не є ідеальним. Прохання не сприймати дану розповідь як повчання, що треба робити. Скоріш, це відкрите питання до спільноти, як даний процес можна покращити. Оскільки наша задача — побачити кожного претендента з кращої сторони, максимально об’єктивно і справедливо для всіх.

Можливо варто обирати завдання певного формату, виходячи з очікуваного рівня спеціаліста, а можливо й пропонувати різні варіанти на вибір. Що скажете?

Щиро зацікавлений почути вашу думку з цього приводу. Як наших потенційних кандидатів, так і колег, які самі займаються підбором девелоперів до своїх команд. Дякую за увагу!

👍ПодобаєтьсяСподобалось22
До обраногоВ обраному2
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Також використовую схожий підхід в інтерв’ю (правда без тестів), це показує гарні результати.
Дякую за цікаву статтю!

використали код зі справжнього мерж-реквеста

Це класна ідея. Тому що мені доводилося стикатися з подібним підходом, але люди наробили трохи зайвого і питання викликав мало не кожний рядок коду. IRL так не буває навіть з джунами і за кількістю кричущих помилок ти вже не помічаєш менш значні.

Взагалі ваш формат виглядає доволі розумним.

Підписатись на коментарі