Привет, читатель! Возникла у меня идея порассуждать на занимательную, на мой скромный взгляд, тему. Возникла она у меня потому что, наверное, просто накипело. Эта тема, как ты уже понял из названия, мультиархитектурность (забавный термин, который я сам же и придумал, хотя не уверен, быть может он существовал и до меня).

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

Что такое мультиархитектурность?

Начнем с того, что же такое архитектура. Архитектура - совокупность важнейших решений об организации программной системы, включающая в себя выбор структурных элементов (составных частей ПО) и их интерфейсов и способы их объединения в единую систему. (Спасибо Википедии за нормальное определение)

Мультиархитектурность - это термин который я придумал глядя на каждый проект над которым работал. Мультиархитектурность - это смешение нескольких архитектур (две и более) в рамках одного проекта. Знакомая проблема? Тогда предлагаю попробовать покопаться в ней. Ну а если нет, тогда прочитать мое мнение на этот счет и согласиться или не согласиться с ним, а может быть высказать свою точку зрения в комментариях.

Откуда ноги растут?

Все команды в разработке, если это хоть сколько-нибудь нацеленные на результат ребята, рано или поздно задумываются о масштабируемости и поддержке продукта (в данном случае программном обеспечении) над которым работают. И разумно задаются вопросом - а что можно сделать для того чтобы продукт был поддерживаемым и расширяемым? Наверное первое, и самое очевидное, что приходит в голову - это выбор архитектуры.

"Умные дяди за нас уже давно все придумали - MVP, MVC, MVVM, FSD, Flux и так далее" - думают одни и начинают завозить в свой проект наиболее понравившуюся архитектуру. Другие же напротив, либо выдумывают свою собственную архитектуру, которая зачастую является выродком от одной или нескольких уже существующих, либо не задумываются о ней вовсе и делают свой проект с нуля по каким-нибудь best practices, которые зачастую просто говорят о том как лучше структурировать папочки в проекте. И последний вариант, на мой взгляд, хуже всего, потому что отсутствие архитектуры - это тоже архитектура, просто такая, в которой каждый делает что хочет, со временем превращая проект в свалку.

Рассмотрим ситуацию когда команда определилась с архитектурой и стартанула проект. Какова вероятность что на этой архитектуре проект проживет год? Два? Три? По моему опыту - крайне мала (хотя может быть мне просто не везло). Как правило, когда команда долго использует одну архитектуру, приходит понимание ее слабых мест, bottle necks и уязвимостей, а может быть у заказчика кардинально поменялись требования и текущее решение уже не подходит. Причин может быть масса. Тогда перед разработчиками встает вопрос «Что же с этим делать?». Зачастую все сводится к одному из трех ответов:

  • продолжать жить с тем что есть, приняв все слабости и неудобства выбранного решения;

  • переехать на другую архитектуру, писав новый код уже на ней и постепенно переписывая старый код;

  • оставить старый код как есть, а новый писать на новой архитектуре;

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

О каждом варианте поподробнее

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

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

Третий же вариант сильно походит на второй, но в нем есть свои плюсы, главный из которых - отсутствие необходимости переписывать весь старый код. Но и минусы, такие как то, что
новым разработчикам, пришедшим в команду не так давно и привыкшим к новой
архитектуре, рано или поздно придется лезть в старые легаси модули, которые не
переписывались на свежее решение, и разбираться еще и в них. Им нужно будет
изучить и привыкнуть уже не к одной архитектуре. Как следствие, эти
разработчики скорее всего будут немного (а может и много) дольше разбираться в
старом коде, и чем таких модулей в приложении останется больше, тем выше вероятность
этого. Еще один серьезный минус такого подхода – если у вас долгоиграющий
проект, который рассчитан на длительную поддержку, то вполне возможно что потом
вы опять захотите переключиться на что-то новенькое в плане архитектуры, так
как найдете уязвимости и серьезные дыры в текущем решении, да и что-то новое и
хайповое выйдет за это время. Но в таком случае, если вы все же решитесь на
смену архитектурного подхода, у вас в проекте их будет уже три – самое первое
решение, которое оставили как есть, решение посвежее на котором реализовывали
уже новые модули, и вот еще одно, к которому вы решили прийти сейчас. И вот у
вас в проекте намешано уже целых три архитектуры! Согласитесь, не круто.

Что дальше?

Вот мы и разобрались в причинах появления в проекте мультиархитектурности и даже затронули некоторые из проблем которые из-за нее возникают. Что же дальше? Предлагаю погрузиться чуть глубже в проблемы и на примерах посмотреть к чему это может привести.

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

Как по вашему должна выглядеть идеальная схема обработки ошибок? Мне кажется что как-то так (ну или близко к этому)

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

То что вы увидели на первой схеме – последствия многолетних преобразований архитектуры. На схеме можно увидеть как некоторые ошибки протекают сквозь несколько слоев абстракций и обрабатываются на самых разных уровнях. У меня ушло около двух недель (примерно по 30 – 40 минут в день в течение двух недель) на изучение обработки ошибок в таком приложении и на поиск пути устранения этой проблемы. В конечном итоге я придумал как сократить количество листьев в этом дереве с 6 до 3, но на ресерч и переработку флоу обработки ошибок пришлось потратить очень много времени. А ведь согласитесь, обработка ошибок это далеко не самая сложная и не самая нагруженная бизнес-логикой часть приложения.

А теперь представьте ту же самую схему, но спроектированную на потоки данных. Данные в таком приложении текут абсолютно по-разному в зависимости от того в какой момент времени был написан тот или иной модуль, так как в разное время в проекте были разные мэйнстримы и подходы к написанию и организации кода. Отладка усложняется в разы и я думаю даже не стоит объяснять почему.

Какие ограничения накладывает мультиархитектурность?

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

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

Как часто встречается такая проблема?

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

В первом проекте, над которым сначала работал лишь один разработчик, это было подобие стандартной flux-архитектуры, но после нескольких месяцев этот разработчик ушел, на его место пришли два других и начали реализовывать другой подход, потихоньку избавляясь от старого. Команда росла, каждый приносил что-то свое, а потом в команду пришел еще один сеньор и принес свои кардинальные изменения, в итоге в приложении было около 3-4 способов обращения к серверу, два кардинально разных подхода по работе с формами, даже навигация не была полностью централизованной.

Во втором моем проекте, еще до моего прихода, он был полностью переписан (как раз тот самый экзотический кейс), да и разработчиком на нем я был единственным, поэтому больших трудностей я не испытал, но испытал их с пониманием проекта, так как и там был ряд проблем. Но с ростом числа разработчиков работать на этом проекте стало бы очень трудно, а с одним разработчиком быстро не поедешь.

Про третий свой проект я рассказал в предыдущем разделе.

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

Выводы

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