Cyclomatic Complexity (СС) - это показатель сложности кода, который рассчитывается на основании графа, построенного по точкам принятия решений, опредеяющим разные пути исполнения кода.
Формула для рассчёта СС:
CC = E - N + 2*P, где:
E — количество рёбер,
N — количество узлов,
P — количество предикативных узлов (то есть узлов, содержащих условие).
Точками принятия решений могут быть операторы if, when (в Kotlin), switch (в Java) и циклы. Чем их больше в одной функции и, чем больше их вложенноть, тем с больше и запутаннее у нас получится граф возможных путей исполнения кода этой функции.
Рекумендуется рассчитывать цикломатическую сложность кода автоматически: с помощью линтеров. Можно попробовать и вручную. Я честно пытался это сделать по методикам, приведённым в википедии: https://ru.wikipedia.org/wiki/Цикломатическая_сложность, но ни по одной из методик результаты моих ручных рассчётов не совпали с результатами линтера.
С помощью показателя цикломатической сложности кода определяют такие характеристики кода, как:
- сложность для сопровождения кода (в основном, что касается изменений),
- определение количества тестов, необходимых для покрытие кода тестами.
Связанным с цикломатической сложностью является понятие когнитивной сложности кода - сложность для понимания человеком. Основное отличие между ними в том, что при подсчёте когнитивной сложности кода:
- игнорируются структуры языка программирования, "искусственно" сокращающие объём кода (синтаксический "сахар")
- показатель когнитивной сложности увеличивается на 1 с каждым оператором, прерывающим поток выполнения (циклы, условия, тернарные операторы)
- показатель увеличивается на 1 с каждой вложенной конструкцией
Как видно на скриншотах, когнитивная сложность может быть как выше, так и ниже цикломатической.
Далее я не буду отдельно рассматривать когнитивную сложность, так как она +/- про то же самое, что и цикломатическая.
Какое значение цикломатической сложности кода можно считать допустимым?
Ответ зависит от контекста. Некоторые линтеры начинают ругаться уже когда цикломатическая сложность кода достигает значения СС 5. Среднее рекомендованное значение – СС 10. Можно допустить и большее, если сделать код проще не получится по причине сложности алгоритма.
У нас линтер на проекте настроен на выбрасываение ошибки при СС от 35. Пока массовых проблем с этим не было.
В качестве эксперимента, я проверял значения СС кода коллег, который казался мне неоправданно сложным. Выяснил для себя, что при СС от 10 мне уже приходится прилагать дополнительные усилия для понимания кода. Одна из самых сложных функций в коде, которую мне пришлось месяца два назад разбирать “по кирпичикам” и потом снова собирать в одно целое, чтобы понять, в каком месте могла быть ошибка, имела СС 21. Но при этом она не набрала и 15 баллов когнитивной сложности. Хотя казалось бы, что должно быть наоборот: когнитивная сложность должна была набрать больше баллов, чем цикломатическая.
Как и когда уменьшать цикломатическую сложность кода?
Уменьшить цикломатическую сложность функции можно, разбив её на несколько функций поменьше, в каждой из которых будет небольшая цикломатическая сложность. Конечно, делать это стоит, руководствуясь соображениями рациональности:
- действительно ли код станет проще для понимания или мы просто напишем несколько дополнительных функций,
- действительно ли нужно конкретно в этом месте делать код проще.