В данной статье я коснусь вопроса порога входа в проект с устоявшейся архитектурой и дам несколько вариантов ответа на очень часто возникающий вопрос: почему так сложно?


Такой вопрос частенько возникает у новичков, которые приходят на проект. Или же бывают ситуации, когда проект уходит на поддержку и развитие в новые руки, и у нового разработчика также появляется этот вопрос. И редко кто из них пытается разобраться в реальных причинах, почему здесь так, а не иначе. Это может привести к печальным последствиям для бизнеса, например, новый разработчик может настоять на том, чтобы переписать все приложение с нуля.
Чтобы минимизировать такие риски, следует вместо вопроса почему так сложно? задаться такими вопросами:


  • Какие требования к процессу разработки закладывал архитектор?
  • Какой результат процесса разработки требуется на выходе?

Требования к процессу разработки


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


  • По какой системе выстроен процесс?

Часто в заказной разработке на вход приходит проект с однозначными требованиями и фиксированным набором функционала. Насколько проработанными они могут быть — это уже тема другой статьи. И процесс разработки в таких проектах чаще всего выстроен по водопадной системе, потому как предполагает непосредственную обратную связь от пользователей продукта — после разработки всего функционала продукта, тогда как при итерационной модели обратную связь можно получить уже после первой итерации. У архитектора для таких проектов обычно уже припасена устоявшаяся архитектура, которая отвечает определенным требованиям к этому процессу. Какие же требования к такому процессу разработки закладывает архитектор?


1) Pipeline процесса разработки должен быть максимально сложным для разработчика. И отбраковывание кода, поступающего в репозиторий проекта, должно по максимуму происходить автоматически и, по возможности, без участия самого архитектора.


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


Поэтому в таком pipeline необходимо использовать:


  • Множество статических анализаторов кода
  • Автоматические тесты и соблюдение пирамиды тестирования
  • Автоматический подсчет покрытия кода тестами
  • Ворота качества кода(Quality gates). По всевозможным метрикам: процент покрытия кода тестами, процент дублирования кода, code smells, security, bugs, т.д.
  • перекрестное Code Review
  • etc

Все эти пункты в совокупности и приводят к появлению у разработчика вопроса: почему так сложно?


Для примера попробуйте написать тесты для вот такого кода:


class SomeService(
    private val restApi: SomeApi // SomeApi - не абстракция, а конкретный класс, который умеет ходить в сеть
) {
    fun doSomething(): Single<SomeResult> = restApi.loadSomething()
            .map { /*какая-то бизнес логика здесь*/ }
}

Вам придется запускать такие тесты на реальном android устройстве, либо на эмуляторе. И это сразу приведет к существенной просадке во втором требовании к процессу разработки:


2) Автоматизированные элементы Pipeline'а процесса разработки должны выполняться как можно с большей скоростью


Если вашим разработчикам приходится ждать выполнения тестов очень долго — это проблема вашего pipeline и если архитектура не позволяет ускорить этот процесс — это проблема вашей архитектуры


Давайте перепишем пример:


interface SomeApi {
    fun loadSomething(): Single<NetworkResult>
}

class NetworkSomeApi : SomeApi {
    override fun loadSomething(): Single<NetworkResult> { /*реализация, которая ходит в сеть*/ }
}

class SomeService(
    private val restApi: SomeApi // SomeApi - уже абстракция, которую легко можно замокать в тестах
) {
    /*CODE*/
}

Мы видим, что сложность кода увеличилась, но с точки зрения процесса разработки — мы сделали архитектуру более гибкой, теперь нам не обязательно запускать тесты бизнес логики для блока map в эмуляторе или на устройстве, достаточно запустить их в быстрых тестах. Это уменьшит количество интеграционных тестов, которые нужно запускать в медленном окружении.


Уменьшить количество дорогих интеграционных тестов может правильно выбранный архитектором паттерн проектирования. Не поленитесь и разберитесь с популярными на сегодня паттернами: MVP, MVVM, MVI.


Давайте еще немного затронем тему навигации в приложении. Мы тоже хотим уметь тестировать ее, и тестировать в быстрых тестах. Опять получаем "усложнение" архитектуры, потому что нам придется прятать систему навигации в приложении за абстракцией.


А еще мы хотим уметь связывать компоненты нашей системы с помощью DI, выстраивать графы зависимостей и проверять их корректность на этапе компиляции проекта, а не в рантайме. Тут на сцене появляется Dagger 2 и его монструозные компоненты и сабкомпоненты с модулями, которые уже в конец запутывают бедного новичка.


И таких неочевидных моментов "усложнения" архитектуры для новичков накапливается очень много. Естественно, без понимания процессов разработки и требований к этим процессам, у них появляется тот самый вопрос — почему так сложно?


Результат процесса разработки


Чтобы оценить успешность выстроенного процесса разработки и, опосредованно, архитектуру проекта, необходимо проанализировать его результат. Как правило, результатом является продукт(приложение если мы говорим про мобильную разработку). И метрики успешности продукта у всех разные: у заказчика — одни метрики, у разработчика — свои метрики, у пользователя продукта — свои.


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


Это непрерывный процесс: разработка -> сбор метрик -> анализ результата -> внесение необходимых модификаций в процесс разработки.


pipeline modification


Таким образом результат влияет на формирование процесса разработки и уже процесс разработки влияет на изменение архитектуры проекта. Т.е. важная мысль, которую я хочу донести — архитектура вторична, первичны результат и процесс разработки.


Заключение


В заключении еще раз проговорим:


  • без понимания процессов разработки и требований к этим процессам у разработчика создается ощущение об усложненной архитектуре проекта;
  • архитектура вторична, первичны результат и процесс разработки;

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


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

Комментарии (9)


  1. GCU
    30.12.2019 17:25

    Статья так и не отвечает на поставленный в заголовке вопрос «усложненность архитектуры».
    Вместо этого уводит в сторону производственного конвейера, типа он «сложный», потому что у нас куча анализаторов кода, CI, всё покрыто тестами и код-ревью.
    В моём понимании архитектура вообще не об инструментах и процессах. При абсолютно тех же производственных процессах и инструментах архитектура приложения может быть как простой, логичной и понятной, так и нет.

    архитектура вторична, первичны результат и процесс разработки

    Результат непосредственно зависит от архитектуры, а разработка по сути стандартна — там ничего «архитектурного» нету, это лишь набор процессов и инструментов.

    P.S. Проект организации строительства монолитных зданий примерно одинаков как для безвкусной бетонной коробки, так и для памятника архитектуры.


    1. goblinr Автор
      30.12.2019 19:19

      Спасибо за комментарий.

      Результат непосредственно зависит от архитектуры, а разработка по сути стандартна

      В статье как раз говорится про разработчиков, которые в принципе не сталкивались с таким процессом разработки, в котором есть автоматизированный pipeline со статическими анализаторами, тестами, quality gates, и пр. На моей практике, я очень редко встречал android проекты, которые попали мне из других рук, и где был бы написан хотя бы один тест. И эти проекты, в принципе, не пригодны к автоматическим тестам. Если бы, в таких проектах было бы требование покрытия кода тестами, их архитектура однозначно бы изменилась в сторону усложнения.

      Т.е. требования к процессу разработки — реально влияют на то, какой будет архитектура проекта.


      1. lamerok
        30.12.2019 19:38

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


        1. goblinr Автор
          30.12.2019 19:41

          Я часто встречаю от новичков некоторое сопротивление, в плане того, что как раз много кода приходится писать. Для них чем меньше кода — тем проще.


          1. innovaIT
            30.12.2019 23:27

            Вот вот. Я тоже часто наталкиваюсь: "зачем ты создаёшь кучу простых процедур/функций, если достаточно поменять две строчки в коде." (с) 1С разработчики, по совместительству архитекторы.
            Сам когда начал изучать другие языки, понял на сколько убого выглядит мой код на 1С. Даже 1с согласилась и добавила автотесты. Это не моя заслуга. Видать там тоже поняли, что большие решения теперь не написать, как в 77.


  1. kamer
    30.12.2019 19:56

    Для приведенного примера легко написать тесты изначально. Можно мокнуть restApi.loadSomething() с помощью Mockito не выделяя интерфейса


    1. goblinr Автор
      30.12.2019 20:02

      Представьте что SomeApifinal класс, такой метод уже замокать не получится. Ну и это считается дурным тоном — мокать конкретные реализации.


  1. flamcircus
    31.12.2019 21:31

    Как я понял, в заметке под сложностью понимается и конструктивная сложность, и труднопреодолимость системы контроля качества кода, и проблематика познания уже созданного. Что касается первой, то да — простые решения чреваты путаницей в дальнейшем, а обобщенный опыт построения больших приложений содержит великое множество принятых соглашений и выбранных подходов (паттернов), но на длинной дистанции это усложнение себя оправдывает. Полоса препятствий для кода по пути в репозиторий — вещь также полезная, глупо спорить. Однако, на мой взгляд, новичок в проекте не спросит «почему так сложно?» до тех пор пока не разберется в задаче и не выносит в голове своё решение, возможно более простое, а порой на это уходят месяцы. Так часто бывает, когда приложение делается для бизнеса, существующего уже лет 20, да к тому же вечно приспосабливающегося к изменениям в окружающей среде, да еще если автоматизация выполняется после ухода носителей знаний, а может, и по причине этого. Так что новичку лучше пожелать попасть в проект с ясной архитектурой и хорошей атмосферой в коллективе. Помогут, обучат.


  1. uncle_doc
    02.01.2020 13:22

    В принципе все написано правильно, единственное что мне режет глаз, так это то что новичку не дают шанса проявить себя. Кто знает, может все действительно сложно и есть путь проще, а у тимлида/архитектора «замылен глаз». Поэтому новичку нужно выставить формальные требования к исходному коду, и не просто ссылкой на solid в википедии, а типа: мы делаем "так" — потому "что".