Хабр, привет! Меня зовут Даниил Пилипенко, я программный директор факультета backend-разработки направления «Программирование» Skillbox, директор центра подбора IT-специалистов SymbioWay. Сегодня разберём практические задачи, с которыми могут столкнуться Java-разработчики junior-уровня на собеседовании. Такого рода задачи мы часто видим на собеседованиях наших клиентов и коллег, а также сами даём соискателям.
Чего хочет работодатель
В процессе подбора кандидатов работодатель хочет понять, насколько ему подходит тот или иной соискатель — насколько он комфортен в общении и работе, обладает необходимым опытом и, что самое важное, техническими навыками для предстоящей работы. Ранее я рассказывал о пяти навыках, которые часто проверяются при приёме на работу у начинающих Java-разработчиков. А в этой статье я приведу примеры конкретных задач, которые могут давать на собеседованиях, и разберу, на что смотрит работодатель при их решении.
Примеры задач
Задача 1. Написать код, выполняющий какую-то несложную задачу. Здесь может быть, например, классический FizzBuzz, задача на сжатие или переворачивание строки. Работодателю здесь важно понять, как соискатель владеет основами синтаксиса языка и может ли писать код сразу чисто. Удивительно, но многие кандидаты испытывают значительные сложности при выполнении задач такого рода.
Одна из задач, которую мы даём соискателям: написать код, который выводит числа от 0 до 1000, которые делятся на 3, но не делятся на 5, и сумма цифр в которых меньше десяти. Задача часто вызывает совершенно немыслимые трудности у людей, утверждающих, что их уровень middle или даже senior. Вот несколько примеров решений такой задачи кандидатами на собеседованиях:
//Пример 1
public class TestClass {
public static final int MAX_LIMIT = 1000;
public static void main(String[] args) {
for(int i = 0; i < MAX_LIMIT; i++) {
boolean enabled = true;
if (i % 3 != 0) {
enabled = false;
}
if (i % 5 == 0) {
enabled = false;
}
if (!testSum(i)) {
enabled = false;
}
if (enabled == true) {
System.out.println("Число: " + i);
}
}
}
public static boolean testSum(int in) {
int res = 0;
while (in > 0) {
res += in % 10;
in = in / 10;
}
return res < 10;
}
}
//Пример 2
public class Test {
public static void main(String[] args) {
for (int i = 0; i<1000; i ++) {
if ( i%3 == 0 && i%5!= 0) {
}
}
}
public int returnNumber( int i) {
int t;
if (i >0){
returnNumber(i/10);
}
}
}
//Пример 3
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class TestTask {
public static List<Integer> getForNumber() {
List<Integer> list = IntStream.range(0, 1000).boxed()
.filter(n -> n % 3 == 0 && n % 5 != 0)
.collect(Collectors.toList());
return list;
}
public static int sumOfNubers(List<Integer> list) {
for (Integer n : list) {
int summ;
while(n>1) {
summ += n % 10;
n /= 10;
}
}
return 0;
}
}
Напишите нам в комментариях, как вы оцениваете каждое из этих решений и почему. Какое из них считаете лучшим, а какое худшим. Сразу отмечу, что решение, которое мы в SymbioWay считаем идеальным, здесь не приводим. Можете также попробовать написать его в комментариях.
С помощью задач такого типа мы проверяем уровень соискателя: как много он писал код сам, а также косвенно — сталкивался ли с «грязным» кодом и рефакторил ли его. По такому заданию можно косвенно увидеть, как быстро и «чисто» (понятно и поддерживаемо) специалист будет писать собственный код.
Задача 2. Создать веб-приложение на фреймворке Spring, которое будет соответствовать определённой спецификации. Спецификация может быть дана в текстовом виде или в виде отдельного Swagger-файла. Например, реализовать backend для анонимного онлайн-чата со следующей спецификацией:
GET /init — запрос инициализации, который по ID сессии отвечает, авторизован ли текущий пользователь или нет.
Запрос без параметров
Формат ответа:
{ "result": true }
POST /auth — запрос, регистрирующий и авторизующий пользователя. Он добавляет пользователя в базу данных и привязывает к текущей сессии.
Формат запроса:
name — имя пользователя
sex — пол пользователя, M или F
Формат ответа:
{ "result": true }
GET /users — запрос, возвращающий список пользователей в чате в порядке от самого свежего, который зарегистрировался недавно, до самого старого.
Запрос без параметров
Формат ответа:
{ "result": true, "data": [ { "id": 567, "name": "Alex Kurnikov", "male": "M" }, ... ] }
GET /messages — запрос, возвращающий ленту сообщений чата от самого нового до самого старого с постраничной навигацией.
Формат запроса:
offset — сдвиг от самого последнего до самого раннего, от 0, по умолчанию равен 0
limit — количество сообщений, которые нужно вывести, по умолчанию — 100
Формат ответа:
{ "result": true, "count": 100, "data": [ { "id": 46273, "time": "18:30 30.01.2022", "authorId": 567, "message": "Some HTML text, may me <b>tagged</b>" }, ... ] }
POST /messages — запрос, создающий новое сообщение от имени текущего пользователя. Форматы запроса и ответа спроектируйте самостоятельно на ваше усмотрение на основе имеющейся документации.
С помощью таких задач мы и работодатели проверяют сразу несколько навыков:
умение правильно проектировать и создавать веб-приложения на фреймворке Spring в соответствии с принятой структурой;
понимание принципов клиент-серверного взаимодействия (знание HTTP и стандарта REST);
умение работать с ORM-системой (обычно Hibernate) и базой данной, умение проектировать структуру этой базы;
умение писать код на Java и владение базовыми навыками при работе с этим языком программирования, в частности, знание ООП, умение работать со строками и коллекциями, лямбда-выражениями и Stream API, привычку их применять при необходимости.
Задача 3. Написать SQL-запрос. Например, задача может быть такой. У вас есть две таблицы:
employee — сотрудники компании
id
department_id
work_start_date
name
salary
department — отделы, в которых работают сотрудники
id
name
lead_id — ID руководителя отдела
Нужно написать такой SQL-запрос, который выведет всех сотрудников, работающих в компании с лета 2021 года, не привязанных к отделам и получающих зарплату меньше 100 000 рублей.
Верным ответом на такую задачу будет запрос:
SELECT
e.id,
e.name
FROM employee e
LEFT JOIN department d ON d.id = e.department_id
WHERE
e.work_start_date >= '2021-06-01' AND
e.salary < 100000 AND
d.id IS NULL
С помощью задач такого плана можно понять, насколько кандидат владеет языком запросов SQL, если это важно в данном проекте.
Вместо задач на написание SQL-запросов, могут предложить написать код с использованием Hibernate. Конечно, владение SQL — фундаментальный навык, который никогда не будет лишним. Но в некоторых проектах предпочитают писать не на чистом SQL.
Задача № 4. Решить заданную проблему устно. Например, рассказать, как предотвращать взаимные блокировки (deadlock) в многопоточных приложениях, или объяснить, как код, работающий с коллекцией, сделать потокобезопасным.
Здесь обычно требуется сначала рассказать о самой проблеме — в чём она состоит и почему её вообще следует решать, а затем объяснить, как именно это можно сделать. И чем понятнее будет ваш рассказ и чем логичнее будут ваши рассуждения, тем лучше.
При этом не обязательно решать задачу до конца идеально. Того, что вы думаете в правильном направлении, будет вполне достаточно для засчитывания ответа как верного. Ведь в реальности невозможно знать и помнить всё, гораздо важнее умение находить решения, понимать, куда «копать», и ставить эксперименты.
Задача № 5. Задача или серия вопросов на понимание принципов и паттернов ООП. Например, создать класс, объект которого может быть представлен только в единственном экземпляре (по сути, реализовать паттерн Singleton).
Тут могут не только попросить в итоге написать код для многопоточного режима работы, но и объяснить, зачем вообще такое может быть нужно, или рассказать, почему Singleton не используется во фреймворке Spring и чем он там фактически заменяется.
Мы сами сталкиваемся с тем, что на вопросы вроде «для чего в Java используется ключевое слово final» или «может ли статический метод переопределяться при наследовании» отвечают менее 20% кандидатов, по резюме имеющих уровень middle или выше.
Как интерпретируют результаты
Вы, возможно, будете удивлены, но во время решения практических задач работодатель не всегда ждёт полного и чёткого ответа по каждой из них. Он рассчитывает скорее на правильный вектор рассуждения и понимание той или иной темы.
Конечно, когда мы просим написать код, то кандидату лучше бы справиться с этой задачей. Так мы понимаем, что он действительно умеет писать код и делает это сразу чисто. Кроме того, здесь часто бывает важно ваше упорство: умение довести задачу до конца — важнейший навык, с которым у многих в наше время возникают проблемы.
Третье, что также важно увидеть в кандидате, — это понимание базовых, фундаментальных концепций. Если вы не знаете, что такое HTTP, как делать простейшие SQL-запросы, для чего вообще нужна многопоточность и что такое потокобезопасность, вам, скорее всего, откажут.
Когда мы подбираем специалистов уровня junior в нашу команду или кому-то из работодателей, мы проверяем три блока навыков:
Понимание базовых концепций (синтаксис, коллекции, чистота кода, ООП, создание приложений на Spring, HTTP, REST и SQL).
Умение быстро, самостоятельно и грамотно решать возникающие проблемы, в том числе находить нужную информацию.
Уровень обучаемости — скорость и качество запоминания и дальнейшего внедрения в свою работу best practices и рекомендаций, которые мы даём новичку.
После приёма на работу начинающего специалиста важно не перегрузить его слишком сложными задачами. Если он хорошо выполняет свою работу и близок к тому, чтобы начать скучать, то постепенно ему доверяют всё более сложные и интересные проекты, которые обеспечивают дальнейший рост.