Сказка про собес наоборот

от автора

Вы помните свое первое собеседование? Я свое помню отлично, преподаватель дольше обычного гонял мою плис в симуляторе Keil-C, придирался к любой мелочи, докапывался до каждой запятой в коде прошивки. А потом начал гонять по алгоритмам трассировки печатных плат, которые мы должны были проходить только в следующем семестре. Я уже мысленно готовился на допсу, видно же что валит, как и предыдущих двух одногруппников. Но в конце сдачи лабы по проектированию мк преподаватель спросил хочу ли я делать «железное железо для железной дороги?» (с). Студенту второго курса ИТМО, которого кормили родители, и подрабатывавшего разгрузкой вагонов ночами, это было сродни офферу в гугль. С тех пор я много раз побывал с обеих сторон стола, и в качестве испытуемого, и как придирчивый лид (отнюдь этим не горжусь, но и не стыжусь), и как группа поддержки у коллег из соседних отделов. Всегда хотелось надеяться, что наши собеседования — это не таинственные квесты, где каждая задача — это каст сложного заклятия, а ошибки не выкидывают с данжа.

Но сначала сказка о том, как к нам попал Миша: однажды в славном городе Панкт-Сетербург погромист-джедай по имени Михаил отправился на собеседование в компанию «Кодозавры». Он был уверен, что знает все, и даже изучил новейшие фреймворки «NoScript» и «Unreal С—«. Как он думал, ничто не может его остановить. Когда Миша пришел на собеседование, его встретил HR-менеджер по имени Анна. Она с улыбкой спросила: «Расскажите, как вы бы пояснили своей бабушке, что такое мьютекс?». Ага, прям с порога наш доблестный HR накинул Мишке затравочный вопрос. Ну а чё, это ж сказка, герои на белых самокатах и все такое…

Почему вы выбрали нашу компанию?

Почему вы выбрали нашу компанию?

Так что пусть наша Аня шарит в примитивах синхронизации. Мишка ушами не хлопал, улыбнулся и начал рассказывать про асинхронные задачи, колбэки и атомики. Любая бабушка будет гордиться таким внуком, подумал он. Затем Михаила пригласили в избу, Анечка упорхнула на кухню варить кофф, не в силах осознать полученную информацию. Ой, нет — она пошла дописывать тестовое задание для новичков. Впрочем неважно, это же сказка — нашего героя провели по офису, показали крепостных на полях румяных девопсов (блин, некрасиво получилось) и направили в переговорку, куда чуть позже подошел лид отдела, в который и хотел попасть тогда еще юный наш герой. Не давая никому в комнате расслабиться, он сразу выдал стандартную загадку: «Как вы перевернете строку задом наперед без использования дополнительной памяти?», и уселся напротив кандидата писать код в своем смак-буке, кинув на себя спелл невидимости, чтобы не мешать Мише думать. Заклинание невидимости не прошло проверку на интеллект, но богатырь не подал виду и попробовал решить задачу в уме, ведь листочка тоже не дали. В итоге в голове у него произошел целый карнавал символов и индексов, в конце концов, он пришел к решению и объявил его «Въ развращеніи словесъ, начнити отъ крещениа и двигатисѧ ко началу, мѣнити буквы, въ обратномъ порядкѣ». Сказанного хватило, чтобы в переговорку телепортнулся босс данжа, т.е. тех лид студии и начал кидать в Мишу файрболы, ой… задавать скользкие и каверзные вопросы. Мишаня отбивался как мог, не путался в ответах, в минуты передышек провел пару атак световыми мечами и давил противника вопросами о техстеке, билдферме и процессе ревью. Наконец мана у босса в кружке опустилась до критического уровня, на последнем глотке тех лид студии «Кодозавры» дал задание переписать пульт управления ракетой на NoScript. Для камлания оставили смак-бук, час времени и какую-то матерь, призыв духов Гугола и Яру особых успехов не принес, зато дважды являлась Аннафея и предлагала испить живой воды, вода правда была коричневого цвета, и отчего-то пахла кофем, который не Нескафе, но Мишаня в фей не верил и изгонял искусительницу вопросами про спинлоки, после чего Аннафея таяла легкой дымкой.

Звезды сошлись, код смешался с загадками, магия ctrl+shift+B позволила увидеть три заветных слова: «Скомпилилось без ошибок!». Из данжа Михаил выходил победителем, апнул уровень харизмы и поднял скилл уверенности в своих силах. Босс как обычно в досадной злобе буркнул свое финальное проклятие — «Мы перезвоним вам через неделю».

Мы вам перезвоним! Aга, после дождичка в полнолуние

Мы вам перезвоним! Aга, после дождичка в полнолуние

И не перезвонил. И отлично, потому что, тогда бы Мишаня не получил оффер от Gaijin, где и начал свое приключение в мире 3d-движков, которые выжимают 60фпс на дохлом железе при сотнях активных объектов на сцене и пром плюсов в полный рост. Знакомо?

Уфф… про световые мечи наверное было лишнее. Сказочным путем Мишани мне довелось пройти всего один раз, видимо сказывались знакомства. Через некоторое время я сам стал проводить собеседования, подбирая людей в команду. Тайны из наших собственных вопросов я никогда не делал, они както сами копились в текстовом файле и даже какое-то время висели на сайте компании в разделе «Карьера» и любой мог их посмотреть. Да и вопросами то их назвать сложно, скорее разные интересные фрагменты кода, которые ведут себя не так как предполагалось, какие-то сложнее, какие-то проще.

Поначалу, участвуя в других собеседованиях, я копировал и сами вопросы и устоявшиеся модели поведения. Но потом это стало надоедать, смысла в том что бы сильно налегать на матчасть или алгоритмы, если человек не впишется в команду не сильно много, если через пару месяцев придется искать нового. А ведь это и мои пара месяцев, которые я потрачу на обучение, отниму это время от своих задач и команды. Я не говорю, что не надо спрашивать алгоритмы, наоборот надо. Пусть человек сам расскажет какие знает и детально расскажет про пару по своему выбору. Перепробовав разные форматы собеседований, от сказочных, хорошо что был мудрый тех лид, который после первого такого праздника вызвал к себе и сказал, чтобы такой фигни он больше не видел, так что этот шаг мы проскочили сразу. Почти. Макс сорян, тебе просто не повезло, я был молод и глуп. Потом были стандартные на 10-15 вопросов на листе в стиле блиц опроса с минутой на обдумывание и небольшого тестового на час здесь и сейчас.

Позже мы с командой пришли к довольно необычному процессу отбора, назовем его «собес наоборот» — именно после таких собесов мы получили несколько отличных спецов в отдел, которые бы не попали к нам по другому. Как потом оказалось, один ненавидел тестовые и просто уходил с собеса, когда ему предлагали их делать, при этом у него шикарный гитхаб (https://github.com/megai2/d912pxy) и человек в одиночку тянул нехилый пет-проект на 1к звездочек. Второй просто выгорел на прошлом месте работы, разочаровался в программировании и спотыкался на базовых вопросах по стандарту и стл, но раскрылся когда речь зашла о низкоуровневых оптимизациях на уровне асма, или надо было пореверсить алгоритм из асма и все подобное этому. Основной упор старались делать на тех людей, знаний которых не хватало в команде, даже если он не проходил по другим критериям. Не утверждаю, что это панацея, собес это в любом случае разговор двух сторон, которые пытаются продать себя друг другу. Как представитель компании, я продаю компанию человеку и смысла врать про пульт управления ракетой нет, если по выходу на пульте придется только перекрашивать кнопки и обновлять разметку. Хвала небесам, это не про игрострой, интересных задач тут навалом, успевай разгребать только.

А че, так можно было?

А че, так можно было?

Со стороны человека который идет в компанию, (как мне кажется) тоже особо нет смысла преувеличивать свое умение чинить пульты разных видов, с плюсиками или бемолями, квадратные или змеевидные. Или хвастаться починкой пультов от Когдазавра и Роботоноида версии 2.0. Все равно квадратный зеленый пульт мы чинить не умеем, или умеем, но еще не знаем как и вечером придется читать документацию. Как представитель себя любимого, я стараюсь получить побольше денег и (поменьше звиздеца в любых проявлениях) интересные для меня качество и количество задач.

В чем идея «собеса наоборот», все тот же список вопросов, будь он неладен, только мы просили выбрать их по желанию и задать нам, сами отвечали на вопросы, иногда сознательно опуская некоторые моменты, иногда внося неточности, а наш будущий коллега, подмечал или не подмечал это. Добавлял и поправлял, или не поправлял наши рассказы, в большинстве случаев человека вовлекали в разговор.

На тех собесах я не помню чтобы мы писали сложный код, чаще вообще не писали, разве что были небольшие пояснения на бумаге. Ломалась сама атмосфера собеседования как экзамена, и вот уже кандидат пытается подловить нас на ответах, задает вопросы не из списка, несколько раз не могли потом успокоить по полчаса — вопросы сыпались один за другим, приходилось кастовать смак-бук и тестовое. Обращали внимание на умение человека ясно и внятно изложить свои мысли, проговорить идею, задать вопрос, потому что матчасть можно подтянуть, не сейчас так через месяц-другой. Скилл в починке скругленных серебристых тачскринов тоже со временем наработается, если человек не особо ленив. А вот культуру умения вести разговор, обмена идеями и обсуждения технических вопросов в ревью, не обидив при этом коллег и не вернув его 120 раз на доработку, осилить (а главное принять) намного сложнее.

Достаем TXT файлик, которому очень много лет

Достаем TXT файлик, которому очень много лет

Собственно это все, что я хотел рассказать. А теперь танцы, т.е. вопросы. Часть вопросов уже поднимались вот тут (https://habr.com/ru/articles/764514/ дичь от Феди, я смотрю многим пришлась по вкусу), надеюсь эти будут не хуже, могут быть дубли. Какие-то вопросы покажутся вам простыми, значит вы много пишите кода, и вообще человек хороший:

0х1…

Начну пожалуй с классического вопроса, что будет выведено в результате работы программы?

void sayHello() {     std::cout << "Hello, World!\n"; } void sayНello() {     std::cout << "Goodbye, World!\n"; } int main() {     sayНello();     return 0; }
А что не так?

А надо скомпилить, тогда в консоль будет выведено «Goodbye, World!». Потому что вторая sayHello написана с использование юникода, который к сожалению не всегда палится. И она же вызывается (clang, latest)

sayHello():                           # @sayHello()         push    rbp         mov     rbp, rsp         mov     rdi, qword ptr [rip + std::cout@GOTPCREL]         lea     rsi, [rip + .L.str]         call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@PLT         pop     rbp         ret "_Z9sayНellov":                         # @"_Z9say\D0\9Dellov"         push    rbp         mov     rbp, rsp         mov     rdi, qword ptr [rip + std::cout@GOTPCREL]         lea     rsi, [rip + .L.str.1]         call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@PLT         pop     rbp         ret

0х2…

А эта функция обладает удивительной способностью изменять реальность в зависимости от того, сколько ног у ее вызывающего.

int abs_legs(int my_legs) {   if (my_legs < 0) {     return -my_legs;   } }
А что не так?

Функция, возвращающая значение, должна возвращать значение (хм!) из всех возможных веток, потому что всегда найдется такое условие которое приведет к неопределенному поведению. Кажется это правило работает не только для функций, которые возвращают значение.

int abs_legs(int my_legs) {   if (my_legs < 0) {     return -my_legs;   }   return my_legs; }

0х3…

Эта функция не раскрывается полностью, оставляя свою магию за покровом компилятора. Где деньги, Зин?

int get_money(int index, const int *pockets) {   int a = index + pockets[++index];   // ...    return a; }
А что не так?

Компилятор может переставить порядок операции для index + pockets[++index], и это приведет к неоднозначному поведению с разными настройками оптимизации. Неупорядоченная или неопределенная последовательность операций приведет к сайд эффекту при работе с переменной index

int get_money(int index, const int *pockets) {   ++index;   int a = index + pockets[index];   // ... }

0х4…

Что будет выведено в bufMatrix?

void umcp_read_buffer_from_pipe() {   char bufKernel[12];   char bufMatrix[12];   std::cin.width(12);   std::cin >> bufKernel;   std::cin >> bufMatrix; }
А что не так?

В этом примере первое чтение не приведет к переполнению, и заполнит bufKernel усеченной строкой. Но второе чтение может переполнить bufMatrix, чтобы этого не произошло надо также вызвать std::cin.width(12); перед получением bufMatrix. Или воспользоваться безопасной работой через строки.

void umcp_read_buffer_from_pipe() {   std::string bufKernel, bufMatrix;   std::cin >> bufKernel >> bufMatrix; }

0х5…

Кажется, в этом коде нильзя-программер просто смешивает случайности с абсурдом и называет это «настройкой отображения».

std::string str_func(); void display_string(const char *);  void set_display_options() {   const char *str = str_func().c_str();   display_string(str); }
А что не так?

Здесь std::string::c_str() вызывается для временного объекта std::string. Полученный указатель будет указывать на освобожденную, но возможно еще валидную, память после уничтожения объекта std::string в конце выражения присваивания. Правда не очень на это надейтесь, это приведет к неопределенному поведению при доступе к этому указателу.

std::string str_func(); void display_string(const char *s);  void set_display_options() {   std::string str = str_func();   const char *cstr = str.c_str();   display_string(cstr); }

0х6…

Сколько раз за вечер бар будет закрыт?

void evening() {   int *bar = new int;   std::shared_ptr<int> p1(bar);   std::shared_ptr<int> p2(bar); }
А что не так?

Закроют бар один раз, потом попробуют снести город, в котором он стоит. Здесь два несвязанных смарт указателя созданы на основе одного и того же базового значения. Когда время жизни переменной p2 закончится, она удалит ресурс, которым управляет. Об этом не узнает переменная p1, которая попробует выполнить удаление этого ресурса повторно, что-то да удалит в итоге. Но это уже совсем другая история.

void evening() {   std::shared_ptr<int> p1 = std::make_shared<int>();   std::shared_ptr<int> p2(p1); }

0х7…

Почему утром опасно ходить в bar?

void morning(const std::string &owner) {   std::fstream bar(owner);   if (!bar.is_open()) {     // Handle error     return;   }    bar << "customer";    std::string str;   bar >> str; }
А что не так?

Потому что можно не выйти. Данные добавляются в конец файла, а затем считываются из того же файла. Однако, поскольку поскольку указатель в файле остался на прежнем месте в конце, то попытка прочитать данные из конца файла приведет к UB. В бар надо ходить вечером, тогда все «замечательно выходит» (с).

void evening(const std::string &owner) {   std::fstream bar(owner);   if (!bar.is_open()) {     // Handle error     return;   }    bar << "customer";    std::string str;   bar.seekg(0, std::ios::beg);   bar >> str; }

0х8…

Какое одно слово надо изменить, чтобы этот код заработал?

struct Foo {   void *foo;   struct foo *next; };  static Foo foos; static std::mutex m;  void consume_list_element(const std::condition_variable &condition) {   std::unique_lock<std::mutex> lk(m);    if (foos.next == nullptr) {     condition.wait(lk);   }    // Proceed when condition holds. }
А что не так?

После того как будет получен сигнал для condition_variable condition, нужно повторно проверить не забрал ли кто элемент пока мы переключали контекст или поток находился в ожидании. Если этого не сделать, возможна работа с указателем на null

  while (foos.next == nullptr) {     condition.wait(lk);   }

0х9…

Почему этот rand() работает как генератор случайных чисел от «Sony»? Здесь надо немного пояснить про этот мем, для генерации rand() в своих секретных ключах Сони использовала секретную функцию, которая из-за ошибки реализации возвращала одно и тоже число.

int main() {   std::string id("ID");   id += std::to_string(std::rand() % 10000);   // ... }
А что не так?

Полученное путем вызова функции rand() число предсказуемо и имеет ограниченную случайность. Чтобы избавиться от повторения сгенерированной последовательности в некоторых реализациях std::rand(), надо выбрать генератор, предоставляющий истинно неповторяющуюся последовательность. Например mtXXXX (https://dl.acm.org/doi/10.1145/272991.272995)

int main() {   std::string id("ID");   std::uniform_int_distribution<int> distribution(0, 10000);   std::random_device rd;   std::mt19937 engine(rd());   id += std::to_string(distribution(engine));   // ... }

0хА…

Получит ли ведьмак монеты?

class Witcher {   int i;  public:   virtual int TakeCoins(int coins) { return i += coins; } };  int main() {   Witcher *geralt = new Witcher();    memset(geralt, 0, sizeof(Witcher));    std::cout << geralt->TakeCoins(100);   return 0; }
А что не так?

Лучше не использовать memset() для работы с полиморфными типами, она потрет указатель на vtbl в классе, такой объект становится невалидным и попытка поработать с ним будут приводить к UB

class Witcher {   int i = 0;  public:   virtual int TakeCoins(int coins) { return i += coins; } };  int main() {   Witcher *geralt = new Witcher();    std::cout << geralt->TakeCoins(100);   return 0; }

0xB…

Станет ли бард пьяницей?

class Bard {   int _beer;   int _meal;  public:   Bard(int meal) : _meal(meal), _beer(_meal - 1) {} };
А что не так?

Cписок инициализации мемберов нарушен и сначала будет установлена переменная _beer, а затем будет инициализирована переменная _meal. Поскольку порядок объявления переменных не соответствует тому, что заявлено в описании класса, попытка прочитать значение _meal приводит к тому, что оно имеем неопределенное значение. Скорее всего бард сопьется без закуси.

class Bard {   int _beer;   int _meal;  public:   Bard(int beer) : _beer(beer), _meal(_beer + 1) {} };

0хС…

В каком случае выстрел по ногам будет особенно результативным?

for (auto format = begin(formats), __end = end(formats); format != __end; ++format) {   if (snd::CodecNamesEq(....)) {     format.is_stereo = true;     formats.push_back(stereo_format);   } }
А что не так?

Если в процессе push_back() произойдет реалокация formats, итератор format будет указывать уже на удаленную память, работа с которой вызовет кучу проблем.

0хD…

Этот код лучше попробовать собрать самому, чтобы понять админ ли вы? (https://onlinegdb.com/rMnPPq5et)

int main() {     std::string access_level = "user";     if (access_level.compare("user‮ ⁦// Check if admin⁩ ⁦")) {         std::cout << "You are an admin.\n";     }     return 0; }

0xE…

Иногда в с++ просто вхух и работает!

.∧_∧ ( ・ω・。)つ━☆・*。 ⊂  ノ    ・゜+. しーJ   °。+ *´¨)          .· ´¸.·*´¨) ¸.·*¨)           (¸.·´ (¸.·'* ☆ Wizz, just works! ☆
Но чаще приходится долго отлаживать

Но чаще приходится долго отлаживать

0xF…

Ни один Мишаня в результате наших собесов не пострадал, Костя ответил на все вопросы и добавил в список пару новых. Особо отличившиеся получили предложение присоединиться к команде, просто умные люди шли дальше на собес в iOS/Android team. А Максим потом нашел себя в бекэнд команде красного банка.

Вот и сказочке конец, а кто дочитал, благодарю за внимание.


ссылка на оригинал статьи https://habr.com/ru/articles/765174/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *