понедельник, 9 января 2012 г.

Code Metrics + Refactoring

MicroscopeДумаю, большинство из вас знают, что в Visual Studio есть набор метрик кода. Моя цель – привлечь к ним ваше внимание, в том числе как к одной из отправных точек для ревью кода и рефакторинга.

Сначала я расскажу о запуске подсчёта метрик. Потом кратко опишу каждую метрику и дам некоторые рекомендации по рефакторингу в случае проблем. Затем немного расскажу о моём опыте использования метрик на практике.

Однако, начиная разговор о метриках кода в Visual Studio, хочу пояснить моё отношение к метрикам кода вообще.

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

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

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

Важное замечание: не стоит думать что код с хорошими метриками действительно хорош. Контр-пример – практически к любому коду можно многократно и фанатично применить рефакторинг “Extract Method”. После чего получим код с очень хорошими метриками, в котором даже автор на следующий день не разберется.

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

Запуск подсчёта метрик

Вы можете запустить подсчёт метрик из Visual Studio 2008/2010 (скажу сразу, что это относится к версиям Premium и Ultimate). Для этого откройте меню Analyze и выберите либо “Calculate Code Metrics for Selected Project(s)” либо “Calculate Code Metrics for Solution”.

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

CodeMetricsResult

В данном примере я построил фильтр так, чтобы он показал код, в котором встречаются методы или свойства с Maintainability Index меньше 60. Значения Maintainability Index меньше 20 подсвечиваются желтым цветом (меньше 10 - красным).

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

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

Помимо работы из Visual Studio есть возможность запускать подсчёт метрик из консольного приложения. Для этого нужно установить Visual Studio Code Metrics PowerTool. При этом требуется соответствующая лицензия на Visual Studio или TFS.

Maintainability Index

Это, на мой взгляд, основная метрика – своего рода KPI для оценки кода с точки зрения поддержки. Остальные метрики, по моему мнению, стоит использовать просто как подсказку при более детальном анализе проблем, локализованных с помощью Maintainability Index. Или же, если с Maintainability Index всё в порядке, а хочется улучшать код дальше, имеет смысл обратить внимание на метрики, не являющиеся основой для его вычисления (Class Coupling + Depth of Inheritance).

Метрика считается по следующей формуле:

MI = MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171

Cyclomatic Complexity и Lines of Code будут описаны далее.

Чем больше MI, тем должно быть проще поддерживать код. Трактовка Майкрософт значений этой метрики следующая:

  • 0-9 – код сложно поддерживать
  • 10-19 – код средненький в плане поддержки
  • 20-100 – код просто поддерживать

Своё мнение о значениях этой метрики я расскажу позже. Пока только скажу, что для того, чтобы получить скриншот со значением 19, мне пришлось написать метод почти на 300 строк с вложенными друг в друга 23 раза switch’ами и рекурсивными вызовами в каждом. Рекурсия скорее всего лишняя, просто сначала попробовал пойти другим путём.

Для этой метрики сложно дать конкретные рекомендации по рефакторингу. Разве что можно сказать, что при MI < 10 вы вряд ли обойдётесь простыми рефакторингами вроде “Extract Method”. В принципе, для проблемных методов где неочевиден способ их улучшения, имеет смысл посмотреть на наиболее плохие значения остальных метрик и действовать в нужном направлении.

Cyclomatic Complexity

Цикломатическая сложность кода – это (упрощённо) количество различных непересекающихся маршрутов выполнения кода. Например, у метода без условных операторов цикломатическая сложность 1, а у метода с одним условным оператором – 2. Подробнее об этом можно прочитать по ссылке выше или в русскоязычной википедии.

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

Методы с большой цикломатической сложностью часто можно упростить с помощью рефакторинга “Extract Method”. Временами хорошо помогают рефакторинги к паттернам “Strategy” или “Template Method”.

Lines of Code (LOC)

Приблизительно показывает количество строк кода.

Важное замечание: строки кода считаются в IL. По этой причине вы можете быть несколько удивлены, увидев количество строк кода в небольшом LINQ-запросе.

С другой стороны, большой LOC у метода в любом случае повод задуматься – небольшие LINQ-запросы тоже, знаете ли, хитро вывернутые бывают.

Методы с большим количеством строк кода – хорошие кандидаты на рефакторинг “Extract Method”. В тяжёлых случаях имеет смысл выделить новые классы и перенести в них некоторые новые методы. “Strategy” или “Template Method” тоже могут быть полезны.

Depth of Inheritance

Глубина наследования понятие достаточно очевидное. Для класса унаследованного только от Object глубина наследования равна 1 и так далее. Для методов эта метрика не считается.

На мой взгляд, значение Depth of Inheritance больше трёх уже повод задуматься и сделать ревью кода, а больше 5 – бить тревогу. В первую очередь можно просто посмотреть на необходимость наследования. А во вторую очередь могу порекомендовать создание Service Layer или рефакторинг к шаблону “Decorator”.

Class Coupling

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

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

При высокой связности рекомендую использовать паттерн “Mediator”. В случае если в проекте уже используется какой-либо Dependency Injection Framework – используйте его. Если ещё не используется – задумайтесь об использовании ;)

Использование метрик  на практике

Сейчас у нас (в ФогСофт) при ревью кода используется Maintainability Index для поиска потенциальных проблем. Учитывая, что среди кода достаточно много “унаследованного”, это весьма хорошее подспорье. Причина в том, что код по факту уже есть, а ревью на него ещё не было. А эта метрика позволяет начать с потенциально наиболее опасных участков кода.

Обычно, когда я пишу свой или редактирую чужой код – стараюсь обращать внимание на MI. Часто намного быстрее сразу отрефакторить даже нормально написанный код, чтобы он был более прост для понимания тех, кто будет его использовать или дописывать. Ценность метрик в данном случае в том, что это первый взгляд на код со стороны, автору ведь всегда кажется, что его код немного понятнее и правильнее, чем есть на самом деле ;)

Добавлю, что, на мой взгляд, MI < 20 для обычных методов и MI < 40 для методов реюзабельных библиотек и ключевых компонентов недопустимо, а хорошие показатели, соответственно, это 40+ и 60+ соответственно.

Резюме

Лично я считаю что метрики кода очень полезны, как я и говорил выше, в качестве отправной точки для ревью кода и рефакторинга. Рекомендую!

Однако нужно понимать, что не всегда код с хорошими метриками хорош, а с плохими – плох. Единственное исключение, пожалуй – Maintainability Index ниже 10. Честно скажу, на практике не встречал ниже 16, поэтому код с метрикой ниже 10 даже страшно представить. Быть может такое можно оправдать жизненно необходимой оптимизацией, но сомневаюсь…

Разумеется, метрики кода умеет считать не только Visual Studio. Самая известная альтернатива для .NET – NDepend.

Если у вас есть замечания, пожелания или новые темы – пишите в комментариях, твиттер или на olegaxenow.reformal.ru. Постараюсь учесть.

2 комментария:

  1. Интересный подход!

    Хотя некоторые данные у меня по проекту смутили - не понятно, как студия их считает.
    Например, раскрываю класс - все его методы и поля имеют MI 95-98-100. При этом на классе самом стоит число 93 - не пойму, откуда?

    Очень не понравилось отображение результатов. Так для CC хотелось бы видеть "среднее по больнице" или максимальное - стоит у всех методов и свойств 1, а на класс показывает 15 ( это сумма, на сколько я понял ). А мне бы хотелось видеть максимальную среди всех детей, ну или, на крайняк, среднюю - а то искать совсем неудобно.

    Вообще интересный инструмент - давно его видел, но разобраться лень был. А тут статья такая хорошая. Спасибо!

    ОтветитьУдалить
  2. To @Vasiliy Shiryaev:
    Интерфейс в студии предназначен скорее для быстрого поиска по ограничениям.
    Для более удобного и детального анализа есть экспорт в Excel.

    ОтветитьУдалить