Использование метрик как вспомогательного инструмента в процессе гибкой разработки программного обеспечения

А.В. Юрченко
ЗАО «ЕС-лизинг»,
г. Тула


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

Метрики кода

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

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

Метрики зависимостей

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

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

- Зависимости препятствуют расширяемости. Гибкое ПО должно быть доступно для расширений, но недоступно для модификаций. Идея заключается в добавлении нового функционала в систему путем расширения существующих абстрактных элементов, и внедрении этих расширений в существующую систему, не проводя грубые, всеобъемлющие модификации. Сильные зависимости обычно могут являться результатом неправильного использования абстракций, и расширение системы может представлять трудную задачу, когда абстрактные элементы отсутствуют в коде.

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

- Зависимости ограничивают тестируемость (проверяемость). Модульное тестирование является одним из фундаментальных принципов, который должен применяться всеми разработчиками. Тесные связи между классами исключает возможность тестировать классы независимо. Тесты дают возможность проводить улучшения дизайна, и выявлять ошибки с помощью модульного тестирования. Они также дают возможность профилактики и выявления нежелательных зависимостей. Сильные зависимости не позволяют проводить тестирование модулей независимо.

- Зависимости ограничивают понимание. Работая над программным проектом, важно понимать конструкции проектирования и архитектурную структуру. Структура со сложными зависимостями очень сложна для понимания.

Традиционные методы анализа и проектирования уделяют много внимания дизайну системы на низком уровне, но редко обращают внимание на высокоуровневые структуры. Чтобы разрабатывать повторно используемое, тестируемое, сопровождаемое и расширяемое ПО, необходимо минимизировать зависимости между модулями на высоком уровне. Например, для языка программирования Java проектирование только отношений между классами, игнорируя отношения между пакетами и *.jar-модулями, может нарушить общую целостность архитектуры. Метрики зависимостей основаны на отношениях между такими модулями.

Зависимости между модулями объявляются либо как входящие, либо исходящие. Центростремительные соединения (Ca) представляют собой количество входящих зависимостей в модуле. Центробежные соединения (Ce) представляют собой количество исходящих зависимостей в модуле. Знание количества центростремительных и центробежных соединений позволяет более эффективно оценивать усилия и стоимость изменений, и вероятность повторного использования. Например, сопровождение модуля со множеством входящих зависимостей является весьма затратным и рискованным, поскольку существует большой риск затронуть другие модули при модификации, а так же это требует более тщательного тестирования интеграции модулей. И наоборот, модуль со множеством исходящих зависимостей сложнее тестировать и повторно использовать, поскольку для этого требуются все зависимые модули. Дополнительные метрики, происходящие из использования центростремительных и центробежных соединений, позволяют оценивать целостность архитектуры модулей.

Абстрактность (A) представляет собой отношение количества абстракций (абстрактных классов, или интерфейсов) в модуле к общему количеству классов в модуле. Диапазон для данной метрики лежит в пределах [0…1]. Если A= 0, то это говорит о том, что все классы в модуле являются конкретными (не абстрактными), а если A= 1, то это говорит о том, что в модуле все классы являются абстрактными. Конкретные модули с большим количеством центростремительных соединений сложно менять из-за высокого числа входящих зависимостей. Модули с множеством абстракций обычно легко поддаются расширению, если зависимости этого модуля связаны только с его абстрактной частью.

Обычно ПО представляет собой композицию модулей с входящими и исходящими зависимостями. Неустойчивость (I) – это отношение центробежных соединений (Ce) к общему количеству соединений (Ce + Ca):

.

Модули, в которых I приближается к единице, являются модулями с большим количеством исходящих соединений и меньшим входящих. И наоборот, модули, в которых I приближается к нулю, имеют большее количество входящих соединений, чем исходящих. Стабильные модули (I = 0) сложно менять из-за входящих соединений. В общем случае модуль должен быть как можно более абстрактным по отношению к его входящим соединениям. Норма |A + I – 1| называется дистанцией (D) и представляет собой баланс модуля между абстрактностью иустойчивостью. Дистанция, стремящаяся к нулю, говорит о том, что модуль является абстрактным по отношению к его входящим соединениям. Дистанция, стремящаяся к единице, говорит о том, что модуль либо полностью конкретный (не абстрактный) со множеством входящих соединений, или полностью абстрактный со множеством исходящих соединений. В первом случае это говорит о нехватке целостности в архитектуре, во втором – о неиспользуемых элементах архитектуры.

Метрики покрытия

Основная польза от метрик кода и метрик зависимостей состоит в том, что с помощью них можно определять участки кода, требующие рефакторинга. Но рефакторинг сам по себе требует надежного набора тестов, чтобы быть уверенным в том, что изменения в одной части системы не затронут другие части системы. Метрики покрытия помогают оценить процент методов и классов, находящихся в тестировании. Разработчик должен стремиться к 100% покрытию тестами классов, которые часто используются и содержат много кода. Цикломатическое число сложности может использоваться как основание для того, чтобы определять количество тестов, требуемых для метода. Если у метода цикломатическое число сложности равно трем, то чтобы проверить все возможные варианты выполнения метода, необходимо выполнить минимум три теста. Дополнительные тесты могут понадобиться для тестирования исключительных (ошибочных) ситуаций и альтернативного поведения. Надежный набор тестов является основанием для выполнения рефакторинга.

Метрики и гибкость

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

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

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

Глоссарий

Гибкое управление (Agilemanagement), или гибкая разработка (Agile software development) – методология разработки ПО, направленная на минимизацию рисков, путём сведения разработки к серии коротких циклов, называемых итерациями. Каждая итерация включает все задачи, необходимые для выдачи мини-прироста по функциональности: планирование, анализ требований, проектирование, кодирование, тестирование и документирование. Хотя отдельная итерация, как правило, недостаточна для выпуска новой версии продукта, подразумевается, что гибкий программный проект готов к выпуску в конце каждой итерации. По окончании каждой итерации, команда выполняет переоценку приоритетов разработки, сбор новых требований.

Разработка через тестирование (test-driven development) – техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест и под конец проводится рефакторинг нового кода к соответствующим стандартам.

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

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

Назад к списку