Обзор основных метрик программного кода

Метрики программного кода, в отличие от Agile или Performance метрик, несут истинную ценность только для программиста. Метрики собираются до и после проведения большого рефакторинга для понимания, что же изменилось и не стало ли хуже. В больших проектах метрики позволят быстро обнаружить проблемные участки кода подобно тому, как performance-профайлеры указывают на проблемы с производительностью в конкретном месте. Метрики встраиваются в CI системы, такие как TeamCity или Jenkins, чтобы иметь возможность контролировать изменения значений метрик с каждым комитом кода в репозиторий.

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

Исходящие зависимости класса (Efferent Couplings или Outgoing dependencies)

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

public class Calculator
{
    private DataSource1 _DS1 { get; } = new DataSource1();
    private DataSource2 _DS2 { get; } = new DataSource2();
    
    public int Calculate()
    {
        return _DS1.GetNumber1() * _DS2.GetNumber2();
    }
}

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

Большое количество исходящих зависимостей (5+) обычно говорит о том, что класс делает слишком много, то есть нарушает принцип единой ответственности. Такие классы тяжело сопровождать и они имеют риск бесконечно увеличиваться в размерах. Привести к норме значение метрики поможет выбор более конкретного имени для класса (не Calculator, а например EmployeeCompensationCalculator), вынесение неподходящей логики в отдельные классы, уместное применение шаблонов, помогающих соблюдать принцип единой ответственности (Decorator, Mediator, Chain o Responsibility, Command и тд).

Если класс спроектирован так, что все его зависимости инициализируются в конструкторе, то их количество легко подсчитать вручную, только изучив тело конструктора. Для автоматического подсчета метрик в Visual Studio 2017 можно перейти во вкладку Analyze -> Calculate Code Metrics -> For Solution. Появится окно с метриками под названием Code Metric Results. Сама метрика будет называться Class Coupling.

Входящие зависимости класса (Afferent Couplings или Incoming Dependencies)

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

public class DataStorage
{
    public int GetValue() 
        => 5;      
}

public class Calculator
{
    public int Calculate() =>
       new DataStorage().GetValue() * 5;
}

public class AnotherCalculator
{
    public int Calculate() =>
        new DataStorage().GetValue() * 10;
}

Класс DataStorage используется в классах Calculator и AnotherCalculator, следовательно, логика обоих может быть поломана в результате изменения в методе GetValue.

Обычно чрезмерное использование некоторого класса во многих местах проекта говорит о том, что он нарушает принцип единой ответственности и является God Object’ом, храня в себе функционал на все случаи жизни. Такой класс требует рефакторинга. Однако, класс может соблюдать SRP и также являться часто используемым в силу своей специфики. В таком случае высокое значение метрики говорит о высоком уровне повторного использования кода.

Visual Studio 2017 позволяет отыскать все ссылки на тип при помощи опции Find All References. Также значение метрики показывают различные статические анализаторы кода.

Цикломатическая сложность (Cyclomatic Complexity)

Метрика определяет сложность некоторого участка кода, например, метода, путем подсчета в нем количества условных операторов, циклов, switch и case операторов.

//Цикломатическая сложность - 1
int Calculate(int x, int y)
{
    return x / y;
}

//Цикломатическая сложность - 2
int Calculate(int x, int y)
{
    if (y == 0) 
        throw new ArgumentException();
    return x / y;
}

//Цикломатическая сложность - 3
int Calculate(int x, int y)
{
    if (x == 0)
        throw new ArgumentException();
    else if (y == 0)
        throw new ArgumentException();
    else //блок else не влияет на значение метрики
        return x / y;
}

//Цикломатическая сложность - 4
static int Calculate(int x, int y)
{
    if (x > y)
        throw new ArgumentException();
    else if (x == y)
        return x;

    int rangeSum = 0;
    for (int i = x; i <= y; i++)
        rangeSum += i;

    return rangeSum;
}

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

Рефакторить код с высокой цикломатической сложностью (больше 25 считается высокой) можно пересмотром выбранного решения в сторону более красивого, повторным использованием дублирующейся логики, применением шаблона проектирования (например, Rules) либо подхода Table-Driven Methods.

В Visual Studio 2017 метрика отображается в окне Code Metric Results.

Глубина наследования (Depth of Inheritance)

Показывает порядковый номер класса в иерархии наследования.

public class A { } // Глубина наследования - 1

public class B : A { } //Глубина наследования - 2

public class C : B { } //Глубина наследования - 3

Наследование добавляет классу новую функциональность в виде свойств и методов. Следовательно, класс со значением метрики 5 будет содержать в себе функционал четырех классов выше по иерархии и анализ его поведения будет затруднительным. C другой стороны, классы, находящиеся «на дне» иерархии, повторно используют функционал, описанный в родителях, что есть хорошо. По этой причине нельзя точно сказать хорошо или плохо наличие в проекте классов с большим значением данной метрики. Необходимо анализировать каждый конкретный случай.

В Visual Studio 2017 метрика отображается в окне Code Metric Results.

Количество строк кода (Lines of Code)

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

В Visual Studio 2017 метрика отображается в окне Code Metric Results.

Maintainability Index

Maintainability Index является комплексной метрикой, которая рассчитывается исходя из цикломатической сложности, количества строк метода и вычислительной сложности (Halstead Volume, чем выше общее количество операторов и операндов в коде, тем выше значение метрики).

Метрика показывает значения от 0 (наихудшее) до 100 (наилучшее).

В Visual Studio 2017 метрика отображается в окне Code Metric Results.

Заключение

Метрики умеют показывать на проблемные участки кода, но если все метрики находятся в пределах нормы, то не обязательно код является качественным. Метод GeneratePDF, с зашкаливающей цикломатической сложностью, можно разбить на три, назвав их GeneratePDF1, GeneratePDF2 и GeneratePDF3. Метрика на уровне методов придет в норму, однако решение сильно рискует быть высмеянным на код-ревью. Соблюдение метрик никак не должно являться самоцелью. Цель метрик — помочь программисту увидеть проблему, которую нужно решать переосмыслением существующего кода, поиском лучшего алгоритма и выбором более элегантного решения проблемы.

Почитать дальше

Code Complete, Steve McConnell

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s