Хороший и плохой код… Иногда мы используем эти понятия в разговоре, но не каждый сможет формализовать их. Можно следовать различным принципам проектирования, изменять workflow и т.д, но сегодня я хочу поведать о численной метрике, описывающей то, на сколько хороша архитектура вашего кода.

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

Метрику, о которой пойдет речь, я увидел в книге Роберта Мартина Боба “Чистая архитектура”. Эта книга, по сути длинная подводка к его оригинальной статье, и по совместительству 22ой главе)
Дядюшка Боб вводит 2 понятия - устойчивость и абстрактность.

Устойчивость - это способность компонента сохранять свое состояние при внешних воздействиях. В контексте разработки ПО, это означает, насколько часто вам приходится изменять свой код из-за изменений в зависимостях. Чем меньше вам приходится это делать, тем более устойчивым считается ваш компонент. Чтобы проиллюстрировать, рассмотрим пример с веб-сервисом, от которого зависит ваше приложение. Если этот сервис решает изменить свой API, и из-за этого вам приходится менять свое приложение, это означает, что ваше приложение неустойчиво к этому изменению.

Абстрактность - это степень, в которой компонент использует абстракции для скрытия деталей своей реализации. Абстракции позволяют нам создавать компоненты, которые легче изменять и расширять, поскольку они отделены от конкретных реализаций. Теперь, когда мы поняли, что такое устойчивость и абстракнтость, введем формулы, по которым будем вычислять эти метрики.

Метрика I (неустойчивость): I = fan_out / (fan_in + fan_out), где fan_in — число входящих зависимостей (сколько компонентов зависит от данного), а fan_out — число исходящих зависимостей (от скольких компонентов зависит данный компонент). Метрика I изменяется от 0 до 1, где 0 означает максимальную устойчивость, а 1 — минимальную.

Метрика A (абстрактность): A = Na / Nc, где Nc — общее число классов в компоненте, а Na — число абстрактных классов или интерфейсов. Эта метрика тоже изменяется от 0 до 1, где 0 означает полностью конкретный компонент, а 1 — полностью абстрактный.

Используя эти метрики мы можем проводить анализ дизайна кода, выявлять потенциальные проблемы и области улучшения. Например, компоненты с высоким уровнем абстрактности и высокой устойчивостью могут считаться хорошо спроектированными, поскольку они легко адаптируются к изменениям и масштабируются. Однако есть и определенные зоны, в которых компоненты могут вызывать проблемы.
Чтобы визуализировать эти метрики и определить, где находятся различные компоненты вашей системы, можно построить график с осью абстрактности (A) и осью неустойчивости (I).

На этом графике мы можем ответить две особенные области:

  1. Зона боли: В этой зоне находятся компоненты очень устойчивые и конкретные. Это означает, что они трудно изменяются из-за большого количества зависимостей, и их нельзя легко расширить, потому что они не используют абстракции. Такие компоненты могут стать источником проблем при изменениях в системе.

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

Оптимально, компоненты должны находиться на так называемой "главной последовательности" — линии, проходящей от точки (0,1) до точки (1,0) на графике. Это означает, что они имеют сбалансированное соотношение абстрактности и устойчивости, что делает их хорошо спроектированными и адаптируемыми к изменениям.
Мы можем построить диаграмму рассеяния компонентов и увидеть на сколько они удалены от главной последовательности. Ниже на рисунке мы видим что некоторые модули удалены больше чем на 2 стандартных отклонения. Это не хорошо.

И вот мы подошли к основной метрике, предложенной Робертом Мартином. Она позволяет оценить, насколько удалены компоненты от главной последовательности: D = |A+I-1|. Значение 0 указывает, что компонент находится прямо на главной последовательности, а значение 1 сообщает, что компонент находится на максимальном удалении от нее.
Мы также можем построить график изменения метрики D с течением времени и принять определенные действия заранее.

Итак, подводя итог, мы сегодня познакомились с метриками устойчивости (I), абстрактности (A) и расстояния от главной последовательности (D), которые могут помочь оценить качество архитектуры кода и выявить области для улучшения.

Возможно, что среди вас найдутся те, кто решит применить эти метрики на практике и написать собственный инструмент для визуализации графиков на Python или другом языке программирования. Если у вас получится создать что-то интересное и полезное, обязательно поделитесь своими результатами в комментариях под этой статьей! Вместе мы сможем сделать наш код еще лучше и эффективнее :-)

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

Комментарии (20)


  1. qbertych
    00.00.0000 00:00
    +3

    Любопытно, но из этой зависимости наверняка есть море исключений.


    Скажем, при работе с внешним железом для драйверов часто пишут один абстрактный класс плюс кучу его конкретных реализаций. Например, абстрактный линейный привод с методами get_position(), go_to_position(), go_home() и разные его имплементации для приводов от разных производителей. Со временем появляются новые производители, меняются поставщики, и конкретных имплементаций становится довольно много.


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


    1. kostyaBro Автор
      00.00.0000 00:00

      Да согласен, но в том и дело что вам не нужно его часто менять, стандартизирован апи и тд.


    1. gybson_63
      00.00.0000 00:00

      Это устойчивая абстракция, идеал.


  1. AnHell6
    00.00.0000 00:00
    +1

    Отличная статья! Кратко и ёмко, а главное осталось поле для практических размышлений!


  1. SadOcean
    00.00.0000 00:00
    +2

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

    А главное, не просто шаблоны и рекомендации, но описание причин, почему они такие.


  1. alexeivanov
    00.00.0000 00:00
    +2

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

    Согласно этому положению, хорошо спроектированным является компонент в координатах
    (1 =неустойчивый,1 = абстрактный), но, в действительности, это "зона бесполезности".
    Кажется, здесь допущена ошибка.

    Для примера, сам Мартин пишет

    Как компонент с максимальной устойчивостью (I=0) сделать гибким настолько, чтобы он сохранял устойчивость при изменениях? Ответ заключается в соблюдении принципа открытости/закрытости (OCP). Этот принцип говорит, что можно и нужно создавать классы, достаточно гибкие, чтобы их можно было наследовать (расширять) без изменения. Какие классы соответствуют этому принципу? Абстрактные.

    , то есть объявляет удачными координаты (0=устойчивый,1 = абстрактный)


    1. kostyaBro Автор
      00.00.0000 00:00
      +1

      Действительно, вы правы, ошибся, поправил.


  1. nin-jin
    00.00.0000 00:00

    • 1 конкретный класс с 10 интерфейсами, но используемый напрямую, получается абстрактным.

    • 10 конкретных классов, используемых через 1 единственную абстракцию, получается не абстрактным.

    • 1 класс, зависящий от 10 со стабильными интерфейсами, получается неустойчивым.

    • 10 классов, зависящих от 1 с постоянно меняющимся интерфейсом, получается устойчивым.

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

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

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


    1. kostyaBro Автор
      00.00.0000 00:00
      +1

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


      1. nin-jin
        00.00.0000 00:00

        Уж не предлагаете ли вы мне писать плохой, низкокачественный код ssl?

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


        1. kostyaBro Автор
          00.00.0000 00:00

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


          1. nin-jin
            00.00.0000 00:00
            -2

            То есть эти метрики не могут помочь оценить качество архитектуры кода и выявить области для улучшения? А какие факторы могут?


            1. kostyaBro Автор
              00.00.0000 00:00

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


              1. nin-jin
                00.00.0000 00:00
                -3

                Дураки учатся на своих ошибках. Умные - на чужих. Вот и получается, что умных учат дураки.


                1. dopusteam
                  00.00.0000 00:00
                  +2

                  Не томите, давайте уже к делу, как $mol поможет писать более качественный код? </sarcasm>


                  1. nin-jin
                    00.00.0000 00:00

                    А $mol тут при чём? Про раскладывание файлов у нас есть $mam.


                    1. dopusteam
                      00.00.0000 00:00
                      +1

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

                      Я конечно не всё прочитал, но вот такой подход выглядит ужасно,


                      1. nin-jin
                        00.00.0000 00:00

                        Фикса багов не приходится ждать месяцами, а миграции между апи проходят плавно - редкий хоррор может похвастать такими ужасами.


        1. kostyaBro Автор
          00.00.0000 00:00

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


  1. dedmagic
    00.00.0000 00:00

    На эту тему есть доклад: https://www.youtube.com/watch?v=eVQsWCnSsps