Страница:
понятия системы. Важной и нетривиальной задачей является поиск
общности среди классов и выделение общей части. Нет точного
определения общности, но следует обращать внимание на общность
для понятий системы, а не просто для удобства реализации. Указаниями,
что два класса имеют нечто общее, что возможно выделить в общий базовый
класс, служат схожие способы использования, сходство наборов операций,
сходство реализаций и просто тот факт, что часто в процессе обсуждения
проекта оба класса появляются одновременно. С другой
стороны, если есть
несколько наборов операций класса с различными способами использования,
если эти наборы обеспечивают доступ к раздельным подмножествам объектов
реализации, и, если класс возникает в процессе обсуждения несвязанных
тем, то этот класс является явным кандидатом для разбиения на части.
В силу тесной связи между понятиями и классами проблемы
перестройки иерархии классов высвечиваются на поверхности проблем
именования классов и использования имен классов в процессе обсуждения
проекта. Если имена классов и их упорядоченность, задаваемая иерархией
классов, кажутся неудобными при обсуждении проекта, значит, по всей
видимости, есть возможность улучшения иерархии. Заметим, что
подразумевается, что анализ иерархии классов лучше проводить не в
одиночку. Если вы оказались в таком положении, когда не с кем
обсудить проект, хорошим выходом будет попытаться составить учебное
описание системы, используя имена классов.
Когда пишешь статью, пытаешься найти подходящую для темы модель. Нужно
не бросаться сразу печатать текст, а поискать статьи на сходные темы,
вдруг найдется такая, которая может послужить отправной точкой.
Если ею окажется моя собственная статья, то можно будет использовать
даже куски из нее, изменяя по мере надобности другие части, и вводить
новую информацию только там, где требует логика предмета. Таким
образом, исходя из первого издания, написана эта книга. Предельный
случай такого подхода - это написание открытки-формуляра, где просто
нужно указать имя и, возможно, добавить пару строк для придания
"личного" отношения. По сути такие открытки пишутся с указанием отличия
от стандарта.
Во всех видах творческой деятельности использование существующих
систем в качестве моделей для новых проектов является скорее правилом,
а не исключением. Всегда, когда это возможно, проектирование и
программирование должны основываться на предыдущих работах. Это
сокращает степени свободы для разработчика и позволяет сосредоточить
внимание на меньшем числе вопросов в заданное время. Начать большой
проект "практически с нуля" - это может возбуждать, но правильнее будет
употребить термин "опьянение", которое приведет к "пьяному
блужданию" в множестве вариантов. Построение модели не накладывает
каких-либо ограничений и не означает покорного следования ей, это
просто освобождает разработчика от некоторых вопросов.
Заметим, что на самом деле использование моделей неизбежно,
поскольку каждый проект синтезируется из опыта его разработчиков.
Лучше, когда использование модели является явно сформулированным
решением, тогда все допущения делаются явно, определяется общий
словарь терминов, появляется начальный каркас проекта и увеличивается
вероятность того, что у разработчиков есть общий подход.
Естественно, что выбор начальной модели является важным решением,
и обычно оно принимается только после поиска потенциальных моделей
и тщательной оценки вариантов. Более того, во многих случаях модель
подходит только при условии понимания того, что потребуются
значительные изменения для воплощения ее идей в иной области
приложения. Но проектирование программного обеспечения - тяжелый
труд, и надо использовать любую помощь. Не следует отказываться
от использования моделей из-за неоправданного пренебрежения к
имитации. Имитация - не что иное, как форма искреннего восхищения,
а, с учетом права собственности и авторского права, использование
моделей и предшествующих работ в качестве источника вдохновения -
допустимый способ для всех новаторских работ во всех видах
деятельности. То, что было позволено Шекспиру, подходит и для нас.
Некоторые обозначают использование моделей в процессе проектирования
как "проектирование повторного использования".
В начале честолюбивого проекта нам неизвестен лучший способ построения
системы. Часто бывает так, что мы даже не знаем точно, что должна
делать система, поскольку конкретные факты прояснятся только в процессе
построения, тестирования и эксплуатации системы. Как задолго до
создания законченной системы получить сведения, необходимые для
понимания того, какие решения при проектировании окажутся
существенными, и к каким последствиям они приведут?
Нужно проводить эксперименты. Конечно, нужен анализ проекта и его
реализации, как только появляется пища для него. Преимущественно
обсуждение вертится вокруг альтернатив при проектировании и
реализации. За исключением редких случаев проектирование есть
социальная активность, которая ведет по пути презентации и
обсуждений. Часто самым важным средством проектирования оказывается
простая грифельная доска; без нее идеи проекта, находящиеся в
зародыше, не могут развиться и стать общим достоянием в среде
разработчиков и программистов.
Похоже, что самый популярный способ проведения эксперимента сводится
к построению прототипа, т.е. уменьшенной версии системы. Прототип не
обязан удовлетворять характеристикам реальных систем, обычно в
изобилии есть машинные ресурсы и программная поддержка, и в таких
условиях программисты и разработчики становятся непривычно опытными,
хорошо образованными и активными. Появляется цель - сделать
работающий прототип как можно скорее, чтобы начать исследование
вариантов проекта и способов реализации.
Такой подход, если применять его разумно, может привести к
успеху. Но он также может служить оправданием неудачно сделанных
систем. Дело в том, что уделяя особое внимание прототипу, можно
прийти к смещению усилий от "исследование вариантов
проекта" к "получение как можно скорее рабочей версии системы".
Тогда быстро угаснет интерес к внутренней структуре прототипа
("ведь это только прототип"), а работа по проектированию будет
вытесняться манипулированием с реализацией прототипа. Просчет
заключается в том, что такая реализация может легко привести к системе,
которая имеет вид "почти законченной", а по сути является пожирателем
ресурсов и кошмаром для тех, кто ее сопровождает. В этом
случае на прототип тратятся время и энергия, которые лучше приберечь
для реальной системы. Для разработчиков и менеджеров есть искушение
переделать прототип в конечный программный продукт, а "искусство
настройки системы" отложить до выпуска следующей версии. Если идти
таким путем, то прототипы отрицают все основы проектирования.
Сходная проблема возникает, если исследователи привязываются
к тем средствам, которые они создали при построении прототипа,
и забывают, что они могут оказаться непригодными для
рабочей системы, и что свобода от ограничений и формальностей, к
которой они привыкли, работая в небольшой группе, может оказаться
невозможной в большом коллективе, бьющимся над устранением длинной
цепи препятствий.
И в то же время создание прототипов может сыграть важную роль.
Рассмотрим, например, проектирование пользовательского интерфейса. Для
этой задачи внутренняя структура той части системы, которая прямо не
общается с пользователем, обычно не важна, и использование прототипов -
это единственный способ узнать,
какова будет реакция пользователя при работе с системой.
Другим примером служат прототипы, прямо предназначенные для изучения
внутренней структуры системы. Здесь уже интерфейс с пользователем
может быть примитивным, возможна работа с моделью пользователей.
Использование прототипов - это способ экспериментирования.
Ожидаемый результат - это более глубокое понимание целей, а не
сам прототип. Возможно, сущность прототипа заключается в том,
что он является настолько неполным, что может служить лишь средством
для эксперимента, и его нельзя превратить в конечный продукт без
больших затрат на перепроектирование и на другую реализацию. Оставляя
прототип "неполным", мы тем самым переключаем внимание на
эксперимент и уменьшаем опасность превращения прототипа в законченный
продукт. Это также почти избавляет от искушения взять за основу
проекта системы проект прототипа, при этом забывая или игнорируя те
ограничения, которые внутренне присущи прототипу. После эксперимента
прототип надо просто выбросить.
Не следует забывать о других способах проведения эксперимента,
которые могут служить во многих случаях альтернативой созданию прототипа,
и там, где они применимы, их использование предпочтительнее, поскольку
они обладают большей точностью и требуют меньших затрат
времени разработчика и ресурсов системы. Примерами могут служить
математические модели и различные формы моделирования. По сути,
существует бесконечная возрастающая последовательность,
начиная от математических моделей,
ко все более и более детальным способам моделирования, затем к
прототипам, к частичным реализациям системы, вплоть до полной системы.
Это подводит к идее построения системы, исходя из начального
проекта и реализации, и двигаясь путем повторного прохождения
этапов проектирования и реализации. Это идеальная стратегия,
но она предъявляет высокие требования к средствам проектирования
и реализации, и в ней содержится определенный риск того, что
программный объем, реализующий решения, принятые
при начальном проектировании, в процессе развития вырастет до такой
величины, что существенное улучшение проекта будет просто невозможно.
Похоже, что по крайней мере теперь такую стратегию применяют
или в проектах от малого до среднего размеров, т.е. там, где
маловероятны переделки общего проекта, или же для перепроектирования
и иной реализации после выдачи первоначальной версии системы, где
указанная стратегия становится неизбежной.
Помимо экспериментов, предназначенных для оценки решений,
принимаемых на этапе проектирования, источником получения полезной
информации может быть анализ собственно проектирования и (или)
реализации. Например, может оказаться полезным изучение различных
зависимостей между классами (см.$$ 12.2), не следует забывать и о
таких традиционных вспомогательных средствах реализации, как
граф вызовов функций, оценка производительности и т.п.
Заметим, что спецификация (результат анализа системы) и проект
могут содержать ошибки, как и реализация, и возможно, они даже
больше подвержены ошибкам, т.к. являются менее точными, не могут быть
проверены на практике и обычно не окружены такими развитыми средствами,
как те, что служат для анализа и проверки реализации. Введение
большей формализации в язык или запись, с помощью которой изложен проект,
в какой-то степени облегчает использования этих средств для
проектирования. Но, как сказано в $$12.1.1, это нельзя делать
за счет ухудшения языка, используемого для реализации. К тому
же формальная запись может сама стать источником трудностей и
проблем. Это происходит, когда выбранная степень формализации плохо
подходит для конкретных задач, когда строгость формализации превосходит
математическую основу системы и квалификацию разработчиков и
программистов, и когда формальное описание системы начинает
расходиться с реальной системой, для которой оно предназначалось.
Заключение о необходимости опыта и о том, что проектирование
неизбежно сопровождается ошибками и плохо поддержано программными
средствами, служит основным доводом в пользу итеративной модели
проектирования и реализации. Альтернатива - это линейная модель
процесса развития, начиная с анализа и кончая тестированием, но
она существенно дефектна, поскольку не допускает повторных
проходов, исходя из опыта, полученного на различных этапах развития
системы.
Программа, которая не прошла тестирование, не работает. Идеал, чтобы
после проектирования и (или) верификации программа заработала с
первого раза, недостижим для всех, за исключением самых тривиальных
программ. Следует стремиться к идеалу, но не заблуждаться, что
тестирование простое дело.
"Как проводить тестирование?" - на этот вопрос нельзя ответить
в общем случае. Однако, вопрос "Когда начинать тестирование?" имеет
такой ответ - на самом раннем этапе, где это возможно. Стратегия
тестирования должна быть разработана как часть проекта и включена
в реализацию, или, по крайней мере, разрабатываться параллельно
с ними. Как только появляется работающая система, надо начинать
тестирование. Откладывание тестирования до "проведения полной
реализации" - верный способ выйти из графика или передать версию
с ошибками.
Всюду, где это возможно, проектирование должно вестись так,
чтобы тестировать систему было достаточно просто. В частности,
имеет смысл средства тестирования прямо встраивать в систему.
Иногда это не делается из-за боязни слишком объемных проверок на
стадии выполнения, или из-за опасений, что избыточность, необходимая
для полного тестирования, излишне усложнит структуры данных.
Обычно такие опасения неоправданы, поскольку собственно программы
проверки и дополнительные конструкции, необходимые для них,
можно при необходимости удалить из системы перед ее поставкой
пользователю. Иногда могут пригодится утверждения о свойствах
программы (см. $$12.2.7).
Более важной, чем набор тестов, является подход, когда
структура системы такова, что есть реальные шансы убедить себя
и пользователей, что ошибки можно исключить с помощью определенного
набора статических проверок, статического анализа и тестирования.
Если разработана стратегия построения системы, устойчивой к ошибкам
(см.$$9.8), стратегия тестирования обычно разрабатывается как
вспомогательная.
Если вопросы тестирования полностью игнорируются на этапе
проектирования, возникнут проблемы с тестированием, временем
поставки и сопровождением системы. Лучше всего начать работать
над стратегией тестирования с интерфейсов классов и их
взаимозависимостей (как предлагается в $$12.2 и $$12.4).
Трудно определить необходимый объем тестирования. Однако,
очевидно, что проблему представляет недостаток тестирования,
а не его избыток. Сколько именно ресурсов в сравнении с проектированием
и реализацией следует отвести для тестирования зависит от
природы системы и методов ее построения. Однако, можно предложить
следующее правило: отводить больше ресурсов времени и человеческих
усилий на тестирование системы, чем на получения ее первой реализации.
"Сопровождение программного обеспечения" - неудачный термин. Слово
"сопровождение" предлагает неверную аналогию с аппаратурой. Программы
не требуют смазки, не имеют движущихся частей, которые изнашиваются
так, что требуют замены, у них нет трещин, в которые попадает
вода, вызывая ржавчину. Программы можно воспроизводить в точности
и передавать в течении минуты на длинные расстояния. Короче,
программы это совсем не то, что аппаратура. (В оригинале:
"Software is not hardware").
Деятельность, которая обозначается, как сопровождение программ,
на самом деле, состоит из перепроектирования и повторной реализации,
а значит входит в обычный цикл развития программного обеспечения.
Если в проекте учтены вопросы расширяемости, гибкости и переносимости,
то обычные задачи сопровождения решаются естественным образом.
Подобно тестированию задачи сопровождения не должны решаться
вне основного направления развития проекта и их не следует откладывать
на потом.
Д. Кнуту принадлежит утверждение "Непродуманная оптимизация - корень
всех бед". Некоторые слишком хорошо убедились в справедливости этого
и считают вредными все заботы об оптимизации. На самом деле вопросы
эффективности надо все время иметь в виду во время проектирования и
реализации. Это не означает, что разработчик должен заниматься
задачами локальной оптимизации, только задача оптимизации на самом
глобальном уровне должна его волновать.
Лучший способ добиться эффективности - это создать ясный и
простой проект. Только такой проект может остаться относительно
устойчивым на весь период развития и послужить основой для
настройки системы с целью повышения производительности. Здесь
важно избежать "гаргантюализма", который является проклятием
больших проектов. Слишком часто люди добавляют определенные
возможности системы "на всякий случай" (см. $$11.3.3.2 и $$11.4.3),
удваивая, учетверяя размер выполняемой программы ради завитушек.
Еще хуже то, что такие усложненные системы трудно поддаются
анализу, а по этому трудно отличить избыточные накладные расходы
от необходимых и провести анализ и оптимизации на общем уровне.
Оптимизация должна быть результатом анализа и оценки производительности
системы, а не произвольным манипулированием с программным кодом,
причем это особенно справедливо для больших систем, где интуиция
разработчика или программиста не может служить надежным указателем
в вопросах эффективности.
Важно избегать по сути неэффективных конструкций, а так же
таких конструкций, которые можно довести до приемлемого уровня
выполнения, только затратив массу времени и усилий. По этой же
причине важно свести к минимуму использование непереносимых по
своей сути конструкций и средств, поскольку их наличие препятствует
работе системы на других машинах (менее мощных, менее дорогих).
Если только это имеет какой-то смысл, большинство людей делает то,
что их поощряют делать. Так, в контексте программного проекта, если
менеджер поощряет определенные способы действий и наказывает за
другие, редкие программисты или разработчики рискнут своим
положением, встречая сопротивления или безразличия администрации,
чтобы делать так, как они полагают нужнымЬ.
Ь Организация, в которой считают своих программистов недоумками,
очень скоро получит программистов, которые будут рады и способны
действовать только как недоумки.
Отсюда следует, что менеджер должен поощрять такие структуры,
которые соответствуют сформулированным целям проекта и реализации.
Однако на практике слишком часто бывает иначе. Существенное
изменение стиля программирования достижимо только при соответствующем
изменении в стиле проектирования, кроме того, обычно и то и другое
требует изменения в стиле управления. Мыслительная и организационная
инерция слишком просто сводят все к локальным изменениям, хотя
только глобальные изменения могут принести успех. Прекрасной
иллюстрацией служит переход на язык с объектно-ориентированным
программированием, например на С++, когда он не влечет за собой
соответствующих изменений в методах проектирования, чтобы
воспользоваться новыми возможностями языка (см. $$12.1), и, наоборот,
когда переход на "объектно-ориентированное проектирование" не
сопровождается переход на язык реализации, который поддерживает
этот стиль.
Часто основной причиной перехода на новый язык или новый метод
проектирования называют то, что это облегчает повторное использование
программ или проекта. Однако, во многих организациях поощряют
сотрудника или группу, когда они предпочитают изобретать колесо.
Например, если производительность программиста измеряется числом
строк программы, то будет ли он писать маленькие программы,
работающие со стандартными библиотеками, за счет своего дохода
и, может быть, положения? А менеджер, если он оплачивается
пропорционально числу людей в его группе, будет ли он использовать
программы, сделанные другими коллективами, если он может просто
нанять еще пару программистов в свою группу? Компания может
получить правительственный контракт, в котором ее доход составляет
фиксированный процент от расходов на проект, будет ли она
сокращать свой доход за счет использования наиболее эффективных
средств? Трудно обеспечить вознаграждение за повторное использование,
но если администрация не найдет способов поощрения и вознаграждения,
то его просто не будет.
Повторное использование является прежде всего социальным
фактором. Повторное использование программы возможно при условии,
что
[1] она работает; нельзя использовать повторно, если это невозможно
и в первый раз;
[2] она понятна; здесь имеет значение структура программы, наличие
комментариев, документации, руководства;
[3] она может работать вместе с программами, которые не создавались
специально с таким условием;
[4] можно рассчитывать на ее сопровождение (или придется делать
это самому, что обычно не хочется);
[5] это выгодно (хотя можно и разделить расходы по разработке
и сопровождению с другими пользователями) и, наконец;
[6] ее можно найти.
К этому можно еще добавить, что компонент не является повторно
используемым, пока кто-то действительно не сделал это. Обычно задача
приспособления компонента к существующему окружению приводит к
уточнению набора операций, обобщению его поведения, и повышению его
способности адаптации к другим программам. Пока все это не проделано
хотя бы один раз, неожиданные острые углы находятся даже у
компонентов, которые тщательно проектировались и реализовывались.
Личный опыт подсказывает, что условия для повторного
использования возникают только в том случае, когда находится
конкретный человек, занятый этим вопросом. В маленьких группах
это обычно бывает тот, кто случайно или запланированно оказывается
хранителем общих библиотек или документации. В больших организациях
это бывает группа или отдел, которые получают привилегию собирать,
документировать, популяризировать и сопровождать программное
обеспечение, используемое различными группами.
Нельзя недооценивать такие группы "стандартных компонентов".
Укажем, что в первом приближении, система отражает организацию,
которая ее создала. Если в организации нет средств поощрения и
вознаграждения кооперации и разделения труда, то и на практике
они будут исключением. Группа стандартных компонентов должна
активно предлагать свои компоненты. Обычная традиционная
документация важна, но ее недостаточно. Помимо этого указанная
группа должна предоставлять руководства и другую информацию,
которая позволит потенциальному пользователю отыскать компонент и
понять как он может ему помочь. Значит эта группа должна предпринимать
действия, которые обычно связываются с системой образования и
маркетинга. Члены группы компонентов должны всегда, когда это
возможно, работать в тесном сотрудничестве с разработчиками из
областей приложения. Только тогда они будут в курсе запросов
пользователей и сумеют почуять возможности использования
стандартного компонента в различных областях. Это является
аргументом за использование такой группы в роли консультанта и в
пользу внутренних поставок программ, чтобы информация из группы
компонентов могла свободно распространяться.
Заметим, что не все программы должны быть рассчитаны на
повторное использование, иными словами, повторное использование не
является универсальным свойством. Сказать, что некоторый компонент
может быть повторно использован, означает, что в рамках определенной
структуры его повторное использование не потребует значительных
усилий. Но в большинстве случаев перенос в другую структуру может
потребовать большой работы. В этом смысле повторное использование
сильно напоминает переносимость. Важно понимать, что повторное
использование является результатом проектирования, ставившего
такую цель, модификации компонентов на основе опыта и специальных
усилий, предпринятых для поиска среди существующих компонентов
кандидатов на повторное использование. Неосознанное использование
средств языка или приемов программирования не может чудесным
образом гарантировать повторное использование. Такие средства языка
С++, как классы, виртуальные функции и шаблоны типа, способствуют
проектированию, облегчающему повторное использование (значит делают
его более вероятным), но сами по себе эти средства не гарантируют
повторное использование.
Человек и организация склонны излишне радоваться тому, что они
"действуют по правильной методе". В институтской среде это часто
звучит как "развитие согласно строгим предписаниям". В обоих случаях
здравый смысл становится первой жертвой страстного и часто искреннего
желания внести улучшения. К несчастью, если здравого смысла не хватает,
то ущерб, нанесенный неразумными действиями, может быть неограниченным.
Вернемся к этапам процесса развития, перечисленным в $$11.3, и
к шагам проектирования, указанным в $$11.3.3. Относительно просто
переработать эти этапы в точный метод проектирования, когда шаг точно
определен, имеет хорошо определенные входные и выходные данные и
полуформальную запись для задания входных и выходных данных. Можно
составить протокол, которому должно подчиняться проектирование,
создать средства, предоставляющие определенные удобства для записи
и организации процесса. Далее, исследуя классификацию зависимостей,
приведенную в $$12.2, можно постановить, что определенные зависимости
являются хорошими, а другие следует считать плохими, и предоставить
средства анализа, которые обеспечат проведение таких оценок во всех
общности среди классов и выделение общей части. Нет точного
определения общности, но следует обращать внимание на общность
для понятий системы, а не просто для удобства реализации. Указаниями,
что два класса имеют нечто общее, что возможно выделить в общий базовый
класс, служат схожие способы использования, сходство наборов операций,
сходство реализаций и просто тот факт, что часто в процессе обсуждения
проекта оба класса появляются одновременно. С другой
стороны, если есть
несколько наборов операций класса с различными способами использования,
если эти наборы обеспечивают доступ к раздельным подмножествам объектов
реализации, и, если класс возникает в процессе обсуждения несвязанных
тем, то этот класс является явным кандидатом для разбиения на части.
В силу тесной связи между понятиями и классами проблемы
перестройки иерархии классов высвечиваются на поверхности проблем
именования классов и использования имен классов в процессе обсуждения
проекта. Если имена классов и их упорядоченность, задаваемая иерархией
классов, кажутся неудобными при обсуждении проекта, значит, по всей
видимости, есть возможность улучшения иерархии. Заметим, что
подразумевается, что анализ иерархии классов лучше проводить не в
одиночку. Если вы оказались в таком положении, когда не с кем
обсудить проект, хорошим выходом будет попытаться составить учебное
описание системы, используя имена классов.
Когда пишешь статью, пытаешься найти подходящую для темы модель. Нужно
не бросаться сразу печатать текст, а поискать статьи на сходные темы,
вдруг найдется такая, которая может послужить отправной точкой.
Если ею окажется моя собственная статья, то можно будет использовать
даже куски из нее, изменяя по мере надобности другие части, и вводить
новую информацию только там, где требует логика предмета. Таким
образом, исходя из первого издания, написана эта книга. Предельный
случай такого подхода - это написание открытки-формуляра, где просто
нужно указать имя и, возможно, добавить пару строк для придания
"личного" отношения. По сути такие открытки пишутся с указанием отличия
от стандарта.
Во всех видах творческой деятельности использование существующих
систем в качестве моделей для новых проектов является скорее правилом,
а не исключением. Всегда, когда это возможно, проектирование и
программирование должны основываться на предыдущих работах. Это
сокращает степени свободы для разработчика и позволяет сосредоточить
внимание на меньшем числе вопросов в заданное время. Начать большой
проект "практически с нуля" - это может возбуждать, но правильнее будет
употребить термин "опьянение", которое приведет к "пьяному
блужданию" в множестве вариантов. Построение модели не накладывает
каких-либо ограничений и не означает покорного следования ей, это
просто освобождает разработчика от некоторых вопросов.
Заметим, что на самом деле использование моделей неизбежно,
поскольку каждый проект синтезируется из опыта его разработчиков.
Лучше, когда использование модели является явно сформулированным
решением, тогда все допущения делаются явно, определяется общий
словарь терминов, появляется начальный каркас проекта и увеличивается
вероятность того, что у разработчиков есть общий подход.
Естественно, что выбор начальной модели является важным решением,
и обычно оно принимается только после поиска потенциальных моделей
и тщательной оценки вариантов. Более того, во многих случаях модель
подходит только при условии понимания того, что потребуются
значительные изменения для воплощения ее идей в иной области
приложения. Но проектирование программного обеспечения - тяжелый
труд, и надо использовать любую помощь. Не следует отказываться
от использования моделей из-за неоправданного пренебрежения к
имитации. Имитация - не что иное, как форма искреннего восхищения,
а, с учетом права собственности и авторского права, использование
моделей и предшествующих работ в качестве источника вдохновения -
допустимый способ для всех новаторских работ во всех видах
деятельности. То, что было позволено Шекспиру, подходит и для нас.
Некоторые обозначают использование моделей в процессе проектирования
как "проектирование повторного использования".
В начале честолюбивого проекта нам неизвестен лучший способ построения
системы. Часто бывает так, что мы даже не знаем точно, что должна
делать система, поскольку конкретные факты прояснятся только в процессе
построения, тестирования и эксплуатации системы. Как задолго до
создания законченной системы получить сведения, необходимые для
понимания того, какие решения при проектировании окажутся
существенными, и к каким последствиям они приведут?
Нужно проводить эксперименты. Конечно, нужен анализ проекта и его
реализации, как только появляется пища для него. Преимущественно
обсуждение вертится вокруг альтернатив при проектировании и
реализации. За исключением редких случаев проектирование есть
социальная активность, которая ведет по пути презентации и
обсуждений. Часто самым важным средством проектирования оказывается
простая грифельная доска; без нее идеи проекта, находящиеся в
зародыше, не могут развиться и стать общим достоянием в среде
разработчиков и программистов.
Похоже, что самый популярный способ проведения эксперимента сводится
к построению прототипа, т.е. уменьшенной версии системы. Прототип не
обязан удовлетворять характеристикам реальных систем, обычно в
изобилии есть машинные ресурсы и программная поддержка, и в таких
условиях программисты и разработчики становятся непривычно опытными,
хорошо образованными и активными. Появляется цель - сделать
работающий прототип как можно скорее, чтобы начать исследование
вариантов проекта и способов реализации.
Такой подход, если применять его разумно, может привести к
успеху. Но он также может служить оправданием неудачно сделанных
систем. Дело в том, что уделяя особое внимание прототипу, можно
прийти к смещению усилий от "исследование вариантов
проекта" к "получение как можно скорее рабочей версии системы".
Тогда быстро угаснет интерес к внутренней структуре прототипа
("ведь это только прототип"), а работа по проектированию будет
вытесняться манипулированием с реализацией прототипа. Просчет
заключается в том, что такая реализация может легко привести к системе,
которая имеет вид "почти законченной", а по сути является пожирателем
ресурсов и кошмаром для тех, кто ее сопровождает. В этом
случае на прототип тратятся время и энергия, которые лучше приберечь
для реальной системы. Для разработчиков и менеджеров есть искушение
переделать прототип в конечный программный продукт, а "искусство
настройки системы" отложить до выпуска следующей версии. Если идти
таким путем, то прототипы отрицают все основы проектирования.
Сходная проблема возникает, если исследователи привязываются
к тем средствам, которые они создали при построении прототипа,
и забывают, что они могут оказаться непригодными для
рабочей системы, и что свобода от ограничений и формальностей, к
которой они привыкли, работая в небольшой группе, может оказаться
невозможной в большом коллективе, бьющимся над устранением длинной
цепи препятствий.
И в то же время создание прототипов может сыграть важную роль.
Рассмотрим, например, проектирование пользовательского интерфейса. Для
этой задачи внутренняя структура той части системы, которая прямо не
общается с пользователем, обычно не важна, и использование прототипов -
это единственный способ узнать,
какова будет реакция пользователя при работе с системой.
Другим примером служат прототипы, прямо предназначенные для изучения
внутренней структуры системы. Здесь уже интерфейс с пользователем
может быть примитивным, возможна работа с моделью пользователей.
Использование прототипов - это способ экспериментирования.
Ожидаемый результат - это более глубокое понимание целей, а не
сам прототип. Возможно, сущность прототипа заключается в том,
что он является настолько неполным, что может служить лишь средством
для эксперимента, и его нельзя превратить в конечный продукт без
больших затрат на перепроектирование и на другую реализацию. Оставляя
прототип "неполным", мы тем самым переключаем внимание на
эксперимент и уменьшаем опасность превращения прототипа в законченный
продукт. Это также почти избавляет от искушения взять за основу
проекта системы проект прототипа, при этом забывая или игнорируя те
ограничения, которые внутренне присущи прототипу. После эксперимента
прототип надо просто выбросить.
Не следует забывать о других способах проведения эксперимента,
которые могут служить во многих случаях альтернативой созданию прототипа,
и там, где они применимы, их использование предпочтительнее, поскольку
они обладают большей точностью и требуют меньших затрат
времени разработчика и ресурсов системы. Примерами могут служить
математические модели и различные формы моделирования. По сути,
существует бесконечная возрастающая последовательность,
начиная от математических моделей,
ко все более и более детальным способам моделирования, затем к
прототипам, к частичным реализациям системы, вплоть до полной системы.
Это подводит к идее построения системы, исходя из начального
проекта и реализации, и двигаясь путем повторного прохождения
этапов проектирования и реализации. Это идеальная стратегия,
но она предъявляет высокие требования к средствам проектирования
и реализации, и в ней содержится определенный риск того, что
программный объем, реализующий решения, принятые
при начальном проектировании, в процессе развития вырастет до такой
величины, что существенное улучшение проекта будет просто невозможно.
Похоже, что по крайней мере теперь такую стратегию применяют
или в проектах от малого до среднего размеров, т.е. там, где
маловероятны переделки общего проекта, или же для перепроектирования
и иной реализации после выдачи первоначальной версии системы, где
указанная стратегия становится неизбежной.
Помимо экспериментов, предназначенных для оценки решений,
принимаемых на этапе проектирования, источником получения полезной
информации может быть анализ собственно проектирования и (или)
реализации. Например, может оказаться полезным изучение различных
зависимостей между классами (см.$$ 12.2), не следует забывать и о
таких традиционных вспомогательных средствах реализации, как
граф вызовов функций, оценка производительности и т.п.
Заметим, что спецификация (результат анализа системы) и проект
могут содержать ошибки, как и реализация, и возможно, они даже
больше подвержены ошибкам, т.к. являются менее точными, не могут быть
проверены на практике и обычно не окружены такими развитыми средствами,
как те, что служат для анализа и проверки реализации. Введение
большей формализации в язык или запись, с помощью которой изложен проект,
в какой-то степени облегчает использования этих средств для
проектирования. Но, как сказано в $$12.1.1, это нельзя делать
за счет ухудшения языка, используемого для реализации. К тому
же формальная запись может сама стать источником трудностей и
проблем. Это происходит, когда выбранная степень формализации плохо
подходит для конкретных задач, когда строгость формализации превосходит
математическую основу системы и квалификацию разработчиков и
программистов, и когда формальное описание системы начинает
расходиться с реальной системой, для которой оно предназначалось.
Заключение о необходимости опыта и о том, что проектирование
неизбежно сопровождается ошибками и плохо поддержано программными
средствами, служит основным доводом в пользу итеративной модели
проектирования и реализации. Альтернатива - это линейная модель
процесса развития, начиная с анализа и кончая тестированием, но
она существенно дефектна, поскольку не допускает повторных
проходов, исходя из опыта, полученного на различных этапах развития
системы.
Программа, которая не прошла тестирование, не работает. Идеал, чтобы
после проектирования и (или) верификации программа заработала с
первого раза, недостижим для всех, за исключением самых тривиальных
программ. Следует стремиться к идеалу, но не заблуждаться, что
тестирование простое дело.
"Как проводить тестирование?" - на этот вопрос нельзя ответить
в общем случае. Однако, вопрос "Когда начинать тестирование?" имеет
такой ответ - на самом раннем этапе, где это возможно. Стратегия
тестирования должна быть разработана как часть проекта и включена
в реализацию, или, по крайней мере, разрабатываться параллельно
с ними. Как только появляется работающая система, надо начинать
тестирование. Откладывание тестирования до "проведения полной
реализации" - верный способ выйти из графика или передать версию
с ошибками.
Всюду, где это возможно, проектирование должно вестись так,
чтобы тестировать систему было достаточно просто. В частности,
имеет смысл средства тестирования прямо встраивать в систему.
Иногда это не делается из-за боязни слишком объемных проверок на
стадии выполнения, или из-за опасений, что избыточность, необходимая
для полного тестирования, излишне усложнит структуры данных.
Обычно такие опасения неоправданы, поскольку собственно программы
проверки и дополнительные конструкции, необходимые для них,
можно при необходимости удалить из системы перед ее поставкой
пользователю. Иногда могут пригодится утверждения о свойствах
программы (см. $$12.2.7).
Более важной, чем набор тестов, является подход, когда
структура системы такова, что есть реальные шансы убедить себя
и пользователей, что ошибки можно исключить с помощью определенного
набора статических проверок, статического анализа и тестирования.
Если разработана стратегия построения системы, устойчивой к ошибкам
(см.$$9.8), стратегия тестирования обычно разрабатывается как
вспомогательная.
Если вопросы тестирования полностью игнорируются на этапе
проектирования, возникнут проблемы с тестированием, временем
поставки и сопровождением системы. Лучше всего начать работать
над стратегией тестирования с интерфейсов классов и их
взаимозависимостей (как предлагается в $$12.2 и $$12.4).
Трудно определить необходимый объем тестирования. Однако,
очевидно, что проблему представляет недостаток тестирования,
а не его избыток. Сколько именно ресурсов в сравнении с проектированием
и реализацией следует отвести для тестирования зависит от
природы системы и методов ее построения. Однако, можно предложить
следующее правило: отводить больше ресурсов времени и человеческих
усилий на тестирование системы, чем на получения ее первой реализации.
"Сопровождение программного обеспечения" - неудачный термин. Слово
"сопровождение" предлагает неверную аналогию с аппаратурой. Программы
не требуют смазки, не имеют движущихся частей, которые изнашиваются
так, что требуют замены, у них нет трещин, в которые попадает
вода, вызывая ржавчину. Программы можно воспроизводить в точности
и передавать в течении минуты на длинные расстояния. Короче,
программы это совсем не то, что аппаратура. (В оригинале:
"Software is not hardware").
Деятельность, которая обозначается, как сопровождение программ,
на самом деле, состоит из перепроектирования и повторной реализации,
а значит входит в обычный цикл развития программного обеспечения.
Если в проекте учтены вопросы расширяемости, гибкости и переносимости,
то обычные задачи сопровождения решаются естественным образом.
Подобно тестированию задачи сопровождения не должны решаться
вне основного направления развития проекта и их не следует откладывать
на потом.
Д. Кнуту принадлежит утверждение "Непродуманная оптимизация - корень
всех бед". Некоторые слишком хорошо убедились в справедливости этого
и считают вредными все заботы об оптимизации. На самом деле вопросы
эффективности надо все время иметь в виду во время проектирования и
реализации. Это не означает, что разработчик должен заниматься
задачами локальной оптимизации, только задача оптимизации на самом
глобальном уровне должна его волновать.
Лучший способ добиться эффективности - это создать ясный и
простой проект. Только такой проект может остаться относительно
устойчивым на весь период развития и послужить основой для
настройки системы с целью повышения производительности. Здесь
важно избежать "гаргантюализма", который является проклятием
больших проектов. Слишком часто люди добавляют определенные
возможности системы "на всякий случай" (см. $$11.3.3.2 и $$11.4.3),
удваивая, учетверяя размер выполняемой программы ради завитушек.
Еще хуже то, что такие усложненные системы трудно поддаются
анализу, а по этому трудно отличить избыточные накладные расходы
от необходимых и провести анализ и оптимизации на общем уровне.
Оптимизация должна быть результатом анализа и оценки производительности
системы, а не произвольным манипулированием с программным кодом,
причем это особенно справедливо для больших систем, где интуиция
разработчика или программиста не может служить надежным указателем
в вопросах эффективности.
Важно избегать по сути неэффективных конструкций, а так же
таких конструкций, которые можно довести до приемлемого уровня
выполнения, только затратив массу времени и усилий. По этой же
причине важно свести к минимуму использование непереносимых по
своей сути конструкций и средств, поскольку их наличие препятствует
работе системы на других машинах (менее мощных, менее дорогих).
Если только это имеет какой-то смысл, большинство людей делает то,
что их поощряют делать. Так, в контексте программного проекта, если
менеджер поощряет определенные способы действий и наказывает за
другие, редкие программисты или разработчики рискнут своим
положением, встречая сопротивления или безразличия администрации,
чтобы делать так, как они полагают нужнымЬ.
Ь Организация, в которой считают своих программистов недоумками,
очень скоро получит программистов, которые будут рады и способны
действовать только как недоумки.
Отсюда следует, что менеджер должен поощрять такие структуры,
которые соответствуют сформулированным целям проекта и реализации.
Однако на практике слишком часто бывает иначе. Существенное
изменение стиля программирования достижимо только при соответствующем
изменении в стиле проектирования, кроме того, обычно и то и другое
требует изменения в стиле управления. Мыслительная и организационная
инерция слишком просто сводят все к локальным изменениям, хотя
только глобальные изменения могут принести успех. Прекрасной
иллюстрацией служит переход на язык с объектно-ориентированным
программированием, например на С++, когда он не влечет за собой
соответствующих изменений в методах проектирования, чтобы
воспользоваться новыми возможностями языка (см. $$12.1), и, наоборот,
когда переход на "объектно-ориентированное проектирование" не
сопровождается переход на язык реализации, который поддерживает
этот стиль.
Часто основной причиной перехода на новый язык или новый метод
проектирования называют то, что это облегчает повторное использование
программ или проекта. Однако, во многих организациях поощряют
сотрудника или группу, когда они предпочитают изобретать колесо.
Например, если производительность программиста измеряется числом
строк программы, то будет ли он писать маленькие программы,
работающие со стандартными библиотеками, за счет своего дохода
и, может быть, положения? А менеджер, если он оплачивается
пропорционально числу людей в его группе, будет ли он использовать
программы, сделанные другими коллективами, если он может просто
нанять еще пару программистов в свою группу? Компания может
получить правительственный контракт, в котором ее доход составляет
фиксированный процент от расходов на проект, будет ли она
сокращать свой доход за счет использования наиболее эффективных
средств? Трудно обеспечить вознаграждение за повторное использование,
но если администрация не найдет способов поощрения и вознаграждения,
то его просто не будет.
Повторное использование является прежде всего социальным
фактором. Повторное использование программы возможно при условии,
что
[1] она работает; нельзя использовать повторно, если это невозможно
и в первый раз;
[2] она понятна; здесь имеет значение структура программы, наличие
комментариев, документации, руководства;
[3] она может работать вместе с программами, которые не создавались
специально с таким условием;
[4] можно рассчитывать на ее сопровождение (или придется делать
это самому, что обычно не хочется);
[5] это выгодно (хотя можно и разделить расходы по разработке
и сопровождению с другими пользователями) и, наконец;
[6] ее можно найти.
К этому можно еще добавить, что компонент не является повторно
используемым, пока кто-то действительно не сделал это. Обычно задача
приспособления компонента к существующему окружению приводит к
уточнению набора операций, обобщению его поведения, и повышению его
способности адаптации к другим программам. Пока все это не проделано
хотя бы один раз, неожиданные острые углы находятся даже у
компонентов, которые тщательно проектировались и реализовывались.
Личный опыт подсказывает, что условия для повторного
использования возникают только в том случае, когда находится
конкретный человек, занятый этим вопросом. В маленьких группах
это обычно бывает тот, кто случайно или запланированно оказывается
хранителем общих библиотек или документации. В больших организациях
это бывает группа или отдел, которые получают привилегию собирать,
документировать, популяризировать и сопровождать программное
обеспечение, используемое различными группами.
Нельзя недооценивать такие группы "стандартных компонентов".
Укажем, что в первом приближении, система отражает организацию,
которая ее создала. Если в организации нет средств поощрения и
вознаграждения кооперации и разделения труда, то и на практике
они будут исключением. Группа стандартных компонентов должна
активно предлагать свои компоненты. Обычная традиционная
документация важна, но ее недостаточно. Помимо этого указанная
группа должна предоставлять руководства и другую информацию,
которая позволит потенциальному пользователю отыскать компонент и
понять как он может ему помочь. Значит эта группа должна предпринимать
действия, которые обычно связываются с системой образования и
маркетинга. Члены группы компонентов должны всегда, когда это
возможно, работать в тесном сотрудничестве с разработчиками из
областей приложения. Только тогда они будут в курсе запросов
пользователей и сумеют почуять возможности использования
стандартного компонента в различных областях. Это является
аргументом за использование такой группы в роли консультанта и в
пользу внутренних поставок программ, чтобы информация из группы
компонентов могла свободно распространяться.
Заметим, что не все программы должны быть рассчитаны на
повторное использование, иными словами, повторное использование не
является универсальным свойством. Сказать, что некоторый компонент
может быть повторно использован, означает, что в рамках определенной
структуры его повторное использование не потребует значительных
усилий. Но в большинстве случаев перенос в другую структуру может
потребовать большой работы. В этом смысле повторное использование
сильно напоминает переносимость. Важно понимать, что повторное
использование является результатом проектирования, ставившего
такую цель, модификации компонентов на основе опыта и специальных
усилий, предпринятых для поиска среди существующих компонентов
кандидатов на повторное использование. Неосознанное использование
средств языка или приемов программирования не может чудесным
образом гарантировать повторное использование. Такие средства языка
С++, как классы, виртуальные функции и шаблоны типа, способствуют
проектированию, облегчающему повторное использование (значит делают
его более вероятным), но сами по себе эти средства не гарантируют
повторное использование.
Человек и организация склонны излишне радоваться тому, что они
"действуют по правильной методе". В институтской среде это часто
звучит как "развитие согласно строгим предписаниям". В обоих случаях
здравый смысл становится первой жертвой страстного и часто искреннего
желания внести улучшения. К несчастью, если здравого смысла не хватает,
то ущерб, нанесенный неразумными действиями, может быть неограниченным.
Вернемся к этапам процесса развития, перечисленным в $$11.3, и
к шагам проектирования, указанным в $$11.3.3. Относительно просто
переработать эти этапы в точный метод проектирования, когда шаг точно
определен, имеет хорошо определенные входные и выходные данные и
полуформальную запись для задания входных и выходных данных. Можно
составить протокол, которому должно подчиняться проектирование,
создать средства, предоставляющие определенные удобства для записи
и организации процесса. Далее, исследуя классификацию зависимостей,
приведенную в $$12.2, можно постановить, что определенные зависимости
являются хорошими, а другие следует считать плохими, и предоставить
средства анализа, которые обеспечат проведение таких оценок во всех