От переводчика
Эрик Липперт — прежде всего известен как ведущий разработчик языка C# (в прошлом), и многие наверняка читали его блог Fabulous adventures in coding. Ранее в MSDN публиковался даже официальный перевод этого блога, что прекратилось после ухода Липперта из Microsoft. Конечно же, нет ничего лучше чтения оригинала, но я решил для разнообразия перевести что-нибудь из недавних постов Эрика. Надеюсь, будет интересно.
Ранее, я повторно опубликовал две свои старые статьи (оригиналы: раз, два — прим. перев.), касающиеся процесса технических собеседований. Полагаю, что мог бы описать более подробно, как провожу собеседования и на что при этом обращаю внимание.
Вот мои главные цели:
- не нанимать плохих работников;
- нанимать хороших работников;
- ост��вить кандидата с положительным впечатлением о компании.
Конечно же, избежать найма плохого работника — гораздо важнее, чем нанять хорошего. Если мы не заполучим отличного сотрудника — это очень плохо, но неудачный подбор персонала может снизить производительность команды на годы.
Последний пункт также является ключевым; если мы хотим принять человека на работу, то, очевидно, должны произвести хорошее впечатление. Но мне также хочется, чтобы все, получившие отказ — также оставались хорошего мнения о нас. У них есть друзья, которые, возможно, захотят собеседоваться. Когда-нибудь они могут принимать решение о покупке нашего продукта, либо рекомендовать его. Собеседование — это дорогостоящий бизнес-процесс; если он не приведет к найму, то, быть может, мы получим клиента, или хотя бы большую благосклонность к себе.
Собеседование проходит следующим образом.
Прежде всего, я должен убедиться, что соискателю комфортно, есть чем утолить жажду, не хочется в туалет, и так далее. Затем, если человек уже собеседовался у кого-то до меня, то я спрашиваю «Как дела?». Цель этого вопроса — разрядить атмосферу, завязать разговор, и посмотреть, есть ли у кандидата какие-либо вопросы касательно прохождения собеседования. Иногда у меня есть информация о том, как уже показал себя кандидат, но она не предназначена для принятия решений. (В Microsoft поощрялось давать обратную связь следующим в цепочке интервьюерам как можно скорее, чтобы они могли копать в сторону потенциальных проблем; в Coverity мы более склонны к тому, чтобы каждый интервьюер начинал беспристрастным. Оба подхода имеют свои плюсы и минусы.)
После этого, я выбираю из резюме кандидата что-нибудь интересное и не слишком старое, сперва прошу дать мне «Elevator Pitch» — несколько предложений, описывающих проект, чем он был интересен, важен — а затем прошу более подробно описать решенную в этом проекте проблему.
Это, опять же, поможет кандидату расслабиться, поскольку он рассказывает о чем-то из своего резюме, и, вероятно, хорош в этом. Я хочу выяснить в первую очередь — может ли собеседуемый нормально общаться? Вы будете удивлены, какое количество людей не может рассказать одним-двумя предложениями о вещах из своего резюме. Во-вторых, чем на самом деле они занимались на этом проекте? Это может шокировать, но люди постоянно приукрашивают свои заслуги. Изучив подробности того, что именно сделал кандидат, я могу понять, действительно ли он «умен и доводит дело до конца» (smart and gets stuff done — прим. перев.) или нет.
Затем мы переходим к действительно интересному этапу: техническая задача. Цель — выяснить, способен ли кандидат писать тот сорт кода, который необходимо будет писать каждый день. Мне известно, что написание кода от руки на доске критикуют, и я с согласен с этой критикой. Это неестественная обстановка, не такая как при реальном написании кода, это стрессовая ситуация и т.д. Я стараюсь придумывать вопросы, минимизирующие подобные трудности, но все еще способные показать мне, справится ли кандидат с реальной работой. (А если человек не желает писать код на доске, мы можем сделать это на компьютере. Однако, задавае��ые мной вопросы редко требуют написания более чем шести строк кода. Самые лучшие соискатели должны быть в состоянии написать полдюжины коротких строчек и без помощи текстового редактора.)
И этот этап действительно важен. Я проверяю, чтобы претенденты соответствовали необычному набору требований. Кандидаты в команды, работающие над инструментами анализа языка, должны быть сильны в теоретических основах информатики, и в то же время способны работать в реальном мире несовершенного кода. Я собеседовал прорву глубоко понимающих теорию ученых, при этом неспособных написать реальный код, а также опытных промышленных программистов, которые не могли сказать мне, что такое бинарное дерево.
Хорошая техническая задача имеет следующие характеристики:
- может быть поставлена и решена небольшим количеством кода за малое время;
- не требует внезапного озарения, чего трудно добиться в стрессовой ситуации;
- это не загадка с подвохом;
- по желанию может быть сделана труднее, либо легче, для того, чтобы лучше оценить уровень кандидата;
- похожа на проблемы, которые соискатель должен будет решать на рабочем месте;
- тестирует не только написание кода, но и другие навыки, такие как умение читать существующий код, или иметь дело с нечеткими требованиями;
Задача, которую я долго использовал в Microsoft, и которая работает даже лучше в Coverity, имеет следующую схему:
Во-первых, есть два написанных на C++ метода из публичного API, каждый из которых состоит из трех строк очень простого кода. API управляет «задачами» на «сервере», и используется «клиентом». Сценарий намеренно очень размыт, но код понятен. Я провожу кандидата по каждой строке и убеждаюсь, что он понимает код. Здесь мне необходимо только базовое понимание простого синтаксиса (подавляющее большинство кандидатов знакомы с C++ согласно их резюме, так как это необходимое требование для работы).
Далее, я поручаю кандидату исследовать этот код на наличие любых проблем. И наблюдаю, в какую сторону он копает при исследовании чужого кода. Представленный код имеет проблемы дизайна, надежности, безопасности, тестируемости и переносимости. Я смотрю, какие проблемы кандидат находит самостоятельно, а если он что пропускает, могу подсказать: «Если клиент намеренно попытается нарушить работу сервера, что вы сделаете?» или «Что будет, если скомпилировать этот код для запуска на 64-разрядной машине?» и посмотрю, поймет ли он намек.
Это реальный навык, который мне необходимо использовать каждый день — во-первых, при code review собственного кода и кода моих коллег. А во-вторых, мой бизнес — написание программ, анализирующих забагованный код и определяющих, что он забагован. Если соискатель не способен проверить чужой код — маловероятно, что он будет успешен вообще где-либо. У того, кто не может найти ни одну из десятка ошибок в программе, содержащей шесть инструкций — вряд ли получится написание анализатора кода, находящего ошибки.
Здесь я ищу понимание, критическое мышление и базовое знание предметной области. Часто на этом этапе проявляются сигналы опасности. У меня были претенденты с «PhD in computer science», которые не знали, например, что в 64-битной архитектуре длина указателя составляет 64 бита. Да и зачем им знать? Как говорится, астрономия — это не наука об изготовлении телескопов. Обширные знания теоретической информатики не обязательно влекут за собой знания о пропускной способности шины между процессором и памятью. Но кандидат, не понимающий, как указатели реализованы в распространенных архитектурах — вряд ли будет успешен, работая в команде по разработке анализаторов, которые ищут связанные с длиной указателя ошибки.
Также многие соискатели определяют ошибки в коде, но не могут описать их последствия. Неопределенное поведение по определению не определено. Но описание того, что может произойти, если вредоносный клиент назначает указатель на произвольный кусок памяти (англ. pointer to arbitrary memory — прим. перев.) и заставляет сервер выполнить виртуальный метод с таким указателем — демонстрирует понимание, как на самом деле компилятор генерирует код.
Это первая фаза технической задачи. Следующий этап включает в себя дизайн, и, возможно, некоторые небольшое количество кодинга. «Вы можете изменить реализацию API, но не сигнатуры методов — как бы вы исправили каждую из найденных ошибок?». Опять же, это основной рабочий навык, который нам приходится использовать каждый день: исправление ошибки на «сервере», будучи не в состоянии изменить поведение «клиента». Возможно, вызывающий код принадлежит заказчику, или другой команде, либо уже продан, да что угодно.
На данном этапе я обращаю внимание на следующее:
- Есть ли еще сигналы опасности? Боже, количество кандидатов, пытавшихся решить проблему переносимости путем сжатия 64-битного указателя в 32-разрядное целое число — огромно. Тут нет хитрой загадки, которую я прошу разгадать. Вы не можете поместить двадцать фунтов муки в десятифунтовый мешок. Должно быть очевидно, что необходимо найти другой способ решения проблемы.
- Какие инструменты могут быть в багаже соискателя? Все кандидаты в конечном итоге понимают, что им необходимо добавить вспомогательную структуру данных для маппинга из 32-битного целого числа в 64-битный указатель. Известны ли им существующие реализации такого маппинга? Если нет, уверены ли они, что смогут написать подобное?
- Может ли кандидат выделить действительно сложную часть задачи? Сложность здесь — не в осознании необходимости маппинга, а в том, чтобы обеспечить уникальность ключей при добавлении новой пары ключ-значение (как я упоминал в предыдущем эпизоде, оригинал — прим.перев.). Даже когда я неоднократно спрашиваю: «В вашем решении вы так и не рассказали, как вычислить значение ключа — так как генерируется это значение?», некоторые кандидаты просто не обращают на это внимания и продолжают объяснять мне, как работает маппинг. Либо они возвращаются к предыдущей точке и пытаются сжать 64-битное значение в уникальный ключ 32-бит, что невозможно, и что является причиной маппинга.
- Как кандидат выйдет из подобной неоднозначной ситуации? Проблема умышленно размыта, неясна, и трудная часть заключается в генерации уникальных значений. Если есть два клиента, каждый из которых создает по две «задачи», а затем отключается, то сгенерировать уникальные 32-битные целочисленные ключи несложно. Если есть миллионы клиентов, генерирующие миллионы «задач», то получать уникальные ключи может быть очень трудно. Многие кандидаты никогда не спрашивают меня, сколько клиентов и «задач» есть в этой системе, и поэтому некоторые выдают слишком переусложненные идеи, а кто-то предлагает решение, которое немасштабируемо в принципе.
- Может ли кандидат провести параллели с решенными ранее проблемами? Это трудно сделать в стрессовой ситуации, но на подобное все равно интересно посмотреть. Некоторые кандидаты очень быстро осознают, что проблема «сгенерировать уникальный 32-битный номер, и не использовать повторно его до тех пор, пока клиент не закончит с ним» должна была быть ранее решена тем, кто написал 32-битную версию менеджера памяти. В конце концов, что такое менеджер памяти (англ. memory manager — прим. перев.): устройство, которое предоставляет вам по запросу уникальный указатель, и не выдает этот же указатель вновь до тех пор, пока вы его не освободите. Поэтому сформулированная мной проблема может быть сведена к ранее решенной. Некоторые кандидаты рассуждают о выделении памяти и подобных вещах, как о магии. Многие поняли, что проблема аналогична реализации кучи; но только один сказал: «Я решу проблему, получив от Windows кучу объемом в 4 миллиарда байт, а затем буду выделять отдельные байты из кучи для того, чтобы генерировать уникальные 32-битные числа» (англ. I’ll solve the problem by having Windows make me a four billion byte heap, and then allocate single bytes out of it to generate unique 32 bit numbers — прим. перев.). Это действительно сведет задачу к уже решенной!
Если кандидат справляется очень хорошо, то усложнить задачу достаточно просто. Предположим, клиенты используются многими потоками; какая стратегия синхронизации сохранит высокую пропускную способность? Допустим, состояния задач должны быть сохранены при перезагрузке компьютера. Что если сервер в кластере и может перенаправить нагрузку на другие сервера. Я хочу выяснить, насколько глубоки их знания. Или я могу упростить задачу, ограничив число клиентов десятью. Предположить, что «задачи» стартуют и завершаются в некотором определенном порядке, так что следующая «задача» стартует после того, как завершается предыдущая. И так далее. Мне бы хотелось, чтобы соискатели почувствовали, что справились с хоть с какой-нибудь проблемой.
В конце собеседования я стараюсь выгадать немного времени, чтобы кандидат успел задать вопросы о команде, вакансии, компании и т.д. (никто ни разу не спросил: «Напишете код, разворачивающий связанный список?», и я был бы очень удивлен, если бы кто-то это сделал). Для меня это еще одна возможность «прорекламировать» нашу компанию, а также понять из вопросов соискателей, что для них важно.