Что такое хорошая программа и чем она отличается от плохой? Сегодня такой вопрос обычно трактуется в плане оценки качества приложения конечным пользователем. Но на самом деле он актуален и для самих разработчиков ПО, правда, при этом они в первую очередь имеют в виду не потребительские свойства программы, а качество ее кода. Собственно, именно в такой постановке вопрос о “хороших программах” был поставлен еще в конце 60-х годов, когда появились первые исследования в этой области, сразу получившие признание как классические (упомянем, в частности, работы Э. Дейкстра и Э. Йордана).

Но, наверное, самое удивительное в этой теме то, что несмотря на, казалось бы, давно расставленные точки над “i”, она до сих пор вызывает горячие дискуссии в программистской среде и новые публикации (см. например, www.visual.2000.ru/develop/talks/talks3.htm). Конечно, объективной причиной тому является творческий характер подобной деятельности, хотя, наверное, данное обстоятельство в то же время отражает и определенные недостатки в курсах методической подготовки программистов.

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

Однако даже определив состав ключевых показателей (метрик) качества кода, практически невозможно создать универсальные критерии, позволяющие считать программу “плохой” или “хорошей”. В любом случае такая оценка носит слишком субъективный характер, и даже для одного программиста она будет варьироваться в зависимости от типа проекта. Поэтому существующие инструменты определения метрик кода (code metrics, CM) в основном ограничиваются вычислением соответствующих значений, интерпретация которых полностью возлагается на человека.

Раньше для выполнения подобных задач в среде Microsoft Visual Studio нужно было использовать дополнительные средства третьих фирм. Но теперь CM-функции появились в бета-версии Visual Studio 2008 Team Edition for the Software Developer, которая должна выйти на рынок в окончательном виде в начале следующего года. Вот за какими метриками кода можно следить уже сейчас:

  • индекс эксплуатационной надежности (Maintainability Index, MI) — комплексный показатель качества кода (от 0 до 100 — чем выше, тем лучше); методика его определения разработана специалистами Carnegie Mellon Software Engineering Institute;
  • циклическая сложность (Cyclomatic Complexity, CC) — показатель, характеризующий число ветвей в программном коде и вычисляемый путем подсчета операторов цикла, условного перехода и переключений (подробнее см. en.wikipedia.org/wiki/Cyclomatic_complexity);
  • глубина наследования (Depth of Inheritance) — характеризует длину цепочек наследования в программном коде;
  • сцепление классов (Class Coupling) — отображает степень зависимости классов между собой (в том числе наличие общих данных, объектов и пр.);

    число строк кода — тут все очевидно: чем больше строк, тем сложнее программа.

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

Получить общее представление о том, как работает механизм метрик кода в VS2008 (что от него можно ожидать, а чего нельзя), поможет такой простой пример. Напишем на языке C# базовый код для класса LineItem1, который задает дату старта:


namespace TimeKeeper
{
public class LineItem1
{
private DateTime _start;
public DateTime StartTime
{
get
{
return _start;
}
set
{
_start = value;
}
}
}
}

Значения метрик кода для него приведены на рис. 1, они показывают, что сопровождение этого кода оценивается как очень простое: MI = 98. Это вполне понятно -- код содержит всего одну ветвь, минимальную глубину наследования (LineItem напрямую связан с System.Object), небольшое сцепление классов (с классом DateTime class) и всего четыре строчки кода.

Теперь на базе LineItem1 создадим новый класс LineItem2, добавив в него немного бизнес-логики:


namespace TimeKeeper
{
public class LineItem2
{
private DateTime _start;
public DateTime StartTime
{
get
{
return _start;
}
set
{
TimeSpan past = new TimeSpan(-7, 0, 0, 0);
TimeSpan future = new TimeSpan(1, 0, 0, 0);
if (value < DateTime.Now.Subtract(past))
throw new ApplicationException
("Дата старта уже прошла");
else
{
if (value > DateTime.Now.Add(future))
throw new ApplicationException
("Дата старта будет еще очень нескоро");
else
_start = value;
}
}
}
}
}

На рис. 2 видно, как изменились метрики кода — они показывают повышение сложности программы. Это отразилось и на трудоемкости тестирования: если в первом варианте нужно было проверить два случая (один для get и один для set), то сейчас — уже четыре (get, past, future и valid).

В целом это хорошо известно: с помощью современных методов объектно-ориентированного программирования повышается скорость разработки и эффективность кода, но при этом возрастают проблемы при тестировании и отладке. Анализ метрик кода позволяет следить за повышением сложности программы и при достижении каких-то критических значений заняться ее упрощением, хотя это и потребует от программиста дополнительных затрат времени. Для некоторых разработчиков это будет хорошим стимулом вспомнить об имеющихся рекомендациях по написанию “хороших” программ.

Конечно, установить контрольные параметры для программных проектов — дело непростое, тем более что данные величины зависят от типа разработки. Например, при создании пользовательского интерфейса можно в качестве допустимого минимума MI взять значение 30, а для драйверов — 10. Но в любом случае даже сама возможность слежения за изменением сложности программного кода будет очень полезной, в том числе в плане контроля за работой группы программистов в рамках одного проекта.