Я учусь на факультете, который «продаёт» студентов в компании-партнёры. После окончания второго курса все обязаны пойти на круглогодичную стажировку (возможно, оплачиваемую). Я, решивший и готовившийся к работе системным аналитиком, проходил соответствующие собеседования. В общей сложности я прошёл их около 10. Не все были связаны с университетом — некоторые компании просто нашли моё резюме.
Мне сделали несколько офферов, и я выбрал организацию, в которой сейчас и работаю. Все собеседования были разные: какие-то показательные, какие-то совсем не относящиеся к системному анализу. Например, вопрос о том, как выделить 8 килобайт памяти на С, меня сильно удивил. Эту компанию, впрочем, тоже можно понять — они проверяли междисциплинарные навыки, что для кого-то может считаться логичным при наборе студентов.
Собеседования были настолько разными, что я задумался: как бы я сам провёл собеседование и пытался выявить у потенциального стажёра необходимые навыки? Так у меня сформулировался вопрос:
Какой ключевой навык аналитики, претендующего на позицию джуна, можно попытаться выявить за ограниченное время собеседования?
Если у кого‑то ответ отличный от моего, то буду рад услышать!
Это был не ключевой вопрос для меня, поэтому он просто «висел» где-то в памяти и ждал подходящего момента. В один из дней я наткнулся на статью «Как написать ТЗ на простую программу (калькулятор)», в которой автор очень подробно разбирает требования, как бы то не казалось простой программе. Основной акцент был сделан на выявлении потребности через умение задавать вопросы, чтобы определить реальные потребности. В итоге нашелся ответ на исходный вопрос, когда сам того не ожидал (знаете, бывает когда внезапно лампочка загорается в голове).
Ведь правда, для построения адекватной модели решения аналитик всегда задает вопросы. Аналитик в целом, что делает, Задает вопросы, чтобы потом самому же на них искать ответы у разных стейкхолдеров, разными способами.
И вот мой ответ про ключевой навык - умение задавать вопросы.
Концепция собеседования
Осознав основную идею, на которой должно строиться собеседование аналитика, мне предстояло выбрать подход из тех, что я выделил для себя. Пусть моя выборка собеседований и не совсем репрезентативна, но я смог выделить два основных подхода, которые применялись на практике:
«Понять» — где ключевой целью было выявить мыслительные цепочки, которые у меня возникают при решении той или иной задачи. Например, на собеседовании в казино (я тогда об этом не знал) у меня пытались понять, чем я руководствовался в учебных проектах: почему принимал те или иные решения, почему создавал какие-то артефакты.
«Спросить» — где основным ожиданием от меня было получение «типового» ответа. Например, на собеседовании в финтех-компанию мне задали задачу: есть 2 таблицы — «покупка» и «пользователь». Нужно написать запрос, чтобы вывести всех пользователей, которые делали покупки в заданном диапазоне дат.
Оба варианта включают в себя блок теории из списка «Топ миллиард вопросов системному аналитику». Однако речь идёт именно о практической части. Субъективно считаю, что первый подход более ценен в собеседовании, поэтому я склоняюсь к вопросам, где нет однозначно правильного ответа.
Итого: необходимо придумать задание, которое будет проверять навык задавать вопросы и позволит оценить рассуждения кандидата без ожидания заранее шаблонного решения.
Мне понравилась идея из статьи про калькулятор: даже к самым простым программам может быть множество требований, которые можно выявить только через вопросы. Ещё как-то мы обсуждали это с руководителем тестирования, и он сказал, что у них раньше (может и сейчас) было модно спрашивать: «Как протестировать сковородку?».
Была сформирована концепция, удовлетворяющая поставленным выше требованиям:
Я играю роль заказчика.
Даю простое требование: приложение с 1–2 функциями.
Бизнес- или нефункциональные требования могут меняться для любого человека, если он работает с тем же самым приложением, что и предыдущий кандидат.
Кандидат должен сформулировать требования к системе, но сделать получится только через вопросы.
С этой идеей я попросился поучаствовать в собеседовании стажёров следующей волны, которые приходили после окончания второго курса. Мою идею поддержали и разрешили мне принять участие в собеседованиях, чему я был очень рад конечно же.
Задания
Сформированная концепция и референс в виде калькулятора позволили быстро накидать достаточное количество идей заданий. Ниже представлены идеи заданий, оформленные в html формате для большей наглядности.
1. Приложение для сложения двух чисел
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Сложение чисел</title> <style> body { font-family: Arial, sans-serif; padding: 20px; } input, button { margin: 5px; } </style> </head> <body> <h2>Сложение двух чисел</h2> <input type="number" id="num1" placeholder="Первое число"> <input type="number" id="num2" placeholder="Второе число"> <button onclick="addNumbers()">Сложить</button> <p>Результат: <span id="result">—</span></p> <script> function addNumbers() { const number1 = parseFloat(document.getElementById('num1').value); const number2 = parseFloat(document.getElementById('num2').value); const sum = number1 + number2; document.getElementById('result').textContent = isNaN(sum) ? 'Ошибка ввода' : sum; } </script> </body> </html>
2. Будильник
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Будильник</title> <style> body { font-family: 'Segoe UI', sans-serif; background: #f0f8ff; text-align: center; padding-top: 50px; } h2 { color: #2c3e50; } input, button { padding: 10px; font-size: 16px; margin-top: 10px; border-radius: 5px; border: 1px solid #ccc; } button { background-color: #3498db; color: white; cursor: pointer; } button:hover { background-color: #2980b9; } .alarm-set { margin-top: 20px; color: green; } </style> </head> <body> <h2>Будильник ⏰</h2> <label for="alarmTime">Установи время:</label><br> <input type="time" id="alarmTime"><br> <button onclick="setAlarm()">Установить</button> <div id="alarmStatus" class="alarm-set"></div> <audio id="alarmSound" src="https://www.soundjay.com/buttons/beep-07.mp3" preload="auto"></audio> <script> let alarmTime = null; let alarmTimeout = null; function setAlarm() { const timeInput = document.getElementById('alarmTime').value; const alarmStatus = document.getElementById('alarmStatus'); if (!timeInput) { alarmStatus.textContent = "Пожалуйста, выбери время."; alarmStatus.style.color = 'red'; return; } const now = new Date(); const alarmDate = new Date(now.toDateString() + ' ' + timeInput); if (alarmDate <= now) { alarmDate.setDate(alarmDate.getDate() + 1); // на следующий день } const timeToAlarm = alarmDate - now; if (alarmTimeout) clearTimeout(alarmTimeout); alarmTimeout = setTimeout(() => { document.getElementById('alarmSound').play(); alert("⏰ Время просыпаться!"); }, timeToAlarm); alarmTime = timeInput; alarmStatus.textContent = "Будильник установлен на " + timeInput; alarmStatus.style.color = 'green'; } </script> </body> </html>
3. Вызов официанта
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Вызов официанта</title> <style> body { background-color: #fff8f0; font-family: 'Segoe UI', sans-serif; text-align: center; padding-top: 100px; } h1 { font-size: 32px; margin-bottom: 40px; } button { background-color: #e67e22; color: white; border: none; padding: 20px 40px; font-size: 24px; border-radius: 12px; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: #d35400; } #status { margin-top: 30px; font-size: 22px; color: #2c3e50; } .bell { font-size: 60px; color: #e67e22; margin-bottom: 20px; } </style> </head> <body> <div class="bell">🔔</div> <h1>Нужна помощь?</h1> <button onclick="callWaiter()">Вызвать официанта</button> <div id="status"></div> <script> function callWaiter() { const status = document.getElementById('status'); status.textContent = "Официант уведомлён, ожидайте..."; setTimeout(() => { status.textContent = ""; }, 10000); // Через 10 секунд статус исчезает } </script> </body> </html>
4. Приложение для заметок
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Заметки</title> <style> body { font-family: 'Segoe UI', sans-serif; background: #f5f5f5; padding: 30px; max-width: 700px; margin: auto; } h1 { text-align: center; color: #333; } textarea { width: 100%; height: 150px; padding: 15px; font-size: 16px; margin-bottom: 10px; border-radius: 8px; border: 1px solid #ccc; resize: vertical; } button { padding: 10px 20px; font-size: 16px; background-color: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; margin-right: 10px; } button:hover { background-color: #27ae60; } ul { list-style-type: none; padding: 0; margin-top: 20px; } li { background: white; padding: 12px; margin-bottom: 10px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); position: relative; } .delete-btn { position: absolute; right: 15px; top: 15px; background: #e74c3c; color: white; border: none; padding: 5px 10px; font-size: 14px; border-radius: 5px; cursor: pointer; } .delete-btn:hover { background: #c0392b; } </style> </head> <body> <h1>Мои заметки</h1> <textarea id="noteInput" placeholder="Введите заметку..."></textarea> <br> <button onclick="addNote()">Сохранить</button> <button onclick="clearAll()">Очистить все</button> <ul id="notesList"></ul> <script> const noteInput = document.getElementById('noteInput'); const notesList = document.getElementById('notesList'); function loadNotes() { const notes = JSON.parse(localStorage.getItem('notes')) || []; notesList.innerHTML = ''; notes.forEach((note, index) => { const li = document.createElement('li'); li.textContent = note; const delBtn = document.createElement('button'); delBtn.textContent = 'Удалить'; delBtn.className = 'delete-btn'; delBtn.onclick = () => deleteNote(index); li.appendChild(delBtn); notesList.appendChild(li); }); } function addNote() { const text = noteInput.value.trim(); if (text) { const notes = JSON.parse(localStorage.getItem('notes')) || []; notes.push(text); localStorage.setItem('notes', JSON.stringify(notes)); noteInput.value = ''; loadNotes(); } } function deleteNote(index) { const notes = JSON.parse(localStorage.getItem('notes')) || []; notes.splice(index, 1); localStorage.setItem('notes', JSON.stringify(notes)); loadNotes(); } function clearAll() { if (confirm("Вы уверены, что хотите удалить все заметки?")) { localStorage.removeItem('notes'); loadNotes(); } } loadNotes(); </script> </body> </html>
5. Приложение для смены картинок
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Смена картинок</title> <style> body { font-family: 'Segoe UI', sans-serif; text-align: center; padding-top: 50px; background-color: #f0f8ff; } img { width: 300px; height: auto; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); margin-bottom: 20px; transition: opacity 0.5s ease; } button { padding: 10px 20px; font-size: 18px; background-color: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer; } button:hover { background-color: #2980b9; } </style> </head> <body> <h2>Нажми кнопку, чтобы сменить картинку</h2> <img id="image" src="https://placekitten.com/300/200" alt="Случайная картинка"> <br> <button onclick="changeImage()">Сменить картинку</button> <script> const images = [ "https://towiki.ru/images/thumb/c/ca/%D0%9F%D1%80%D0%BE%D1%81%D0%BF%D0%B5%D0%BA%D1%82-%D0%9B%D0%B5%D0%BD%D0%B8%D0%BD%D0%B0-DSC31052.jpg/300px-%D0%9F%D1%80%D0%BE%D1%81%D0%BF%D0%B5%D0%BA%D1%82-%D0%9B%D0%B5%D0%BD%D0%B8%D0%BD%D0%B0-DSC31052.jpg", "https://picsum.photos/300/200", "https://placebear.com/300/200", "https://place-puppy.com/300x200", "https://loremflickr.com/300/200/nature" ]; let currentIndex = 0; function changeImage() { currentIndex = (currentIndex + 1) % images.length; const img = document.getElementById('image'); img.style.opacity = 0; setTimeout(() => { img.src = images[currentIndex]; img.style.opacity = 1; }, 300); } </script> </body> </html>
6. Полив растений
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Полив растений</title> <style> body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(to top, #d0f0c0, #ffffff); margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; min-height: 100vh; } h2 { margin-top: 40px; color: #2e7d32; } button { font-size: 18px; padding: 12px 28px; margin-top: 20px; background-color: #4caf50; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: #388e3c; } #plant-container { position: relative; margin-top: 40px; width: 200px; height: 250px; } #plant { width: 100%; height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } .water-drop { position: absolute; width: 12px; height: 12px; background: rgba(30, 144, 255, 0.7); border-radius: 50%; animation: drop 1.2s ease-out forwards; } @keyframes drop { 0% { top: -20px; opacity: 1; } 100% { top: 140px; opacity: 0; } } </style> </head> <body> <h2>Полей растение 🌱</h2> <button onclick="waterPlant()">Полить</button> <div id="plant-container"> <img id="plant" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTqq2wORHOycpYxphh6btWVQogQPPI1kxKBug&s" alt="Растение"> </div> <script> function waterPlant() { const container = document.getElementById('plant-container'); for (let i = 0; i < 10; i++) { const drop = document.createElement('div'); drop.className = 'water-drop'; drop.style.left = `${30 + Math.random() * 40}%`; drop.style.animationDuration = `${0.8 + Math.random() * 0.6}s`; container.appendChild(drop); setTimeout(() => { drop.remove(); }, 1500); } } </script> </body> </html>
Результаты
Мои ожидания были более оптимистичными. Какое количество заданий, такое и количество студентов пытались к нам попасть. Всем было сказано, что я заказчик (что нужно задавать вопросы, я напрямую естественно не говорил) и мне необходима следующая система, попробуйте описать требования к этой системе.
В итоге: все забыли про какие-то требования кроме функциональных и только двое пытались задавать вопросы. В основном все просто говорили только то, что должна делать система, по сути, переформулировка моих слов, но никто не задался вопросом Зачем и Как. Меня этот факт реально удивил. При чем, чем более простое задание, тем более сложным оно оказалось. В ситуации со сложением двух чисел, кроме функции непосредственно подразумеваемой в названии, кандидатам было сложно что-то сказать - приходилось делать явные намеки.
Хотя результаты были не самыми положительными, задания оказались очень показательными: они наглядно продемонстрировали важность ключевого для аналитика навыка — умения задавать вопросы, который на самом деле далеко не так прост и очевиден.
P.s. тех двоих, кто задавал какие-то вопросы мы в итоге взяли на стажировку.
Это была моя история, которой мне захотелось поделиться, если кто-то имеет другое мнение по отношению к ключевому навыку аналитика или к подходу проведения собеседований, или задачам предлагаемым к кандидатам, то буду рад услышать.
Думаю, что я не единственный кто придумал и использовал подобные задания, поэтому если у вас есть другие варианты или истории, то было бы круто пополнить коллекцию!
Также хочу отметить, что аналитик, в том числе стажер, должен обладать разным навыками, для каждой позиции они свои, о них можно поговорить в другой раз или в другой статье.
