God object. Анализ сложных проектов

    Введение


    Ни для кого не секрет, что такой архитектурный антипаттерн как God object препятствует эффективному поддерживанию кода проекта. Однако его все равно можно встретить в Legacy-системах корпоративного сектора. Со временем код становится настолько сложным, что изменить его функциональность, даже при наличии Unit-тестирования, становится большой проблемой. Такие системы никто не хочет поддерживать, все боятся что-либо улучшать, количество проблем в трекере держится постоянным числом, но может и расти. Как правило, у команды упавшее настроение, которое со временем становится чемоданным: все хотят свалить.
    Иллюстрация проблемы


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

    Взгляд в код


    Анализируемая система является слоем доступа к базе данных и представляет собой класс с интерфейсом, содержащим большое число методов. Внутри каждого метода содержится SQL-запрос к базе данных в формате строки и вызов хелпер-класса, выполняющего операции подстановки параметров, чтения и записи данных. Этот слой используется в Web-части приложения, которая вызывает отдельные методы. Такой незамысловатый способ применялся на протяжении многих лет. Были некоторые попытки со стороны разработчиков решить назревшую проблему, однако успеха не получили: требуемое фичивание заглушало процессы улучшения кодовой базы. В конце концов проект, начатый почти десяток лет назад, стал требовать капитального ремонта.

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

    План работ


    Задача решалась в несколько этапов:
    1. Сбор количественной информации кода слоя доступа к данным.
    2. Анализ количества таблиц, используемых методом.
    3. Анализ количества методов, использующих таблицу.
    4. Кластеризация методов с учета используемых ими таблиц.
    5. Группировка кластеров по видам сущностей предметной области.
    6. Автоматический рефакторинг переноса методов в рассчитанные кластеры-классы.
    7. Ручное исправление особых случаев.

    Жирным выделены те пункты, которые рассмотрены в этой статье. Остальные в следующей.

    Поехали!


    Сбор количественной информации

    Так как кульминационной стадией задачи является кластеризация полученных данных, то необходимо собрать данные в таком виде, в котором было бы удобно отражать связи между методами и таблицами. На мой взгляд, таким форматом может служить матрица, строки которой отражают методы, столбцы — таблицы, а ячейки могут принимать два значения: 0 (таблица методом не используется) и 1 (таблица методом используется):
    Таблица 1 Таблица 2
    Метод 1 1 1
    Метод 2 0 1

    Остается только собрать такие данные по коду. Слой доступа к данным написан на C#, что несколько упрощает эту задачу. Есть такой проект, как NRefactory. Он представляет из себя парсер и преобразователь C#-кода для таких широко известных в узких кругах IDE как SharpDevelop и (с недавнего момента) MonoDevelop. Вменяемой документации на текущую версию найти не удалось, зато есть отличная демка, из которой понятны основные принципы работы с библиотекой. За пару часов был написан сборщик (мусора) информации, который пробегал по проекту и собирал информацию в вышесказанном виде. Список вообще существующих таблиц был заранее сформирован SQL-запросом к БД. Попутно выяснилось, что некоторые из таблиц в коде не используются вовсе. :-) Возможно, они необходимы хранимым процедурам, однако такой анализ в ходе решения задачи не выполнялся.

    Анализ

    После получения искомой матрицы, мне было интересно посмотреть, как выглядят графики количества таблиц на один метод и количества методов на таблицу, если после суммирования по строкам и столбцам отсортировать данные в убывающем порядке. Результат представлен на рисунках 1 и 2.
    Рисунок 1
    Рисунок 1.
    Рисунок 2
    Рисунок 2.

    На обоих графиках можно выделить три области:

    1. Область крутого подъема (в начале графиков).
    2. Область плавного спуска (в конце графиков).
    3. Область вида “вроде бы подъем, хотя уже спуск” (в середине графиков).

    Мы могли бы задаться некоторыми величинами для определения коридора 3-ей области, однако быстрый гуглинг не дал каких-либо вменяемых результатов, а разрабатывать свои показатели на основе какого-нибудь среднего не хотелось — все равно бы вышла игра в салки. Поэтому я выполнил разбивку обоих графиков на три кластера с помощью алгоритма карт Кохонена в ПО Deductor. Пример результата для случая количества методов на таблицу показан на рисунке 3.

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

    Рассмотрим подробнее диаграмму на рисунке 3. На ней хорошо видно, что есть некоторые такие таблицы, которые входят в большое число методов. Я предполагаю, что сущности, представленные этими таблицами, являются основными, базовыми, “агрегационными” (если говорить в терминологии DDD): они используются практически во всех бизнес-операциях. Следующая устойчивая группа, отмеченная красным цветом, может представлять собой такие сущности, которые являются наиболее характерными представителями отдельных служб и модулей системы. Это основное поле действий в будущем. Последняя группа, отмеченная синим цветом, представляет собой маленькие сущности, которые, скорее всего, являются локально значимыми в пределах службы. Таким образом было получено черновое представление системы в виде набора служб, простых сущностей и корней агрегации.

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

    1. Очень сложные методы, кластеризацию которых в автоматическом режиме выполнить вряд ли получится. Представляют собой жуткий спагетти-код, собирающий куски огромного SQL-запроса в единое демоническое целое и шарахающее результат в пользователя. Только ручная работа, только хардкор.
    2. Сложные методы, с которыми надежда еще есть. Их все равно придется рефакторить руками, однако выполнить кластеризацию попробовать можно.
    3. Простые методы, затрагивающие не более 6-ти таблиц. Результат кластеризации должен быть очень хорошим.
    4. Как особый случай, можно выделить методы, оперирующие всего одной таблицей, для них кластеризацию выполнять не нужно, здесь и так все понятно. Т.е. мы еще даже не подошли к кульминационной части, а уже почти 25% всей работы сделали.


    Итоги


    Подведу некоторые итоги, статья получилась немаленькая, а все еще впереди. Удалось взглянуть на проблему с дальней дистанции, выяснить некоторые особенности предметной области. Уже понятно, какие объекты являются ключевыми, а какие — второстепенными. Далее, на основе выделенных методов, оперирующих одной таблицей, уже можно выделить некоторые классы. Если учесть, что таких методов 25%, то уже на четверть подзадача анализа решена.

    Что дальше?


    В следующей части (частях?): завершение анализа, автоматическое выделение классов на основе результатов кластеризации, создание инструмента на основе SDK Resharper для более тонкого выделения классов и методов в ручном режиме.

    • Интересно ли почитать статью по NRefactory?
    • Стоит ли написать отдельно статью по написанию рефакторингов в Resharper?
    • Кто-либо занимался чем-то подобным? Очень интересны результаты, гуглинг что-то не помогает.
    • А как выглядят такие графики у вас?
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 18
    • +5
      Занимаюсь разработкой «с нуля» новой версии САПР на основе старой немецкой. Причин было много — основные — крайне устаревшая модель системы 30летней давности, проблемы с пересборкой и и необходимость внедрения нового функционала.
      Никаких графиков не чертил, а сначала изучал старую систему в течение 2х лет, занимаясь ее допиливанием и поддержкой. Когда стало ясно, что перспектива не лучшая, убедил начальство, что надо разработать систему заново на современных платформах (Java), и не старый код курочить, а писать понимая суть дела.
      Затем написал ТЗ, рисовал кучу архитектурных картинок — просто, в Visio, без наворотов.
      Потом были проектирование БД, споры с коллегами как лучше сделать, презентации новой системы.
      Что-то рисовали конечно, но старый код и реализации вообще не копировали и не рефакторили. Брали за основу только принципы.
      Сейчас прошло 2 года, над системой работали несколько человек, и на носу 2хтысячная SVN-ревизия. Новая система уже запущена.
      • +1
        Мне как-то не хватило убедительности на такой шаг, да и мы перед этим проводили анкетирование разных самарских компаний по поводу «переписать все или нет». Если сумею, то выложу результаты этой анкеты и саму анкету. Но кратко, там практически единоличное мнение «ни в коем случае нельзя все переписывать». В любом случае Вы герой.
        • 0
          У нас тоже было практически единоличное мнение начальства против. За наше с коллегой «новый САПР» на презентации нас ругали на чем свет стоит.

          У вас пожалуй посовременнее система, раз там есть прослойка к БД на C#.
          А у нас разработка 80х кодов — С, Фортран, Shell, гора файлов, скриптов и бинарников. И куча ошибок.
          так что просто не было другого выхода.
          • 0
            Это надо решать не анкетированием, а сравнением трудозатрат: цена поддержки старого плюс стоимость внесения изменений в старый код по сравнению со стоимостью переписывания и оценочной стоимостью внесения изменений в новый код.
            • 0
              > а сравнением трудозатрат

              Напишите про это статью? Мне было бы интересно почитать, как и кем это делается на практике.

              > не анкетированием

              Да, метод не точный, зато хорошо выявляет настроение людей. Мы не машины, если мы изначально на что-то смотрим с негативом, то работа не пойдет в полном темпе. Зато после анкетирования мы со всеми лучше познакомились, с кем-то отдельно встретились, нам дали ценные советы и т.д… Польза все-таки была.
        • –9
          Не, ну чего люди не придумают — лишь бы не программировать :)
          • 0
            А программирование по вашему это быстрый набор корректных с точки зрения синтаксиса наборов символов в IDE?
            • 0
              Речь о том, что делать подобное «исследование» (кластеризацию) — это лишь трата времени. Вроде же речь о C#, тогда вообще какая проблема получить методы обращающиеся к одной таблице — вида «дай все ссылки, где это используется». И потом работать с каждой таблицей отдельно. Зачем красивые графики?
              • 0
                > это лишь трата времени

                Так говорили и про Unit-тестирование, и про TDD, и про написание человеческих спецификаций. Трата времени — делать что-либо не имея возможности построить прогноз. Однако в этом случае мы уже посмотрели на айсберг снизу, еще не начав рыть верхушку.

                > получить методы обращающиеся к одной таблице

                Это вы как хотите сделать? Через поиск подстроки? Утомитесь, я думаю. Несколько сотен таблиц и пара тысяч методов… Мой средний темп был не более 20-ти методов в день. Есть методы, которые за один день-то не сделаешь. Я попробовал недельку и быстро понял, что пора как-то по-другому браться за задачу.

                > Зачем красивые графики?

                Зачем иллюстрации? Ну вот не все люди понимают текст без опоры на визуальную информацию. Я, например, такой. Очень рад за Вас, если куча текста без единой иллюстрации воспринимаются Вашей головой в полном объеме и без проблем.
          • 0
            Спасибо за интересное исследование.

            В книжке про чистый код упоминается паттерн active record.
            Я правильно понимаю, что в Вашей статье, речь как раз про выявление таких объектов, для связи с БД?
            • 0
              Структурно это довольно близко к active record.
              • 0
                Это было бы так, если ваши методы занимались лишь записью/считыванием (select/insert/update/delete одной конкретной таблицы). Но судя по тому, что вы написали — это не так. Я так понимаю в рамках метода может быть обращение к нескольким разным процедурам. Это аналогично хранимой процедуре в SQL-сервере. А раз так, то вам нужно было выбрать только те методы которые занимаются записью/считыванием, а не обработкой данных. Тогда это и было бы создание active record.
                • 0
                  > в рамках метода может быть обращение к нескольким разным процедурам

                  и соответственно, к разным таблицам.
                  • 0
                    Я не утверждаю, что это AR. Однако действительно есть классы вида «класс-на-таблицу» с методами операций CRUD. Также будет генерироваться класс-Reference, в который будут сброшены такие методы, которые оперируют несколькими таблицами, но одна из низ признана главной. Все будет в продолжении, не переживайте.
            • 0
              Очень интересно. Только это не взгляд со стороны предметной области, а попытка (надеюсь успешная) разбить систему на подсистемы не вникая в предметную области а с точки зрения абстрактной функциональности и простой арифметики.
              Я ни в коем случае не берусь критиковать такой подход, если он работает — это было бы круто. На практике все может разрушиться из-за пары мелких нюансов, незаметных без глубокого изучения бизнес-логики.
              • +1
                Безусловно, некоторые нюансы были, однако тем не менее удалось немалую часть работы со своих плеч сбросить на математику и алгоритмы. Подробнее об этом постараюсь написать в следующей части.
              • +1
                Интересно ли почитать статью по NRefactory?
                Стоит ли написать отдельно статью по написанию рефакторингов в Resharper?

                Весьма интересно будет почитать и чтобы достаточно подробно касательно технических моментов.
                • +1
                  Ок, как только завершу с этой статьей, то попробую изложить по Resharper, а то документации нет почти, Дмитрий Нестерюк начинал что-то писать про это, но уже прошло много времени.

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