«Жил-был принц, он хотел взять себе в жены принцессу. Вот он и объехал весь свет... Да повсюду было что-то не то: принцесс было полно, а вот настоящие ли они, этого он никак не мог распознать до конца, всегда с ними было что-то не в порядке»
Пытаясь найти опытного разработчика, сталкиваешься с похожей проблемой. На объявление о вакансии много откликов. Как определить соответствие кандидата необходимому уровню профессионализма? Специальность программиста считается перспективной. Количество соискателей с двухмесячными курсами за плечами больше, чем фальшивых принцесс в период феодальной раздробленности.
Когда в 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. Понимает принципы внутреннего хранения данных и индексов. Имеет представление о создании «зеркал» и репликации БД и т.д. |
Оценка уровня соискателя субъективна в размере допустимой погрешности. Но претенденты, как правило, соглашаются с результатом тестирования. На испытательном сроке в большинстве случаев кандидат имеет возможность быстро продемонстрировать уровень, превышающий оценочный.