В нашем блоге мы уже давали рекомендации по прохождению собеседований для разработчиков. Важная часть процесса получения новой работы для ИТ-специалиста — выполнение тестового задания. Сегодня мы поговорим о том, каких распространенных ошибок стоит избегать при решении таких демонстрационных задач.
Больше кода, не значит лучше
Существует понятие overengineering, которое описывает желание сделать решение простой по своей сути задачи более масштабным, чем оно того заслуживает в реальности.
Предположим, по условию задачи нам необходимо обойти все файлы в указанной пользователем директории и сложить записанные в них числа. Хоть это задание и предлагается выполнить на объектно-ориентированном языке программирования, нет ничего плохого в решении, написанном в процедурном стиле. Весь код может уместиться буквально в пару десятков строк, так зачем «размазывать» его на нечто большее?
Многие разработчики могут подумать, что подход с написанием классов даже в таком простом случае может показать представителям компании, что кандидат разбирается в основных принципах ООП, а также заботится о дальнейшем сопровождении и развитии кода. На самом же деле зачастую гораздо приятнее видеть, что разработчик может провести грань между «демонстрационным заданием» и «проектом с 10 годами поддержки в будущем».
Не нужно забывать о том, что код тестового задания будет читать человек, у которого, возможно, не так много времени на его доскональное изучение. Краткость — сестра таланта!
Нет единого стиля
Не всегда компания, отправившая кандидату тестовое задание, указывает в требованиях к его выполнению использование определенного стиля программирования. Это не значит, что этого стиля не должно быть — в рамках тестового проекта код должен соответствовать единым стандартам. Какие именно стандарты это будут — решать кандидату, но стиль всех частей решения должен быть единым.
Создание «велосипедов»
При изучении кода одним из первых моментов, которые могут броситься в глаза — наличие в нем «велосипедов», то есть попыток изобрести новое решение тех задач, с которыми можно справиться с помощью стандартных подходов и инструментов.
Зачастую такое желание связано с незнанием (или плохим знанием) стандартной библиотеки использующегося языка или тех библиотек и фреймворков, которые применяются в решении. Наиболее популярные велосипеды — isdigit, to_string, directory_iterator и т.д. Не нужно забывать, что многие компании были бы не против использования сторонних библиотек и решений в проекте: чтобы не проделывать работу, которую уже давно сделали другие разработчики, стоит не стесняться уточнить этот момент как можно раньше.
«Плохие» наименования сущностей и некорректная декомпозиция классов и функций
Пояснять здесь что-то излишне — печально, когда путь до директории называется s, а число, прочитанное из файла, хранится в переменной под названием mynumber.
Дурным тоном также считается выполнение в одной и той же функции нескольких несвязанных по своей сути вещей. Например, когда функция, читающая число из указанного файла, не только, собственно, читает этот файл, но и прибавляет его к переменной, обозначающей сумму.
Неверная организация логирования
Частично этот пункт связан с предыдущим, то есть с неспособностью продумать грамотную декомпозицию функций. Печально видеть, когда get_file_content(const std::string& file_name) самостоятельно пишет в лог в случае ошибок при открытии файла или считывании из него данных. Гораздо лучше пробрасывать информацию о произошедшей ошибке уровнем выше, где она и будет обрабатываться должным образом.
Многим кандидатам будет полезно узнать, что функция std::ifstream::read сама пишет в лог.
Применение слишком низкоуровневых инструментов
Как это ни странно, но многие разработчики так и норовят использовать в своих решениях такие low-level API, как WinAPI или POSIX API, даже тогда, когда аналогичные средства доступны и в стандартной библиотеке соответствующего языка. Некоторые делают это ради того, чтобы показать, что работа с низкоуровневыми интерфейсами не является для них существенной проблемой, кто-то стремится к «увеличению производительности», и лишь немногие делают это из-за того, что они по-настоящему хорошо знакомы с данным API.
Не нужно пытаться удивить работодателя низкоуровневыми подходами к реализации решения. Каждой задаче — свои инструменты! К тому же, чем более низкоуровневый инструмент используется, тем, как правило, выше шанс допустить ошибку при его применении.
Преждевременная оптимизация
Частично этот пункт уже был затронут выше, но здесь речь идёт не столько об использовании WinAPI / POSIX, сколько об усложнении общей архитектуры приложения ради получения решения, которое работает лишь немного быстрее.
Чем проще читается решение, тем меньше времени потратит на него представитель нанимающей компании, а это повышает шансы кандидата перейти на следующий этап отбора. Важно помнить — разработчики чаще читают код, чем его пишут, так что всегда следует задумываться о тех людях, которые будут поддерживать создаваемое решение. Будет ли им удобно разбираться в проделанных кандидатам оптимизациях? Увеличат ли они время, затрачиваемое на внесение изменений в код, и стоят ли они того?
Оптимизацией следует заниматься только в том случае, если в условии задачи явно были оговорены требования по производительности приложения, и только в тех местах, которые являются «бутылочным горлышком» — тогда имеет смысл вооружиться профилировщиком или отладочной печатью.
Создание неуниверсальных решений
Многие кандидаты пишут решения, которые работают лишь на определённом классе входных данных. Например, предположение о том, что у файла есть расширение, и оно всегда состоит из трёх символов.
Что самое интересное, зачастую на написание универсального решения будет затрачено немногим больше времени, чем на написание частной версии. Так зачем мучать себя и других?
Использование магических чисел
Ох уж эти магические числа! Сколько всего было сказано и написано на эту тему! Как бы то ни было, разработчики до сих продолжают использовать их в своих решениях.
Иногда это 256 для буфера, содержащего путь до файла, 8 в качестве размера thread pool и 512 в качестве размера для содержимого файла.
Следует помнить, что в большинстве API есть соответствующие инструменты для подобных сущностей — в WinAPI есть MAX_PATH, в стандартной библиотеке C++ и boost есть hardware_concurrency, а размер буфера, хранящего содержимое файла, можно и не задавать вовсе — вместо него достаточно просто использовать std::string.
Отсутствие RAII
Если речь идёт о языках, по своей природе располагающих к автоматическому освобождению ресурсов, этим надо пользоваться в 99% случаев. Вместо new и delete — умные указатели, вместо fopen и fclose — std::fstream… В общем, идея понятна.
Автор: Никита Трофимов, разработчик ПО
Другие интересные статьи в блоге GMS:
- Простые правила успешного собеседования по телефону: Советы соискателям
- Секреты успешного выполнения тестовых заданий от Palantir: преодоление сложности в разработке алгоритмов
- Какие вопросы на собеседовании больше всего нравятся разработчикам
- Руководство для разработчиков: 7 советов по прохождению собеседования
- Интервью с разработчиками: на что обращают внимание в Microsoft, Google, Amazon и других ИТ-компаниях