PHP Profi

Качество кода: 'взбиваемость' (churn) и сложность (complexity). Как отслеживать легаси. Перевод

Cложность кода (code complexity)

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

Сложность кода является показателем для нескольких вещей:

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

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

Если вас интересует цикломатическая сложность вашего PHP-проекта, то phploc даст вам хорошее представление об этом, например:

Complexity
  Cyclomatic Complexity / LLOC                    0.30
  Cyclomatic Complexity / Number of Methods       3.03

Взбиваемость (churn)

Сложность кода не всегда должна быть большой проблемой. Если класс имеет высокую сложность кода, но вы никогда не должны касаться его, то вообще нет никаких проблем. Просто оставьте его. Он работает, просто не трогайте его. Конечно, это не идеал и вы бы хотели свободно прикасаться к любому коду в вашем репозитории. Но так как вы не должны, нет никакой реальной задачи поддержки этого кода. Этот плохой класс не будет "стоить" вам многих усилий.

Что действительно опасно для проекта, когда класс с повышенной сложностью кода часто должен изменяться. Каждое изменение будет опасно. Не существует тестов, поэтому нет защиты от регрессий. Отсутствие тестов также означает, что поведение класса не описано нигде. Становится сложно понять является ли кусок странного кода "багом или фичей". Другие участки кода, использующие этот, возможно, опираются на его странное поведение. В общем, будет опасно изменять что-либо и очень трудно исправить. Это означает, что изменение этого класса может дорого вам обойтись. Уйдут часы, чтобы прочитать код, понять, найти места, в которых нужно сделать изменения, убедиться, что приложение не поломалось самым неожиданным образом и т. д.

Майкл Физерс (Michael Feathers) ввел слово "churn"(на русском: маслобойка или пахтать – взбивать масло) для замера скорости изменения файлов в проекте. У "взбиваемости" есть собственное значение, — как и у сложности кода. Учитывая предположение о том, что каждый класс определен в отдельном файле, и каждый раз, когда класс меняется, создается новый коммит в систему управления версиями, удобный способ количественной оценки "взбиваемости" для класса будет просто количество коммитов, которые затронули файл, содержащий его.

Рассуждая о контроле версий: есть гораздо более ценные сведения, которые можно извлечь из истории вашего проекта. Взгляните на книгу "Ваш код как место преступления" Адама Торнхила (Adam Tornhill) если хотите почерпнуть больше идей и предложений.

Сведения о коде

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

Так как утилиты должны быть специфичны для языка программирования и системы контроля версий, я не могу подсказать вам те, что будут работать для всех, но для PHP и git, есть churn-php. Вы можете установить его в ваш проект через Composer, или запустить его в docker-контейнере. Он покажет вам таблицу классов с высокой взбиваемостью и высокой сложность кода, например:

+-----------------------+---------------+------------+-------+
| File                  | Times Changed | Complexity | Score |
+-----------------------+---------------+------------+-------+
| src/Core/User.php     | 355           | 875        | 1     |
| src/Sales/Invoice.php | 235           | 274        | 0.234 |
+-----------------------+---------------+------------+-------+

Колонка "Times Changed" представляет собой значение, рассчитанное для "взбиваемости", а "Complexity" расчетную сложность кода для этого файла. "Score" — это то, что я придумал, это нормированное расстояние от линии, которая представляет собой "всё в порядке".

Расположение этих классов на графике будет выглядеть примерно так:

Plotting classes against complexity and churn axes

"Всё в порядке"

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

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

Гипотезы, наблюдения, советы

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

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

Далее, я предлагаю вам также прочитать на эту тему статью Санди Мец (Sandi Metz) "Breaking up the behemoth". Сэнди подозревает, что классы с такими "нежелательными выбросами" не только намного больше, чем другие классы, но также представляют основные понятия в предметной области. Первоначально это было довольно удивительно для меня. Если класс представляет собой основное понятие области, я предполагаю, что вы не позволяете ему скатываться до такого низкого качества кода. Но в этом есть смысл. По мере того как желаемое поведение становится более чистым и понятным, поскольку команда получила более глубокие знания о предметной области, связанные классы также необходимо модифицировать. Но, как вы знаете, изменения со временем требуют пересмотра более крупного дизайна, что не всегда возможно. Между тем на эти центральные классы завязано все больше и больше другого кода, что приводит к тому, что их изменение практически невозможно без поломки зависящего кода.

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

2018-01-22 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий