В этом разделе мы обрисуем проблемы, связанные с дублированием, и предложим общие стратегии по тому, как с ним справиться.

Как возникает дублирование?

Большинство наблюдаемых явлений дублирования подпадают под одну из следующих категорий:

• Навязанное дублирование. Разработчики чувствуют, что у них нет выбора – им кажется, что дублирования требует среда окружения.

• Неумышленное дублирование. Разработчики не осознают, что они тиражируют информацию.

• Нетерпеливое дублирование. Разработчики ленятся и осуществляют дублирование, потому что им кажется, что так проще.

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

Рассмотрим эти четыре категории дублирования более подробно.

Навязанное дублирование

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

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

Немного изобретательности – и дублирование вам не понадобится. Зачастую ответ сводится к написанию простого фильтра или генератора текста программы. Конструкции с использованием нескольких языков можно собрать из обычного представления метаданных, применяя простой генератор текста программ всякий раз при осуществлении сборки программы (пример этого показан на рисунке 3.4). Определения класса могут быть сгенерированы автоматически из интерактивной схемы базы данных или из метаданных, используемых для построения схемы изначально. Фрагменты программ в этой книге вставлялись препроцессором всякий раз при форматировании текста. Уловка состоит в том, чтобы сделать процесс активным: это не может быть однократным преобразованием, в противном случае мы опять окажемся в положении людей, тиражирующих данные.

Документация в тексте программы. Программистов учат комментировать создаваемый ими текст программы: удачный текст программы снабжен большим количеством комментариев. К сожалению, им никогда не объясняли, зачем тексту программы нужны комментарии: неудачному тексту требуется большое количество комментариев.

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

Документация и текст программы. Вы пишете документацию, затем создаете текст программы. Что-то меняется, и вы исправляете документацию и обновляете текст. И документация, и текст содержат представления одного и того же знания. И все мы знаем, что в суматохе, когда приближается контрольный срок, а важные заказчики высказывают требования, обновление документации стараются отложить.

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

Языковые аспекты. Многие языки навязывают значительное дублирование в исходном тексте программы. Зачастую это происходит, когда язык отделяет интерфейс модуля от его реализации. Языки С и С++ используют файлы заголовка, которые тиражируют имена и печатают информацию о переменных экспорта, функциях и классах (для С++). Язык Object Pascal даже тиражирует эту информацию в том же самом файле. Если вы используете удаленные вызовы процедур или технологию CORBA[URL 29], то при этом происходит дублирование интерфейсной информации в спецификации интерфейса и тексте программы, его реализующей.

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

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

Неумышленное дублирование

Иногда дублирование происходит в результате ошибок в проекте.

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

Но что происходит, если водитель по имени Салли заболевает и приходится менять водителя? Классы Truck и DeliveryRoute содержат описание водителя. Какой из них мы должны изменить? Ясно, что это дублирование неудачно. Нормализуйте его в соответствии с базовой бизнес-моделью – необходим грузовику водитель как часть базового набора атрибутов? А маршрут? Возможно, необходим третий объект, который связывает воедино водителя, грузовик и маршрут. Каким бы ни было окончательное решение, стоит избегать этого типа ненормализованных данных.

Есть не столь очевидный тип ненормализованных данных, который имеет место при наличии множественных взаимозависимых элементов данных. Рассмотрим класс, представляющий отрезок:

class Line {

public:

 Point start;

 Point end;

 double length:

};

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