В данной статье хочу рассказать о приеме, который поможет грамотнее спроектировать приложение (да и не только приложения, но об этом в другой раз), даже если вы не сильно владеете общеизвестными архитектурными принципы типа SOLID и прочих. При этом отмечу сразу, что я не предлагаю здесь какие-то новые принципы - это скорее лишь следствие их и просто прием, который позволит следовать как минимум части из них естественным образом.
На самом деле под “переводом стрелок” имеются в виду сразу два подхода, и по очереди о них расскажу. Также отмечу, что приемы эти не является какими-то ноу-хау, и более-менее опытные разработчики пользуются ими "на автомате". Но полагаю, если сформулировать их в явном виде, это будет полезно для менее опытных специалистов или даже вообще начинающих.
Начну сразу с примера. Представим, что у нас есть некий интернет-магазин, и flow покупки в нем вполне стандартный: пользователь на сайте наполняет корзину и оформляет заказ. Далее пользователю необходимо этот заказ оплатить, после чего менеджеру прилетит уведомление на почту. Все просто для простенького магазина. Если представить этот порядок действий визуально, будет примерно так:
А суть приема (первого его воплощения) предельно проста: реализуя эту логику, представить мысленно (или так же на бумажке), как у каждого из этих узлов появляются дополнительные стрелки, как входящие, так и исходящие. Или если быть точнее, не просто их представить, а задать себе вопрос: насколько разрабатываемое приложение к этому будет готово?
Взять, к примеру, входящую стрелку к “Корзине”. В текущем варианте корзину наполняет просто сам пользователь. И наша задача представить, что таких входящих стрелок может быть несколько. В общем случае нам даже неважно, что это именно будут за стрелки (абстрагируемся от этого), но полезно и пофантазировать. Например, в будущем корзину должен будет уметь наполнить не только сам пользователь, но и менеджер за него. И вполне вероятно уже даже не через основную “морду” магазина, а через отдельную админ-панель.
При этом тут важно понимать: сейчас, пока мы лишь мысленно это представляем, не нужно реализовывать эти сценарии (и уж точно не нужно уже сейчас пилить админ-панель, если ее еще нет ????). Важно лишь понять, насколько наша реализация “Корзины” будет готова к тому, что наполнять ее будут когда-нибудь потом из совершенно разных мест. А не только сам пользователь на сайте магазина. И другой момент, о котором тоже сразу оговорюсь: говоря сейчас об этих узлах типа “Корзина”, “Заказ”, стоит в первую очередь понимать их не как какие-то уже конкретные компоненты, классы или методы. Эти узлы, в первую очередь, - лишь абстракции, форма реализации которых может быть разной.
Продолжая пример с менеджером, который помогает пользователю наполнить корзину, эта же ситуация напрашивается как пример и для новой входящей стрелки к “Заказу”. То есть не только сам пользователь может создать заказ, но и менеджер.
Но говоря об этих стрелках, я также сказал, что представить нужно не только другие входящие, но и исходящие стрелки. Взять, как пример, снова “Корзину”. Казалось бы, какие тут еще могут варианты исходящих стрелок? И вот тут важно вспомнить, о чем я говорил чуть выше: наша задача изначально просто представить, что такие стрелки возможны, даже если в голову не приходят конкретные примеры, либо вероятность их появления кажется ничтожной. В данном случае постановка вопроса будет такой: “Корзина может быть применена где-то еще помимо того, что на ее основе создается заказ”. Примеры уже можно будет придумать позже или по ходу дела, но в идеале нужно уметь ориентироваться и без них. Это как раз потому и важно, что никогда не знаешь заранее, какие задачи от бизнеса прилетят, и суть грамотной архитектуры как раз в том, чтобы быть максимально готовым к изменениям от заказчика. Особенно, если работать приходится в предметной области, которая не является для нас “нативной”, то есть не используемой нами самими в повседневной жизни. Например, сейчас нам бизнес и не говорил про какое-то еще применение корзины, а сами мы, вроде как, и не видим других применений. Но завтра бизнес придет с примерно такой задачей: “Пользователи часто возвращают товары, потому что изначально берут несовместимые между собой детали. Нужен функционал, чтобы пользователь без оформления заказа мог отправить сформированную корзину на проверку специалисту.”. Ну и так далее. Вариантов задач у бизнеса всегда найдется масса. Впрочем, в этой статье я не буду особо углубляться в вопрос, зачем вообще нужны какие-то архитектурные принципы, почему их важно соблюдать, и уж тем более разъяснять их. Это все уже много раз и очень хорошо расписано во многочисленных других материалах.
Но все же подводя черту под вышесказанным, к чему мы приходим с точки зрения “сухой теории”? Пожалуй, самое важное: мы обеспечили слабое связывание (low coupling). Корзина у нас ни коим образом не завязана на Web, а равно как и на то, что там с ней дальше будут делать. Помимо прочего, в зависимости от ситуации, мы так или иначе придем к соблюдению и всех принципов SOLID. К сожалению, расписывание всех примеров раздуло бы статью до слишком больших размеров, но, например, представление многочисленных стрелок в компоненте “Оплата” (разные способы оплаты) неизбежно бы привело нас к соблюдению и Open-Closed, и Liskov Substitution, и так далее. В общем, как уже сказал в начале статьи, даже не вдаваясь особо во все эти принципы, с таким приемом мы так или иначе все равно придем к их соблюдению. Хотя знать их и понимать, безусловно, нужно.
Что касается второго воплощения “перевода стрелок” в проектировании, то оно достаточно просто. Каков фигуральный смысл этой фразы в быту? Это когда ты приходишь к условному Васе и спрашиваешь: “Почему не сделано?”, а в ответ слышишь: “Так за это не я, а Петя отвечает”. Но как это можно применить в разработке? Причем не только ПО, но и вообще. Дело в том, что тут очень четко просматривается Single Responsibility и декомпозиция задачи. Временно отстранившись от ПО, представим такую задачу: “Вот список заказов с адресами, надо доставить”. Если ее дать просто водителю (допустим, тот же Вася), он вполне справедливо может сказать: “Слушайте, я просто водитель, а у вас тут все адреса не пойми в каком порядке. Дайте мне готовый маршрут, а я развезу”. Вася таким образом скинул с себя не свои обязанности, фактически “переведя стрелки” на неопределенного (относительно него самого) другого человека, а может и не человека даже) Но ему это не важно. Ему главное получить на входе уже готовый маршрут для развоза, по которому он поедет, а кто (или что) именно для него этот маршрут составит - ему неважно. Фактически, Вася уже готов к многочисленным "входящим стрелкам". Точно так же стоит поступать и при решении задач в разработке, где все ваши классы/методы/компоненты - это те самые простые “Васи”, “Пети” и так далее. И где каждый из них хоть и простой, но все же соображает, что делать не свою работу не стоит, а то ведь и отвечать за нее то потом тоже нужно будет)
Здесь же кроется и другое правило проектирования: откладывать решения на потом. Условный и упрощенный пример: в вашем приложении активизировались боты и вы понимаете, что их нужно массово банить. Садитесь за задачу и можно рассуждать так: “По каким именно критериям определять бота пока непонятно, поэтому сделаю (ну или просто спроектирую) пока просто инструмент (назову его Петей), который будет массово банить по входящему списку пользователей, а уж кто там бот или не бот - это пускай Вася определит.” Где Вася - это просто другой инструмент. Может быть, вы сами потом и создадите этот инструмент. А может быть это поручат другому человеку, и вы будете вести разработку параллельно. А может быть эту задачу и вовсе поручат другому отделу. Но для Пети это не имеет никакого значения, поскольку мы уже фактически обеспечили ему множество “входящих стрелок” - кто ему сообщит списки пользователей совершенно неважно. Может это будет админ, который будет формировать эти списки вручную. А может это будет и отдельный программный компонент, определяющий ботов на основе формальных критериев, или вообще инструмент на базе машинного обучения. А может и все они вместе) А еще Петя может сообразить, что он не хочет брать на себя ответственность решать, кого на сколько банить: пожизненно ли или всего на один день. А равно как и то, как об этом сообщить пользователю. Пускай за него это решат другие.
Таким образом, применяя подход “перевода стрелок”, можно естественным путем приходить к вполне грамотной архитектуре, даже не зная ее теоретической основы. Но и помнить также нужно, что вопрос этот часто подразумевает компромисс между тем-то и тем-то. Поэтому что сами принципы, что озвученные в данной статье подходы стоит рассматривать не как непреложные законы, а как правила. Надеюсь, суть этих подходов смог доступно объяснить. Но любой фидбэк естественно приветствуется)