- PVSM.RU - https://www.pvsm.ru -
Сначала хотел написать комментарий к статье "Я десять лет страдал от ужасных архитектур в C#... [1]", но понял две вещи:
Disclaimer: я не критикую @pnovikov [2] или его задумку в целом. Текст качественный (чувствуется опытный редактор), часть мыслей разделяю. Архитектур много, но это нормально (да, звучит как название корейского фильма).
Однако давайте по порядку. Сначала моё мнение о том, что влияет на архитектуру, потом про спорные моменты в статье об «исправлении архитектур». Ещё расскажу о том, что у нас хорошо работает — может, пригодится кому-нибудь.
И, конечно, всё сказанное здесь является личным мнением на основе моего опыта и когнитивных искажений.
Я часто принимаю архитектурные решения. Когда-то — большие, когда-то — маленькие. Изредка придумываю архитектуру с нуля. Ну, как с нуля — наверняка всё придумано до нас, но про что-то мы не знаем, поэтому приходится придумывать. И не из любви к велосипедостроению (скажем так, не только из любви к нему), а потому что для некоторых задач не нашлось готового решения, которое устроило бы по всем параметрам.
Почему я считаю, что совершенно разные архитектуры имеют право на жизнь? Можно порассуждать о том, что программирование — искусство, а не ремесло, но я не буду. Моё мнение: когда-то искусство, когда-то — ремесло. Речь не об этом. Главное, что задачи разные. И люди. Уточню — под задачами подразумеваются требования бизнеса.
Если когда-то мои задачи станут однотипными, я напишу или попрошу кого-то написать нейросеть (а может, хватит и скрипта), которая меня заменит. А сам займусь чем-то менее безрадостным. Пока же мой и, надеюсь, ваш личный апокалипсис не наступил, давайте подумаем, как влияют задачи и прочие условия на разнообразие архитектур. TL&DR; — разнообразно.
Вот это самая правильная, пожалуй, причина поменять архитектуру. Если, конечно, не проще старую архитектуру адаптировать к новым требованиям. Но тут сложно кратко рассказать что-то полезное.
Допустим, сроки (дважды убедился, что написал через «о») сильно сжаты. Тогда нам не до того чтобы выбирать, а, тем более, придумывать архитектуру — бери знакомые инструменты и копай. Но есть нюанс — иногда сложные проекты можно сделать вовремя только применив (и, возможно, придумав) что-то принципиально новое. Кто-то может сказать, что пригласить заказчика в баню — старый прием, но я сейчас про архитектуру…
Когда сроки комфортные — часто получается парадоксальная ситуация — вроде и можно придумать что-то новое, но зачем? Правда, многие успешно поддаются соблазну заняться другим более горящим проектом и сводят ситуацию к предыдущей.
В моей практике сроки редко приводят к революциям в архитектуре, но бывает. И это прекрасно.
Бывает так — команда (или кто-то из руководства) замечают, что скорость разработки снизилась или за итерацию багов многовато набежало. Нередко виновной в этом признают «неправильную архитектуру». Иногда — заслуженно. Чаще — просто как самого удобного обвиняемого (особенно, если в коллективе нет её «родителя»).
В принципе, в одних случаях всё сводится к фактору сроков. А в других — к поддерживаемости, о ней далее.
Неоднозначная тема. Потому что всё очень субъективно и зависит много от чего. Например — от команды, языка программирования, процессов в компании, количества адаптаций под разных клиентов. Давайте про последний фактор, мне он кажется самым интересным.
Вот вы сделали заказной проект. Успешно, в сроки и бюджет уложились, заказчик всем доволен. Было и у меня такое. Теперь вы смотрите на то, что использовали и думаете — так вот она — золотая жила! Мы сейчас используем все эти наработки, быстро сделаем один B2B-продукт, и… Сначала всё хорошо. Продукт сделали, пару раз продали. Наняли ещё продавцов и разработчиков («нужно больше золота»). Заказчики довольны, платят за сопровождение, случаются новые продажи…
А потом один из заказчиков говорит человеческим голосом — «мне бы вот эту штуковину совсем по-другому сделать — сколько это может стоить?». Ну, подумаешь — несколько if’чиков с другим кодом воткнуть (допустим, некогда было DI прикрутить), что плохого может случиться?
И в первый раз действительно ничего плохого не случится. Я бы даже не советовал в такой ситуации что-то специальное городить. Преждевременное усложнение архитектуры сродни преждевременной оптимизации. Но когда это случается во второй и третий раз — это повод вспомнить про такие штуки как DI, паттерн «стратегия», Feature Toggle и иже с ними. И, на время, это поможет.
А потом наступает день, когда вы смотрите на настройки проекта (всего-то несколько сотен опций) для конкретного тестового стенда… Вспоминаете, как посчитать число сочетаний и думаете — как же, вашу мать, это можно протестировать? Понятно, что в идеальном мире это просто — ведь каждая фича спроектирована и реализована так, что она никак не влияет на другую, а если влияет, то всё это предусмотрено и вообще наши разработчики никогда не ошибались.
Конечно, я сгустил краски — можно выделить какие-то наборы фич, которые используются у реальных заказчиков, написать больше тестов (как и каких — тема отдельного разговора) и немного упростить задачу. Но вдумайтесь — каждый серьезный релиз нужно протестировать для всех заказчиков. Напоминаю, это не B2C, где можно сказать «выкатим фичу для 5% пользователей и соберём фидбек» — для B2B фидбек можно по судам начать собирать…
Решения? Например, разделить продукт на модули с отдельным жизненным циклом (не забывая тестировать их взаимодействие). Это снизит сложность сопровождения, хотя и усложнит разработку. И я сейчас не о благодатной для холиваров теме «монолит vs. микросервисы» — в монолите тоже можно устроить подобное (хотя и сложнее, на мой взгляд).
И, заметьте, с прагматической точки зрения на каждом этапе у нас была неплохая архитектура.
Я не хочу вас (и себя) утомлять перечислением других причин для изменений в архитектуре. Давайте сейчас согласимся, что архитектуры имеют свойство со временем меняться, в зависимости от многих факторов. А значит: идеальная архитектура, решающая «ну вот все проблемы» не существует.
Если я вас в этом еще не убедил — посмотрите на разнообразие языков программирования и фреймворков (только не во фронтенде — не надо вскрывать эту тему). Если кто-то скажет, что это плохо, предлагаю провести мысленный эксперимент — представьте мир, в котором есть один конкретный язык программирования. С одним важным условием — он вам не нравится. Например, потому что вы никогда его не использовали, да и не собирались этого делать.
И, признаюсь, есть еще один веский довод — придумывать что-то новое, оптимизируя несколько параметров, играя компромиссами — это чертовски увлекательно. А теперь, когда мы все (правда?) согласны, что разнообразие архитектур — это нормально…
Про IoC соглашусь, что портянкам место в армии, а модули — это вселенское добро. Но вот всё остальное…
Если, конечно, послушать некоторых апологетов «чистого кода», то можно накодить гору сервисов, в каждом из которых будет в среднем полтора метода, а в методе — две с половиной строки. Но зачем? Вот честно, вы точно хотите соблюдать принципы, которые помогут справиться с маловероятными проблемами в далеком будущем, но размазывают даже несложную логику по десяткам файлов? Или вам, всё-таки, достаточно сейчас написать прилично работающий код?
К слову сказать, сейчас работаю над модулем, который точно будет использоваться в разных продуктах и, скорее всего, будет активно «тюниться». Так и там стараюсь не «мельчить». Не окупается. Хотя вот в нём использую единственные реализации интерфейсов чаще обычного.
Так вот, если у нас есть модули и мы не «мелочны», то откуда взяться проблемам производительности IoC или неподдерживаемых «портянок IoC-конфигураций»? Я не сталкивался.
Правда, уточню наши условия работы:
Совет: интерфейсы можно держать в том же файле, что и класс — удобно (если, конечно, пользуетесь нормальной IDE, а не блокнотом). Исключения делаю, когда интерфейсы (или комментарии к ним) разрастаются. Но это всё вкусовщина, конечно.
Да я и сам скажу, что не так — многие из них слишком далеки от SQL. Но не все. Поэтому, вместо того, чтобы «терпеть, пока O/RM удаляет 3000 объектов» или придумывать ещё один, найдите тот, который вас устроит.
Совет: попробуйте LINQ to DB [3]. Он хорошо сбалансирован, есть методы Update/Delete для нескольких строк. Только осторожно — вызывает привыкание. Да, нет каких-то фич EF и немного другая концепция, но мне понравился намного больше EF.
Кстати, приятно, что это разработка наших соотечественников. Игорю Ткачеву — респект (не нашёл его на Хабре).
UPD: RouR [4] отметил в комментариях, что есть расширение для EF Core [5], позволяющее использовать массовые операции. LINQ to DB всё равно не брошу, потому что он хороший.
Да они будут медленнее, чем на данных в памяти. Фатально ли это? Да нет, конечно же. Как решать эту проблему? Вот два рецепта, которые лучше применять одновременно.
Рецепт №1. Берёшь крутого разработчика, который любит делать всякие прикольные штуки и обсуждаешь с ним, как красиво решить эту проблему. Мне повезло, потому что force [6] решил проблему быстрее, чем она появилась (даже не помню, обсуждали её или нет). Как? Сделал (за день, вроде) тестовую фабрику для ORM, которая подменяет основное подмножество операций на обращения к массивам.
Для простых юнит-тестов — идеально. Альтернативный вариант — юзать SQLite или что-то подобное вместо «больших» БД.
Комментарий от force [6]: Тут надо сделать пару уточнений. Во-первых, мы стараемся не использовать сырые запросы к базе данных в коде, а максимально используем ORM, если он хороший, то лезть в базу с SQL наголо не требуется. Во-вторых, разница поведения с базой есть, но ведь мы не проверяем вставку в базу, мы проверяем логику, и небольшое различие в поведении тут несущественно, т.к. особо ни на что не влияет. Поддержка корректной тестовой базы гораздо сложнее.
Рецепт №2. Бизнес-сценарии я предпочитаю тестировать на настоящих БД. А если в проекте заявлена возможность поддержки нескольких СУБД, тесты выполняются для нескольких СУБД. Почему? Да всё просто. В утверждении «не хочется тестировать сервер баз данных», увы, происходит подмена понятий. Я, знаете ли, тестирую не то, что join работает или order by.
Я тестирую свой код, работающий с БД. А зная, что даже разные версии одной СУБД могут выдавать разные результаты на одинаковых запросах (пруф [7]), я хочу основные сценарии проверять именно на тех БД, с которыми этот код будет работать.
Обычно подобные тесты у меня выглядят так:
Совет: если такие тесты у вас выполняются объективно долго (а не потому, что пора оптимизировать запросы к базе) — сделайте билд, который будет запускать их реже (категории тестов или отдельный проект в помощь). Иначе разработчики не захотят сами запускать и остальные — быстрые тесты.
Просто дополню историю «транзакция в БД по каким-то причинам упала, а e-mail ушёл». А какое веселье будет, когда транзакция подождёт недоступного почтового сервера, поставив колом всю систему из-за какого-нибудь уведомления, которое пользователь потом отправит в корзину, не читая…
Правда, я всегда считал, что письма в транзакции отправляют только джуны в дикой природе в отсутствие ревью. В нашей команде за такое канделябром бьют (пока виртуальным).
В целом, если @pnovikov [2] не имеет планов по захвату мира с помощью единственно верной идеологии архитектуры, других, достойных упоминания, расхождений во взглядах не нашёл. Для каких-то задач, безусловно, озвученные им принципы подойдут. С удовольствием прочитаю следующие статьи и комментарии, может, найду какие-то полезные идеи для себя.
Предлагаемый фреймворк вряд ли буду использовать. Причина проста — у нас уже есть идеальная архитектура…
P.S. Если у вас будет желание обсудить что-то в комментариях, буду рад принять в этом участие.
Автор: Олег Аксенов
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/356811
Ссылки в тексте:
[1] Я десять лет страдал от ужасных архитектур в C#...: https://habr.com/ru/post/517404/
[2] pnovikov: https://habr.com/ru/users/pnovikov/
[3] LINQ to DB: https://github.com/linq2db/linq2db
[4] RouR: https://habr.com/ru/users/rour/
[5] расширение для EF Core: https://github.com/borisdj/EFCore.BulkExtensions
[6] force: https://habr.com/ru/users/force/
[7] пруф: https://yarfullstack.com/post/2018-06/mssql-2000-and-backward-compatibility/
[8] Источник: https://habr.com/ru/post/518182/?utm_source=habrahabr&utm_medium=rss&utm_campaign=518182
Нажмите здесь для печати.