Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Упрощение кода приложений Python с помощью рефакторинга. Часть 1

В этой серии статей рассказано о способах измерения сложности кода и о том как избавиться от излишней сложности с помощью рефакторинга.

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

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

В этой статье вы узнаете:

  • Как измерить сложность кода Python и ваших приложений
  • Как изменить свой код, не нарушая его
  • Каковы общие проблемы в коде на Python, которые вызывают дополнительную сложность и как их можно исправить

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

Сложность кода в Python

Сложность приложения и его кодовой базы зависит от задачи, которую оно выполняет. Если вы пишете код для лаборатории реактивного движения НАСА (буквально rocket science), то обычно это будет сложный код.

Вопрос не столько в том, «сложен ли мой код?», Сколько в том, что «насколько мой код сложнее, чем должен быть?»

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

https://t.me/data_analysis_ml – анализ данных на Python

Через центр Токио проходят скоростные транспортные сети Toei и Tokyo Metro, а также поезда Japan Rail East. Даже для самого опытного путешественника навигация по центру Токио может быть ошеломительно сложной.

Вот карта железнодорожной сети метрополитена Токио:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Если ваш код начинает выглядеть немного похожим на эту карту, то самое время почитать данное руководство.

Мы рассмотрим 4 показателя сложности, которые могут дать вам шкалу измерения относительного прогресса в миссии по упрощению кода:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Изучив метрики, вы узнаете об инструменте под названием wily для автоматизации расчета этих метрик.

Метрики для измерения сложности

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

Количество строк кода

LOC ( Lines of Code), или Количество строк, является самой грубой мерой сложности. Это спорный вопрос , есть ли прямая связь между количеством строк кода и сложностью приложения, но косвенная корреляция очевидна. В конце концов, программа с 5 строками, вероятно, проще, чем программа с 5 миллионами.

При просмотре метрик Python мы стараемся игнорировать пустые строки и строки, содержащие комментарии.

Количество строк кода можно подсчитать с помощью команды wc в Linux и Mac OS, где file.py — это имя файла, который вы хотите измерить:

$ wc -l file.py

Если вы хотите подсчитать строки во всех файлах в папке путем рекурсивного поиска в файлах *.py, вы можете объединить wc с командой find:

anaferon.ru

РЕКЛАМА

$ find . -name \*.py | xargs wc -l

Для Windows PowerShell предлагается команда подсчета слов в Measure-Object и рекурсивный поиск файлов в Get-ChildItem:

$ Get-ChildItem -Path *.py -Recurse | Measure-Object –Line

В ответе вы увидите общее количество строк.

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

В Python рекомендуется размещать по одной инструкции в каждой строке. Этот пример состоит из 9 строк кода:

x = 5
value = input("Enter a number: ")
y = int(value)
if x < y:
    print(f"{x} is less than {y}")
elif x == y:
    print(f"{x} is equal to {y}")
else:
    print(f"{x} is more than {y}")

Но если вы использовали только строки кода в качестве меры сложности, это будет совершенно неверный показатель.

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

x = 5; y = int(input("Enter a number:"))
equality = "is equal to" if x == y else "is less than" if x < y else "is more than"
print(f"{x} {equality} {y}")

Но этот результат трудно понять, и у PEP 8 есть рекомендации по максимальной длине строки и разрыву строки. Вы можете почитать, о том как писать красивый код Python с помощью PEP 8 (How to Write Beautiful Python Code With PEP 8), чтобы узнать больше о PEP 8.

Этот блок кода использует 2 функции языка Python, чтобы сделать код короче:

  • Составные операции: использование ;
  • Условные последовательности или тернарные операторы: name = value if condition else value if condition2 else value2

Мы сократили количество строк кода, но нарушили один из фундаментальных законов Python:

“Читаемость имеет значение”

— Тим Питерс, Дзен питона

Этот сокращенный код потенциально сложнее поддерживать, потому что сопровождающие кода — это люди, и этот короткий код труднее читать.

Далее мы рассмотрим более интересные и полезные метрики для сложности.

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

Цикломатическая сложность — это мера количества независимых путей кода в вашем приложении. Путь — это последовательность операторов, которой интерпретатор может следовать, чтобы добраться до конца приложения.

Один из способов думать о цикломатической сложности и путях кода — это представить, что ваш код в виде железнодорожной сети.

Для поездки вам может понадобиться смена поезда, чтобы добраться до пункта назначения. Железнодорожная система метрополитена Лиссабона в Португалии проста и удобна для навигации. Цикломатическая сложность любой поездки равна количеству линий, по которым вам нужно пройти:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Если вам нужно было добраться от Alvalade до Anjos, то вам нужно будет проехать 5 остановок на linha verde (зеленая линия):

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

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

Если вам нужно было поехать из Аэропорта (Aeroporto), чтобы попробовать еду в районе Белен (Belém), то это более сложное путешествие. Вам придется пересесть на поезд в Alamedaand Cais do Sodré:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Эта поездка имеет цикломатическую сложность 3, потому что вы берете 3 поезда. Так что возможно лучше взять такси!

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

Давайте рассмотрим этот пример:

x = 1

Существует только 1 способ выполнения этого кода, поэтому его цикломатическая сложность равна 1.

Если мы добавим решение или ответвление к коду в виде оператора if, это увеличит сложность:

x = 1
if x < 2:
    x += 1

Хотя существует только 1 способ выполнения этого кода, поскольку x является константой, этот код все равно имеет цикломатическую сложность 2. Все анализаторы цикломатической сложности будут обрабатывать if как ветвь.

Это также пример слишком сложного кода. Оператор if в данном случае бесполезен, так как x имеет фиксированное значение. Вы можете просто изменить этот пример следующим образом:

x = 2

Это был игрушечный пример, так что давайте рассмотрим что-то более реальное.

main() ниже имеет цикломатическую сложность 5. Я пометил (#n) каждую ветку в коде, чтобы вы могли видеть, где они находятся:

# cyclomatic_example.py

# cyclomatic_example.py
import sys
def main():
    if len(sys.argv) > 1:  # 1
        filepath = sys.argv[1]
    else:
        print("Provide a file path")
        exit(1)
    if filepath:  # 2
        with open(filepath) as fp:  # 3
            for line in fp.readlines():  # 4
                if line != "\n":  # 5
                    print(line, end="")
if __name__ == "__main__":  # Ignored.
    main()

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

Примечание. Показатель цикломатической сложности был разработан Томасом Дж. МакКейбом, в 1976 году. Вы так же можете встретить упоминание этой метрики как метрика МакКейба (McCabe) или число МакКейба.

В следующих примерах мы будем использовать библиотеку radonиз PyPi для вычисления этой метрики. Вы можете установить его командой:

$ pip install radon

Чтобы вычислить цикломатическую сложность, используя radon, вы можете сохранить наш пример в файл cyclomatic_example.py и использовать radon из командной строки.

Команда radon принимает 2 основных аргумента:

  1. Тип анализа (cc для цикломатической сложности)
  2. Путь к файлу или папке для анализа

Выполните команду radon с аргументом cc для файла cyclomatic_example.py. Добавление аргумента -s отобразит цикломатическую сложность на экране:

$ radon cc cyclomatic_example.py -s

cyclomatic_example.py

F 4:0 main – B (6)

Вывод немного загадочный. Вот что означает каждая часть:

  • F означает функцию, M означает метод, а C означает класс.
  • main — это имя функции.
  • 4 — строка, с которой начинается функция.
  • B — оценка от A до F. A — лучшая оценка, то есть наименьшая сложность.
  • Число в скобках, 6, является цикломатической сложностью кода.

Метрики Холстеда

Метрики сложности Холстеда относятся к размеру кодовой базы программы. Они были разработаны Морисом Х. Холстедом в 1977 году. В уравнениях Холстеда есть 4 меры:

  • Операнды — это значения и имена переменных.
  • Операторы — это все встроенные ключевые слова, например if, else, for или while.
  • Длина (N) — это число операторов плюс количество операндов в вашей программе.
  • Словарь (h) — это число уникальных операторов плюс количество уникальных операндов в вашей программе.

Затем есть 3 дополнительных показателя с этими показателями:

  • Объем (Volume V) представляет собой произведение длины и словарного запаса.
  • Сложность ( Difficulty D) представляет собой произведение половины уникальных операндов и повторного использования операндов.
  • Усилие (Effort E) — это общая метрика, которая является продуктом объема и сложности.

Все это очень абстрактно, поэтому давайте сформулируем это в относительном выражении:

  • Усилия вашего приложения будут самыми высокими, если вы используете много операторов и уникальных операндов.
  • Усилие вашего приложения будет меньше, если вы используете меньше операторов и меньше переменных.

В примере cyclomatic_complexity.py операторы и операнды находятся в первой строке:

import sys # import (operator), sys (operand)

import — это оператор, а sys — это имя модуля, так что это операнд.

В немного более сложном примере могут быть несколько операторов и операндов:

if len(sys.argv) > 1:

В этом примере 5 операторов:

  1. if
  2. (
  3. )
  4. >
  5. :

Кроме того, есть 2 операнда:

  1. sys.argv
  2. 1

Помните, что radon учитывает только подмножество операторов. Например, круглые скобки исключаются при любых вычислениях.

Чтобы рассчитать меры Холстеда в radon, вы можете запустить следующую команду:

$ radon hal cyclomatic_example.py
cyclomatic_example.py:
    h1: 3
    h2: 6
    N1: 3
    N2: 6
    vocabulary: 9
    length: 9
    calculated_length: 20.264662506490406
    volume: 28.529325012980813
    difficulty: 1.5
    effort: 42.793987519471216
    time: 2.377443751081734
    bugs: 0.009509775004326938

Почему radon дает метрику для времени (time) и ошибок (bugs)?

Холстед предположил, что вы можете оценить время (time), потраченное в секундах на кодирование, поделив усилие (effort E) на 18.

Холстед также заявил, что ожидаемое количество ошибок можно оценить, поделив объем (V) на 3000. Имейте в виду, что это было написано в 1977 году, еще до того, как Python был изобретен! Так что не паникуйте и просто начните искать ошибки.

Индекс поддерживаемости

Индекс поддерживаемости приводит показатели цикломатической сложности McCabe и объем Холстеда в масштабе примерно от нуля до ста.

Если вам интересно, оригинальное уравнение выглядит следующим образом:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

В уравнении V — метрика объема Холстеда, C — цикломатическая сложность, а L — число строк кода.

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

Это уравнение используется во многих инструментах и языках, поэтому это одна из самых стандартных метрик. Однако существует множество редакций этого уравнения, поэтому точное число не следует воспринимать как факт. radon, wily и Visual Studio ограничивают число от 0 до 100.

В шкале индекса поддерживаемости (Maintainability Index) все, на что нужно обращать внимание, это когда ваш код становится значительно ниже (ближе к 0). Шкала считает, что все, что ниже 25, трудно поддерживать, а что больше 75 — легко поддерживать. Индекс поддерживаемости (Maintainability Index) также называют MI.

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

Чтобы рассчитать индекс поддерживаемости по radon, выполните следующую команду:

$ radon mi cyclomatic_example.py -s

cyclomatic_example.py – A (87.42)

В этом результате A — это оценка, которую Радон применил к числу 87,42 по своей шкале. По этой шкале A является наиболее поддерживаемым, а F — наименьшим.

Использование wily для получения и отслеживания сложности ваших проектов

wily — это библиотека с открытым исходным кодом, предназначенная для сбора метрик сложности кода, включая те, которые мы рассматривали до сих пор, такие как метрика Холстеда (Halstead), цикломатической сложности (Cyclomatic) и количества строк (LOC). wily интегрируется с Git и может автоматизировать сбор метрик в ветках и ревизиях Git.

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

Установка wily

wily доступен в PyPi и может быть установлен с помощью pip:

$ pip install wily

После установки wily в вашей командной строке появятся некоторые команды:

  • wily build: перебирать историю Git и анализировать метрики для каждого файла
  • wily report: увидеть историческую тенденцию в метриках для данного файла или папки
  • wily graph: отобразить график набора метрик в файле HTML

Создание кеша

Прежде чем вы сможете использовать wily, вам нужно проанализировать свой проект. Это делается с помощью команды wily build.

В этом разделе руководства мы проанализируем очень популярный пакет requests, используемый для общения с HTTP API. Поскольку этот проект с открытым исходным кодом и доступен на GitHub, мы можем легко получить его и загрузить копию исходного кода:

$ git clone https://github.com/requests/requests
$ cd requests
$ ls
AUTHORS.rst        CONTRIBUTING.md    LICENSE            Makefile
Pipfile.lock       _appveyor          docs               pytest.ini
setup.cfg          tests              CODE_OF_CONDUCT.md HISTORY.md
MANIFEST.in        Pipfile            README.md          appveyor.yml
ext                requests           setup.py           tox.ini

Примечание. Пользователям Windows следует использовать командную строку PowerShell для следующих примеров вместо традиционной командной строки MS-DOS. Чтобы запустить интерфейс командной строки PowerShell, нажмите Win + R, введите powershell, затем Enter.

Здесь вы увидите несколько папок для тестов, документации и конфигурации. Нас интересует только исходный код для пакета Python для requests.

Вызовите команду wily build из клонированного исходного кода и укажите имя папки с исходным кодом в качестве первого аргумента:

$ wily build requests

Это займет несколько минут, чтобы проанализировать, в зависимости от того, сколько ресурсов процессора имеет ваш компьютер:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Сбор данных о вашем проекте

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

  • Строки кода
  • Индекс поддерживаемости
  • Цикломатическая Сложность

Это 3 метрики по умолчанию в wily. Чтобы просмотреть эти показатели для определенного файла (например, requests/api.py), выполните следующую команду:

$ wily report requests/api.py

wily распечатает табличный отчет по метрикам по умолчанию для каждого коммита Git в обратном порядке дат. Вы увидите самый последний коммит вверху и самый старый в нижней части:

RevisionAuthorDateMILines of CodeCyclomatic Complexity
f37daf2Nate Prewitt2022-01-13100 (0.0)158 (0)9 (0)
6dd410fOfek Lev2022-01-13100 (0.0)158 (0)9 (0)
5c1f72eNate Prewitt2021-12-14100 (0.0)158 (0)9 (0)
c4d7680Matthieu Moy2021-12-14100 (0.0)158 (0)9 (0)
c452e3bNate Prewitt2021-12-11100 (0.0)158 (0)9 (0)
5a1e738Nate Prewitt2021-12-10100 (0.0)158 (0)9 (0)

Это говорит нам о том, что файл requests/api.py имеет:

  • 158 строки кода
  • Идеальный показатель поддерживаемости 100
  • Цикломатическая сложность 9

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

$ wily list-metrics

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

Чтобы запросить альтернативные метрики в команде отчета, добавьте их имена после имени файла. Вы можете добавить столько метрик, сколько пожелаете. Вот пример с рангом поддерживаемости и исходными строками кода:

$ wily report requests/api.py maintainability.rank raw.sloc

Вы увидите, что в таблице теперь есть 2 столбца с альтернативными метриками.

Графические Метрики

Теперь, когда вы знаете имена метрик и как их запрашивать в командной строке, вы также можете визуализировать их в виде графиков. wily поддерживает HTML и интерактивные диаграммы с интерфейсом, аналогичным команде report:

$ wily graph requests/sessions.py maintainability.mi

Ваш браузер по умолчанию откроется с интерактивной диаграммой, подобной этой:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Вы можете навести указатель мыши на определенные точки данных, и они покажут сообщение Git commit, а также данные.

Если вы хотите сохранить файл HTML в папке или хранилище, вы можете добавить флаг -o с путем к файлу:

$ wily graph requests/sessions.py maintainability.mi -o my_report.html

Теперь будет файл my_report.html, которым вы сможете поделиться с другими. Эта команда идеально подходит для командных панелей.

wily и pre-commit

Wily может быть настроен так, что перед тем, как вы внесете изменения в свой проект, он может предупредить вас об улучшении или ухудшении сложности.

У wily есть команда wily diff, которая сравнивает последние проиндексированные данные с текущей рабочей копией файла.

Для запуска команды wily diff укажите имена файлов, которые вы изменили. Например, если я внес некоторые изменения в requests/api.py, вы увидите влияние на показатели, запустив wily diff с путем к файлу:

$ wily diff requests/api.py

В ответе вы увидите все измененные метрики, а также функции или классы, которые были изменены для цикломатической сложности:

Что такое цикломатическая сложность ? Какие бывают метрики для измерения сложности кода на примере Python?

Команда diff может быть связана с инструментом pre-commit. pre-commit вставляет хук в вашу конфигурацию Git, который вызывает скрипт каждый раз, когда вы запускаете команду git commit.

Для установки pre-commit вы можете установить из PyPI:

$ pip install pre-commit

Добавьте следующее в .pre-commit-config.yaml в корневом каталоге ваших проектов:

repos:
-   repo: local
    hooks:
    -   id: wily
        name: wily
        entry: wily diff
        verbose: true
        language: python
        additional_dependencies: [wily]

После установки этого вы запускаете команду pre-commit install для завершения:

$ pre-commit install

Теперь всякий раз, когда вы запускаете команду git commit, она вызывает wily diff вместе со списком файлов, которые вы добавили в свои поэтапные изменения.

Wily — полезная утилита, позволяющая оценить сложность вашего кода и измерить улучшения, которые вы делаете, когда начинаете рефакторинг.

https://t.me/ai_machinelearning_big_data

источник

+1
0
+1
0
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *