Clean Decomposition

    В данной статье я хочу рассмотреть подход к разбиению задач на подзадачи при использовании Clean Architecture.

    С проблемой декомпозиции столкнулась команда мобильной разработки компании NullGravity и ниже то как мы ее решали и что в итоге получилось.




    Предыстория


    Была осень 2018-го, мы разрабатывали очередное приложение для телеком оператора. Но этот раз отличался. Сроки были достаточно сжатыми и привязанными к маркетинговой кампании клиента. Android команда выросла с 3 до 6-7 разработчиков. В спринт брали по несколько задач и стал вопрос о том, как их эффективно декомпозировать.

    Что мы имеем в виду когда говорим эффективно:

    1. Максимально количество параллельных задач.
      Это дает возможность занять все имеющиеся ресурсы.
    2. Уменьшение размера merge request-ов.
      Их будут смотреть не для галочки, и можно еще на этапе code review отловить потенциальные проблемы.
    3. Уменьшение количества merge конфликтов.
      Задачи будут вливаться быстрее и не нужно переключать разработчика на разрешение конфликтов.
    4. Возможность собрать статистику затрат времени.
    5. Автоматизация создания задач в Jira.


    Как мы решили задачу?


    Мы разделить все подзадачи на такие типы:

    • Data
    • Domain
    • Empty
    • UI
    • Item
    • Custom
    • Integration


    Data и Domain соответствуют слоям в Clean Architecture.
    Empty, UI, Item и Custom относятся к presentation слою.
    Integration относиться и к domain и к presentation слоям.


    Рисунок 1. Расположение задач относительно слоев Clean Architecture

    Давайте рассмотрим каждый тип в отдельности.

    Data


    Описание DTO, интерфейс API, работа с базой данных, datasource и т.д.

    Domain


    Интерфейс репозитория, описание бизнес моделей, interactor-ы.

    Так же реализуется интерфейс репозитория в data слое.
    Такое несколько нелогичное, с первого взгляда, разделение позволило максимально изолировать задачи типа data и domain.

    UI


    Создание основного макета экрана и дополнительных состояний, если таковые имеются.

    Item


    Если экран – это список элементов, то под каждый тип нужно создать модель — Item. Для мапинга Item-а в макет нужен AdapterDelegate. Мы используем концепцию адаптер делегатов но с некоторыми доработками.
    Дальше создание примера работы с элементом списка в PresentationModel.

    Empty


    Базовые классы необходимые для задач типа ui или item: PresentationModel, Framgent, layout, модуль DI, фабрика AdapterDelagate. Связывание интерфейсов и реализаций. Создание точки входа на экран.

    Результат выполнения задачи – экран приложения. Он содержит Toolbar, RecyclerView, ProgressView и т.д. то есть общие элементы интерфейса, добавление которых могло бы дублироваться разными разработчиками и привело б к неизбежным merge конфликтам.

    Custom


    Реализация нестандартного UI компонента.

    Дополнительный тип нужен чтоб отделить разработку нового компонента от задачи типа UI.

    Integration


    Интеграция domain и presentation слоев.

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

    Порядок выполнения задач


    Задачи типа data, empty и custom можно начинать сразу после старта спринта. Они не зависят от других задач.

    Задача domain выполняется после задачи data.

    Задачи ui и item после задачи empty.

    Задача integration выполняется в последнюю очередь так как требует завершения всех предидущих задач.


    Рисунок 2. Timeline выполнения задач

    Не смотря на то что часть задач заблокированы другими задачами их можно стартовать одновременно или с небольшой задержкой. К таким задачам относятся domain, ui и item. Таким образом процесс разработки ускориться.


    Рисунок 3. Timeline выполнения задач с блокировками

    Под каждый конкретный функционал набор задач может варьироваться.
    Может быть разное количество задач empty, ui, item и integration, а некоторые типы могут просто отсутствовать.

    Автоматизация процесса и сбор статистики


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

    Для автоматизации также удалось найти решение. Так как задачи типичны, то почему их описание в Jira должно отличаться. Мы разработали шаблоны для summary и description. Сначала это был просто json файл, Python parser этого файла и подключено Jira REST API для генерации задач.

    В таком виде скрипт просуществовал почти год. Сегодня он превратился в полноценное desktop приложение, написанное на Python с использованием PyQt и MVP архитектурой.

    Возможно MVP была overhead, но когда первая версия на Tkinter крешила MacOS версии 10.14.6 и не все команды могли пользоваться приложением, мы с легкостью за пол дня переписали view на PyQt и все заработало. Мы в очередной раз убедились, что использование архитектурных подходов хоть и для таких простых задач имеет свои преимущества. Cкриншот JiraSubTaskCreator показано на рисунке 4.


    Рисунок 4. Главный экран JiraSubTaskCreator

    Выводы


    1. Мы разработали подход к декомпозиции задач на минимально зависимы друг от друга подзадачи;
    2. Сформировали шаблоны для описания задач;
    3. Получили merge request-ы небольшого размера, который дает возможность внимательно провести review и менять код изолировано
    4. Уменьшили количество конфликтов при merge request-ах;
    5. Получили возможность более точно давать оценку и проводить анализ затрат времени на каждый тип задачи;
    6. Автоматизировали часть рутинной работы.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 7

      0
      Учитывая то, что вы так подошли к формализации, рассматривали ли вы вариант кодогенерации и/или некого DSL для описания, грубо говоря, модели и последующей генерации всей структуры?
        +1
        Мы используем package, file и live шаблоны для генерации кода. Для задач типа data, domain, presentation есть package шаблоны генерирующие всю структуру пакетов и файлы заглушки, для более мелких действий: bind зависимостей — используем live шаблоны.
        0
        Не смотря на то что часть задач заблокированы другими задачами их можно стартовать одновременно или с небольшой задержкой.

        Вот тут не совсем понятно — они все ж заблокированы или нет? Или «заблокированы» в вашем случае означает нечто иное? Разверните, пожалуйста.
          0
          Имеется в виду то что полностью закончить задачи нельзя, так как есть часть кода которую должны написать в других задачах, но начать делать — можно.
            0
            А как вы тогда определяете в этом случае процент — на сколько процентов заблокировано? И после какого порога — принципиально не берете, пока не разблокируется? Наши просто частенько опасаются брать задачи, которые хоть немного залочены другими задачами.
              0
              Процент мы не высчитываем. Здесь нужно понимать задачи типа data и domain часто достаточно объемные, когда фича разрабатывается с нуля, поэтому в обоих много работы, и времени ± нужно одинаково, соответственно их можно начать одновременно и только в конце буде блок.

              Задача empty, как правило довольно типична и ее можно сгенерировать через package шаблон и дописать специфику, а это 1 час, максимум 2.

              Задачи ui и item могут быть разными по времени но так как задача empty не слишком затратная то их можно начать, а затем подтянуть себе изменения с feature ветки когда empty будет готова.
                0
                Спасибо.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое