Условия работы в СКб Контур

Правила, для которых нет исключений, или несколько строк, которые можно отнести к каждой из вакансий СКБ Контур.
Подробнее...

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

Итак, поехали!

Формат ввода и вывода

Примерно треть всех решений не соблюдают форматы ввода и вывода. Они либо имели лишний вывод на экран, либо лишнее ожидание нажатия любой кнопки в конце работы, либо неверное форматирование вывода (полный путь, вместо имени файла без пути, пробелы вместо табов, и т п.), либо неверные имена файлов с результатами. Мы старались не откидывать решения только из-за этого вида ошибок. Однако всё это несколько усложнило процесс автоматического тестирования ваших решений.

Очень сложно работать в команде с людьми, которые небрежно относятся к договоренностям и систематически решают не ту задачу, которую нужно было. Учитесь не совершать таких ошибок!

Корректность

Часть решений на некоторых входных данных завершали свою работу необработанным исключением. Естественно, более качественное тестирование помогло бы вам избежать таких ошибок. В частности можно было последовать совету, данному в задаче и прогнать свое решение на исходниках каких-либо больших open-source проектов. Несколько десятков мегабайт разнообразного кода помогли бы вскрыть все особые необычные частные случаи.

Программист ответственен за качество своего кода. Он может делать все что угодно, чтобы гарантировать это качество. Хороший программист — всегда немножко параноик, никогда себе не доверяет и всегда выдумывает какие-то дополнительные способы проверить коректность своего творения. Кому-то для этого нужно написать кучу тестов, кто-то способен увидеть ошибки в коде просто перечитывая его на свежую голову, кто-то старается организовать код так, чтобы ошибки в нем были лучше видны. И лишь Чак Норрис способен держать все детали задачи в голове и выдавать на гора сразу корректный код. Вы, скорее всего, не Чак Норрис!:-)

Определение “функции”

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

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

Однако одна из самых частых проблем в решениях — неправильная интерпретация этого определения. Например, некоторые совершенно безосновательно считали, что Roslyn будет представлять все “функции” наследниками класса BaseMethodDeclarationSyntax.

Человеческий мозг склонен иногда делать необоснованные умозаключения и логически некорректные выводы. Хороший программист должен уметь избегать подобных ошибок. Это ещё одна причина быть немножко параноиком и перепроверять все свои действия.

Производительность

Запуск вашего решения на большом объеме исходников заодно помог бы вам обнаружить ещё и проблемы с производительностью вашего решения. Какая производительность является приемлемой? Например, можно было бы сравнить скорость работы вашего решения со скоростью простого чтения и парсинга всех исходников из той же директории. Конечно же поиск самых длинных и самых вложенных функций не должен давать ощутимой добавки к скорости чтения и парсинга.

В наше время производительность программистов гораздо более деффицитная штука, чем производительность процессоров. Поэтому во многих случаях имеет смысл немного пожертвовать производительностью в пользу читаемости и простоты кода. Однако, никому не нравятся программы работающие в 100 раз медленней, чем могли бы. Умение создать простой код, одновременно являющийся ещё и эффективным — это все ещё важное конкурентное преимущество хороших программистов над плохими!

Размер решения

Краткость сестра таланта. Хорошо отформатированное и аккуратно оформленное корректное решение задачи легко умещалось всего в 150-200 строк кода.

При этом были решения, которые превысили этот предел в 3-4 раза.

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

Качество кода

Как и обещали, при проверке мы особенное внимание уделяли качеству кода. Это очень большая и сложная тема. Мы приведем лишь несколько пунктов, с которыми чаще всего были проблемы в ваших решениях.

Длинные методы и большой уровень вложенности — это плохо, и мы недвусмысленно намекали на это в условии. Однако, в некоторых решениях все равно встречались методы размером до 100 операторов и уровень вложенности более 5. Старайтесь, чтобы ваши методы не разрастались более 20-30 операторов, а уровень вложенности не был больше 3. Если метод становится слишком большим — подумайте как наиболее логично можно его разбить на подзадачи.

Для тренировки вы можете попробовать очередную программу написать с искусственным ограничением “не более 4 строк на метод”. Конечно, в промышленном программировании такое ограничение не имеет смысла, однако это отличное упражнение для тренировки умения проводить декомпозицию.

Основной способ сделать код понятнее — это давать хорошие смысловые имена функциям, аргументам и переменным. Однобуквенные и малоосмысленные имена к сожалению присутствовали в некоторых решениях. О том, как правильно давать имена в ваших программах хорошо написано в книге Роберта Мартина “Чистый код”. Краткий пересказ основных идей можно прочитать тут.

Часто можно было увидеть нарушение принципа DRY (Dont repeat yourself) и длинные блоки почти полностью повторяющегося кода. Такой код обычно сложно поддерживать и трудно читать.

Некоторым не хватило знания языковых возможностей C#. Например, сортировку функций в нужном порядке можно делать и без создания собственных компараторов. Файловый ввод-вывод можно делать без явного манипулирования StreamReader-ами. А для манипулирования данными в C# существует удобный язык LINQ-запросов.

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

Авторское решение

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

Если оно покажется вам не идеальным — добро пожаловать в комментарии под этим gist-ом!

Заполните, пожалуйста, все поля.

Предложение, замечание, просьба или вопрос.