Как мы определяем уровень профессионализма разработчика?

Как мы определяем уровень профессионализма разработчика?

4 октября 2016

«Жил-был принц, он хотел взять себе в жены принцессу. Вот он и объехал весь свет... Да повсюду было что-то не то: принцесс было полно, а вот настоящие ли они, этого он никак не мог распознать до конца, всегда с ними было что-то не в порядке»

Г. Х. Андерсен, Принцесса на горошине

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

Когда в EDISON требуется программист, в объявлении указывается степень квалификации. Например, Middle-разработчик. Возникают вопросы от соискателей о дифференциации уровней. Единой классификации степени профессионализма нет. Можно сказать, что Junior — начинающий разработчик с опытом до 2 лет, Middle — от 2. Стаж Senior начинается с 5. На вершине системы уровень Lead, подразумевающий руководство командой специалистов. Но стаж не гарантирует обладание необходимыми навыками. Можно 5 лет плодить сайты-одностраничники и не стать Senior-разработчиком. И наоборот: попав к грамотному наставнику и принимаясь за серьезные задачи, через год достичь уровня «Middle». Об отборе кандидатов в EDISON кратко написано здесь.

Практика предварительной беседы с соискателями не подошла из-за временных затрат. Этап первичного отбора передали специалисту кадровой службы. Сначала соискатель проходит анкетирование, самостоятельно оценивая компетентность в областях программирования по 5-балльной шкале. Указывает срок использования технологии, заполняет таблицу «Выполненные проекты». Полученные сведения дают общее представление об опыте соискателя и профессиональном кругозоре. Начинающим разработчикам свойственно завышать оценку. К примеру, кандидат считает уровень знания Рython на 4, «готов решить любую задачу», а опыт использования языка указывает 2 недели.

Компетентность соискателя оценивается на практике. Кандидат выполняет тестовое задание. На основании анализа определяется уровень.

Первый фактор оценки — время выполнения

На идентичное задание Junior-разработчику понадобится неделя. Senior выполнит тест за несколько часов. Показательна и оценка срока выполнения тестового задания от соискателя. Разработчик уровня «Junior» смотрит на поставленную задачу чересчур оптимистично, недооценивает сложность. И из-за нехватки опыта не укладывается в сроки. Специалист уровня «Middle» склонен пессимистично смотреть на задачу. Сказывается опыт в качестве Junior-разработчика. Чрезмерно увеличивает прогнозируемый срок реализации. Senior-разработчик реалистичен. Закладывает риски разумно без лишнего завышения сроков.

Второй момент — качество кода

Несколько лет для оценки соискатели писали простую браузерную игру «Крестики-нолики». В зависимости от вакансии рекомендовалось использовать определенный язык или технологию. Если планировалось значительное расширение штата, у кандидатов была свобода выбора инструментария.

Сейчас у нас десятки вариантов выполнения проверочного задания. Тестировщики EDISON выбрали 3 фрагмента кода (обработка запроса веб-приложения), написанные на PHP разработчиками разного уровня, и добавили комментарии.

Начнем с примера так называемого «говнокода».

$user = userRequestWithPassword($_COOKIE['login'], $_COOKIE['password']);

Хранение логина и пароля пользователя в куках. Явная ошибка безопасности. Куки передаются от браузера к серверу при запросе (открытии/обновлении страницы). Потенциальная возможность перехвата.

if ($user != null) {
    if (isset($_POST['submitEdit'])) {

Определение действий сайта на основе параметра POST-запроса последовательными условными блоками. Код усложняется, становясь громоздким и нечитаемым. Для облегчения реализации задачи опытные программисты придумали маршрутизацию и паттерны, например MVC.

$deal = dealRequest($_GET['dealId']);
$connect = mysqli_connect(BAZA_SERVER, BAZA_USER, BAZA_PASSWORD, BAZA_MYSQL);
$name = mysqli_real_escape_string($connect, $_POST['name']);
$date = mysqli_real_escape_string($connect, $_POST['date']);
$insured = mysqli_real_escape_string($connect, $_POST['insured']);
$obligor = mysqli_real_escape_string($connect, $_POST['obligor']);
$countryObligor = mysqli_real_escape_string($connect, $_POST['countryObligor']);
$amount = mysqli_real_escape_string($connect, $_POST['amount']);
$currency = mysqli_real_escape_string($connect, $_POST['currency']);
$percent = mysqli_real_escape_string($connect, $_POST['percent']);
$tenor = mysqli_real_escape_string($connect, $_POST['tenor']);
$type = mysqli_real_escape_string($connect, $_POST['type']);
$responseDate = mysqli_real_escape_string($connect, $_POST['responseDate']);
$person = mysqli_real_escape_string($connect, $_POST['person']);

Громоздкий код для элементарных операций. Опытный программист напишет блок в одну строку.

if (!empty($_FILES['upload']['name'])) {
    $path_directory = 'documents/';
    $filename = $_FILES['upload']['name'];
    if (preg_match('/[.](doc)|(docx)|(pdf)|(xls)|(jpg)$/', $_FILES['upload']['name'])) {
        $source = $_FILES['upload']['tmp_name'];
        $target = $path_directory . $filename;
        $fileName = $moved = move_uploaded_file($source, $target);
    }
}
else {
    $filename = $deal['documents'];
}

$query = "update Deals
          set name='$name', date='$date', nameOfTheInsured='$insured', nameOfTheObligor='$obligor',
    countryOfTheObligor='$countryObligor', amount='$amount',
          currencyOfTheDeal='$currency', percentToBeInsured='$percent', tenorOfTheExposure='$tenor',
          typeOfTheDeal='$type', targetResponseDate='$responseDate', nameOfTheContactPerson='$person',
    documents='$filename'
          where id='".$_GET['dealId']."'";

Подстановка параметра GET-запроса (строки, приходящей от пользователя при открытии страницы в браузере) прямо в SQL-запрос (обращение к базе данных). Потенциальная уязвимость в безопасности (SQL-инъекция).

mysqli_query($connect, $query);
mysqli_close($connect);
header("location: http://example.com/view.php?dealId=".$_GET['dealId']);

Хардкод URL'ов. Адрес страницы приложения может меняться. Для отсылки на новый адрес программисту придется искать и менять данные в коде вхождения старого URL.

else if (isset($_POST['addComment'])) {
    $connect = mysqli_connect(BAZA_SERVER, BAZA_USER, BAZA_PASSWORD, BAZA_MYSQL);

Именование переменных на разных языках — частный случай использования транслита в коде. Распространенная ошибка начинающих говнокодеров.

$dealId = mysqli_real_escape_string($connect, $_GET['dealId']);
    $userId = mysqli_real_escape_string($connect, $user['id']);
    $comment = mysqli_real_escape_string($connect, $_POST['comment']);

    $query = "insert into Comments (dealRefer, userRefer, comment) values('$dealId','$userId','$comment')";
    mysqli_query($connect, $query);
    mysqli_close($connect);
    header("location: http://example.com/view.php?dealId=".$_GET['dealId']);
}

Пример кода уровня «Junior».

if (isset($_GET['action']) && ($_GET['action'] == 'online'))

Определение действий, исходя из параметров GET-запроса последовательными условными блоками. Аналогично прошлому примеру.

{
        $document = new Document();
        $document->SetLanguage($cur_lang);

        if ($starter = $db->GetFByQuery("SELECT u.login FROM games g
            LEFT JOIN users u ON g.starter=u.id
            WHERE g.`invited`=$uid"))
        {    		echo "<a href = '#' onclick = 'return invite(0)'>$starter " . $document->Translate(17) .
                             "</a><br><a href = '#' onclick = 'return deny()'>" . $document->Translate(19) . "</a>";
        }

«Echo» в коде является не лучшим решением для вывода текста или верстки в браузер. Усложняет процесс изменения внешнего вида сайта. Верстка должна находиться в отдельных файлах-шаблонах. По аналогии справедливо и для JS-, CSS-вставок. Обязательно разделение по разным файлам, желателен разброс по папкам.

else
    {
         $rows = $db->GetByQuery("SELECT id, login FROM users WHERE `lastping`>" . (time() - 30) . " AND `id`<>$uid");
         if (count($rows))
             foreach ($rows as $row)
             {
                 echo "<a href = '#' onclick = 'return invite(\"$row[login]\")'>$row[login]</a><br>";
             }

Захардкоденный обработчик события click. Аналогично предыдущему пункту. Весь JS нужно выносить в отдельные файлы.

        else
        {
            echo $document->Translate(11);
        }
    }
}
elseif (isset($_GET['action']) && ($_GET['action'] == 'creategame'))
{
...
}
elseif (isset($_GET['action']) && ($_GET['action'] == 'getfields'))
{
...
}
...

Код Middle-разработчика прост для понимания и содержит комментарии для разбора сложных участков. Используется ORM (Object-relational mapping) взамен написания нативных запросов к базе. Значительно снижается риск SQL-инъекций. Применяется ООП и MVC.

public function actionStatistics() {
           // Получение из БД общего количества игр и количества игроков.
           $GamesNumber = tableGame::model() -> count();
           $PlayersNumber = tableUser::model() -> count();
           $GeneralStatistics = array(
               'GamesNumber' => $GamesNumber,
               'PlayersNumber' => $PlayersNumber
           );
           $Player = new Player();
           // Получение из БД списка игроков с самым высоким рейтингом.
           $dbModel = tableUser::model() -> findAllByAttributes(
               array('Enable' => 1),
               array('limit' => self::TOP_PLAYERS_LIST_SIZE, 'order' => 'Rating DESC')
           );
           // Формирование массива сводной информации по лучшим игрокам.
           foreach ($dbModel as $PlayerData) {
               if ($Player -> Load($PlayerData -> ID)) {
                  $PlayersList[] = clone $Player;
               }
           }
           // Загрузка данных авторизованного игрока.
           $Player -> Load(Yii::app() -> user -> getId());
           // Вывод представления.
           $this -> render('statistics', array(
               'GeneralStatistics' => $GeneralStatistics,
               'PlayerData' => $Player,
               'PlayersList' => $PlayersList
           ));
}

Различия между Middle- и Senior-разработчиком по фрагменту кода прослеживаются слабо и заключаются в выборе верных архитектурных решений.

Обобщенные критерии оценки сведены в таблицу. Список не ограничивается приведенными примерами.

Критерий оценки Junior Middle Senior
Декомпозиция задачи Последовательные строчки кода. Copy/paste — для повторного использования кода. Создает многократно используемые функции/объекты, решающие общие задачи. Использует соответствующие структуры данных и алгоритмы. Создает общий/объектно-ориентированный код, инкапсулирующий условия задачи.
Декомпозиция системы Не способен думать о системе сложнее одного класса или файла. Производит декомпозицию задачи и проектирует систему в пределах одной платформы или технологии. Визуализирует и проектирует сложные системы с несколькими линейками продуктов, интегрирует с внешними системами. Проектирует системы поддержки работы: мониторинг, генерация отчетов, аварийные переходы на запасные ресурсы.
Общение Не может выразить свои мысли/идеи. Плохо с правописанием и грамматикой. Его понимают. Хорошее правописание и грамматика. Общается эффективно. Понимает и объясняет мысли/дизайн/идеи/специфику в точно выраженной форме. В общении соответствует ситуации.
Организация кода в файле Нет четкой организации в файле. Методы сгруппированы логически и по вызовам. Код разделен на регионы. Имеет грамотные комментарии со ссылками на файлы-исходники.
Организация кода между файлами Нет четкой организации кода с делением на файлы. Физический файл выполняет одну функцию. Например, служит для объявления класса или для реализации одного функционала и т. д. Организация кода на физическом уровне соответствует проекту. Наглядность способа проектирования реализации через имена файлов и структуру папок.
Читабельность кода Односложные имена. Хорошие имена файлов, переменных, классов, методов и т. д. Нет длинных функций. Нестандартный код, багфиксы и допущения в коде поясняются комментариями. Допущения в коде сопровождаются командами Assert. Поток операций в коде естественный (без глубокой вложенности условий или методов).
Безопасное программирование Не понимает данной концепции. Проверяет аргументы методов, возвращаемое значение и обработку исключений в потенциально важном коде. Имеет собственную библиотеку, помогающую в безопасном программировании. Пишет юнит-тесты для имитации сбоев.
Обработка ошибок Пишет код для «идеального» случая без сбоев. Обработка ошибок в коде (кидает исключение или генерирует ошибку). Пишет код с функцией раннего определения ошибок. Придерживается последовательной обработки исключений в слоях кода и формулирует принципы процесса в системе.
Требования Понимает выставленные требования и пишет код в соответствии со спецификацией. Видит картину в целом и сразу выявляет дополнительные аспекты с последующим описанием в спецификации. Задает вопросы, касающиеся неучтенных случаев. Предлагает альтернативы и следует выставленным требованиям, основываясь на собственном опыте.
Базы данных Знает основы баз данных, транзакции. Пишет простые select-запросы. Проектирует нормализованные схемы БД с учетом запросов. Умело использует представления, хранимые процедуры, триггеры и собственные типы данных. Понимает разницу между кластеризованными и некластеризованными индексами. Специалист в использовании ORM инструментов. Осуществляет администрирование, увеличивает производительность и оптимизирует индексы БД. Пишет сложные select-запросы. Заменяет использование курсора вызовами функций SQL. Понимает принципы внутреннего хранения данных и индексов. Имеет представление о создании «зеркал» и репликации БД и т.д.

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