История одного фееричного провала тестового задания на C#

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

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

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

Во вложение класс C#, который предлагается реализовать. Описание методов - в xml-комментах. Обращаю Ваше внимание, что класс должен быть эффективным и не использовать много памяти и ресурсов даже тогда, когда в расписании задано много значений. Например очень много значений с шагом в одну миллисекунду.

Вложенный в задание файл schedule.cs
using System;

namespace Test
{

	/// <summary>
	/// Класс для задания и расчета времени по расписанию.
	/// </summary>
	public class Schedule
	{
		/// <summary>
		/// Создает пустой экземпляр, который будет соответствовать
		/// расписанию типа "*.*.* * *:*:*.*" (раз в 1 мс).
		/// </summary>
		public Schedule()
		{
		}

		/// <summary>
		/// Создает экземпляр из строки с представлением расписания.
		/// </summary>
		/// <param name="scheduleString">Строка расписания.
		/// Формат строки:
		///     yyyy.MM.dd w HH:mm:ss.fff
		///     yyyy.MM.dd HH:mm:ss.fff
		///     HH:mm:ss.fff
		///     yyyy.MM.dd w HH:mm:ss
		///     yyyy.MM.dd HH:mm:ss
		///     HH:mm:ss
		/// Где yyyy - год (2000-2100)
		///     MM - месяц (1-12)
		///     dd - число месяца (1-31 или 32). 32 означает последнее число месяца
		///     w - день недели (0-6). 0 - воскресенье, 6 - суббота
		///     HH - часы (0-23)
		///     mm - минуты (0-59)
		///     ss - секунды (0-59)
		///     fff - миллисекунды (0-999). Если не указаны, то 0
		/// Каждую часть даты/времени можно задавать в виде списков и диапазонов.
		/// Например:
		///     1,2,3-5,10-20/3
		///     означает список 1,2,3,4,5,10,13,16,19
		/// Дробью задается шаг в списке.
		/// Звездочка означает любое возможное значение.
		/// Например (для часов):
		///     */4
		///     означает 0,4,8,12,16,20
		/// Вместо списка чисел месяца можно указать 32. Это означает последнее
		/// число любого месяца.
		/// Пример:
		///     *.9.*/2 1-5 10:00:00.000
		///     означает 10:00 во все дни с пн. по пт. по нечетным числам в сентябре
		///     *:00:00
		///     означает начало любого часа
		///     *.*.01 01:30:00
		///     означает 01:30 по первым числам каждого месяца
		/// </param>
		public Schedule(string scheduleString)
		{
		}

		/// <summary>
		/// Возвращает следующий ближайший к заданному времени момент в расписании или
		/// само заданное время, если оно есть в расписании.
		/// </summary>
		/// <param name="t1">Заданное время</param>
		/// <returns>Ближайший момент времени в расписании</returns>
		public DateTime NearestEvent(DateTime t1)
		{
		}

		/// <summary>
		/// Возвращает предыдущий ближайший к заданному времени момент в расписании или
		/// само заданное время, если оно есть в расписании.
		/// </summary>
		/// <param name="t1">Заданное время</param>
		/// <returns>Ближайший момент времени в расписании</returns>
		public DateTime NearestPrevEvent(DateTime t1)
		{
		}

		/// <summary>
		/// Возвращает следующий момент времени в расписании.
		/// </summary>
		/// <param name="t1">Время, от которого нужно отступить</param>
		/// <returns>Следующий момент времени в расписании</returns>
		public DateTime NextEvent(DateTime t1)
		{
		}

		/// <summary>
		/// Возвращает предыдущий момент времени в расписании.
		/// </summary>
		/// <param name="t1">Время, от которого нужно отступить</param>
		/// <returns>Предыдущий момент времени в расписании</returns>
		public DateTime PrevEvent(DateTime t1)
		{
		}
	}
}

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

Меня сразу насторожило неконкретное требование «класс должен быть эффективным и не использовать много памяти и ресурсов», ведь понятия «эффективно» и «много» каждый понимает по-своему. Чтобы грубо не нарушать эти требования, я решил сразу отметать плохо зарекомендовавшие себя в плане эффективности практики типа регулярных выражений и частого выделения объектов в «куче» (heap) чтобы не нагружать сборщик мусора. А также предусмотреть потенциальные пути оптимизации на случай если нужно будет улучшать быстродействие или уменьшать выделяемую память. Добиваться каких то экстремальных показателей в плане оптимизации нет смысла, потому что это приведёт к снижению такого важного показателя как поддерживаемость кода, а будет ли от этого польза — непонятно, поскольку неизвестны условия эксплуатации. На случай будущего сравнения разных оптимизаций, сразу добавил в проект бенчмарки.

Главное, на чём я решил сосредоточиться при выполнении задания — аккуратность обращения с календарём. Ведь, как известно, наш Григорианский календарь является нерегулярным. Все знают, что не каждый год содержит 365 дней и не каждый месяц содержит 31 день. В дополнение к этому, не каждая минута содержит 60 секунд. Не говоря уже о введениях/отменах перехода на зимнее время. Поэтому сразу было решено отказаться от арифметических операций с временами и датами и использовать для этого только библиотечные методы в классах DateTime или DateTimeOffset.

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

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

Моё решение размещено на гитхабе в виде проекта Visual Studio. Я не понимаю, почему я получил оценку «отвратительно, халтурно»! И неужели сейчас принято так оценивать задания: не говорить в чём проблема, не давать направлений для дальнейшего совершенствования специалиста? Я показал проект уважаемому коллеге, он указал только на те недостатки, которые я и сам вижу и это не объясняет низкой оценки. Уважаемые специалисты, объясните, что не так с моим тестовым заданием? Обещаю, что дополню статью выявленной информацией.

Источник: https://habr.com/ru/post/571342/


Интересные статьи

Интересные статьи

«Раздражающий, чарующий, манящий — вы можете подобрать острову Оук любое прилагательное, и оно окажется верным», — считает Чарльз Баркхауз, историк, с которым для шоу «Пр...
Aqua-Fi — это новая технология для скоростного беспроводного интернета под водой. Обычный Wi-Fi в этих условиях бессилен. Подводные телекоммуникации всегда были проблемой. Радиосиг...
В прошлый раз мы рассказали о том, как появление удобных ПК помогло эволюции образовательного ПО, в том числе и виртуальных преподавателей. Последние оказались достаточно продвинутыми прототипами...
Задача: Есть ПК без интернета но есть возможность перекинуть файл по USB. Есть планшет с интернетом с которого этот файл можно перекинуть. На планшет можно скачать нужный торрент но не достато...
Некоторое время назад мне довелось пройти больше десятка собеседований на позицию php-программиста (битрикс). К удивлению, требования в различных организациях отличаются совсем незначительно и...