— Если учитывать параметр длины строки, можно убедиться, что код не выходит за конец строки. Хорошо бы проверить значение параметра длины для надежности. Строки, завершаемые null-символом, — лучшие друзья хакера.
— Если существует вероятность того, что буфер может быть модифицирован другим потоком во время выполнения этой функции, это может создать проблемы с безопасностью.
— Должна ли эта проверка выполняться в конструкции try-catch? Если вызывающий код не ожидает исключений, вероятно, ему следует вернуть код ошибки. Если существуют коды ошибок, насколько четко они определены и задокументированы?
Так кандидат показывает, что он думает широко, учитывая окружение кода своей функции и условия ее выполнения. Такой подход помогает избежать путаницы и упущений в дальнейшем.
В итоге самые лучшие кандидаты найдут свой угол зрения на эти вопросы. Любой способ решения задачи хорош, если он обоснован.
На заметку
Хорошему кандидату на роль разработчика в тестировании не нужно напоминать, что написанный им код нужно тестировать. Он должен считать тестирование частью решения.
На самом деле любой инженер, прошедший вводный курс программирования, сможет написать код для решения этой задачи. А вот вопросы и подход к решению отличают лучших кандидатов. Мы стараемся создать комфортную обстановку на собеседовании, чтобы кандидат не стеснялся задавать вопросы. Если он молчит, то мы можем легонько подтолкнуть его к разговору, чтобы понять, не вызвано ли стремление поскорее погрузиться в код атмосферой интервью. Сотрудники Google должны уметь задавать любые вопросы, лишь бы это помогло решению задачи.
Было бы скучно перебирать сотни правильных реализаций и распространенные ошибки — все-таки книга не о программировании и не о проведении собеседований. Поэтому мы покажем только одну простую и очевидную реализацию. Заметим, что кандидаты обычно используют язык, который знают лучше всего, например Java или Python. При этом стоит задать дополнительные вопросы, чтобы убедиться, что они правильно понимают специфические детали языка: как язык работает с памятью и типами данных, особенности компиляции и выполнения.
int64 Acount(const char* s) {
if (!s) return 0;
int64 count = 0;
while (*s++) {
if (*s == 'a') count++;
}
return count;
}
Кандидат должен уметь объяснять свой код, показывать, как меняются значения указателей и счетчиков в процессе выполнения кода с тестовыми входными значениями.
Еще раз. Достойный кандидат на роль разработчика в тестировании:
— Решает эту задачу без особых проблем. Он пишет код с первого раза, не путается в базовом синтаксисе и не смешивает конструкции из разных языков.
— Правильно понимает работу указателей и не засоряет память.
— Реализует проверку входных данных, чтобы избежать проблем, связанных с null-указателями, или может объяснить, почему он этого не делает.
— Понимает, что время выполнения его кода линейно зависит от объема входных данных. Если зависимость нелинейная, это может говорить о творческом подходе, но может оказаться просто ошибкой.
— Исправляет мелкие недочеты в коде, если вы на них укажете.
— Пишет четкий и легко читаемый код. Если кандидат использует побитовые операции или пишет несколько команд в одной строке — это плохой признак, даже если код работает.
— Может рассказать последовательность выполнения своего кода для тестового ввода «A» или null.
Более талантливые кандидаты способны на большее. Они:
— Предлагают использовать int64 для типа счетчиков и возвращаемого значения, чтобы обеспечить будущую совместимость и предотвратить переполнение, если кто-то использует функцию для подсчета букв «A» в очень длинной строке.
— Пишут код, который работает для сегментированного или распределенного выполнения вычислений. Некоторые кандидаты, не знакомые с MapReduce, могут самостоятельно прийти к простым способам уменьшить задержки, обрабатывая большие строки параллельно.
— Записывают свои предположения в примечаниях или комментариях к коду.
— Используют много вариантов входных данных и исправляют все найденные баги. Если кандидат на роль разработчика в тестировании не ищет и не исправляет баги — это тревожный признак.
— Тестируют свои функции до того, как их попросят. Наши люди должны тестировать без напоминаний.
— Продолжают попытки оптимизировать свое решение до тех пор, пока их не попросят остановиться. Никто не может быть уверен, что его код идеален, через пару минут программирования и прогона нескольких тестовых вариантов входных данных. Кандидаты должны стремиться к тому, чтобы в итоге все работало правильно.
Дальше мы должны понять, сможет ли наш кандидат протестировать свой код. Запутанный или нелогичный тестовый код — это, наверное, самое худшее в мире. Уж лучше вообще без тестов. При отладке упавшего теста должно быть очевидно, что этот тест делал. Если это не так, разработчики могут отключить тест, пометить его как ненадежный или проигнорировать сбой — такое бывает. В том, что плохой тестовый код оказался в репозитории, виноваты все, кто писал и рецензировал код.
Разработчик в тестировании должен уметь тестировать и методом черного ящика, предполагая, что функцию написал кто-то другой, и методом белого ящика, зная, какие тест-кейсы не имеют смысла из-за особенностей реализации.
Подытожим. Признаки достойного кандидата:
— Действует методично и систематично. Выстраивает тестовые данные по определенной понятной схеме, например по размеру строки, а не просто выдает в виде случайного набора.
— Фокусируется на генерации реально полезных тестовых данных. Думает о том, как проводить большие тесты и где взять реальные тестовые данные.
Признаки лучшего кандидата:
— Пытается реализовать выполнение функции в параллельных потоках, чтобы выявить перекрестные помехи, дедлоки и утечку памяти.
— Создает тесты с большой продолжительностью выполнения. Например, запускает тесты в цикле while(true), чтобы убедиться, что они не падают со временем.
— Не перестает выдавать тестовые сценарии и предлагать новые подходы к тестированию, выбору данных, проверке и выполнению тестов.
Пример отличного кандидата
Джейсон Арбон
Одного нашего кандидата (который, кстати, уже великолепно справляется с работой в Google) спросили, как бы он организовал тестирование граничных условий для версии этой функции с 64-разрядными целыми числами. Он быстро догадался, что задача физически неразрешима из-за ограничений по времени и объемам данных. Но из любопытства, подогретого нетривиальностью задачи, прикинул, как можно хотя бы разместить большие объемы данных для таких тестов. В качестве входных данных он взял веб-индекс Google.
Как же он проверил свое решение? Кандидат предложил использовать параллельную реализацию и посмотреть, дадут ли обе реализации одинаковый результат. Он предложил применить метод статического выборочного контроля: знаем ли мы, какова предполагаемая частота появления буквы «А» на веб-страницах? Если нам известно количество проиндексированных страниц, мы можем сравнить результат вычислений программы с результатом, полученным теоретическим путем. Это подход к тестированию в духе Google. Хотя мы не стали реализовывать эти гигантские тесты, сама возможность таких решений наталкивает на интересные идеи для реальной работы.
Еще один параметр, который мы проверяем на собеседовании, — «гугловость», то есть соответствие нашей культуре. Насколько кандидат технически любознателен? Может ли он интегрировать в свое решение новые идеи? Как он справляется с неоднозначностью? Знакомы ли ему академические методы проверки качества, например доказательства теорем? Разбирается ли он в метриках качества или автоматизации в других областях, например в самолетостроении?
Пытается ли он оправдать наличие багов, которые мы нашли в его реализации? Может ли мыслить масштабно? Кандидаты, конечно, не обязаны обладать всеми перечисленными свойствами, но чем больше совпадений, тем лучше. И наконец, хотим ли мы вообще работать с этим человеком каждый день?
Важное замечание: если человек, проходящий интервью на должность разработчика в тестировании, оказался не таким уж сильным программистом, из него может получиться хороший инженер по тестированию. Некоторые из наших лучших инженеров по тестированию изначально пробовались на должность разработчика в тестировании.
Интересно, что мы часто упускаем хороших кандидатов, потому что за время собеседования они склоняются в сторону чистого программирования или, наоборот, зацикливаются на тестировании. Мы хотим, чтобы кандидатов собеседовали специалисты разных должностей, которым потом придется с ними работать, потому что роль разработчика в тестировании — это сплав двух ролей, и иногда ее сложно правильно оценить на собеседовании. Нужно быть уверенным в том, что плохие оценки выставлены людьми, понимающими такую двуликость хорошего кандидата.