Почему-то подумал я, что уж ради столь уважаемой здесь компании можно-таки поступиться принципами. И вот чего получилось.
Прошу уважаемое сообщество выдать свою оценку. Конкретно, где имеет место быть несоответствие заданию, какие есть минусы и в чем я не прав. Времени ушло много, и очень хочется вынести хоть какую-нибудь пользу из этого.
Позиция: Senior C# Developer (Москва).
Задание: здесь
Мое решение: здесь
Мои комментарии, отправленные вместе с решением: здесь
Резюме специалиста, проверявшего задание (с большим трудом удалось извлечь): здесь
Мой ответ на резюме специалиста (выслал его вместе с доработанным вариантом решения) выложу отдельным постом. Надо ли говорить, что больше писем из уважаемой компании мне не поступало...
Еще пара слов по поводу "архитектурного решения". Само собой, для максимальной производительности в реальной жизни следовало бы реализовать разбиение на блоки, таблицу этих блоков, дефрагментацию и еще много чего. Но я сделал так, как сделал, потому что:
Скажем так: мое желание работать в уважаемой компании, в принципе, измеряется 20-ю жопочасами, потраченными на этот вариант. Но никак не 60-ю жопочасами, которые бы ушли на выработку и отладку более эффективного решения.
Мои попытки сотворить новую файловую систему для Линукса в рамках выполнения тестового задания смотрелись бы ну очень забавно.
1. Синхронизации я так и не нашел, что, собственно, и удалось легко подтвердить, запустив многократно "Start
massive test in a separate thread", что привело к падению.
А параллельная работа с файловой системой — первое требование.
Параллельная работа с файловой системой реализована. Синхронизацию можно увидеть здесь:
/// <summary>
/// A facade for VirtualFS. Thread-safe.
/// </summary>
[Synchronization]
public class FsDisk : ContextBoundObject, IDisposable
{
...
}
и здесь:
/// <summary>
/// A synchronized FileStream
/// </summary>
[Synchronization]
internal class FsSyncFileStream : ContextBoundObject, IDisposable
{
...
}
Конкретно, она реализована с помощью контекста синхронизации. Все публичные методы класса FsDisk — атомарны. Все публичные методы класса FsFileStream — также атомарны. Поскольку экземпляры FsFileStream создаются внутри экземпляра FsDisk, они используют тот же контекст, т.е. пока выполняется любой метод FsDisk, другие потоки не могут войти в другие методы FsDisk и в методы FsFileStream.
Насколько я понял, "падение" возникало следующее:
При параллельной работе нескольких тестовых потоков периодически возникает ситуация, когда два потока пытаются обратиться к одному и тому же файлу. В соответствии с заданием, в этом случае FsDisk кидает исключение:
UnauthorizedAccessException("The file is open");
которое ловится в вызывающем методе. Эта ситуация нормальна, и тест специально реализован таким образом, чтобы демонстрировать правильность работы в этом случае. Но Visual Studio по умолчанию всегда останавливает выполнение программы при этом исключении, независимо от того, обработано оно или нет. Чтобы этого не происходило, нужно либо убрать соответствующую галочку в окне Debug->Exceptions, либо не запускать тест под отладчиком.
В новом варианте по кнопке запускается не один, а сразу 10 потоков.
2. Жаль, что асинхронную активность нельзя прервать.
Реализовано в новом варианте. Текущий прогресс отображается в "статус-баре", а прервать операцию можно кнопкой Cancel. Обращаю внимание, что в исходном задании эта функциональность не значилась.
3. FsFileTable::CreateFolder зачем-то написана рекурсивно, хотя рекурсия легко сворачивается в цикл.
Согласен. Виноват. Переделано.
4. Само архитектурное решение сомнительно, но это как раз повод для очного разговора.
Ни в коем случае не претендую на максимальную производительность, а скорее претендую на золотую середину между производительностью и временными затратами на разработку. Буду рад обсудить все это при очном разговоре.
_>Еще пара слов по поводу "архитектурного решения". Само собой, для максимальной производительности в реальной жизни следовало бы реализовать разбиение на блоки, таблицу этих блоков, дефрагментацию и еще много чего. Но я сделал так, как сделал, потому что: _>
_> Скажем так: мое желание работать в уважаемой компании, в принципе, измеряется 20-ю жопочасами, потраченными на этот вариант. Но никак не 60-ю жопочасами, которые бы ушли на выработку и отладку более эффективного решения. _> Мои попытки сотворить новую файловую систему для Линукса в рамках выполнения тестового задания смотрелись бы ну очень забавно. _>
_>Всем спасибо за ответы!
Обходить такие интервью и тестовые задания стороной надо. Интервьюеры зажрались, это без сомнения.
Если тестовое задание требует больше 4 часов для выполнения — это уже не тестовое задание, и должно оплачиваться, так как это, бесспорно, чья-та оплачиваемая работа..
Ты и в правду считаешь, что синхронизацию на уровне атомарности методов целого класса можно назвать хотя-бы удовлетворительным решением? У тебя по коду в результате сплошные блокировки, где надо и не надо.
А почему ты согласен с тем, что рекурсию можно и нужно свернуть в цикл?
Название компании-то выложи
Судя по всему, ты очень хочешь работать в этой компании. Учитывая твою способность мыслить самостоятельно, а также твою адекватность, — я бы взял. Интервьюер слегка подзажрался, да.
Здравствуйте, kaa.python, Вы писали:
KP>Ты и в правду считаешь, что синхронизацию на уровне атомарности методов целого класса можно назвать хотя-бы удовлетворительным решением? У тебя по коду в результате сплошные блокировки, где надо и не надо.
Глобально — нельзя назвать. В данном случае — это вполне себе решение. Тяжелых методов всего два: FsFileStream.Read() и FsFileStream.Write() — те, что физически общаются с диском. Позволить двум потокам параллельно писать в один и тот же физический файл не получится, хоть ты тресни. Позволить одному писать, а другому читать получилось бы, но в условии задачи сказано, что этого не требуется. Остается только делать Read()/Write() асинхронными, а для этого вводить блоки... ну т.е. делать файловую систему для Линукса. Ребята, это ж еще 100К кода!
Все остальные методы делают простейшие операции с коллекциями в памяти. Их синхронизация заключалась бы в lock()-ах вокруг всего тела метода. Т.е. то же самое, что делает контекст синхронизации.
Еще есть методы, возвращающие IEnumerable. Но они на то и возвращают IEnumerable, чтобы быть быстрыми и неблокирующими.
KP>А почему ты согласен с тем, что рекурсию можно и нужно свернуть в цикл?
Потому что она там действительно тупо сворачивается:
public void CreateFolder(string folderName, bool failIfExists)
{
var pathParts = SplitPathAndCheck(ref folderName);
// checking parent foldersstring curFolder = InternalPathSeparatorString;
for(int i = 2; i < pathParts.Length-1; i++)
{
curFolder = curFolder + InternalPathSeparatorString + pathParts[i];
if(!FolderList.ContainsKey(curFolder))
FolderList.Add(curFolder, new FsInternalFolderInfo());
}
if
(
(!failIfExists)
&&
(FolderList.ContainsKey(folderName))
)
return;
FolderList.Add(folderName, new FsInternalFolderInfo());
}
Здравствуйте, scale_tone, Вы писали:
_>Мои попытки сотворить новую файловую систему для Линукса в рамках выполнения тестового задания смотрелись бы ну очень забавно.
Ну а зачем вообще было разрабатывать ФС с нуля? У M$, AFAIR, еще со времен COM есть structured storage. В задании ведь ни слова не сказано о том, что нельзя пользоваться стандартными средствами?
Здравствуйте, Miroff, Вы писали:
M>Здравствуйте, scale_tone, Вы писали:
_>>Мои попытки сотворить новую файловую систему для Линукса в рамках выполнения тестового задания смотрелись бы ну очень забавно.
M>Ну а зачем вообще было разрабатывать ФС с нуля? У M$, AFAIR, еще со времен COM есть structured storage. В задании ведь ни слова не сказано о том, что нельзя пользоваться стандартными средствами?
Может они хотели увидеть нечто подобное:
/// <summary>
/// Диск ФС
/// </summary>internal class Disk {
/// <summary>
/// Создаёт диск
/// </summary>
/// <param name="fileName">Файл в котором будет диск</param>
/// <param name="lenght">Размер диска</param>internal Disk(String fileName,int lenght){}
/// <summary>
/// Предоставляет доступ к кластеру по номеру
/// </summary>
/// <param name="number">Номер кластера</param>
/// <returns>Байты</returns>internal byte[] this[int number] { get;set;}
}
Я вообще-то тоже хотел это увидеть.
Это низкий уровень. А на более высоком уровне уже интерфейс ФС с блокировкой на уровне файлов.
M>Ну а зачем вообще было разрабатывать ФС с нуля? У M$, AFAIR, еще со времен COM есть structured storage. В задании ведь ни слова не сказано о том, что нельзя пользоваться стандартными средствами?
Ну блин, есть еще куча чего: SQL Server Compact, ADAM, MS Access, просто сериализация в .Net...
И задача бы свелась к созданию GUI для базы данных
Но мне почему-то показалось, что интервьюверы хотели бы увидеть немножко программирования.
Впрочем, я уже не знаю, что интервьюверы хотели бы увидеть...
Здравствуйте, microcod, Вы писали:
M>Я вообще-то тоже хотел это увидеть.
M>Это низкий уровень. А на более высоком уровне уже интерфейс ФС с блокировкой на уровне файлов.
Да, наверно, хотели увидеть это. Только забыли в задании написать...
Здравствуйте, scale_tone, Вы писали:
_>Ну блин, есть еще куча чего: SQL Server Compact, ADAM, MS Access, просто сериализация в .Net...
И что из этого подходит на роль виртуальной ФС в одним файле?
_>И задача бы свелась к созданию GUI для базы данных
Ну да, суть хорошего программиста в том, что он умеет сводить сложные задачки к простым.
_>Но мне почему-то показалось, что интервьюверы хотели бы увидеть немножко программирования. _>Впрочем, я уже не знаю, что интервьюверы хотели бы увидеть...
Есть три варианта.
1) Интервьюер принципиальный велосипедист. Тогда он хочет увидеть полностью свой велосипед, желательно с гудком и фотографией красотки на руле.
2) Интервьюер прагматик и хочет увидеть стандартное решение (structured storage).
3) Интервьюер прагматик и хочет увидеть велосипед. Тогда structured storage его приятно удивит, потому что люди, не кидающиеся сразу же писать собственный велосипед довольно редки.
Учитывая задачу и нишу C#, первый вариант выглядит маловероятеным.
_>Впрочем, я уже не знаю, что интервьюверы хотели бы увидеть...
Дать человеку весьма объемное задание, увидеть по количеству кода, что потрачено немало времени на выполнение, при это все работает как и требовалось, и не пригласить на собеседование — верх неуважения.
Или просто отсутствие нужных навыков.
У Вас еще есть желание работать в этой компании, с этим человеком?
А ведь тоже самое можно было написать проще.
И асинхронный импорт/экспорт/что-там-еще от Вас не требовали.
Вместо плоского FolderList я бы сделал дерево.
Не думаю, что они хотели видеть работу с ФС на низком уровне, эдак рассуждая можно скатиться до блинов и головок, а не только учитывать сектора и кластеры.
Здравствуйте, Miroff, Вы писали:
M>И что из этого подходит на роль виртуальной ФС в одним файле?
Лучше всего подходит смонтированный ISO-image
Чуть хуже SqlServerCE (один SDF-файл, внутрипроцессная DLL-ка и даже какая-никакая поддержка многопоточности).
Дальше ADAM. Это сервис, и тормознутый, но зато поддержка иерархии есть.
_>>И задача бы свелась к созданию GUI для базы данных
M>Ну да, суть хорошего программиста в том, что он умеет сводить сложные задачки к простым.
Согласен. Буквально моими словами говорите.
M>Есть три варианта. M>1) Интервьюер принципиальный велосипедист. Тогда он хочет увидеть полностью свой велосипед, желательно с гудком и фотографией красотки на руле. M>2) Интервьюер прагматик и хочет увидеть стандартное решение (structured storage). M>3) Интервьюер прагматик и хочет увидеть велосипед. Тогда structured storage его приятно удивит, потому что люди, не кидающиеся сразу же писать собственный велосипед довольно редки.
M>Учитывая задачу и нишу C#, первый вариант выглядит маловероятеным.
Я все-таки хочу лучше думать об интервьювере, так что скорее первый вариант
Structured storage — это COM-овский фреймворк. Он даже (официально) не поддерживается в .Net, потому что вместо него есть сериализация. Интервьювер хотел увидеть массив массивов байт, периодически сериализуемый на диск?!!
D>Дать человеку весьма объемное задание, увидеть по количеству кода, что потрачено немало времени на выполнение, при это все работает как и требовалось, и не пригласить на собеседование — верх неуважения. D>Или просто отсутствие нужных навыков. D>У Вас еще есть желание работать в этой компании, с этим человеком?
Как Вам сказать... Ощущение обтекания не из приятных, конечно. И с этим человеком работать нет желания. С другой стороны, я понимаю, что мне могло просто сильно не повезти. И в этой компании работает много хороших людей, в том числе модераторов этого форума.
Здравствуйте, Oleg Kosenkov, Вы писали:
OK>Обходить такие интервью и тестовые задания стороной надо. Интервьюеры зажрались, это без сомнения. OK>Если тестовое задание требует больше 4 часов для выполнения — это уже не тестовое задание, и должно оплачиваться, так как это, бесспорно, чья-та оплачиваемая работа..
Ну почему же? Если задание требует больше 4 часов, но при этом оплачивается вне зависимости от результатов — можно и поучаствовать. Так делают все компании в Европе (там, знаете ли, людей уважать принято). Я, например, так на свою первую работу попал
OK>Обходить такие интервью и тестовые задания стороной надо. Интервьюеры зажрались, это без сомнения. OK>Если тестовое задание требует больше 4 часов для выполнения — это уже не тестовое задание, и должно оплачиваться, так как это, бесспорно, чья-та оплачиваемая работа..
ну почему. Оно может оплачиваться неявно, в виде последующей высокой зарплаты в случае оффера. Ну или интересных(TM) проектов(C).
Ради посредственных условий конечно нет смысла тратить много усилий.
Здравствуйте, x64, Вы писали:
x64>Судя по всему, ты очень хочешь работать в этой компании. Учитывая твою способность мыслить самостоятельно, а также твою адекватность, — я бы взял. Интервьюер слегка подзажрался, да.
Скорее, интервьюер просто устал. Столько кандидатов... Все чего-то хотят... Тут за день и скомпилировать-то все не успеешь, не то что сопроводительное письмо прочитать или в код глянуть