Я Десять Лет Страдал От Ужасной Архитектуры В Приложениях На C# — И Теперь Нашел, Как Это Исправить



Я десять лет страдал от ужасной архитектуры в приложениях на C# — и теперь нашел, как это исправить

Я занимаюсь разработкой бизнес-приложений на .

NET уже второе десятилетие и каждый раз вижу одни и те же проблемы — жлобский код и хаос.

Смесь сервисов, UoW, DTO и вспомогательных классов.

В других местах прямой доступ к базе вручную, логика в статических классах, километровые портянки IoC-конфигурации.

Когда я был молодым и резвым мидлом, я тоже так писал.

Затем он ударил кулаком в стену, крича: «Хватит! В следующий раз я сделаю это по-другому».

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

Однако эволюция — вещь беспощадная: моя последняя система показалась мне более-менее близкой к идеалу.

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

Я взял эти результаты за основу, улучшил их и теперь анонсирую вам свою новую разработку: Reinforced.Tecture. Откуда берётся беспорядок? Я задал огромному количеству людей вопрос, откуда берутся реки вонючего кода в наших приложениях.

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

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

Если отбросить человеческие аспекты и оставить технико-архитектурные, то откуда тогда весь бардак?



- Это IoC!

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

Но что находится в наших контейнерах? Подключение к базе данных (по одному на каждое подключение), пачка каких-то параметров и учетных данных, вытащенных из web.config (обычно синглтон) и огромные, бесконечные километры различных сервисов-UoW-репозиториев.

В худшем случае в формате реализации интерфейса.

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

Благородная цель! Ради него, наверное, можно хранить портянки конфигурации IoC и по два файла на каждый компонент (интерфейс-реализация).

Или нет? И здесь я хочу помолиться за команды, которые додумаются использовать модульный механизм, предусмотренный практически любым IoC-фреймворком: ребята, вы хоть что-то делаете, соберитесь.



— Это потому, что нет юнит-тестов!

Я скажу вам, почему их там нет. Давайте будем честными: пробовали ли вы писать модульные тесты для традиционных C#-проектов, выполненных по методологии «UoW и репозиторий»? Вам необходимо написать заглушки ко всему вышеперечисленному содержимому контейнера и ввести тестовые данные (вручную).

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

И очищайте их после каждого тестового запуска.

В результате на одно честное тестирование одного метода бизнес-логики у вас уйдет около суток.

А потом он начинает периодически глючить из-за того, что поменялась инфраструктура (база данных была перенесена в другое место), истекло время соединения, данные пришли позже и т. д. Стандартный ответ на это — всеми любимый «Сборка упала? Это ерунда, перезапусти».

Такими темпами вы постепенно отказываетесь от написания тестов, ограничиваясь проверкой какой-то тривиальной хрени вроде того, что метод копирования полей в класс из 10 строк действительно, блин, копирует поля! Такие тесты остаются в системе навсегда и всегда выполняются «для галочки», терпя неудачу, может быть, раз в год, когда неопытный юниор по глупости стирает строку во время слияния.

В остальное время это театр кодовой безопасности.

Все прекрасно понимают, что настоящее тестирование проводится отделом QA «вручную» (в лучшем случае автоматизировано, сквозное), и пользователи вроде бы не жалуются.

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

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



- Это потому что мы лезем в базу с руками!

Да неужели? А я думал, что вы, как настоящие мужчины, терпите, пока О/РМ удаляет 3000 объектов.

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

Ну потому что она база.

Потому что объектная модель натягивается на реляционную, как сова на глобус — долго, медленно и со слезами (см.

«объектно-реляционное рассогласование импедансов»).

И тогда O/RM поощряет такие крендельки, оставляя доступ напрямую к базе данных (потому что если этого не сделать, ее пользователи сожрут ее целиком).

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

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

Однако делают они это не потому, что у них хорошая жизнь.

Это касается не только базы данных — письма часто отправляются прямо из логики.

В очередь помещаются как запросы к внешним веб-сервисам, так и сообщения.

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

Откатить транзакцию можно, но вернуть письмо не получится.



- Это потому, что мы не следуем шаблонам!

Кто тебе мешает? Пожалуйста, следуйте.

Но давайте попробуем реализовать простую функцию.

Что там нужно? Напишите пользовательский репозиторий.

Интерфейс + реализация, то же самое сделать для заказов, создать на их основе Единицу Работы.

Конечно, также уделим внимание интерфейсу и реализации.

Затем сделайте два DTO (а возможно и больше), затем сервис, в котором будет использоваться описанная Unit of Work, также разбив ее на две части.

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

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

Эта функция занимает свое место в долгом ящике.

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

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

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

И после нескольких таких фазовых переходов превращается в ту самую огромную IT-компанию, где есть золотые слитки и юниоры.



— Это потому, что у нас нет архитектора!

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

И даже если они это сделают, они не будут стоить денег.

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

С UML они учатся сами возиться, бедняги.

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

А можно просмотреть весь код, лично отрывая руки тем, кто посмеет нарушить целостность системы.

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

Оказывается, красивая и грамотная архитектура (в 99% случаев) требует множества телодвижений для решения простейших задач и внесения простейших правок.

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

Верх постиронии – если 50 билетов получит сам архитектор.

О, это незабываемое зрелище, о эти незабываемые звуки! Принципиальный хранитель шаблонов и правил сам начинает писать пахнущие кодом портянки, минуя любую архитектуру.

Пыхтит, корчится, шаблон трещит по всему офису — лишь бы начальство не пинало его под зад за срыв сроков.

Словами не выразить: блажен, кто увидел мир в его роковые минуты.

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

Раз это так, то происходящее печально, но не удивительно.



— Это потому, что XXX-фреймворк — мусор!

Черт возьми, да, большинство технологий — отстой.

Open source работает плохо, платные решения тоже не без проблем.

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

Обычно тезис из подзаголовка дополняется фразой «давайте перепишем все в ГГГ»! Это именно те изречения, которые выдают розовощекие дети, вытирая лица печеньем после кофе-брейка на конференции.

Могу поспорить, именно там они услышали о волшебном ГГГ.

И если бы эти ребята ели чуть меньше печенья, а думали чуть больше своей головой, они бы заметили, что YYY, который компания GGG сделала для себя, ориентирован на решение проблем GGG. В эпизод подкаста "We're Doomed" со мной, Фил хорошо выразился на примере redux: «redux — это фреймворк для управления состоянием… Но не вашим состоянием!» И пусть вас не вводят в заблуждение вдохновляющие примеры а-ля «сделаем приложение для подсчета коров по ГГГ за 5 минут».

Все еще существует некоторая разница между подсчетом коров и обычной системой автоматизации похоронного бизнеса.

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

Будет ли он так же хорош? Очень часто технология YYY предсказуемо выходит из строя из-за несовместимости своего любимого «я» с объективными реалиями вашей системы.

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

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

Как убрать весь этот мусор Однако этого достаточно, чтобы критиковать объективную реальность.

Ныть может любой.

Лично я хочу иметь инструмент, который я могу взять, когда начинаю проект, просто использовать его, и он просто работает. Формализуем мою «просто работу» на конкретные пункты, которые я хочу видеть в результате:

Долгосрочная жизнеспособность

Я уже обновил 2000 файлов в проекте несколько раз за один раз (один раз даже с помощью транслятора VB.NET-C#), больше делать этого не хочу.

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



Минимум кода, максимум логики

Ну, я не хочу создавать по 100500 классов каждый, чтобы соблюсти принципы проектирования.

Это просто не так.

Я хочу разрабатывать функции с невероятной скоростью.

И в то же время не ставить под угрозу ремонтопригодность, пробивая пол и сбивая соседей снизу.

я хочу сделать это Верно был Только .

То есть это требовало меньше усилий, чем сделать это неправильно.

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



Четкая организация

Я устал думать о том, куда поставить следующий сервис, в какую сборку положить интерфейс, в какое неймспейс закинуть DTO. Ладно, я думаю, эти мысли просто замедляют мою работу.

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

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

Можно выпендриваться и говорить «формирование императива поведения у разработчика», но я скромен.

Вот тут-то и приходит на помощь грануляция.

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



Строгая типизация

Более строгая типизация! Нам дали компилятор — так давайте же извлекем из него максимум пользы! Пусть он работает на благо бизнеса.

Компилятор всегда сможет собрать ваши интерфейсы, написанные по образу и подобию Java. Другое дело, что мы пишем на C# и у нас гораздо больше возможностей языка — хорошие дженерики, лямбды, различные модификаторы доступа, расширения, ковариантность! Я, конечно, не поддерживаю сумасшедших ФП, которые хотят проверить всё на свете ещё на этапе компиляции и стремятся к полному автоматизированному доказательству корректности.

Особенно когда они предлагают это сделать посредством темных и непонятных абстракций.

Однако стоит признать определенную правильность и убойную пригодность некоторых подходов.

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



Удобное абстрагирование от внешних систем

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

Обычно для таких кренделей нужно самостоятельно выбрать нужные интерфейсы и напичкать ими DI-контейнер.

Здесь я хочу заранее, из коробки, иметь общие рабочие интерфейсы.

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

Проблемы с логикой – это одно, а проблемы с внешними системами – это другое.

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

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

Я имею в виду, что у меня один подход к ошибкам в логике и другой подход к ошибкам во внешних системах.

Хотелось бы, чтобы дизайн системы как-то.

ну не знаю.

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

Решите, так сказать, задачу абстрактно.



Решение проблем с тестированием

Тесты нужны и важны.

Не просто компилятор.

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

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

А здесь можно брать в количестве.

Как говорится, главное не провалиться – а потом пинаем ногами.

Совершенно необходимо как-то решить проблему с хранением тестовых данных.

У меня, конечно, есть тестовая база для разработки.

Но поднимать это в рамках CI-билда мне категорически не хочется.

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

Необходимо, чтобы мой код, который я написал, был протестирован.

А все остальное не нужно.

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

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

И будет очень хорошо, если на их написание не уйдет полдня.

Еще один момент, на который стоит обратить внимание: как нам развиваться? Создаем тестовые данные, что-то пишем, запускаем и отлаживаем, уточняем требования, пишем что-то снова, снова отлаживаем.

Потом в какой-то момент коммитим задачу, пропускаем ее через QA, сливаем ветку, после чего задача считается решенным.

Это логично, все так делают. Так вот, хотелось бы в этот процесс впаять автоматизированное тестирование, чтобы после приемки, когда все - от QA до бизнеса сказали "да, это работает правильно", мы могли настроить тесты без изменения функционала и скинуть их в общую кучу так что каждая сборка может быть запущена.

Вы даже можете добавить некоторое покрытие кода.

Если вы измените логику, вы увидите, что упало.

Ну это круто! Но TDD — это полная трата, если честно.

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

Поэтому я не буду зацикливаться на TDD. И поэтому.

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

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

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

Оставляйте пока свои комментарии - мне интересно услышать ваше мнение, даже если оно отрицательное.

УПД: Была ссылка на репозиторий проекта, но я ее удалил, так как она провоцировала пока не актуальные дискуссии.

Теги: #программирование #базы данных #C++ #тестирование ИТ-систем #.

NET #архитектура приложений #бизнес-логика #.

net core #ASP #ASP #reinforced #reinforced

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.