К основному контенту

Метрика Cognitive complexity или простой способ измерить сложность кода

Это репост моей статьи на Habr: https://habr.com/ru/post/565652/ 

 

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

Цель этой статьи: рассказать не об инструменте, а о метрике, о которой пока написано и рассказано совсем немного. Уверен, что это поможет улучшить качество многих проектов и немного облегчит жизнь программистов.


Самодокументированный код

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

«Читай код, там всё написано»

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

По определению, самодокументированный код — это код, спроектированный (designed) и написанный (implemented) таким образом, что он не требует дополнительной отдельной документации. Из определения следует, что нужно применить специальные навыки проектирования (design) и потратить дополнительные усилия, чтобы этого достичь. На практике же оказывается, что самодокументированный код – это сложная задача проектирования.

Почему это так? Давайте посмотрим на то, как мы вникаем в чужой код. Сильно упростив, этот процесс можно разбить на две важные части:

  1. Сначала нам нужно узнать, что код должен вообще делать, какова конечная задача и цель? Мы пытаем собрать некоторые знания о проекте, или части проекта, и о решаемой задачи. Как правило, для этого мы "ходим" по тикетам, опрашиваем коллег, и выясняем ЧТО нужно сделать (а особенно опытные коллеги ещё и выясняют ПОЧЕМУ).

  2. Зная это, мы начинаем читать код и выяснять КАК именно он выполняет поставленную задачу.

Иначе говоря, читая код, мы узнаем, КАК задача решена, а не ЧТО за задача была поставлена (или ПОЧЕМУ). Узнать какую задачу код решает тоже можно, но чтобы это перестало быть предположением, вам нужно будет потратить очень много усилий (например, запустить, протестировать, и т.д.).

Некоторые утверждают, что языки вроде SQL и HTML отвечают на оба вопроса сразу. Это может быть и так, но я позволю себе это утверждение проигнорировать, так как в статье речь больше идёт о компилируемых general-purpose языках.

Программисту, читающему код, нужно "распутать ниточку", понять какую именно задачу решает код. Это принято называть "ментальной моделью решаемой проблемы". Даже если код написан откровенно плохо, быстро, без применения каких-либо специальных навыков проектирования, в нём всё равно есть какая-то задумка автора, ментальная модель. Это может быть доменная модель (если вы применяете Domain Driven Design), или же людо другой способ отражения мыслительного процесса программиста.

На хабре ранее было несколько статей о том, что такое самодокументированный код и даны конкретные рекомендации по его написанию (например, вот, вот и вот). Правильные комментарии (описывающие «ЧТО» или «ПОЧЕМУ», а не очевидное «КАК»), правильно использованные конструкции языка, чистый код – всё это также важные характеристики самодокументированного кода. Но если суммировать, то единственный способ писать самодокументированный код – это писать такой код, который в большей мере раскрывает детали модели проблемы и при этом скрывает несущественные детали имплементации.

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

Ментальная модель отвечает на вопрос «ЧТО?» (то есть, какая задача, цель, почему код писали), а сам код отвечает на вопрос «КАК?».

На самом деле, ответы на вопросы «КАК?» и «ЧТО?» ортогональны, ведь одну и ту же цель можно достигнуть несколькими путями, а значит на один вопрос «ЧТО?» есть несколько ответов «КАК?». Именно тут и проявляется связь между самодокументированным кодом и навыками проектирования автора, позволяющими сделать код читаемым.

 


Измеряем читаемость кода

Фредерик Брукс, автор всемирно известной книги «Мифический человеко-месяц», в своём фундаментальном труде No Silver Bullet – Essence and Accident in Software Engineering) выделил два типа сложности в программном обеспечении:

  • Essential complexity – необходимая сложность, которая определяется самой проблемой и никак не может быть удалена.

  • Accidental complexity – непреднамеренная сложность, которая добавляется программистами во время проектирования и написания кода, и которая самими программистами и может быть устранена или хотя бы достаточно снижена.

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

В 1976 году была изобретена метрика цикломатической сложности кода (Cyclomatic Complexity). К сожалению, эта метрика тесно связана со строками кода, и поэтому хорошо помогает нам в подсчёте покрытия кода тестами, но никак не помогает узнать о сложности кода. Проиллюстрирую проблему на примере:Пример подсчёта цикломатической сложности кодаПример подсчёта цикломатической сложности кода

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

  • Действительно ли название метода совпадает с поставленной задачей?

  • Действительно ли он решает задачу?

  • Какие ограничения у этого кода?

  • И т.д.

В 2017 году компания Sonar Source изобрела новую метрику под названием Cognitive Complexity. Как видно из примера ниже, она отлично решает поставленную задачу, явно указывая на сильно большую сложность кода слева.Пример подсчёта когнитивной сложности кодаПример подсчёта когнитивной сложности кода

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

  1. Игнорировать структуры языков программирования, позволяющих сокращать написание кода и делать его более читаемым (это касается обновлённого синтаксиса любого из языков).

  2. Увеличивать метрику на единицу для каждого оператора, прерывающего поток исполнения кода

    a. Циклические конструкции for, while, do while, ...

    b. Условные конструкции: if, #if, #ifdef, тернарные операторы

  3. Увеличивать метрику на единицу в случае вложенных (nested) конструкций

Как известно, компания Sonar Source производит статические анализаторы кода, вроде SonarQube и SonarCloud, а также расширения для IDE, позволяющие более быстро получить метрики о коде. И важно то, что все эти инструменты доступны и в бесплатных версиях.

В Sonar Cloud найти эту метрику можно так: Project -> Issues -> Rules -> Cognitive ComplexityКак найти метрику Cognitive Complexity 

Как найти метрику Cognitive Complexity

А вот как выглядит детальный отчет на командном портале SonarCloud:Как выглядит детальное построчное вычисление метрики 

Как выглядит детальное построчное вычисление метрики

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

К сожалению, в расширении Sonar Lint для Visual Studio 2019 эту метрику пока не успели добавить. Или просто я не умею искать :).

По-умолчанию, показателями хорошего кода являются следующие отсечки (default threshold):

  • Cognitive Complexity

    • 15 (все остальные языки)

    • 25 (семейство C-языков)

  • Cyclomatic Complexity = 10 (все языки)

Я добавил Cyclomatic Complexity сюда для сравнения, так как эти метрики зачастую очень сильно отличаются. Давайте взглянем на простой пример (нужно просто зайти в Sonar Cloud -> Measures -> выбрать группу Complexity):Сравнение показателей метрик Cyclomatic и Cognitive complexityСравнение показателей метрик Cyclomatic и Cognitive complexity

Слева видна суммарная сложность в одной из папок с файлами кода. Тут мы уже видим разницу в 2 раза: 134 против 64. Если посмотреть на конкретные файлы, то на примере простого LoggerHelper’а видно, что мы сильно превысили когнитивную сложность кода, хотя с точки зрения цикломатической сложности - всё не так плохо. И наоборот, там, где когнитивная нагрузка равна нулю, цикломатическая сложность выше.

Выводы

Итак, мы вычислили сложность кода. Что дальше?

Теперь мы всегда сможем сказать насколько код читаемый, а самое главное - насколько он НЕчитаемый. Используя эту информацию мы можем:

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

  • Планировать рефакторинг: видеть самые плохие участки кода, с которых стоило бы начать рефакторинг в первую очередь

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

  • Перестать спорить с коллегами и начать избегать излишней сложности кода на ранних этапах разработки

  • Упростить жизнь нашим коллегам, читающим и поддерживающим наш код

  • В конце концов, увеличить качество продукта

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

Надеюсь, я предоставил достаточно информации, чтобы заинтересовать аудиторию и начать использовать метрику Cognitive Complexity в ежедневной практике.

Всем спасибо!

 

Комментарии

Популярные сообщения из этого блога

Делаем себе бесплатный VPN на Amazon EC2

Читать этот пост в Telegraph. Другие посты в канале в Telegram. Кто только не расписывал уже пошаговые инструкции по этой теме. Однако, время идёт, ПО меняется, инструкции нуждаются в обновлении, а люди в современной России всё больше нуждаются в применении VPN. Я собираюсь описать все шаги для создания бесплатного сервера на Amazon EC2 с операционной системой Linux и необходимые команды для настройки VPN сервера на нём. Чтобы не повторяться о деталях, которые были много раз описаны на русскоязычных и англоязычных ресурсах, по ходу статьи я просто приведу целую кипу ссылок, где можно почерпнуть необходимую информацию, а где информация устарела - опишу подробнее что нужно сдеать. В итоге, сервер будет доступен для вас из любой точки планеты, с любой операционной системы, и бесплатно (с определёнными ограничениями по трафику). Шаг первый - Регистрируемся на Amazon AWS Нужно зайти на сайт https://aws.amazon.com/ru и сразу перейти к Регистрации, нажав одноимённую кнопку. При р

В помощь программисту: инструкции по работе с Ubuntu сервером

Программистам чаще приходится писать код и заботиться о его чистоте, правильных абстракциях в коде, корректных зависимостях и прочих сложностях профессии. При этом, настройка и обслуживание серверов, хоть и связанная область - это отдельный навык, необходимый не каждому, и помнить о котором в деталях сложно. Поэтому, я делаю ряд микро-инструкций, которыми буду пользоваться и сам, когда необходимо. Это не статьи, а пошаговые помощники, которые я буду дополнять и наполнять по мере надобности. Делаем бесплатный VPN на Amazon EC2 Создание ключей SSH Подключение к серверу через SSH Передача файла с Linux сервера наWindows машину Делаем VPN сервер на Ubuntu 20.04 используя OpenVPN и EasyRSA  Отображение GUI с Linux сервера на Windows машине

Выбираем все плюсы из трех парадигм Entity Framework

Между парадигмами разработки с Entity Framework (Code First, Model First, Database First) я выбрал промежуточную, потому что ни одна меня не устраивала полностью. В Code First меня радуют чистые POCO классы, но не устраивает невозможность моделирования базы. В Database First и Model First мне не нравится генерация EDMX и другого всего лишнего. Таким образом, я нашел для себя такое решение: 1. Я моделирую схему в любой удобной программе (тут любая внешняя программа моделирования, генерирующая SQL Server-совместимые скрипты генерации базы) Рис. Смоделированная схема БД. 2. Создаю базу в SQL Management Studio 3. Делаю Reverse Engineering базы в POCO классы (как в Code First) с помощью плагина Entity Framework Power Tools Рис. Установленный плагин для Reverse Engineer. Рис. Вот так делается Reverse Engineer базы данных в POCO классы. Рис. Результат генерации POCO классов на основе базы данных: папочка Models с готовым контекстом, классами объектов и маппинг-классами.