procursor / javaqa-3.1.1-2-radio-complexity

3.1.1-2 «Объектно-ориентированное программирование и проектирование»

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Java CI

Цикломатическая сложность кода

Есть несколько формул для вычисления ЦС. В любом случае, для их понимания сначала необходимо усвоить общую теорию графов и иметь представление о графе потока управления программы (Control Flow Graph, далее CFG).

Поскольку в нашем курсе тестировщиков этому не учат, то просто скопируем текст русской статьи из Википедии и побробуем разобраться на очень простом примере из ДЗ:

Определение ЦС:

Цикломатическая сложность части программного кода — количество линейно независимых маршрутов через программный код. Например, если исходный код не содержит никаких точек ветвления или циклов, то сложность равна единице, поскольку есть только единственный маршрут через код. Если код имеет единственный оператор IF, содержащий простое условие, то существует два пути через код: один если условие оператора IF имеет значение TRUE и один — если FALSE.

Математически цикломатическая сложность структурированной программы определяется с помощью ориентированного графа, узлами которого являются блоки программы, соединенные рёбрами, если управление может переходить с одного блока на другой. Тогда сложность определяется как:[3]:

Формула

M = E − N + 2P

где:

M = цикломатическая сложность, E = количество рёбер в графе, N = количество узлов в графе, P = количество компонент связности.

Для примера рассмотрим метод int next() из класса Radio, который увеличивает текущий номер радиостанции с определенным условием.

условие:

Если текущая радиостанция - 9 и клиент нажал на кнопку next (следующая) на пульте, то текущей должна стать 0-ая.

public int next() {
    int num = getStationNumber();
    if (num == LAST_STATION) {
        num = FIRST_STATION;
    } else {
        ++num;
    }
    return tune(num);
}

Теперь построим блок-схему метода (CFG activity diagram):

Control Flow Graph

На диаграмме наглядно видно, что в этом методе есть только два линейно независимых маршрута. Соответственно, по определению: ЦС = 2.

Это подтверждается формулой:

N = 5, E = 5, P = 1

M = 2

JaCoCo тоже с нами согласен (Cxty == 2), значит только для этого метода (а всего таких пять) придется написать два unit-теста, чтобы обеспечить 100% покрытие по счетчику BRANCHES (ветвление).

Ограничение сложности при разработке

Автор метрики (McCabe, 1976) рекомендует:

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

Однако, следует заметить, что аксиоматически этот метод refactoring'а (декомпозиция) не уменьшает общую ЦС кода, обусловленную бизнес-логикой, а главное конкретной реализацией сложного модуля разработчиком.

Кроме того, согласно многим практическим исследованиям ЦС сама по себе не добавляет новой информации о сложности кода (предсказумое количество ошибок) по сравнению с общим количеством строк кода:

Well-designed code will be both more concise and have fewer conditionals. Therefore, in comparison to the easier-to-collect “number of lines of code,” cyclomatic complexity provides very little new information: it is actionable, but typically not useful.

Более полезная метрика:

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

Назовем эту метрику СС100 и выразим ее процентах от общего количества строк кода, который потребовался для решения этой задачи (не считая кода тестов):

СС100 = 17 (СС) / 34 (LoC) = 54%

Видно, что с точки зрения тестировщика код достаточно сложный. Для решения первой задачи, где требуется 100% покрытие по Ветвлению, пришлось написать 10 тестов на 34 строки кода.

Это целых 117 строк кода (да, более простого, без логики, но все же). То есть для написания тестов потребовалось в три раза больше кода с комментариями*, чем собственно project codebase!

*) для тех, кто будет пользоваться тестами после нас

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

В заключение заметим, что помимо декомпозиции*, можно и нужно использовать ОО свойства языка программирования, такие как: полиморфизм, наследование, перегрузка и проч. Это позволит заметно снизить ЦС и, соответственно, количество тестов!

*) декомпозиция делает исходный код несколько проще с точки зрения читабельности и поддержки, но при этом все-таки не уменьшает общую ЦС, а следовательно количество необходимых unit-тестов.

В данном случае использовались только процедурные свойства Java, поэтому для корректного решения первой задачи, в соответствие условию, нужно написать никак не меньше меньше 10 тестов, чтобы "добиться 100% покрытия".

About

3.1.1-2 «Объектно-ориентированное программирование и проектирование»


Languages

Language:Java 100.0%