Помню, недавно выполнял задание на вакансию масленка после технического собеседования.

Формулировка следующая:

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

В общем, простое задание на джуна, хоть не аналог авито писать, и на том спасибо.

Оригинальная формулировка задания:

Create a program which will execute the next steps:
1) Create collection of randomly generated objects in memory by provided models, number of ofjects 10000;
2) Serialyze it to JSON format;
3) Write the serialization result to the current user desktop directory, the text file name should be "Persons.json";
4) Clear the in memory collection;
5) Read objects from file;
6) Display in console persons count, persons credit card count, the average value of child age.

Use POSIX format for dates.
Use lowerCamelCase JSON notation in result file.
// Data models
class Person
{
	public Int32 Id { get; set; }
	public Guid TransportId { get; set; }
	public String FirstName { get; set; }
	public String LastName { get; set; }
	public Int32 SequenceId { get; set; }
	public String[] CreditCardNumbers { get; set; }
	public Int32 Age { get; set; }
	public String[] Phones { get; set; }
	public Int64 BirthDate { get; set; }
	public Double Salary { get; set; }
	public Boolean IsMarred { get; set; }
	public Gender Gender { get; set; }
	public Child[] Children { get; set; }	
}
class Child
{
	public Int32 Id { get; set; }
	public String FirstName { get; set; }
	public String LastName { get; set; }
	public Int64 BirthDate { get; set; }
	public Gender Gender { get; set; }
}
enum Gender
{
	Male,
	Female
}

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

Подключаем Faker

Faker можно добавить в проект через менеджер пакетов NuGet.

У данного пространства есть целая куча классов, с помощью которым можно генерировать те или иные данные:

Faker.Address - генерация адресов

Faker.Boolean- генерация логических данных

Faker.Company- генерация компаний

Faker.Identification- генерация идентификационных данных

Faker.Finance- генерация финансовых значений

Faker.Name- генерация имен

Подключаем нужное пространство имен и начинаем пользоваться:

using Faker;
using Collection.Data;

namespace Collection.Generator
{
    internal class PersonsGenerator
    {
        public List<Person> persons = new List<Person>();
        Random random = new Random();


        DateTimeGeneration birthdayDate = new DateTimeGeneration();
        PhonesGenerator phones = new PhonesGenerator();
        ChildGenerator children = new ChildGenerator();
        CreditCardsGeneration creditCards = new CreditCardsGeneration();

        public PersonsGenerator(int count)
        {
            for (int i = 0; i < count; ++i)
            {
                persons.Add(new Person
                {
                    Id = i,
                    TransportId = Guid.NewGuid(),
                    
                    // Случайная генерация имен
                    FirstName = Faker.Name.First(),
                    LastName = Faker.Name.Last(),
                    
                    SequenceId = i,
                    CreditCardNumbers = creditCards.CreateCreditCards(),
                    
                    // Числа тоже можно генерировать
                    Age = Faker.RandomNumber.Next(18, 100),
                    
                    Phones = phones.CreatePhones(),
                    BirthDate = ((DateTimeOffset)birthdayDate.CreateBirthdayDate()).ToUnixTimeSeconds(),
                    Salary = (double)Faker.RandomNumber.Next(10000, 100000),
                    IsMarred = Faker.Boolean.Random(),
                    
                    // Можно рандомизировать константы из перечислений
                    Gender = Faker.Enum.Random<Gender>(),
                    
                    Children = children.CreateChildren(out childrenCount)
                });
            }          
        }
    }
}

Теперь можно попробовать вывести все это в консоль, я написал небольшой класс с рефлексией, который выводит данные в консоль:

    internal class ConsoleWriter<T> where T: class
    {
        public ConsoleWriter(List<T> collection)
        {
            Type type = typeof(T);
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var o in collection)
            {
                for (int i = 0; i < properties.Length; i++)
                {
                    Console.WriteLine($"{properties[i].Name}:\t{properties[i].GetValue(o)}");
                    
                    // Не обращаем внимание, так, для себя сделал
                    if (properties[i].PropertyType.IsArray)
                    {
                        Console.WriteLine("Array!");
                    }
                        
                }
                Console.WriteLine();
            }
            
        }
    }
static void Main(string[] args)
{

	// Здесь генерация объектов
	PersonsGenerator generator = new PersonsGenerator(5);
  JsonWriter<Person> jsonWriter = new JsonWriter<Person>();
  JsonReader<Person> jsonReader = new JsonReader<Person>();

  // Вот здесь вывод данных в консоль после генерации
  ConsoleWriter<Person> debuger = new ConsoleWriter<Person>(generator.persons);
  Console.WriteLine("Reflection is end. Push ENTER to continue...");

  // Запись данных в JSON
  jsonWriter.WriteToFile(generator.persons);
  Console.WriteLine("Serialization is complete!");
 
  // Очистка после подтверждения
  Console.ReadLine();
  generator.persons.Clear();
  generator.Dispose();

  // Чтение из файла
  var persons = jsonReader.ReadFromFile();

  // Вывод данных
  ValueCounter counter = new ValueCounter();
  
  Console.WriteLine($"Persons count is {persons.Count}");
  Console.WriteLine($"Average children age is {counter.AverageChildAge(persons)}");
  Console.WriteLine($"Credit cards count is {counter.CreditCardCount(persons)}");

  Console.ReadKey();
}

Результат в консоли следующий:

Имена конечно иногда странные, но, как мне кажется для тестов вполне сойдет.

Итоги

В конечном итоге, меня не взяли на работу масленком, дав следующий ответ:

HR: Даниил, здравствуйте!
Мы благодарим Вас за проявленный интерес к нашей компании и желание с нами сотрудничать.
На позицию, которую мы Вас рассматривали, мы пока не готовы Вам сделать предложение о работе. С Вашего позволения мы хотели бы Ваше резюме добавить в кадровый резерв, чтобы в случае, если у нас появится подходящая вакансия, связаться с Вами.
Также мы желаем Вам успехов в поисках работы!

Я: Опишите пожалуйста более точно причину отказа.

HR: Уточнила, тимлид обещал ответить сегодня вечером или завтра до обеда

---"Через 3 часа"---

HR: Даниил, по поводу обратной связи: самое основное - недостаточно теоретических знаний и отсутствие опыта работы с asp net.

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

Было бы интересно почитать ваши советы и отзывы по поводу статьи, так как пишу первый раз что-то подобное. Спасибо!

Комментарии (10)


  1. kost
    27.06.2022 22:31
    +1

    Что такое масленок?


    1. JordanCpp
      27.06.2022 22:44

      Это желторотик:)


  1. JordanCpp
    27.06.2022 22:50

    Наверное требовалось русскоязычное наполнение, не всякие Джоны и Томсоны. А отечественные Ивановы и Сидоровы. Я для тестовой базы делал файлы: фамилий, имен, отчеств, названий фирм и улиц. После чего читал данные файлы и рэндомно создавал адреса, ФИО, и т.д И сохранял в csv после чего в PostgreSQL, писал через copy. Первичные ключи, тоже генерил и лил в базу. Плюсы, полный контроль за данными, можешь генерировать любые диапазоны.


    1. vabka
      28.06.2022 13:37

      Bogus можно расширять своими словарями и генераторами.

      И всякие имена с адресами там вроде как из коробки могут генерироваться на нужном языке.

      Делал однажды свой такой генератор правдоподобных автомобильных номеров.


  1. Shreedeer
    27.06.2022 23:24
    +2

    Странное задание, непонятно, что оно вообще должно проверять кроме совсем базовых знаний языка и

    и отсутствие опыта работы с asp net.
    как знания asp net должны помочь в рандомной генерирации данных, может речь шла о том, что нужно удалённо данные запросить у какого-то открытого api сервиса генерации данных.


  1. Meloman19
    28.06.2022 03:58
    +2

    Мне кажется, что тут просто очень формально к проверке подошли. В приложении вполне конкретно указано, что и куда должно выводиться. А судя по коду там в консоль лишние данные выводятся (ConsoleWriter не забыл убрать?) и ещё непонятно, как в json пишется (на рабочий стол нужно + в lowerCamelCase). Им может не хочется заморачиваться: что-то лишнее или не так в выводе - не прошёл. Тут задание всё-таки на джуна, наверняка ждут чёткую реализацию поставленной задачи.

    Так-то задание в принципе подходит джуну: недолгое, проверяет базовые знания, а главное способность следовать ТЗ.

    А по коду, может быть проблема в какой-то излишней переусложнённости. От джуна ожидаешь решение в лоб, а не класс под каждое действие.

    Про Faker - в принципе хорошо, но, кажется, это не принципиально было для них, хватило бы любых рандомных значений, подходящих под типы. PersonsGenerator, генерирующий в конструкторе один раз - не очень решение, если уж генератор, то нужен метод типа Generate возвращающий список без сохранения в поле класса. Про сериализацию/десериализацию сложно что-то сказать. Как я понял JsonWriter|JsonReader - это кастомные классы? Не вижу, чтобы пути к файлу указывал. Если уж решил в ООП, то нужно передавать путь к файлу в них извне. ConsoleWriter даже не требовался по заданию. ValueCounter как-то тоже кажется лишним, там два простых Linq запроса. Это из того, что увидел (ссылки на исходники я чёт не нашёл).


    1. min0ru
      28.06.2022 17:23

      Очень дельные замечания, со многим согласен, но я бы, пожалуй дополнил еще несколькими:

      1) Генерируемые данные рассогласованы.

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

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

      Пол не согласуется с именем. Пускай это и в духе современных трендов, но мне кажется, что в данном случае это скорее не заранее продуманная фича.

      Возраст детей не согласуется с возрастом родителя. Вполне вероятно, что случайные данные нагенерируют 12-летних детей у которых есть свои дети возрастом под 68 лет.

      Фамилия детей не согласуется с фамилией родителя.

      Возраст не согласован со статусом вступления в брак. 3 летние женатые дети.

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

      Не понятно, должен ли идентификатор ребенка Child.Id быть согласованым с Person.Id. Но выглядит так, что скорее всего да. Тогда детей нужно создавать как Person, а родитель должен ссылаться на реальных Person с реальными Id.

      2) Нет консистентности в названии классов.

      Одни классы называются ***Generation, другие ***Generator. Думаю, что следует придерживаться одного подхода к именованию.

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

      Про то, что плохая практика в конструкторе класса делать "тяжелые" операции, не связанные с инициализацией объекта уже написали.

      4) Я бы предложил использовать автоматическое выведение типов (var) в конструкциях, вроде:

      JsonWriter<Person> jsonWriter = new JsonWriter<Person>();

      JsonReader<Person> jsonReader = new JsonReader<Person>();

      Заменил бы на

      var jsonWriter = new JsonWriter<Person>();

      var jsonReader = new JsonReader<Person>();

      Это лучше читается, для такого "масла масленного" этот сахар и был придуман.

      5) Вызов метода Dispose() у класса PersonsGenerator.

      Класс PersonsGenerator судя по коду из статьи не реализует ни интерфейс IDisposable, ни собственно метод Dispose(). Скорее всего это опечатка, иначе этот код просто бы не работал.

      Не понятно, что вообще должен в данном случае делать этот метод, так как очистка PersonsGenerator.persons производится явно, вне данного метода. Вероятно, эту операцию как раз надо засунуть внутрь Dispose(). И тогда использовать генератор вместе с конструкцией using:

      using (var personsGenerator = new PersonsGenerator()) { ... }

      Но я бы вообще не стал хранить в генераторе сгенерированные данные. Ответственность генератора - генерировать сущености, а не хранить их.

      6) Не стоит сохранять в финальном коде отладочные куски, я про это:

      // Не обращаем внимание, так, для себя сделал

      7) В дополнение к 6), для вывода небольших массивов в отладочной строке можно было бы воспользоваться методом string.Join(), тогда можно было бы элементы массивов выводить через разделитель, например через запятую.

      Вообще мне кажется, что тут рефлексия не особенно нужна, можно было бы для класса Person переопределить метод ToString() и в явном виде форматировать данные. Или вообще для отладочного вывода просто печатать готовый json в котором уже произведена почти аналогичная сериализация данных, так сказать бесплатно и без лишнего кода.

      8) Мне понравилась обертка для Faker, которая называется Bogus (https://github.com/bchavez/Bogus). Рекомендую посмотреть на ее реализацию создания декларативных генераторов, мне кажется, что это довольно красивое решение, которое иногда избавляет от необходимости написания своих классов-генераторов. Плюс эта библиотека содержит кучу дополнительных расширений, среди которых, например есть возможность генерировать данные для не-англоязычных сред, например имена на русском языке.

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


      1. nronnie
        28.06.2022 22:46
        +1

        Заменил бы на

        var jsonWriter = new JsonWriter<Person>();

        var jsonReader = new JsonReader<Person>();

        В C# 10 и выше еще лучше:

        JsonWriter<Person> jsonWriter = new();
        JsonReader<Person> тако	 ужас, = new();

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

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


      1. Enfriz
        29.06.2022 12:41

        Дополню ещё.

        1. Модификаторы доступа для полей не указаны, нужно всегда явно указывать. У классов тоже не у всех.

        2. Если это private-поля, то не соблюдён кодстайл именования — должно быть _phones, _children итд

        3. Int32, Int64 вместо int и long

        4. Вообще типы все напрямую а не алиасами, как рекомендовано ( String > string, аналогично для double, boolean итд)

        5. IsMarred вместо IsMarried

        6. Нет никакого смысла выводить вот эти вот System.String[]. Это просто визуальный мусор, не несущий для человека информации.

        7. Вообще, кажется, в выводе есть лишнее. Самих Person не просили выводить, только записать и считать, как я понимаю. А вывести только общие показатели.


  1. Pest85
    28.06.2022 08:12
    +1

    Оригинальный Faker идет с Ruby.
    Он так же есть для Java, JS, PHP, python итд.
    Вдруг кому пригодится.