Модель идентификации дефектов объектно-ориентированного программного кода
Секция: Технические науки
лауреатов
участников
лауреатов
участников
XXX Студенческая международная научно-практическая конференция «Технические и математические науки. Студенческий научный форум»
Модель идентификации дефектов объектно-ориентированного программного кода
По мере роста программных проектов кодовая база становится всё больше, и добавляемый функционал выходит за рамки решения изначально поставленных задач. На некотором этапе развития проекта может произойти неприемлемое запутывание кода. Это проявляется в том, что даже малые изменения требуют изменения множества других зависимостей. Такое количество цепных изменений повышает риск возникновения ошибок даже в тех местах, которые до этого были хорошо отлажены, и возникают противоречия. А если учесть, что такие изменения вносят множество людей, то становится понятно, что цена каких бы то ни было изменений становится слишком высокой, а код хрупким.
Типичными признаками проблемы являются следующие.
Закрепощенность: система с трудом поддается изменениям, поскольку любое минимальное изменение вызывает эффект "снежного кома", затрагивающего другие компоненты системы.
Неустойчивость: в результате осуществляемых изменений система разрушается в тех местах, которые не имеют прямого отношения к непосредственно изменяемому компоненту.
Неподвижность: достаточно трудно разделить систему на компоненты, которые могли бы повторно использоваться в других системах.
Вязкость: сделать что-то правильно намного сложнее, чем выполнить какие-либо некорректные действия.
Неоправданная сложность: проект включает инфраструктуру, применение которой не влечёт непосредственной выгоды.
Так как тематика выявления и устранения подобных дефектов является очень широкой в рамках одной работы, то целью будет выбрано фокусировка пользователя на контексте и выявление подобных дефектов в рамках этого контекста.
Обобщённая схема процесса идентификации дефектов объектно-ориентированного кода, которая показывает выполнение последовательности процессов, представлена на рисунке.
Рисунок. Обобщенная схема процесса идентификации дефектов кода
В первую очередь необходимо определиться с выбором объектов, взаимосвязи которых будут исследованы. Такими основными объектами, для выбранной платформы .NET и языка C#, в частности, являются члены классов, основные из них – это методы, поля и свойства, именно они непосредственно образуют цепочки вызовов, используются для хранения информации, и исследование этих членов является достаточным, чтобы описать типы связности и зацепления.
Так как в данной работе не ставится цель исправлять исходный код, а только находить наиболее неприемлемые связи, то для сбора информации о связях используется библиотека Mono.Cecil, позволяющая получать информацию из уже скомпилированных сборок.
Объектом исследования являются связи между членами классов, для получения достаточной информации загружаем только классы, непосредственно участвующих в работе, избегая сгенерированные компилятором вспомогательные классы. Так как на содержимое конечной сборки влияет компилятор, то для начала необходимо отсеять вспомогательные члены, сгенерированные им. В работе планируется использовать несколько форм представления взаимодействия связей, одна из форм – это расположение классов внутри пространств имён, к тому же необходимо отобразить вложенность пространств имён друг в друга, поэтому будут проанализированы названия пространств имён, и построено дерево, состоящее из вложенных друг в друга пространств имён, также содержащих классы.
Для отображения прогресса достижения цели удобней будет реализовывать не только сбор и анализ данных, но и параллельно пользовательский интерфейс. Одной из минимальных единиц отображения являются рамки членов классов, именно между этими рамками будут отображены связи. Элементы отображения классов являются основными в пользовательском интерфейсе, так как целью является именно отображение качества связей между классами. Связи между членами классов также являются первичным объектом для исследования, именно качество связей будет подвергаться последующему анализу. Минимально возможную связь между членами классов можно представить в виде отрезка, где начало отрезка – это метод или свойство, а конец отрезка – это иной член какого-либо класса, который вызывается или к которому имеет доступ элемент из начала отрезка, такое упрощение позволяет обрабатывать множество связей достаточно простыми в реализации алгоритмами фильтрации, сами же отрезки возможно объединять, что позволяет использовать их как части более сложных связей между объектами.
Для упрощения реализации отображения различных типов связностей и зацепления можно произвести разграничение этих типов на те, что зависят от динамической природы выполнения программного кода, и на те типы, где такая природа не важна. Например, чтобы найти временную связность, необходимо проводить анализ последовательности доступа к переменным, а чтобы отследить процедурную связность, такая информация не нужна.
Общий сбор информации представляет собой сбор информации о начале отрезка связи, который является методом или свойством, и о конце отрезка, к которому обращается начало, контекст не учитывается, учитывается только наличие упоминания. Для этого тело каждого метода (начала отрезка) обрабатывается на наличие инструкции, в которой операнд содержит упоминание члена какого-либо класса.
Чтобы была возможность анализировать контекст связей необходимо учитывать состояние объектов, как минимум, определять последовательность и качество действий над ними. Однако для реализации данного сбора существенным препятствием выступает динамическая природа выполнения программного кода, и получается, чтобы определить, например, последовательность доступа к объекту, необходимо выполнять программный код. Однако это является неприемлемым для статического анализа, поэтому необходимо создать обходной вариант учёта состояний объектов, меняемых инструкциями без фактического выполнения этих самых инструкций, и на этом этапе возникает ещё одна проблема – ветвление кода. Первым способом, что приходит на ум, является обход всех возможных вариантов ветвлений, даже вложенных друг друга, для этого необходимо реализовать класс, который, получая тело метода, преобразовывал бы его во множество более простых – прямых потоков инструкций, поддающихся последовательному анализу.
Проблема, которая возникает при реализации данного класса это вероятность попадания в циклические ветвления. Чтобы обойти эту проблему необходимо помечать уже пройденную последовательность инструкций, чтобы после ветвления она не повторялась, например, хэшировать последовательности выполненных инструкций и передавать копию таких хэшей отросткам, создаваемым при распутывании ветвления.
Когда проблема с ветвлениями и циклами решена, можно приступить непосредственно к реализации класса, позволяющего создавать логи изменения состояния стека и состояний объектов. Такой подход позволяет пройтись по всему телу метода, не выполняя фактически сами инструкции. На этом этапе есть все необходимые инструменты для начала анализа и выделения типов связностей и зацепления.
Согласно описанию принципа единой ответственности – чем лучше связность (сила связей внутри класса) и меньше зацепление (сила связей между классами), тем лучше этот принцип выполняется. Тогда целью становится не отдельный поиск связности внутри класса, а поиск связей, которые подпадают под определение связности, но выходят за рамки класса, если такие типы связей имеют возможность быть перемещены внутрь одного класса, тем самым образуя классическую связность внутри класса. Именно они должны быть отображены в приоритете, так как перемещение этих связей внутрь класса одного приведёт к тому, что связность станет сильнее, а зацепление слабее.
Процедурная связность с точки зрения реализации является наиболее простой, всё, что требуется это отобразить, – дерево вызовов от инициирующего метода до завершающего.
В ходе исследований и разработки выяснилось, что не существует чёткого подхода, гарантирующего 100% выявления дефектов связей объектов и даже дающего возможность утверждать, что, то или иное решение подпадает под понятие дефекта, однако комбинируя подходы, можно улучшить как объективную, так и субъективную оценки качества программного обеспечения.
Исследованный подход с применением понятия связности к связям множества объектовдаёт возможность определять те члены классов, которые в последующем можно перемещать из одного класса в другой для повышения классической связности внутри класса и уменьшения зацепления между классами, то есть для реализации выполнения принципа единой ответственности.