Всем привет! Я Виктор Брыксин, руковожу разработкой Яндекс Телемоста. В статье поговорим про декомпозицию задач в проекте и как можно получить реальные сроки его выполнения.
Спойлер: вы все равно ошибетесь, прогнозируя сроки. Но что можно сделать? Минимизировать шанс на ошибки и сделать их менее фатальными. Я расскажу про рабочие инструменты, которые помогли мне в свое время, — брать их на вооружение или нет, решайте сами. Если вы не знаете, как подступиться к декомпозиции сложного проекта и с чего начать, — эта статья вам в помощь.
Фантазируй Декомпозируй
Оценка сроков проекта бывает задачкой со звездочкой. Допустим, к вашему проекту предлагают подключить еще пять человек. Возможно ли сделать план работ команды так, чтобы никто не подрался за задачи? Если да, то как именно?
Или к вам прибегает менеджер и просит оценить сроки проекта. Вы чешете голову и прикидываете: «Ну где-то два месяца». У него возникает естественный вопрос: «Почему так долго?» Ведь задача в голове менеджера может выглядеть сильно меньше, чем в вашей.
Или, например, к вам приходит руководитель и говорит: «Вот тебе задача, проработай ее и декомпозируй. Ты же сеньор, ты должен знать способы»… «А если я не уверен, что эти способы приведут к желаемому результату?»
Со всеми этими вопросами и будем разбираться в статье.
Когда у вас сложный и крупный проект, не получится избежать ошибок, однако важно уменьшить вероятность серьезных и фатальных. Это не про «делайте так, а по-другому не делайте». Если вы не знаете, как подступиться к оценке сроков вашего проекта, или не уверены, что ваши модели хорошо работают, то можете взять и попробовать для себя этот инструмент. В этой статье хочу поделиться своим опытом — то, что прочитал в книгах и пропустил через себя.
Раскладываем проект на команду: над какими вопросами придется подумать
Предположим, у вас есть проект по предварительной оценке на четыре месяца. А получили вы ее, применяя проверенные веками способы оценки сложных проектов: гадание на костях, оценка по потолочным трещинам или раскладывание карт Таро. И теперь нужно этот проект проработать и распределить задачи по команде. На какие вопросы вам придется ответить:
Какую команду стоит задействовать: размер, уровень экспертизы? Как это сделать наиболее оптимально?
Какой будет график работ?
Здесь самое время обратиться к приемам декомпозиции. По итогу мы получаем граф работ, в котором можем варьировать сроки и количество людей. Как его построить, разберемся дальше.
Виды декомпозиции
Сразу оговорюсь, чтобы не закидали помидорами, — в статье буду использовать свои авторские термины, потому что так проще избежать разночтений.
Итак, глобально есть два подхода к декомпозиции задач: технологическая и маршрутная.
Технологическая декомпозиция — это когда вы хорошо разбираетесь в предметной области и можете решить задачу в уме.
Маршрутная декомпозиция — это когда всё, что у вас есть, это картинки и некое описание того, как оно должно работать.
Важно отметить, для какой команды и каких процессов будут справедливы наши дальнейшие рассуждения:
Команда взаимозаменяемых разработчиков, например, T-shaped специалисты, или те, кто занимаются одной платформой.
Agile-подобный процесс со спринтами фиксированной длины и коммитментами на задачи, взятые в спринт.
И отсюда растут ограничения, с которыми мы работаем:
Мы не можем брать в спринт задачи, которые зависят от других незавершенных задач. Есть риски, что последние сдвинутся по срокам, соответственно, новые не пойдут в работу, но место в спринте займут.
Если структура команды другая, например, кросс-функциональная с разработчиками, которые не могут друг друга заменить, то оптимальный процесс декомпозиции может быть другим.
Маршрутная декомпозиция: когда нужно «еще вчера», и нет специальных знаний
Наверняка вы сталкивались с ситуацией: только пришли в проект, и вам кидают задачу: «держи, разгребай», а вы еще ничего не знаете. И как это обычно бывает — документации нет, спросить не у кого — тогда что делать? Можно использовать маршрутную декомпозицию.
Это декомпозиция по user story — вы пытаетесь переложить пользовательскую историю на задачи, причем каждая из задач становится точкой, в которой система переходит в заранее заданное состояние. И то, что вам нужно, — довести систему из некоторого состояния А в состояние Б.
В чем смысл такой декомпозиции? Мы раскладываем все, что делает пользователь на некоторые элементы, каждый из которых имеет самостоятельную ценность и помогает нам как разработке сделать следующий шаг, наращивая функциональность.
Я когда-то был тимлидом iOS-команды — давайте разберемся на простом примере из этой части моего опыта. У вас есть Мессенджер, и в него нужно добавить фичу шаринга чего-либо.
![](https://habrastorage.org/getpro/habr/upload_files/a38/8c3/581/a388c35814a67fd56326861c788dfa9e.png)
Рассмотрим, как эту задачу можно разложить в случае маршрутной декомпозиции:
По клику показывать пустое окно с отменой (например, ценность этого элемента — новый экран)
Заполнить окно аватарками (тут, например, ценность в том, что мы показываем людей, кому можно расшарить контент)
При выборе давать вводить текст
Отправлять сообщение
Добавить поиск
Здесь отталкиваемся от того, как думает и действует пользователь — вроде ничего сложного. В чем преимущества такого подхода:
Плюсы:
Не нужна техническая экспертиза. Декомпозицию может делать кто угодно — не нужно быть разработчиком и глубоко разбираться в технических деталях.
Делается быстро.
Теперь перейдем к недостаткам — они, как ни странно, растут из преимуществ.
Минусы:
Размеры задач в спринтах могут сильно отличаться. Если не иметь экспертных знаний, то можно декомпозировать так, что одна задача будет занимать два дня, другая — месяц. В чем здесь проблема? Чем объемнее задача, тем выше в ней уровень неопределенности, и, как следствие больше рисков, что она дальше будет по срокам разрастаться, а наша оценка окажется неверна.
Зачастую получаются линейные цепочки задач. Взять в работу следующую задачу можно только после завершения предыдущей. Соответственно, текущая задача развалилась по срокам — это сдвигает сроки для следующих.
Технологическая декомпозиция: когда можете уйти глубоко в детали
Этот вид декомпозиции стоит выбирать, если у вас есть специальные знания, позволяющие выполнить задачу близко к коду. Здесь мы погружаемся в то, что именно нужно сделать, концентрируемся на модулях, в которых нужно внести изменения. Тут есть приятный бонус — любое изменение изолированно в своем модуле, меньше шансов попасть в ситуацию конфликта зон ответственности. В случае, например, маршрутной декомпозиции изменения могут затрагивать несколько модулей сразу.
Вернемся к примеру из моего iOS-ного опыта про фичу шаринга в Мессенджере. В случае технологической декомпозиции задачи будут ставиться по реализации новой возможности в коде, например:
Сделать пустой Share Extension
Перенести модуль для работы с WebSocket в общую библиотеку
Перенести модуль для работы с REST API в общую библиотеку
Подключить модуль авторизации к пустому Share Extension
Синхронизировать авторизацию между Share Extension и основным приложением
Реализовать базовую навигацию + ViewController с отменой
Подключить модуль для работы с REST API, связать его с авторизацией
Нарисовать список получателей по ответу из REST API
Подключить авторизацию к модулю для работы с WebSocket
Реализовать отправку сообщений через WebSocket
Шаги в этом примере требуют специальных знаний об устройстве приложения и если вы «зашли с улицы», то вам может быть далеко не все ясно. В этом специфика технологической декомпозиции.
Плюсы:
Возможность более четко оценивать сроки задач. Разработчик с экспертными знаниями понимает, какое именно изменение нужно сделать в модуле, знает его размер и сложность. Соответственно, может четко варьировать размер задачи.
Граф задач произвольной сложности. Можно распараллелить задачи на разных людей. Все зависит от того, насколько можно сделать так, чтобы они не пересекались по модулям. Естественно, могут возникать блокирующие задачи — но при должном подходе их можно избежать.
Минусы:
Требуется высокая экспертиза. Вы практически не можете провести такую декомпозицию, если у вас нет специальных знаний. Это значит: вы много времени потратили на задачу и, как следствие, ее уже решили. Либо вы уже двадцать раз с такими задачами сталкивались, и это — двадцать первый.
Например, к вам пришел продакт и просит ввести новый тип сообщения в Мессенджере — например, карту. Вы сталкивались с подобной задачей уже N раз, соответственно знаете, каких десять действий нужно сделать, чтобы ее решить.
Подвержена ошибкам проектирования.
Важный момент — в случае маршрутной и технологической декомпозиции по разному распределена ответственность.
В маршрутной она смещена на разработчика, который делает эту задачу, он принимает решения в моменте. В технологической — ответственность на человеке, кто эту декомпозицию проводит. По сути он один придумывает решение за всех, а остальным остается просто написать код под диктовку.
Что из этого следует? Если у вас команда скилловых и опытных ребят, то лучше выбирать маршрутную — они разберутся. Если вы один сильный и красивый, а у вас команда желторотых джунов, то сделайте все за них.
Смешанная декомпозиция: собираем лучшее из двух предыдущих
Бывает еще смешанный вариант. Это мой любимый, потому что изолирует сложность технологической декомпозиции в рамках отдельных изменений.
На уровне большой сложной фичи делается маршрутная декомпозиция — выстраиваются состояния системы. Затем каждый переход системы из одного состояния в другое прорабатывается и делается технологическая декомпозиция.
Разберем вариант простейшей декомпозиции на примере той же фичи шаринга контента в Мессенджере.
![](https://habrastorage.org/getpro/habr/upload_files/b08/7be/471/b087be47101d3de1ce672ca4ebf39187.png)
Сверху мы видим маршрутную декомпозицию. Начинаем с пустого списка, добавляем туда адресатов, текстовое поле, делаем отправку.
Переходим к технологической декомпозиции. На каждой точке мы понимаем, что именно нам нужно. Например, чтобы показать адресатов, нам необходимо затащить сетевой стек. Мы знаем, что он требует авторизации, и еще нам бы хорошо подклеить одно к другому. Текстовое поле — не рокет сайенс, с этим шагом все понятно.
Переходим к отправке — для нее потребуется WebSocket. Тут, кстати, подключаются наши экспертные знания. Например, мы понимаем, что адресатов можно получить только через HTTP-запросы, а отправку сообщений сделать только через WebSocket, потому что так устроено наше API. Соответственно, WebSocket нам нужно будет подключить к авторизации. В итоге мы получаем такой интересный граф задач.
Но что дальше с этой радостью делать? Важно понять, какой у нас критический путь. Отмечу, что мы будем рассматривать критический путь в рамках модели наивных сроков, о которой дальше подробнее расскажем.
Модель наивных сроков и расчет критического пути
Критический путь — это цепочка зависящих друг от друга задач наибольшей продолжительности. Именно критический путь определяет, сколько фича реально будет делаться. Критический путь — это линейная цепочка задач, поэтому, она не параллелится, и ускорить ее добавлением большего числа людей невозможно.
Важно понять длительность критического пути, так как это напрямую влияет на оценку сроков вашего проекта.
Прежде, чем рассчитаем критический путь для нашей задачи — рассмотрим модель наивных сроков.
Она держится на трех наивных предположениях:
Каждая задача занимает не более одного спринта
В спринт не берутся блокирующие друг друга задачи
Емкость спринта в задачах бесконечна
В рамках трех предположений мы можем оценить, когда примерно сможем рассчитывать на результат, причем речь о минимально возможном сроке.
![](https://habrastorage.org/getpro/habr/upload_files/847/4d6/873/8474d68738f67de7f3843d9eab41b5fe.png)
Итак, а теперь рассчитаем критический путь. Он начинается от конечной задачи: идем по графу и смотрим расстояния от каждого узла графа до нее. Получается, что у нас критический путь имеет длину четыре. И начинается с двух задач — авторизации и сетевого стека. Казалось бы, нужно начать с пустого списка, это же первая задача. Но нет! Лучше начать не с него, так как мы можем не успеть сделать сетевой стек и авторизацию. Если начать с пустого списка, критический путь будет занимать не четыре, а пять спринтов.
После оценки критического пути переходим к оценке сроков.
![](https://habrastorage.org/getpro/habr/upload_files/007/700/27a/00770027af4965bf01acd9871e23f583.png)
Мы раскладываем наш граф таким образом, чтобы мы начинали с задач, которые лежат на критическом пути и так, чтобы в один спринт не бралось блокирующих задач. И при этом считаем, что в спринт может влезть бесконечное количество задач.
Продолжим разбирать пример:
1 спринт: делаем сетевой стек.
2 спринт: делаем пустой список и подклеиваем сетевой стек к авторизации.
3 спринт: делаем адресатов на базе подклеенных сетевого стека и авторизации. Далее затаскиваем WebSocket. Видим, что мы могли затащить его и в первом, и во втором спринте — в зависимости от того, сколько у нас было людей.
4 спринт: подключаемся к авторизации, добавляем текстовое поле.
5 спринт: склеиваем все и добавляем отправку.
При критическом пути в четыре — в конце пятого спринта по наивной модели мы получим готовое решение. Почему это оценка сроков, а не коммитмент? Потому что от готового решения до релиза нужно заложить еще много чего: например, тестирование, раскатку. Но по крайней мере в этой точке мы можем сказать тестированию: «Ребят, у вас будет работа через пять спринтов».
Допустим, нам это не нравится и хочется быстрее. Пришел менеджер и сказал: «Нам надо завтра!». Ну а мы ему что: «Сорян чувак, завтра не получится». Но что мы можем сделать? Ресурсы из воздуха не берутся — мы можем варьировать количество людей, которые делают задачи в параллель, за счет реорганизации задач.
Есть два варианта:
Когда задача параллелится на N разработчиков, а у нас N - 1. Тогда мы можем привлечь еще одного, и, таким образом сделать задачу быстрее.
Когда задача не параллеится, однако по объему небольшая, и ее можно объединить с другой. Даже если они блокирующие в итоге вы сможете сэкономить время.
В этом примере адресаты и текстовое поле были две блокирующие задачи. Но мы посидели-посчитали и поняли, что эти две задачи влезут в один спринт, поэтому можем объединить их и не делать цепочку блокирующей. Как я уже говорил, по модели наивных сроков нельзя брать блокирующие задачи — если первая вылезла за сроки, то вторую мы даже не возьмем. В итоге: поздравляем — вы сэкономили один спринт, теперь задача вместо пяти спринтов сходится за четыре.
Полная и неполная декомпозиции: лучше не строить граф из 822 задач на 8 команд
Поговорим про еще 2 типа декомпозиции: полную и неполную.
Чем сложнее проект, тем больше шансов, что проработка будет многоуровневая. Мы не пытаемся сразу составить граф задач на 822 задачи на восемь разных команд, а находим точку, где есть осмысленный результат. И далее разбиваем задачу на этапы и декомпозируем каждый этап. Однако в таком случае мы не можем точно сказать, к какому сроку проект будет завершен. Но мы серьезно снижаем риски, что в случае ошибки что-то пойдет не так. Ничего не бывает хуже, если вы проработали гигантскую задачу, а в середине ее выполнения поняли, что в начале допустили ошибку, и весь ваш титанический труд можно выкинуть в помойку.
Если вас не жгут сроки, то для сложных фичей лучше полную декомпозицию не делать, вы точно облажаетесь. Весь смысл работы — ошибаться, но не фатально. Оставьте себе возможность ошибиться и локализуйте количество работы, которое вам придется сделать в случае ошибки.
Получаем из сроков дедлайны
Итак, мы смогли решить задачу «когда будет готово». Пришло время её усложнить и ответить на вопрос — когда точно будет готово?
Естественно, что предсказывать будущее еще никто не научился, поэтому наша задача — заложить время на устранение сработавших рисков. То есть по сути спланировать все так, как будто весь проект пошел не по плану в каждой точке.
Для этого можно воспользоваться следующим подходом:
Считать, что любая задача займет в N раз больше времени, чем оценивается. Для практического применения N=2 дает неплохой баланс между точностью оценки и увеличением сроков
Считать, что исполнители задач выйдут из строя в самое неподходящее время (например, в середине критически важной задачи) на 1-2 недели
Заложить время на все активности за пределами разработки — тестирование, возвраты от тестирования. Можно, например, заложить два-три цикла возврата, рассчитывая что устранение дефектов, найденных QA, займет в 2 раза больше времени, чем их поиск. Ещё мероприятия, связанные с релизным процессом: особенно если у вас мобильное приложение, внезапные затыки на ревью в Apple Store еще никто не отменял. Также стоит накинуть время на проведение экспериментов, сбор статистики, доработки по результатам выпуска, внезапные баги и падения, которые не были обнаружены на QA. Ну и конечно, стоит заложить время на все нюансы ваших процессов разработки, включая внезапные правки от самых неравнодушных, которые необходимо будет внести в самый последний момент.
Пересчитать критический путь с учетом этих допущений и новых задач.
Если же какие-то риски не прогнозируемы, тогда не стоит разговаривать о дедлайнах до момента, пока они все не будут отработаны. Потому что главная задача, когда вы озвучиваете дедлайн, — понимать, как вы будете его обеспечивать. Поэтому хорошо, например, планировать все RnD-работы для выполнения в самом начале, а о дедлайнах говорить уже после оценки их результатов.
Если же дедлайн требуют сейчас, а результаты RnD будут через месяц, воспользуйтесь внутренним чутьем разработчика. И если оно ничего не подсказывает вам — значит, вы не можете назвать срок, который способны гарантировать, и лучше честно об этом сказать.
Заключение
Так как же понять, какую декомпозицию использовать? По моему опыту лучше исходить из двух факторов: какой у вас состав команды и какого уровня технической сложности ваша задача. Если у вас сложный и масштабный проект — лучше начните с маршрутной и изолируете техническую сложность в рамках отдельных изменений. И помните, вы все равно ошибетесь, однако, надеюсь, эта статья поможет вам уменьшить фатальность ваших ошибок.
Понятно, что существует много разных способов декомпозиции задач в проекте и оценки сроков. Предлагаю обсудить в комментариях, как вы декомпозируете задачи в проекте?