Попробуй пройди за 4 часа тестовое задание в канадскую компанию

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

Я сам опытный интервьювер и собеседовал сотни кандидатов. А так же достаточно часто собеседовался в поисках работы. Открою карты сразу и опишу свое отношение к тестовым заданиям и процессам интервью: обычно я стараюсь их избегать при прочих равных в выборе. Я не против оплачиваемых тестовых заданий, либо совместных кодинг сессий за компьютером с интервьювером. Считаю что задачка должна быть достаточно компактной и решение занимать не больше пары часов — с одной стороны так можно проверить навыки кодинга кандидата, а с другой стороны не забирать драгоценное время жизни на сферический код в вакууме. Ну и это должен быть диалог двух специалистов, а не выдача «на дом» того с чем кандидату могут помогать поисковая система и сокурсники из Мумбая.

Это только присказка-сказка впереди

Пару слов про процессы интервью. Работодатель может реально оценивать себя и то чего хочет достичь. Если нужен реальный инженер, который знает, умеет и имеет подтвержденный опыт в индустрии и компания не из FAANG: Google, Microsoft, Facebook, Amazon итп., то можете не придумывать свои мануалы для кандидатов про процессам длинной в жизнь с алгоритмическими задачами, поведенческими интервью, комитетами и прочей бюрократией. Сюда же и ожидания что инженер будет час расхваливать историю компании и цели при вопросе «Почему вы выбрали нашу компанию? Что вы знаете о ней?». IMHO человеку с опытом в индустрии разработки ПО достаточно просто не быть чудаком с буквы м, чтобы пройти отбор на софтскилы. Мы не говорим сейчас про менеджеров в управлении разработкой, там царят другие правила отбора.

С чем я столкнулся с октября при поиске работы, что рынок перевернулся с ног на голову — сейчас это не рынок кандидата на иностранные вакансии, место работодатели диктуют условия на отбор. Опять же все это пока и ждем когда вернется дифицит, все циклично. Почему я не хочу работать на какого-либо ИП или российскую корпорацию оставим вне контекта этой публикации. Хотя… Последние 9 лет я работал в международных компаниях и привык к комфорту для психики, баланса личного времени и финансов в сравнении с тем что предлагал российский рынок. Также я проработал в другой стране с декабря 2021 в одном из крупнейших туристических агрегаторов на азиатском рынке, бигдатя бигдату. Только сейчас начал признаваться себе что где-то я погорячился с уходом с последнего места работы. Пусть сфера туризма еще полностью не отошла от пандемии, со всеми вытекающими для работника.

Из нескольких вариантов компаний что нашел месяц назад, я решил податься туда где процесс был более простой и реалистичный. Первый этап — созвон с HR специалистом и разговоры о жизни и мотивации. Второй этап — hackerrank тест на 45 мин. Третьий — поведенческое интервью с техническим руководителем. Четвертый этап это тестовое задание на Java, по словам HR это где-то на 4–6 часов, но строго с секундомером не следят за временем выполнения. После всех этих шагов может последовать оффер или еще один этап с CTO.

Типичные процессы интервью в последние 16 лет у меня были — общение с HR, техническое интервью иногда совмещенное с интервью с менеджером или техдиром и оффер следом иногда с проверкой службы безопасности до этого! Почувствуйте разницу.

Альтернативой в тот момент еще было общение с HR на вакансию Fivetran — я сразу же попрощался когда узнал какой у них длинный процесс с решением алгоритмических задач в виде онлайн кодинга. Да и «усталость» и тон разговора со мной кадрового специалиста насторожила с первых же минут видео созвона. Потом посмотрел что у них прилично разработчиков в офисе в Индии и сделал вывод «откуда у этого процесса растут ноги».

Пока опущу часть самопрезентации, чтобы не перегружать статью и сфокусироваться на главном!

Интервью

Итак возвращаемся к основной теме и потенциальному работодателю этой публикации. Первое — было удержать себя и не послать куда подальше при предложении пройти отсев через hackerrank, второе — через тестовое задание. Я конечно рассказывал кадровику о своем опыте, кидал ссылки в письме на open source проекты и доклады, но это вызывало лишь покерфейс и бубнение что «мы верим в наши процессы отбора, они помогают нам найти лучших работников». Вы то верите в это, а кандидату скорее показывает что нет никакой гибкости в процессе найма и вам лень тратить свое время на переход на мои PR Github одного из 10K* проекта разрабатывамого под крылом Apache Software Foundation. Ладно «запишем на карандаш» и перейдем к следующему шагу.

Hackerrank тесты были простые: 8 заданий, общие вопросы по микросервисам, разработке ПО и одна задачка на что-то там алгоритмическое с картриджами. Сделал за 43 минуты из выделенных 45, при этом первые 3 минуты боролся с отвращением и мотивировал себя пройти через это «унижение» для матерого специалиста. Камон ребята, я же не выпускник коледжа по разработке программ из Бангалора. Что только не разрабатывал за свою карьеру и тем более активно участвовал в open source проектах в свое свободное время.

Задачка про картриджи:

414de79be4a5d8fb0b6392bb40e01531.png

Решение выбрал для выполнения на привычной мне Java и реализация прошла все тесты:

aff2cbf109a0821c3241edcaec2e2e93.jpg

Поведенческое интервью с техническим руководителем мне понравилось тем что тут уже все было как обычно — рассказать о своем опыте, спросить про их проект. Сначала представился потенциальный руководитель Александр, рассказал о своем опыте на проекте Technology Compatibility Kit (TCK) для JavaME. Потом описал себя как профессионала в трех прилагательных. Потом была моя очередь, распушил хвост и рассказал о своем опыте, лучших проектах, релевантном опыте, где и как я боролся со сложностями в реальных проектах и потом без подготовки тоже выдал какие-то три рандомных и позитивных прилагательных о себе. Этот этап прошел успешно, обещали подумать пару дней, но ответ что я успешно прошел это интервью пришел уже через несколько часов.

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

We all have really enjoyed our conversations with you so far.

 You’ve
done great during the interviews, so we would like to move to the final
phase of the interview process with you — which is a Test Task.

This
is a technical task that could take about 4–6 hours to complete, this
estimation may vary depending on the candidate’s availability and
skills. We have decided that it would be the best to confirm when you
are ready to start working on a project like that and send it by that
time, so we can expect to receive the results back in 1–3 days after we
send the task. Could you please let us know when would it be convenient
for you to receive the task from us?

I’ve also cc’ed Alexander in case you will have any technical questions when you receive the task.

Thanks for taking the time to go through our process, we’re looking forward to hearing from you!

Я конечно попытался напомнить о том, что я уже говорил на HR интервью, чтобы избежать бесплатного программирования без какого-либо практического смысла для себя:

Thank you for good news. I’ll have some time to implement Test Task tomorrow after 19:00 PM Maldivian time (GMT +5) but not with pleasure as from my point of view it looks like exercise for student without real experience and hiring process is not flexible enough… I have open for anyone strong proof of my work experience in top rated open source projects like Apache Arrow, Spring Framework/Boot, SchemaSpy, H2 etc

The most fresh example my Apache Arrow 10.0.0 (26 October 2022) contributions:  

https://github.com/apache/arrow/pulls? q=is%3Apr+author%3Aigor-suhorukov+is%3Aclosed

and in  https://arrow.apache.org/release/10.0.0.html

This technology using under the hood in many cutting edge Big Data frameworks and databases as Apache Spark and PySpark, Dremio, ClickHouse DB…

I hope that your company also shares common agile software methodology value «Individuals and interactions over processes and tools»

  Regards,

      Igor

На что был ответ:

Hi Igor,  Hope your weekend was great.Yes, we do understand your point, but at the same time we need to evaluate all the candidates in the process fairly to each other and we need to compare the results with the same type of the task. Sure, I will send you the task by the time you.  Thank you and have a great rest of the day!

Тестовое задание

После того как я получил по почте следующий текст и тестовые данные в zip архиве, я засучил рукава и начал работать.

# Project:
It’s known that emails can have attachments, some even have other emails as attachments. This
allows them to have attachments at any level of nesting. Emails can be stored in so called
Internet Message Format, files in this format usually have .eml extension.

Let’s assume that someone could execute the following operations for arbitrary number of times
in a random order:
- Attach EML or ZIP file to the email
- Archive list of emails to the root folder of ZIP archive

So the sequence of operations is a storing format for the original set of files. For example ZIP
archive can contain emails with ZIP archive as attachment and the last one ZIP archives contain
emails with email as attachment (see samples provided).
  
# Requirements:
You need to write a CLI program that extracts files from the provided file based on the provided
format. See sample output for the sample above.
You must not guess input file format and the program should allow the user to specify input
format in some way, as a command line argument for example. Program should raise an error if
an input file cannot be converted based on the provided format.
For EML files handling it is highly recommended to use Java Mail library
( https://javaee.github.io/javamail ), MimeMessage, Multipart,BodyPart classes in particular.
For ZIP files handling it is recommended to use java.util.zip package from Java SE.
Solution should have quite good quality, which means it should be covered by tests, be able to
work with real-life emails (as this one) and quite large ZIP archives.

Код моего решения я выдал за 4.5 часа сразу же. Это при том что я еще задал пару вопросов по почте в самом начале после ознакомления с заданием, но из-за лимита времени приступил к следующим подзадачам:

  • Уговорить себя не отказаться сразу, а сделать неоплачиваемый тест. Почти час я боролся с собой и смог сфокусироваться только на задаче.

  • Разобраться с парсингом MIME формата сообщений и как сделать рекурсивную распаковку содержимого в ZIP.

Первым делом я добавил в проект тестовые данные из письма с заданием, потом начал искать зависимости на mvnrepository.com

Мое решение со всеми артефактами доступно на github. Основная логика задачи:

package com.github.isuhorukov.email;

import com.beust.jcommander.JCommander;
import org.apache.commons.io.IOUtils;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * My email attachment example for IoT system https://github.com/igor-suhorukov/alarm-system
 * also referenced from https://camel.apache.org/community/articles/
 */
public class EmailProcessor {

    public static final String CONTENT_TYPE = "Content-Type";
    public static final String MULTIPART_MIXED = "multipart/mixed";
    public static final String APPLICATION_X_ZIP_COMPRESSED = "application/x-zip-compressed";
    public static final String MESSAGE_RFC_822 = "message/rfc822";

    private final Session session = Session.getInstance(System.getProperties(), null);
    private final AtomicLong fileNameIncremental = new AtomicLong();

    public static void main(String[] args) throws Exception{
        final CLIParameters parameters = new CLIParameters();
        JCommander jCommander = JCommander.newBuilder().addObject(parameters).build();
        jCommander.parse(args);

        EmailProcessor emailProcessor = new EmailProcessor();
        emailProcessor.process(parameters);
    }

    void process(CLIParameters parameters) throws IOException, MessagingException {
        Objects.requireNonNull(parameters.getType(), "Expected source file type parameter");
        File sourceFile = FileUtils.getSourceFile(parameters);
        File outputDirectory = FileUtils.getOutputDirectory(parameters);
        switch (parameters.getType()){
            case ZIP:
                processZipArchive(sourceFile, outputDirectory);
                break;
            case EML:
                processMimeMessage(sourceFile, outputDirectory);
                break;
            default:
                throw new UnsupportedOperationException();
        }
    }

    void processZipArchive(File zipFile, File outputDirectory) throws IOException, MessagingException {
        if(!isZipStream(zipFile)){
            throw new IllegalArgumentException("ZIP stream magic sequence is not found");
        }
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile))){
            ZipEntry zipEntry;
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                if(zipEntry.isDirectory()){
                    throw new IllegalArgumentException("Only root files in zip archive supported");
                }
                File payload = File.createTempFile("payload", "eml");
                try (FileOutputStream payloadStream = new FileOutputStream(payload)){
                    IOUtils.copy(zipInputStream, payloadStream);
                    processMimeMessage(payload, outputDirectory);
                } finally {
                    if(payload.exists() && !payload.delete()){
                        throw new RuntimeException("It is not possible to delete temporary entry copy from zip");
                    }
                }
                zipInputStream.closeEntry();
            }
        }
    }

    void processMimeMessage(File payload, File outputDirectory) throws IOException, MessagingException {
        if(isZipStream(payload)){
            throw new IllegalArgumentException("Expected text content but found ZIP stream magic sequence");
        }
        try (FileInputStream mimeStream = new FileInputStream(payload)){
            MimeMessage message = new MimeMessage(session, mimeStream);
            if(message.isMimeType(MULTIPART_MIXED)) {
                MimeMultipart content = (MimeMultipart) message.getContent();
                int contentCount = content.getCount();
                for(int contentIndex = 0; contentIndex < contentCount; contentIndex++){
                    BodyPart bodyPart = content.getBodyPart(contentIndex);
                    if(bodyPart.getSize()>0){
                        String[] contentType = bodyPart.getHeader(CONTENT_TYPE);
                        if(contentType.length>0 && contentType[0].contains(APPLICATION_X_ZIP_COMPRESSED)){
                            File zipPayload = File.createTempFile("payload", "zip");
                            try (InputStream attachmentInStream = bodyPart.getInputStream();
                                 FileOutputStream zipOutStream = new FileOutputStream(zipPayload)){
                                IOUtils.copy(attachmentInStream, zipOutStream);
                                processZipArchive(zipPayload, outputDirectory);
                            } finally {
                                if(zipPayload.exists() && !zipPayload.delete()){
                                    throw new RuntimeException("It is not possible to delete temporary zip file");
                                }
                            }
                        } else if(contentType.length>0 && contentType[0].contains(MESSAGE_RFC_822)){
                            String[] contentTypeParts = contentType[0].split("\\n");
                            String resultFileName = FileUtils.getResultFileName(contentTypeParts, fileNameIncremental);
                            File outputFile = new File(outputDirectory, resultFileName);
                            if(outputFile.exists()){
                                do{
                                    outputFile = new File(outputDirectory, FileUtils.getSyntheticIncrementalName(fileNameIncremental.getAndIncrement()));
                                } while (outputFile.exists());
                                //throw new IllegalArgumentException("File already exists "+outputFile.getAbsolutePath());
                            }
                            MimeMessage mimeMessage = (MimeMessage) bodyPart.getContent();
                            try (FileOutputStream emlOutputStream = new FileOutputStream(outputFile)){
                                mimeMessage.writeTo(emlOutputStream);
                            }
                        }
                    }
                }
            }
        }
    }

    boolean isZipStream(File payload) throws IOException {
        try (FileInputStream fileInputStream = new FileInputStream(payload)){
            byte[] magicBytes = IOUtils.toByteArray(fileInputStream, 4);
            return Arrays.equals(magicBytes, new byte[]{80, 75, 3, 4});
        }
    }
}

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

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

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

Could you please make call with me to discuss what do you means as «Storing format is a sequence of operations». I asked about this points from first questions for this test task. Now for me it looks like not well defined requirements for 4–6h test exercise.

As I am invested my unpayable time for software development I expect detailed review why this solutions is not fit and what are you expect as a result

   Regards,

         Igor

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

Результат

Вчера мы созвонились и то что выяснил в диалоге, с моей точки зрения сильно выходит за 4–6 часов на задачу, с учетом покрытия тестами запрашиваемой реализации и ее сложности. Я вообще считаю что фактор стресса из-за ограниченности по времени решения, непонятности деталей задачи могут понизить производительность, как и ежедневный отдых и дайвинг с 19 сентября у океана. Эта ситуация отдаленно мне напомнила кадры из фильма Password Swordfish когда Джон Траволта тестирует профпригодность и стрессоустойчивость Майкла Джекмана. Но что точно могу сказать что хорошо что когда-то давно я уже сталкивался с MIME форматом и мне не пришлось погружаться в его детали впервые. Так что IMHO в оценках времени на задачу компания слукавила.

Хоть я и не прошел тестовое задание в эту компанию и инвестировал суммарно день своей жизни на все процессы интервью, я рад что все так как произошло. Конечно, успешное выполнение задания больше подняло мою самооценку чем итоговый отказ, но точно создало бы дилемму. Были у меня сомнения что бизнес компании не очень этичный с моей точки зрения и что я торгуюсь со своей совестью и переступаю через себя в доказательствах что я опытный специалист с подтверждаемым и доступным для ознакомления опытом в open source проектах. Но если же кто нибудь пойдет этому же пути, у вас будет больше информации для прохождения процесса интервью и принятия окончательного решения.

Поиск работы последние полтора месяца для меня показал что:

  • Похоже hh.ru уже в запустении по вакансиям и компаниям, кто готов оплачивать поиск на этом ресурсе.

  • Мой LinkedIn профиль с февраля этого года в затишье. Раньше он был постоянным фоновым источником вакансий и информации о состоянии рынка разработки ПО.

  • По позициям синьорных разработчиков и тимлидов теперь отсев по letcode, hackerrank и много где решение алгоритмических задач.

Пишите в комментариях ваши предположения о том что в моем варианте выполнения задания нужно было сделать иначе и чего там не хватает. Интересно как другие разработчики интерпретируют то же описание и достаточно ли в нем информации для решения. Ответы Александра и дополнительные примеры в почте не прояснили для меня, а что же такое «sequence of operations is a storing format» Вы так же можете сделать PR на github решения тестового задания чтобы исправить его так, как ожидалось. Этим вы можете лучше любых слов помочь кому-либо пройти это же задание в будущем или же лучше подготовиться к интервью!

© Habrahabr.ru