Несколько месяцев назад я консультировал IT-компанию. Компания разработала проект для завода и хотела сделать из проекта тиражируемый продукт на другие предприятия в этой отрасли. После получения второго заказа они столкнулись со сложностью внесения изменений и настройки проекта. Это не позволяло им легко сделать второе внедрение, не говоря уже про постановку таких внедрений на поток.
Я поговорил с руководством компании и всеми заинтересованными лицами, провел анализ кода и выявил ряд проблем, которые мешали сделать переход к тиражируемому продукту.
Одним из серьезных препятствий оказалось то, что 90% бизнес-логики было написано на хранимых процедурах, остальная часть написана на .NET/C#. Ситуация осложнялась тем, что первая версия проекта была реализована и внедрена на Oracle, а второй заказчик настаивал на MSSQL.
Подход к продаже идеи
Понятно, что если бы я пришел к руководству компании, программистам и специалистам по БД и показал куда и как двигаться, то это не принесло бы желаемого результата. Поэтому для "продажи" идеи я использовал стандартный подход, обратите внимание на последовательность:
- Выявление проблемы (потребности)
- Выявление последствий проблемы
- Предложить решение, обсудить его выгоды
- Рассмотреть опасения
- Установить безопасное окружение для пилотирования
- Profit
Шаг 1. Выявление проблемы
У разработчиков были мысли, что бизнес-логику нужно выносить из БД, но куда и как они не знали. Плюс нужно было как-то обосновать для начальства время на рефакторинг и создание нового прототипа. Кроме того, в компании работал очень сильный администратор БД. Он оппонировал всем, кто хотел убрать бизнес-логику из БД и ни в какую не соглашался на смену архитектуры. Основным аргументом был вопрос о потере производительности.
В системе можно было выделить 3 компонента переплетенных между собой:
- Работа с пользовательским интерфейсом - UI
- Обработка данных - DB
- Набор множества объектов типа *Helper, *Manager, *Wrapper и т.п.
Основной вопрос, ответ на который я хотел услышать от разработчиков - Где описана бизнес-логика системы? Им было действительно трудно ответить и вот почему.
Посмотрите на один из самых простых примеров кода из блока UI:
public EditGoodForm(DataRow dr) { InitializeComponent(); connection = new SqlConnection(MainSQLConnection.Connection.ConnectionString); connection.Open(); comboBoxType.DataSource = Populate("SELECT distinct id, Name FROM dbo.HB_GoodsType"); comboBoxType.ValueMember = "ID"; comboBoxType.DisplayMember = "Name"; textBoxName.Text = _dr.Field<string>("Name"); textBoxTolerance.Text = dr.Field<decimal?>("Tolerance")!=null?dr.Field<decimal?>("Tolerance").ToString():string.Format("{0:F}",0); comboBoxType.SelectedValue = _dr.Field<int>("HB_GoodsType_ID"); comboBoxUnit.SelectedValue=_dr.Field<int>("HB_Unit_ID"); comboBoxRouteMap.SelectedValue = _dr.Field<int>("HB_RouteMap_ID"); checkBoxSkladUchet.Checked = _dr.Field<bool>("IsStoreMove"); }Вот вырезка из типичной хранимой процедуры:
--Получаем инфу по рецепту SELECT @IsProduction=IsProduction,@Workcenter_ID = MF_WorkCenter_ID,@Good_ID = HB_Goods_ID,@RecipeOutcome=ProductionOutcome,@RecipeDateTimeShift=MinimalTime,@SingleObject = SingleObject,@MinimalObjectWeight = MinimalObjectWeightPercent * ProductionOutcome / 100,@NotPerformTechnicalReglament=NotPerformTechnicalReglament FROM MF_Recipe WHERE id = @IterationRecipeID IF @SingleObject IS NULL SET @SingleObject = 1 SET @ObjectTime = DATEADD(hh,-@RecipeDateTimeShift,@TaskDateTime) --@TaskDateTime - @RecipeDateTimeShift IF @RecipeOutcome = 0 SELECT @RecipeOutcome = SUM(Income) FROM MF_RecipeRow WHERE MF_Recipe_ID = @IterationRecipeID --SET @Rule = 0 DECLARE @ObjectsOutput AS TABLE ( ID INT, ObjectOutcome DECIMAL(30,10), RecipeMultipler DECIMAL(30,10), AlredyExists BIT ) -- --Фикитивная запись, для нумерации INSERT @ObjectsOutput (ID, ObjectOutcome ,RecipeMultipler) VALUES (0 ,0,0) IF (dbo.F_MF_GetAuthorWorkcenter(@Workcenter_ID)=-1) BEGIN SET @Error = 'Для рабочего центра с ИД '+CAST(@Workcenter_ID AS VARCHAR(10))+' не найден родительский РЦ' RAISERROR(@Error,12,1) RETURN END
Про Helper'ы и Wrapper'ы нечего говорить, потому что они представляют собой просто кучу статических объектов переплетённых между собой.
Ну и где же описана бизнес-логика? От ответа на этот вопрос зависит куда мы пойдем, когда требования заказчика изменятся.
Перечисляем проблемы
Пока я листал код на проекторе кто-то узнавал свой код и начинал оправдываться, кто-то смеялся и говорил, что даже трогать это боится. Менеджер проекта и IT-директор, сидевшие вместе с нами, впервые услышали реальное отношение разработчиков коду.
Итак, после обсуждения все вместе выявили ряд самых острых проблем:
- Дублирование в C# коде
- Дублирование в SQL-коде
- Дублирование между C# и SQL-кодом
- Бизнес-логика во всех слоях приложения
- Нет разделения ответственности между классами и слоями приложения
- В целом множество технических долгов
Как следствие из вышеперечисленного:
- Трудно внести изменения и ничего не сломать
- Трудно искать причины багов
- Трудно гарантировать, что баг снова не выскочит
- Рефакторинг значительно усложнен. Невозможно без опасения рефакторить SQL-код
- Отсутствие модульных тестов: в C# коде сильная связанность и зависимость от внешних ресурсов, а на хранимые процедуры сложно писать модульные тесты
Отсутствие модульных тестов, которые можно запускать автоматически, приводило к ручному тестированию всей системы после каждого изменения. А чаще каждый релиз просто отдавали в «боевое» тестирование пользователям, чем последние были крайне не довольны.
Шаг 2. Выявление последствий проблемы
Последствия были для всех очевидны, оставалось только выписать их по пунктам:
- Низкая скорость разработки
- Непредсказуемые баги
- Отсутствие Definition of Done
- Невозможность быстро реагировать на изменения требуемые заказчику
- Незаменимость разработчиков
- Сложность создания универсального решения
- По факту нет коробочной версии, а это значит, что нет потока денег для компании
Такое положение вещей никак не устраивало руководство компании.
Шаг 3. Предложить решение
Мое предложение строилось на внедрении Domain Driven Design и создании прототипа с новой архитектурой. Мы подробно разобрали следующие темы:
Прямо во время обсуждение рождалось море идей по тому, как можно просто и красиво реализовать задачи проекта, как изменить архитектуру системы, как реализовать работу с БД. Вдохновение витало в воздухе, появлялись конструктивные споры.
Шаг 4. Рассмотреть опасения
Одно дело признать проблемы, узнать про новые возможности, совсем другое принять решение о начале изменений. Важным шагом является совместный расчет рисков и опасений связанных с грядущими изменениями:
- Если мы внесем изменения в систему, как это повлияет на уже работающую версию?
- Не упадет ли скорость работы, когда бизнес-логика перейдет из БД в .NET?
- В проекте есть работа с датчиками, датчики сидят на общей шине, которая настроена писать данные напрямую в БД. Можно ли будет перенастроить систему на отсылку сообщений в .NET?
- После перехода к новой архитектуре смогут ли все разработчики в ней разобраться?
- Начнется ли сопротивление со стороны администратора БД из-за потери своего влияния в компании? Если да, то как это можно будет смягчить?
Теперь осталось выбрать путь, по которому изменения пройдут с наименьшими рисками, а значит и без сопротивления.
Шаг 5. Установить безопасное окружение для пилотирования
Мы совместно с IT-директором компании выделили двух программистов, которые очень активно хотели уйти от бизнес-логики из БД. Они пообещали в течение месяца сделать прототип системы, исходя из новой для них парадигмы - Domain Driven Desing. Эти программисты пообещали, что больше никогда не предложат подобный рефакторинг, если идея с переходом на DDD окажется для их проекта не жизнеспособной.
Шаг 6. Profit
Меньше, чем за месяц программисты вынесли всю бизнес-логику из БД, написали на нее модульные тесты, сделали эмулятор железок и протестировали систему на быстродействие. Сейчас система готовится к выходу на новый объект для внедрения. К слову, первая версия системы делалась несколько лет целой командой.
Выводы
Я надеюсь, что эта история вдохновит многих разработчиков, которые находятся в заложниках обстоятельств. Знайте, что у вас почти всегда есть шанс донести новые идеи и воплотить их в жизнь. Чтобы понять как это сделать, посмотрите на причины возникновения проблем в компании и обстоятельства, за счет которых их удастся решить.
Какие я вижу предпосылки к появлению проблем в описанной компании:
- Бизнес-логика исторически была в хранимых процедурах, вызывала проблемы, но никто не решался это изменить
- Новые проекты писали по-старинке и бились об те же грабли снова и снова
- Команда очень мало общалась, проблемы редко выносились на обсуждение
- Авторитет в виде сильного администратора БД, с которым никто не хотел связываться
- Программисты, которые были за хорошие идеи не знали как "продать" их без внешней помощи
Почему удалось продать новые идеи:
- Руководство компании было действительно заинтересовано в скорейших результатах
- Мне удалось показать связь между хорошим кодом, тестами и прибылью для компании
- Продажа идеи шла по шагам без спешки, без рассказа про серебряные пули
- В компании мы нашли энтузиастов, которые взяли на себя ответственность по созданию прототипа
Ссылки
Видео с AgileDays 2010 - Как продать Agile заказчику, Асхат Уразбаев
Книга СПИН-продаж, Нил Рекхэм
Взаимоотношения или споры в команде, группа DotNetConf