Этапы профессиональной карьеры разработчика: какие задачи решают junior, middle и senior

907773837ebae2db9645ed24da9b978b.jpg

В своем блоге на Хабре мы уже не раз писали о Python. Хотя бы потому, что это один из наиболее популярных в мире языков программирования. В начале этого года по версии Tiobe он занял первое место. Популярность его объясняется достаточно просто — язык можно относительно быстро выучить на базовом уровне и начать двигаться к вершинам профессии. Но какие они, эти вершины? На что способен senior, какие задачи решает middle, а какие — junior? Об этом мы поговорили с Алексеем Некрасовым (@znbiz), лидером направления Python в МТС, программным директором направления Python и спикером профессии «Python-разработчик» в Skillbox.

Начнем с того, что должен знать junior, middle и senior

Конечно, все это усредненная информация, для разных случаев и мест работы ситуация может отличаться. Но база все же более-менее общая. Важный момент — в IT чем выше позиция разработчика, тем большим объемом soft-скиллов он должен обладать. Конечно, хард-скиллы тоже никто не отменял. Вот достаточно подробный список знаний и умений специалистов разного уровня.

Junior-разработчик:

hard: знать основы ЯП (языка программирования);

hard: иметь минимальные знания стека, используемого в продукте;

hard: уметь самостоятельно решать простые типовые задачи;

soft: адекватно воспринимать критику;

soft: помимо 8 часов работы самостоятельно дополнительно обучаться 2–3 часа в день.

Middle-разработчик:

hard: знать хорошо основной ЯП, в том числе его тонкости, ограничения, best practices;

hard: знать и применять шаблоны и стандарты промышленной разработки;

hard: уметь подбирать несколько решений нужной задачи и находить оптимальный вариант;

hard: знать и уметь работать со стеком, используемым в компании;

soft: быть наставником для менее опытных коллег;

soft: находить общий язык с тем, от кого зависит решение задач: аналитик, тестировщик, DevOps и другие разработчики;

soft: понимать требования бизнеса;

soft: оценивать свои силы и трудозатраты по своим задачам;

soft: новое качество (на удаленке). Тайм-менеджмент и умение разделять работу и личную жизнь.

Senior-разработчик:

hard: уметь быстро изучить и внедрить новый инструмент в продукт;

hard: прорабатывать архитектуру новых компонентов в продукте;

hard: смотреть на продукт шире и видеть все его ограничения, узкие места и т.д.;

soft: понимать, для чего создается продукт, и задавать направление дальнейшего его развития;

soft: проводить декомпозицию задач;

soft: делегировать задачи;

soft: быть наставником для middle разработчиков;

soft: переключаться между своей ролью и другими ролями в команде, к примеру иногда быть как аналитиком, так и DevOps;

soft: видеть сильные и слабые стороны менее опытных коллег, помогать команде стать лучше;

soft: новое качество (на удаленке). Тайм-менеджмент и умение разделять работу и личную жизнь.

Теперь — о задачах

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

Junior. Если коротко, то основная задача junior разработчика — учиться и перенимать опыт у более опытных коллег. Часто обучение проходит в таком формате: брать все более сложные задачи (их даёт наставник или более опытный коллега), выполнение которых каждый раз будет требовать выходить за рамки того, что он знает.

Кроме того, он может и самостоятельно справиться с определенным кругом задач, не отвлекая других разработчиков. В основном это простые и типовые задачи:

  • Разработать валидацию входных/выходных данных,

  • «Причесать» код в соответствие с новым правилом из линтера,

  • Написать автотесты и т.п.

Но даже такие простые задачи проходят проверку у наставника, так как на них junior «набивает руку» и приучается к тем стилям разработки, которые используются в компании.

Вариант задачи для junior — написать тест на создание чего-либо. Например, слова:

from typing import Optional
from unittest import mock

import pytest

from app.factories import UserFactory
from app.models import Lexicon, User
from app.views import LexiconResourceList

from tests.conftest import deleted


@pytest.fixture
def user():
   user = UserFactory()
   yield user
   deleted(user)


class TestLexiconResourceList:
   f_post = LexiconResourceList().post.__wrapped__

   @mock.patch('app.views.request')
   def test_post(self, mock_request, user: User):
       data = {'value': "Word_1"}
       mock_request.json = data
       mock_request.user = user
       resp = self.f_post()

       assert resp['value'] == data['value']
       lexicon: Optional[Lexicon] = Lexicon.query \
           .filter(Lexicon.value == data['value'], Lexicon.status == Lexicon.Status.active) \
           .first()
       assert lexicon is not None

       # try again to add the word
       resp = self.f_post()
       assert 'This word already exists' in resp['errors'][0][0]

Middle-разработчик. Он — основная рабочая сила в команде, в среднем в компаниях на них приходится около 80% всех задач. Если junior делает что-то работающее, то задача middle — не просто сделать, «чтобы работало», но и выполнить свою её в оптимальный срок и качественно. Middle-разработчику чётко говорим, что нужно сделать и можно быть уверенным, что он это выполнит.

Пример задачи для middle: создать метакласс для реализации фабрик. Упрощённый вариант решения:

from typing import Union, List, Dict, Any

from faker import Faker
from app import db
from app.models import User


class ExceptionBeforeCreate(Exception):
   pass


class ExceptionAfterCommit(Exception):
   pass


class ExceptionBeforeCommit(Exception):
   pass


class ErrorCreateObject(Exception):

   def __init__(self, model, description, field_name: str = ''):
       self.model = model
       self.message = description
       self.field_name = field_name
       self.description = description
       super().__init__(self.message)


class BaseFactory:
   class Meta:
       model = None

   data = {}

   def __new__(cls, *args, **kwargs) -> Union[List[Meta.model], Meta.model]:
       new_kwargs = dict()
       new_kwargs.update(kwargs)
       fixture_data = cls.data
      
       for name, val in fixture_data.items():
           if name not in new_kwargs:
               new_kwargs[name] = val()

       try:
           new_kwargs = cls.before_create(**new_kwargs)
       except ExceptionBeforeCreate:
           pass
       new_object = cls.Meta.model(**new_kwargs)
       new_object.save(commit=False, session=db.session)

       try:
           cls.before_commit(new_object)
       except ExceptionBeforeCommit:
           pass

       db.session.commit()

       try:
           cls.after_commit(new_object, kwargs=kwargs)
       except ExceptionAfterCommit:
           pass

       db.session.commit()
       return new_object

   @classmethod
   def before_create(cls, **kwargs) -> Dict:
       """
       Executes logic before the factory starts
       :param kwargs:
       :return: named parameters for object creation
       """
       raise ExceptionBeforeCreate

   @classmethod
   def before_commit(cls, result_data: Union[List[Meta.model], Meta.model]) -> None:
       """
       Executes logic after the object is created, but before the commission in the database
       :return:
       """
       raise ExceptionBeforeCommit

   @classmethod
   def after_commit(cls, result_data: Union[List[Meta.model], Meta.model], kwargs: dict = None,) -> None:
       """
       Executes logic after data commit
       :return:
       """
       raise ExceptionAfterCommit


fake = Faker()


class UserFactory(BaseFactory):
   """
   Factory for creating users
   """
   class Meta:
       model = User

   data = {
       'first_name': lambda: fake.first_name()[0:20].title(),
   }

   @classmethod
   def before_create(cls, **kwargs) -> Dict[str, Any]:
       data_for_create_user = dict()
       data_for_create_user = cls.set_first_name(data_for_create_user, kwargs)
       return data_for_create_user

   @classmethod
   def set_first_name(cls, data_for_create_user: Dict, kwargs: Dict) -> Dict:
       name_field: str = 'first_name'
       max_len_str: int = 20
       value = kwargs.get(name_field)

       if not value:
           raise ErrorCreateObject(User, 'First name required', name_field)

       value = str(value)
       if len(value) > max_len_str:
           raise ErrorCreateObject(User, 'The first name must be no more than 20 characters long.', name_field)

       data_for_create_user[name_field] = value

       return data_for_create_user

Senior-разработчик. Его основная функция в команде — принимать правильные решения по разработке продукта и направлять его технологическое развитие. Эта задача одна из самых ответственных, так как ошибки допущенные на этом уровне, могут очень дорого обойтись компании в будущем. Senior в силу своего опыта и знания бизнес-области на ранней стадии предотвращает крупные проблемы с минимальными затратами. Это разработчик, который смотрит в будущее и направляет команду в сторону создания идеального продукта, с помощью участия в проработке его архитектуры, декомпозиции бизнес-задач на технические задачи и их делегирование.

Задачу для senior просто так уже не показать в виде кода. Поэтому приведем приблизительные примеры тасков, которые могут упасть на senior-разработчика:

Нужно хранить иерархическую структуру сотрудников организации. При необходимости должно быть возможно:  

  1. максимально быстро выгрузить всю структуру руководителей конкретного сотрудника;

  2. максимально быстро выгрузить всех подчинённых конкретного руководителя (не только прямых подчинённых).

Также стоит учитывать, что в данном примере используется реляционная СУБД, например, PostgreSQL.

При решении данной задачи senior может прийти к нескольким вариантам:

  1. Добавить в продукт графовую базу данных.

  2. Делать рекурсивные запросы в текущей БД при минимальных затратах на разработку сложного функционала.

  3. Реализовать специальные алгоритмы, которые могут решить данную задачу в рамках РСУБД (реляционной СУБД). Например, вложенные множества.

На каких этапах разработки продукта подключаются кодеры разных уровней?

Сразу скажем, что разработчиков разной квалификации ищут не только и не столько в определенную компанию, сколько в конкретную компанию под конкретный продукт.

В зависимости от стадии развития жизненного цикла продукта, потребности в разработчиках отличается:

Стадия проверки гипотез. Когда задачи нужно решать в сжатые сроки и с минимальным количеством затрачиваемых ресурсов. Как раз в этом случае и нужен сеньор, поскольку  он может не просто решить задачу, но и предложить оптимальное решение.

Разработка. Создание MVP (minimum viable product — минимально жизнеспособный продукт). На этом этапе закладывается ядро продукта. Конечно, наши читатели могут сказать, что MVP нужно выкидывать и всё писать заново и правильно, но сколько вы видели случаев, когда бизнес так делал? «Ничего не бывает настолько постоянным, как временное», в случае разработки это высказывание ну очень актуально. И именно поэтому здесь нужны senior-разработчики, которые с очень высокой степенью вероятности сразу все сделают правильно. Если нанять специалиста с более низкой квалификацией, то, скорее всего, потом придётся переписывать большую часть кодовой базы проекта.

Наш собственный кейс — сеньоры проделали львиную часть работы в разработке одного сложного продукта — платформы для проведения торгов по товарам/услугам в реальном времени между b2b, b2c, c2b и т.д. Они заложили в фундамент важные свойства availability (доступность), performance (производительность) и modifiability (модифицируемость). Ну, а все остальное достроили миддлы с джуниорами.

Выпуск продукта на рынок. Стадия перехода из MVP в production — в этот момент количество задач начинает расти. Данные задачи в будущем будут частью core составляющей продукта и для их реализации желательно искать senior-разработчиков, которые смогут заранее заложить качественную архитектуру. К сожалению, здесь есть одна проблема. Дело в том, чтобы найти одного хорошего senior-разработчика, нужно потратить не менее 2–3 месяцев. Но выход есть. Так, если в команде уже есть один или два сильных разработчика, то под их начало можно привлекать middle-разработчиков, которые закроют потребность на этой стадии.

Рост продаж. Стадия быстрого роста продукта. Тут впервые сталкиваемся с highload. Если заложили подходящий фундамент в виде продуманной архитектуры, соответствующей НФТ (не функциональным требованиям), то можем усилить команду разработчиками уровня middle. В противном случае у команды будут бессонные ночи.

Зрелость (насыщение рынка). К этому времени появляется много задач по техническому долгу плюс идёт оптимизация ресурсов. Появляется возможность взять на обучение разработчиков уровня junior и вырастить их под свой стек и продукт.

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

Завершая статью, отмечу, что за прошедшие несколько лет разрыв в плане знаний/скиллов между разными категориями если увеличился, то не сильно. Что гораздо важнее — значительно усилились требования ко всем разработчикам. Если рассматривать juinior«а, то пять лет назад можно было знать только базовую часть языка без различных фреймворков, баз данных и т.д. Сейчас же с этими знаниями тяжело даже попасть на должность стажёра куда-либо.

© Habrahabr.ru