суббота, 29 мая 2010 г.

[work] Тестовое задание, которое я давал кандидатам на должность C++ программиста

В продолжение разговоров о тестовых заданиях перед собеседованием (часть I, часть II, часть III) публикую решение той задачи, которую я до недавнего времени давал всем кандидатам на должность C++ программиста. Спецификация задачи в том виде, в котором я ее отсылал кандидатам, находится под катом.

Через один-два дня я опубликую свои комментарии по этой задаче: почему она мне нравится, что она показывает, какие качества разработчика она, на самом-то деле, раскрывает и т.д.

Введение

Есть множество текстовых файлов (исходных текстов программ) в которые нужно вставить текст лицензионного соглашения. Например, был файл:

#include <iostream>

int
main()
   {
      std::cout << "Hello, world!" << std::endl;
   }

из которого должен быть получен файл:

/*
   Copyright 2004 Вася Пупкин
*/
#include <iostream>

int
main()
   {
      std::cout << "Hello, world!" << std::endl;
   }

Так же, должна быть необходимость смены лицензионной информации. Т.е. заменить существующий фрагмент на новый фрагмент. Например, в приведенном выше тексте сменить лицензионную информацию на:

/*
   Copyright 2004 Вася Пупкин
   Copyright 1982-2004 Б.Страуструп
*/

А так же должна быть возможность изъятия лициензионной информации. Например,
в приведенном примере вернуть текст программы к первоначальному виду.

Спецификация

Должно быть создано консольное приложение с именем licenseins, которое получает все аргументы из командной строки. Параметры командной строки должны задаваться в двух формах: короткой (начинаются с дефиса, за которым следует один символ) или длинной (начинаются с двух дефисов, за которым следует идентификатор произвольной длины). Должны поддерживаться следующие аргументы:

  • -m {ins|del|chanege} (--mode {ins|del|change})
    режим работы приложения:
    • ins -- вставка лицензионной информации в начало файла;
    • del -- изъятие лицензионной информации из файла;
    • change -- замена старой лицензионной информации на новую. В этом режиме на вход приложения должно быть передано два имени файлов с лицензионной информацией: старый текст, который должен быть изъят, и новый текст.
  • -f <имя> (--file <имя>)
    задает имя одного файла, в котором нужно выполнять преобразование. В случае, если должны быть обработаны несколько файлов, для каждого файла должен быть указан свой аргумент -f (—file). Имена файлов должны указываться без wildcard-символов (т.е. без * и ?).
  • -l <имя> (--license <имя>)
    задает имя файла, в котором находится текст лицензионной информации для вставки или удаления из файла.
  • -o <имя> (--old <имя>)
    задает имя файла, в котором находится текст старой лицензии. Этот аргумент может использоваться только в режиме change.
  • -h (—help)
    выдача справки об использовании программы.

Приложение, запущенное без аргументов, должно вести себя так, как будто ему задан один аргумент -h (—help).

Например:

1. Вставка лицензионной информации из файла copying2003 в файлы interface.hpp, interface.cpp:

$ licenseins --file interface.cpp --file interface.hpp --license copying2003 -m ins

2. Замена лицензионной информации в файле interface.cpp на новую из файла copying2004:

$ licenseins -f interface.cpp -l copying2004 --mode change --old copying2003

3. Изъятие лицензионной информации из файла interface.hpp

$ licenseins --mode del -f interface.hpp --license copying2003

Приложение должно помещать новое содержимое модифицированного файла в файл с тем же самым именем. Не должно оставаться никаких back-up файлов, если приложение завершило свою работу успешно. Нужно реализовать возможность восстановления содержимого файла, если приложение завершилось аварийно (например, исходный файл переименовывается в *.tmp, создается новый файл с исходным именем, в него записывается новое содержимое, *.tmp уничтожается). Политика обеспечения восстановления после сбоев не специфицируется и может быть выбрана по усмотрению разработчика. Если для выбранной политики требуются дополнительные аргументы (например, путь для сохранения временных файлов), то они могут быть добавлены разработчиком по своему усмотрению, но должны допускать две формы записи.

Приложение должно завершаться с нулевым кодом возврата, если все преобразования завершились успешно. В противном случае приложение должно возвращать не нулевой код.

Бонусное условие

Необходимо предусмотреть возможность задания большого количества аргументов -f (--file) (больше, чем может поместиться в командной строке) путем поддержки т.н. response-файлов. Если в командной строке указан аргумент вида @<имя>, то <имя> нужно интерпретировать как имя файла, в котором задаются не поместившиеся в командную строку аргументы. В response файле аргументы не обязательно должны перечисляться в одну строку: они могут быть расположенны по одному в одной строке, по несколько в одной строке и т.д. В response-файле могут быть пустые строки. В response-файле не может быть ссылок на другие response-файлы – это ошибка. В командной строке может быть указано несколько response-файлов.

Примечание. Реализация response-файлов необязательна. Если поддержка response-файлов будет сделана – очень хорошо. Если нет – не страшно.

Требования

Требования к оформлению исходных текстов

В данной задаче не предъявляются.

Требования к языку

Приложение должно быть разработано на языке C++. Стиль разработки не оговаривается -- приложение может быть разработано в объектно-ориентированном или процедурном стиле. Разработчик должен выбрать тот стиль, который обеспечивает ему наибольшую скорость разработки приложения.

Требования к используемым библиотекам

Должны использоваться только стандартные библиотеки C и C++. Если у кого-то есть собственные библиотеки, которые могут упростить написание приложения (например, средства разбора аргументов командной строки или высокоуровневые операции ввода-вывода в файлы), то они могут быть использованы при выполнении следующих условий:

1) библиотеки написаны с использованием только стандартных библиотек C и C++;

2) исходные тексты библиотек включены в состав исходных текстов приложения.

Другие условия и ограничения

Размеры файлов

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

Нельзя расчитывать на то, что каждый из исходных текстов может быть загружен полностью в оперативную память. Например, могут быть большие XML-файлы с "снимками" какой-либо БД.

Концы строк

На платформе Windows в конце каждой строки текстового файла используется маркер из двух символов: \r\n (возврат каретки, первод строки). На платформе Unix в качестве маркера используется один символ: \n. На платформе Mac используется один символ: \r.

При поиске места вхождения лицензионной информации в исходном файле необходимо игнорировать различия в маркерах концов строк.

При вставке текста лицензионной информации нужно использовать те маркеры, которые были в файле текста лицензии.

15 комментариев:

Alexander P. комментирует...

> 2. Замена лицензионной информации в файле interface.cpp на новую из файла copying2004:
> $ licenseins -f interface.cpp -l copying2003 --mode change --old copying2003
Видно, очепятка при копировании: в вызове `-l copying2004`.

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

Вообще, кажется, хорошей задачкой. Не слишком сложной и всё-таки более-менее нужной.

eao197 комментирует...

>Видно, очепятка при копировании: в вызове `-l copying2004`.

Ага, опечатка. Спасибо, поправил.

>Вообще, кажется, хорошей задачкой.

Дождитесь комментариев, там будет интересно :)

>Не слишком сложной и всё-таки более-менее нужной.

Не столько нужной, сколько осмыленной.

имя комментирует...

задачка приятная, но есть замечание:

совершенно избыточные и неприспособленные для юникса флаги; удобнее было бы:

для замены лицензии: program -i new_lic.txt -d old_lic.txt file1.h file2.cpp file3.cpp

для вставки лицензии, если в файлах есть ведущие минусы: program -i new_lic.txt -- -lead_minus.xml

для вставки лицензии, список файлов задается в файле: program -i new_lic.txt -f filelist.txt

имя комментирует...

ну и те, кто решить не смог, просто не старались или были еще детьми, не осознавшими, что у них ошибки могут быть

имя комментирует...

про длинные варианты опций не написал т.к. казалось очевидным:

для замены лицензии: program --insert new_lic.txt --delete old_lic.txt -- file1.h file2.cpp file3.cpp

eao197 комментирует...

2имя:

>для замены лицензии: program -i new_lic.txt -d old_lic.txt file1.h file2.cpp file3.cpp

По большому счету это дело вкуса. Тем более, что если уж поддерживать Unix-стиль, то нужно было бы заставляь кандидатов обрабатывать ситуации вроде -lcopying2003, -l copying2003, --lice copying2003, --license copying2003, --license=copying2003 и т.п.

Ключ -m позволяет посмотреть, как кандидат будет задавать константы ins, del, change в коде. Как будет вводить в коде режим работы -- через флаги или еще как-то.

Ключ -f сказывается при разборе response-файлов -- его нужно специальным образом вылавливать.

>ну и те, кто решить не смог, просто не старались или были еще детьми, не осознавшими, что у них ошибки могут быть

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

имя комментирует...

> Тем более, что если уж поддерживать Unix-стиль, то нужно было бы заставляь кандидатов обрабатывать ситуации вроде

совсем не обязательно; а вот необходимость постоянно вставлять -f не дает возможность задать файлы по маске в юниксе или даже в cygwin, где * обрабатывает шелл, а не command.com/cmd.exe

имя комментирует...

я хотел сказать "а не сама программа"

eao197 комментирует...

>а вот необходимость постоянно вставлять -f не дает возможность задать файлы по маске в юниксе или даже в cygwin, где * обрабатывает шелл, а не command.com/cmd.exe

Эти соображения актуальны для реального инструмента. Для тестового задания проще, когда все параметры единообразны -- флаг + значение.

night beast комментирует...

у кандидата была возможность задавать уточняющие вопросы, возникающие по ходу решения?
отсутствие комментариев сильно влияло на оценку кандидата?

eao197 комментирует...

>у кандидата была возможность задавать уточняющие вопросы, возникающие по ходу решения?

Да. Вопросы, которые кандидат задавал по заданию влияли на оценку кандидата так же, как и качество кода. Со временем при отсылке задачи кандидату, я даже стал делать обязательную приписку, что лучше переспросить, чем понять задачу по-своему и сделать неправильно.

>отсутствие комментариев сильно влияло на оценку кандидата?

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

night beast комментирует...

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

просто задача вроде "одноразовая" (сделал и забыл).
комментарии не сильно нужны (ИМХО)

eao197 комментирует...

>комментарии не сильно нужны (ИМХО)

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

9000 комментирует...

Хорошее задание. Но писать его на C++ довольно странно, когда есть питон, руби, перл, на худой конец. Это как писать оптимизированную расчётную программу на питоне, когда есть C++ или даже фортран :)

eao197 комментирует...

>Хорошее задание. Но писать его на C++ довольно странно, когда есть питон, руби, перл, на худой конец.

Это если писать работающий инструмент, тогда у руби и питона будут преимущества. А в качестве тествой задачи для C++ самое оно. Тем более, что если человек более-менее знает STL, то с помощью std::vector и std::string данная задача решается без особых проблем.

А вот на чистом C это действительно оверкил.