Учебное пособие по курсу «Технология программирования»

Вид материалаУчебное пособие

Содержание


3. Технологические процессы и жизненный цикл программного обеспечения.
Внутреннее проектирование
Оба метода
Проектирование сверху вниз
Подобный материал:
1   2   3   4   5   6   7   8   9   10
^

3. Технологические процессы и жизненный цикл программного обеспечения.



В программировании выделяют следующие технологические процессы:
  • постановка;
  • разработка;
  • эксплуатация.

В Соединённых Штатах делают более подробное деление:
  • требования/спецификации;
  • проектирование;
  • реализация;
  • отладка;
  • сопровождение/экплуатация,

мы со своей стороны между отладкой и сопровождением добавим ещё внедрение. Принципиально эта часть есть и «у них», но в России и США на этой стадии выполняются совершенно разные действия, которые обязательно будут рассмотрены ниже. Давайте совместим эти два деления, выполним их определения и той или иной мере рассмотрим и обсудим суть этих процессов.

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

Итак: постановка, разработка, эксплуатация.


Постановку определим, как процесс получения документа называемого «Задание на программирование». Иначе его могут называть «Техническое задание». Далее следовало бы определить, что за документ «задание на программирование» и его содержание. Мы поступим по-другому: опишем суть процесса постановки и, скорее всего, по ходу описания получим и содержание «задания на программирование».
Насколько важна стадия постановки? Чрезвычайно важна. Она определяет успех всего проекта. Можно долго искать философский камень и в конце концов с удивлением понять, что его просто не существует. Постановку выполняют чаще всего одни специалисты, а программируют другие. Крайними в этой схеме являются те, кто программирует: они выполняют окончательную сдачу «работающего проекта» и часто они, косвенно, несут ответственность за ошибки постановщиков.

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

В науке есть утверждение: решить задачу может каждый, а вот поставить – нет.
Что значит поставить задачу? Поставить задачу – значит описать, как её решить. Не следует забывать, что между постановкой задачи и её решением часто лежит пропасть.

Ещё в 1954г. Клод Шеннон в своей статье «Могут ли машины играть в шахматы?» доказал, что ЭВМ может играть в эту замечательную игру. Свою статью он закончил фразой «таким образом, в реализации программы остаются только технические трудности». На преодоление этих технических трудностей ушло более сорока лет при смене четырёх поколений ЭВМ.

Таким образом, в постановке должно быть описано, как решить задачу, то есть постановка должна содержать с одной стороны алгоритм решения задачи, а с другой стороны конкретные спецификации задачи: входные/выходные документы, технологические допуски на данные; сами данные и их форматы и т.д.

Вот почему, как указывалось выше, в США разделяют постановку на две части:

а) требования, цели, спецификации;

б) проектирование.


Требования, цели, спецификации: эта фаза по-другому может быть названа системным анализом. В этой фазе изучается и определяется задача. Подчеркнём: задача определяется, но не решается. Очень часто требования и цели смешиваются с процессом проектирования, который в свою очередь совмещается с разработкой программы, особенно часто, когда постановку выполняет программист, что бывает гораздо чаще, чем хотелось бы. Причина в большинстве своём — неправильное понимание, как потребностей пользователя, так и сути самой задачи. Отсюда стремление уйти от решения трудных или плохо определённых проблем в задаче и соблазниться её частичным решением или подменить декларируемую цель на некоторую другую, ту, которая получается в процессе программирования. Всё это обязательно приводит к переработке уже существующих программ, надо ли говорить насколько это трудоёмкий и неприятный процесс, к тому же плохо оплачиваемый.

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

Майерс [6] выделяет три группы программных проектов: управляемый пользователем, контролируемый пользователем и независимый от пользователя.

В случае проекта управляемого пользователем требования к ПО разрабатываются непосредственно организацией-пользователем. Разработчик ПО является подрядчиком или субподрядчиком этой организации. Чаще всего это правительственные заказы или заказы крупных организаций.

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

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

Важно участие в определении требований, как пользователя, так и разработчика. Не участие в определении требований разработчика ПО повышает его шансы неправильно понять или неправильно интерпретировать требования.

Требования к средней или крупной системе должна разрабатывать небольшая группа. Одним из членов этой группы должно быть должностное лицо, обладающее высокими полномочиями. Директор предприятия, главный инженер, его заместители. Важно уже на этапе определения требований привлечь на свою сторону руководство предприятия. Дж. Мартин в [7] прямо указывает на необходимость участия уже в процессе проектирования программной системы членов администрации. Выше было показано, что любое внедрение ПО влечёт за собой административные мероприятия и именно администрация лучше всего понимает, как и что в таком случае необходимо будет сделать. Проект, лишённый административной поддержки, в лучшем случае будет оплачен. То, что он не будет внедрён – однозначно. Вину же за не работающий проект чаще всего возлагают на программистов. Однако пользователем ПО чаще всего бывает не администрация, а другие специалисты. В выработке требований к ПО и далее в проектировании должен участвовать именно специалист – пользователь: если это система продажи билетов, то наличие кассира в группе постановки обязательно. Только специалист – пользователь естественным образом определит требования к системе, причём сделано это будет в терминах и понятиях пользователя.

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

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


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

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

Цели проекта должны давать ответы на вопросы (Майерс):

1. Ориентировочная стоимость каждого процесса;

2. Календарный план проекта;

3. Цели для каждого процесса тестирования;

4. Цели в области адаптируемости: должно быть ясно в какой степени проектируемое ПО может быть модифицировано;

5. Критерии оценки готовности продукта к использованию;

6. Вопросы сопровождения и документации.


Цели программного обеспечения зависят от задач. Но необходимо сделать несколько кратких замечаний об общих правилах формулирования целей.


Цели должны быть чёткими, явными, разумными и измеримыми.

Цель, которую нельзя понять  бесполезна.

Цели должны быть известны всем разработчикам проекта.

Цели должны быть достижимы.

Все цели необходимо формулировать по возможности в количественных терминах.

Каждая цель должна быть сформулирована достаточно подробно.

Майерс рекомендует вместе с целями сформулировать и не цели.


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

Детальные спецификации должны обязательно иметь:

1. Описание входных данных: точное описание форматов представления, допустимых значений, области изменения.

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

3. Преобразования системы: Требуется описать преобразования и состояния системы с точки зрения пользователя. Например, в библиотечной системе при сдаче книги пользователем, необходимо изменить запись в карточке пользователя и изменить запись о наличии книг в библиотеке.

4. Защита системы: Необходимо перечислить все уровни защиты системы. Кто к какой информации имеет доступ и какие функции может запускать.

5. Эффективность: Минимальные требования к скорости работы системы и потребляемым ресурсам.

6. Некоторые замечания по программированию, например: систему необходимо реализовать так, чтобы она имела сопряжение с другой заданной системой и т.п.

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

Обратим внимание, что всё это внешние спецификации – данные, форматы и преобразования с точки зрения пользователя. Здесь нет ни способов, ни форматов представления информации в машине. Всё это появится только в результате процесса внутреннего проектирования системы, выполняемого программистом.


Итак, внешнее проектирование: процесс описания ожидаемого поведения разрабатываемого продукта с точки зрения внешнего по отношению к нему наблюдателя. Цель – построение внешних взаимодействий будущей системы без конкретизации её внутреннего устройства. Результатом этого процесса должны быть полные и правильные внешние спецификации. Методологии этого процесса не существует. Можно делать опросы, всяческие предварительные обследования, заполнять различные предварительные входные формы и т.д. Мы не можем точно сказать, как именно следует выполнять эту часть проектирования, хотя точно известно, что должно появиться в результате выполнения этого процесса. Но два момента требуется осветить. Во-первых - эту часть проектирования желательно выполнять как можно меньшим числом персонала, в идеале всего одним специалистом, если такое возможно. Во-вторых – они не должны быть программистами. Внешнее проектирование, прежде всего, касается понимания обстановки, проблем и потребностей пользователя, психологии общения человека с машиной.

Тут требуется заметить, что все «домашинные» документы имели, чаще всего, не машинный формат. В числовой колонке мог появиться прочерк, что, вообще говоря, не число. Ячейка таблицы могла содержать сложную дробь числитель и знаменатель, которые в свою очередь состояли из натуральных дробей т .п. Кроме того, многие документы вообще не имели стандартной формализованной формы. Разумеется, пользователь меньше всего стремится изменить уже сложившийся документооборот. Но всё хорошо в меру. Бездумная попытка приспособить электронный документооборот к ручному, «домашинному», приводит к неоправданному усложнению системы, а иной раз и к невозможности реализации. Следует искать компромисса. Иногда достаточно переставить в документе две строки местами, чтобы вдвое снизить время работы системы по выпуску документа! Например, реализовывалась, простая программа вывода ведомости основных средств предприятия, естественно с указанием износа и остаточной стоимости. Безусловно, в результате работы программы подсчитывалась стоимость основных средств по группам и выводилась итоговая стоимость. Любому специалисту ясно, что итоговые цифры получаются в конце просмотра информации, а не в начале. Однако, захотелось главному бухгалтеру предприятия на первом листе распечатки увидеть итоговые цифры и пришлось программистам идти на простые, но трудоёмкие ухищрения: распечатка сначала выводилась на магнитную ленту (тогда в дисками было трудно), затем лента перематывалась на начало и снова читалась, а уж затем выводилась ведомость в том виде, в каком запрашивало её должностное лицо. Эта, так сказать, прихоть обходилась предприятию в лишних четыре часа машинного времени, не говоря уже о лишних потребляемых ресурсах (магнитная лента). Если учесть, что среднее время бессбойной работы ЭВМ тогда было порядка трёх – четырёх часов, то понятно, что регулярный выпуск пресловутой ведомости превращался для обслуживающего персонала в кошмар.

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

Принципиально, чем более «человеческий» вид имеют входные и выходные документы, тем более естественнее выглядит система, но тем она и сложнее. Необходимо тщательно взвесить между «псевдочеловечностью» и сложностью системы. Неоправданное усложнение системы обязательно приведёт к рыхлости реализации. Система потеряет в скорости функционирования и потребует значительно больше ресурсов. То же самое можно утверждать и по поводу предоставления пользователю всех «обычных» возможностей. Нередко этих возможностей становится столько, что пользователь теряется, путает их и никак не может отыскать оптимальный для себя вариант. Практика показала, что до 30% - 40% возможностей никогда не используются или же применяются очень редко. Возникает вопрос: а следовало ли эти возможности реализовывать.


^ Внутреннее проектирование: как уже ясно из термина этот процесс выполняется при проектировании внутренней структуры проекта. Чаще всего этот процесс выполняется программистом совместно с постановщиком, но нередко и одним программистом. Здесь необходимо рассмотреть все внутренние функциональные и другие связи. Провести анализ форматов представления данных, темпа обмена данными и последовательностью их появления. Попытаться проанализировать предполагаемые временные характеристики системы. Рассмотреть всю структуру ввода/вывода и согласовать по форматам. Рассмотреть всю структуру программы в целом для проверки распределения требований по компонентам, распределения памяти, последовательности вычислений и работой с базами данных. Конечно же, этот процесс (внутреннее проектирование) проводится для больших и средних систем. Для одиночной программы это уже стадия разработки.

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

Интуитивно вроде бы ясно, где кончаются малые проекты и начинаются большие. Роберт Гласс в «Руководстве по надёжному программированию» приводит условное деление по численности бригады, выполняющей разработку. Сейчас, особенно, нельзя так подходить. Однако по трудоёмкости всё же можно судить. Отнесём к малым проектам проекты трудоёмкостью до сотни человеко-месяцев. Это могут быть простые трансляторы, генераторы отчётов и т.п. Средние проекты – до 1000 человеко-месяцев и большие свыше 1000.

Основные проблемы малых разработок находятся в области нехватки ресурсов, денежных средств, кадров.

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

Ф. Брукс подробно рассматривает в своей книге трудности, с которыми сталкивается реализация больших проектов. Он один из не многих даёт конкретные рекомендации и даже более того во втором издании ввёл 19 главу «Мифический человеко-месяц двадцать лет спустя», где он обсуждает правильность своих рекомендаций в сегодняшних реалиях. Сейчас нам достаточно знать о различии в реализации больших и малых проектов, желающих получить конкретные рекомендации, отсылаем к замечательной книге Брукса.

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

Теперь, перед переходом к стадии «разработка», считаем нелишним, привести соотношение производительности труда программистов. Впервые эта цифра появилась в исследованиях Сакмана. Он приводит, что производительность труда у разных программистов соотносится как 1 : 25. Такого соотношения производи-тельности труда нет ни в одной отрасли. Это, безусловно, крайние цифры, но 1 : 10 вы гарантированно встретите в собственной практике. Кроме того, программы написанные более опытным программистом чаще всего требуют меньше ресурсов (1 : 2) и работают быстрее (1 : 2), не говоря уже о том, что они содержат меньше ошибок вообще и не найденных в частности. (Сегодня нельзя утверждать соотношение 1 : 2 так, как очень часто программы реализуются в таких средах программирования, где ресурсы сильно зависят от самой среды: MicroSoft Visual C++ и т.п.). Программы, написанные опытным программистом легче читать, они яснее концептуально.

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

Существует предложение выработать критерии правильности программ и всегда стремиться их соблюдать. Однако всё не так просто. Кто вам сказал, что программа, отвечающая определённым критериям, обязательно будет работоспособной? Тем не менее, наличие определённого списка требований лучше его отсутствия.

1. Программа не должна содержать ошибок. Возникает вопрос, что такое ошибка? Кроме того, наиболее продвинутые в реализации больших программных систем, американцы пришли к выводу, что в больших программных средах остаётся 20% ошибок, и снизить их число нереально. Более того, существуют абсолютно безошибочные программы, которые на определённых совокупностях данных выдают неправильные результаты.

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

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

4. Программа должна быть оптимальной. Возникает вопрос относительно чего?

Список можно продолжать, но очевидно ясно, что это не даст нам ничего нового.
Список требуемых свойств хорошей программы сильно зависит от класса программы. В общем, основным и единственным критерием правильной программы служит критерий  «программа должна выдавать ожидаемые пользователем результаты».


Разработка  собственно процесс написания и отладки программ. Как указано выше, ранее эта стадия делилась на реализацию и отладку. Но уже тогда появилась такая методология разработки, как «сверху – вниз», где отладка и реализация практически совмещались. Сейчас, когда программы «накидываются» на мониторе, процесс реализации и отладки явно не разделить, особенно при использовании визуальных сред.

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

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

Процесс разработки архитектуры  шаг, необходимый при проектировании систем, но не обязательный при создании програм­мы. Если вы разрабатываете программную систему, то следующий шаг проектирования  разработка архитектуры, а за ним  проектирование структуры программы. Мы можем сказать, что программная система представляет собой набор решений множества различных, но связанных между собой задач. Один из методов построения архитектуры системы – уровни абстракции (более подробно см. Г. Майерс [6] ).

Концепция уровней абстракции была предложена Дейкстрой. Система разбивается на различные иерархически упорядо­ченные части, называемые уровнями, удовлетворяющие определен­ным проектировочным критериям. Каждый уровень является груп­пой тесно связанных модулей. Идея уровней призвана минимизи­ровать сложность системы за счёт такого их определения, которое обеспечивает высокую степень независимости уровней друг от дру­га. Это достигается благодаря тому, что свойства определенных объектов такой системы (ресурсы, представление данных и т. п.) упрятываются внутрь каждого уровня, что позволяет рассматривать каждый уровень как «абстракцию» этих объектов.

Возможны две общие структуры таких уровней, изображенные на рис. 3.1 и 3.2 соответственно (заимствовано [6]) . Рис. 3.1 иллюстрирует такой под­ход, когда задача рассматривается как создание «машины пользова­теля», начиная с самого низкого уровня  уровня аппаратуры или операционной системы. Последовательность уровней, называемых абстрактными машинами, определяется так, что каждая следующая машина строится на предыдущих, расширяя их возможности. Каж­дый уровень может ссылаться только на один, отличный от него са­мого уровень (вызывать его), а именно тот, который ему непосред­ственно предшествует.

В структуре, изображенной на рис. 3.2, уровни не являются пол­ными абстракциями более низких уровней, каждый из них может ссылаться на любой предшествующий уровень.



рис. 3.1. Уровни абстракции рис. 3.2. Другой подход к
уровням абстракции


Нет строгого определения уровней абстракции. Чаще вместо определения уровней абстракции перечисляют их желаемые свойства:


1. На каждом уровне ничего не известно о внутреннем строении других уровней. Связь между уровнями осуществляется только через заранее определенные связи.


2. На каждом уровне ничего не известно о существовании более высоких уровней и, соответственно, ничего не известно об их свойствах.


3. Предположения, которые на каждом уровне делаются относи­тельно других уровней, должны быть минимальны. Эти предположения могут принимать вид соотношений, которые должны соблю­даться перед выполнением функции, либо относятся к представле­нию данных или факторов внешней среды.


4. Связи между уровнями ограничены явными аргументами, пере­даваемыми с одного уровня на другой. Недопустимо совместное ис­пользование глобальных данных несколькими уровнями.


5. Каждый уровень располагает определенными ресурсами и ли­бо скрывает их от других уровней, либо предоставляет другим уровням некоторые их абстракции.


6. Каждый уровень представляет собой группу модулей (раздель­но компилируемых подпрограмм). Некоторые из этих модулей яв­ляются внутренними для уровня, т. е. недоступны для других уров­ней. Имена остальных модулей, вообще говоря, известны на следую­щем, более высоком уровне. Эти имена представляют собой сопря­жение с этим уровнем.


7. Каждый уровень должен иметь высокую прочность и слабое сцепление. Это значит, что всякая функция, выполняемая уровнем абстракции, должна быть представима единственным входом. Аргументы, пересылаемые между уровнями, должны быть отдельными элементами данных, а не слож­ными структурами.


Ещё раз повторим: «Идея уровней призвана минимизи­ровать сложность системы за счёт такого их определения, которое обеспечивает высокую степень независимости уровней друг от дру­га».


Где мы чаще всего сталкиваемся с явным представлением по уровням абстракции – представление сетевых протоколов, например TCP/IP: физический уровень, транспортный уровень и т.д. Например: «TCP/IP для чайников» Кендейс Лейден и Маршалл Виленски. (2001г., стр 77)




рис 3.3. В пятом уровне модели ТСР/IР совмещены три уровня модели OS1


Плюсы и минусы подобного подхода.

1. Лёгкое внесение модификаций в систему. Ясно, что модификации ограничиваются изолированными частями системы. Кроме того, уровни абстракции чётко указывают, какие части подлежат изменению.

2. Следующим несомненным плюсом будет мобильность: простота изменений

системы при переносе в другую операционную систему.

3. Несомненным плюсом является ясность системы: строение уровней часто определяет, какой уровень выполнить аппаратным, а какой программно.

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

Минусы:

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

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

Концепция достаточно идеальна: предположение о минимизации на уровне сведений о других уровнях, а ещё лучше, полное отсутствие знаний даже о наличии других уровней, практически невыполнимо. Обратите внимание, что само разбиение производится при полном знании структуры системы.

Знатоки ООП (объектно-ориентированное программирование) обратят внимание на то, что концепция уровней абстракции сильно напоминает концепцию класса/объекта. У нас есть открытые и закрытые члены класса, член-функции класса.

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

Иерархия уровней – иерархия классов. Вот чего нет в концепции уровней, так это наследования.

Другим методом проектирования систем является подсистема, управляемая портом (Майерс). Под портом понимается список  очередь – стек входных сообщений. Основное внимание здесь обращено не на распределение функций между уровнями иерархии, а на методы связи между подсистемами или программами. Каждая система рассматривается как асинхронный процесс. Если одна из систем передаёт данные другой, то она просто посылает сообщение в порт другой подсистемы/системы. Ясно, что если наш порт представляется списком, то сообщения из него выбираются, так как это определено в подсистеме. То есть, подсистема сама определяет, какое сообщение ей выбирать из списка. Если порт очередь, то сообщение, пришедшее первым, и обрабатывается первым: сообщения обрабатываются в порядке поступления. Наконец, если порт будет стеком, то последнее пришедшее сообщение обрабатывается первым. Отметим, что порт может быть и техническим устройством.

Однако так описывали этот метод двадцать пять лет назад. С точки зрения современности тут явно изложена идея системы, управляемой сообщениями. Сегодня мы все знаем несколько реализаций таких систем: Windows. С точки зрения технологии программирования, здесь несколько особенностей: Во-первых, существенно меняется структура программы – главными становятся обработчики получаемых сообщений, а не модули вызывающие эти обработчики. Во-вторых, поскольку поступление сообщений случайно, то обработчики любого сообщения могут быть вызваны в любой момент, что приводит к глобальному хранению сведений обо всех обработчиках (реестр). В-третьих, обработчики сообщений вызываются асинхронно, а, следовательно, о синхронной обработке сообщений должен заботится сам процесс, инициализируемый этими сообщениями. Кроме того, асинхронность неявно требует увеличения объёма используемых ресурсов, так как каждое сообщение может обрабатываться параллельно с любым другим. В-четвёртых, так как число сообщений постоянно растёт, а все они молчаливо предполагаются системными, то операционная система начинает неоправданно разбухать. Понятие системный  не системный начинает размываться.

Очень важно: такие системы чрезвычайно трудно продумать и правильно спроектировать.

Отсюда видно, что даже этот поверхностный анализ идеи показывает часть недостатков, присущих системам, реализуемым по схеме «система, управляемая портом (сообщениями)». Поэтому на вопрос «Могла ли фирма Microsoft сделать свой продукт лучше (с точки зрения программиста, а не пользователя)?» автор всегда отвечал: «Конечно же, могла, но родимые пятна всё равно останутся». Такие системы всегда будут рыхлыми, потребляющими неоправданно много ресурсов и неустойчивыми в работе. А что вы хотите? Имея хаос на входе, вы на выходе тоже получите хаос.

^ Оба метода описывают желаемые свойства системы, а не сам процесс проектирования. Другими словами оба метода, фактически, задают требования и спецификации. Плохо это или хорошо. Роберт Гласс в «Руководстве по надёжному программированию» (1982г) приводит следующий любопытный пример.

«Если вам дадут требования и спецификации на слона и попросят его спроектировать, вы, вероятно, задумаетесь: с чего начать, особенно если вы никогда до этого не видели слонов. Начнёте ли вы с хобота, поскольку это наиболее необычная часть, или с ног, потому что они вам понятнее, или с мозга, так как он управляет поведением животного? А может быть, со всего сразу, потому что вам надо осознать задачу в целом?

Проектировщик программ сталкивается с аналогич­ной проблемой. Как вы отнесетесь к задаче в целом, если ее спецификация содержит тысячи мельчайших деталей? Решить эту проблему очень важно, так как многие программы имитации слонов в действительности имитируют собак с прицепленным хоботом. Очевидно, решение должно состоять в разделении спецификации на обозримые части. Одним из методов применяемых здесь будет «проектирование сверху вниз».

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

Смысл проектирования сверху вниз состоит в том, что оно дает обозримое описание на каждой стадии, а также представление взаимосвязи всех составных частей проекта. Такой подход позволяет своевременно замечать возникающие проблемы и не переходить к последующей детализации до тех пор, пока полностью не завершён предыдущий уровень. Рассматриваемый подход иногда называют «иерар­хической декомпозицией» из-за ступенчатой природы процесса.

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

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

Кроме того, здесь имеется ещё одна сложность. Поскольку иерархи­ческая декомпозиция состоит в том, чтобы переходить на следующий уровень не прежде, чем закончится проек­тирование предыдущего, она может задержать опреде­ление разрешаемости проблем, которые таит в себе бо­лее низкий уровень. Истиной является то, что в програм­мировании имеются серьезные проблемы на всех уров­нях детализации и в большинстве случаев они вскрыва­ются только тогда, когда начинается детализация самого нижнего уровня. Иерархическая структура может, конечно, рухнуть, если один из ее низших элементов развалится.

Возможный пример. (Р. Гласс. [5] стр. 58)

Допустим, вам предложили построить программу имитации слона. Заказчик дает вам хо­рошую спецификацию на слона, включая характери­стики его имитатора, и вы решаете применить метод проектирования сверху вниз:

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




Рис. 3.4 Имитатор поведения слона.

Вместо проек­тирования сверху вниз, при котором мы раз­деляем спецификации на отдельные компоненты, можно воспользоваться проектированием структур данных. При этом внимание концентрируется на структуре и потоке информации, а не на выполняемых функциях.

Ценность метода проектирования структуры данных (иногда называемого методом Джексона) (Р. Гласс) состоит в по­пытке сузить область перебора в тех случаях, когда, при проектировании сверху вниз возникает неопределен­ность вершины. Проектировщик структуры данных имеет более чёткую последовательность задач: определить структуру и поток данных, а также операции, которые создают этот поток. Одна из возможных проблем возникающих здесь – конфликт данных: разные последовательности данных; разные форматы данных. Чаще всего этот метод применяется в экономических задачах, как задачах имеющих структурированный вход (набор входных таблиц) и структурированный выход. То есть данные, и их структура уже определены и требуется лишь определить последовательность действий получения выходных данных из входных. Метод широко используется при визуальном проектировании наборов баз данных.


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

Представьте, что вы пишите программный комплекс, имитирующий поведение человека. Интуитивно хочется разделить этот комплекс на два подкомплекса: мужчина и женщина. Однако, спроектировав структуры подкомплексов мужчина/женщина, вы тотчас же обнаружите, что список функций (модулей) с обеих сторон будет совершенно одинаков, за исключением, по всей видимости, полового поведения и поведения во время беременности. Естественно желание реализовывать каждую функцию одним модулем, что должно сэкономить ресурсы, уменьшить размеры программы и т.д. Вот так и появляются гермафродиты: поскольку каждый модуль будет обладать возможностью «поведения» как мужчины, так и женщины. Мало того, что каждый модуль станет сложнее и, конечно же, менее надёжен, мы потеряем возможность по вызову модуля быстро определить в какой среде (мужчина/женщина) этот модуль работает в данный момент. (Действительность менее удручающа – большинство современных средств позволяют иметь в системе несколько модулей, выполняющих очень похожие функции, имеющих одно имя: перегрузка функций в C++).

Возвращаясь к жизненному циклу программного обеспечения, рассмотрим, сколько занимает каждая стадия в «жизни программы». Обычно рассматривают распределение стоимости каждой стадии (схемы приведены из [5] Р. Гласс):



Рис. 3.5. Жизненный цикл программного обеспечения,

распределение стоимости по фазам.


Откуда видно, что, несмотря на свою ценность, стадия проектирования, как таковая занимает не более 20% стоимости программного обеспечения. Стадия собственно программирования около 30%. А вот стоимость сопровождения составляет аж половину стоимости программного обеспечения, что было далеко не очевидно до исследования. Р. Гласс приводит следующее рассуждение: «Бедное, старое, никем не любимое сопровождение. Оно заключается в удовлет­ворении потребностей пользователя: устранении ошибок, проведении доработок по просьбе пользователя и, во­обще, повышении полезности программы. Программисты стремятся избежать такой работы, не находя в ней твор­ческой «искры». Если проект  сигнал к бою, его реали­зация и отладка  атака и возвращение с победой, то сопровождение  это ежедневная оборона, жизненно необходимая, но обычно не почётная. Как ни парадок­сально, но занимающийся сопровождением программист, который, возможно, является наиболее важным лицом, определяющим судьбу программы у заказчика, зачастую далеко не самый способный и уважаемый человек у себя в коллективе. Этим и обусловлена самая большая помеха при сопровождении  отсутствие квалифициро­ванных кадров. Прекрасно настроенная скрипка Стра­дивари в руках такого «специалиста» может стать при­годной только для растопки печи. Подобным отноше­нием, лишающим сопровождение очарования и почета, можно загубить все то хорошее, что было достигнуто на предыдущих фазах разработки». Если сопровождение превалирует по стоимости, то стадия проектирования превалирует по числу допущенных ошибок.

При проектировании возникает до 61% - 64% ошибок и лишь остальные при реализации. Кроме того, ошибки, допущенные при проектировании, влияют на все последующие стадии, а их ликвидация обходится гораздо дороже. Иной раз ошибки, пропущенные на этой стадии, приводят к пересмотру всей схемы реализации программы. Ф. Брукс отмечает, что ошибки делаемые программистом в большинстве случаев менее существенны, чем концептуальные ошибки, получаемые при проектировании.

Распределение выявленных ошибок приведено на рис. 3.6. Из рисунка видно, что в основном ошибки выявляются на этапе сопровождения, хотя и при отладке (рис. 3.7) выявляется достаточно значительное количество ошибок. Принципиально это зависит от квалификации и добросовестности программиста. Квалифицированный программист делает меньше собственных ошибок и лучше и быстрее находит как свои, так и чужие ошибки. Добросовестный программист просто пропускает большее число контрольных примеров.




Рис. 3.6 Жизненный цикл программного обеспечения:
распределение допущенных ошибок по фазам



Рис. 3.7 Жизненный цикл программного обеспечения:
распределение выявленных ошибок по фазам.

Интересно просмотреть, сколько стоит ликвидация ошибки в зависимости от стадии (см. рис. 3.8):



Рис. 3.8 Жизненный цикл программного обеспечения:
распределение стоимости устранения ошибок по фазам

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

Не следует забывать, что выявление ошибок продолжается до конца жизненного цикла программы. Наибольшее число ошибок обнаруживается после или во время приёмо-сдаточных испытаний. Однако следует принимать во внимание, что «самая сложная задача программирования — получить полную и непротиворечивую спецификацию, и сущность создания программы на практике во многом состоит в «отладке спецификации». (Ф. Брукс) [3].


Вопросы к главе 3.


1. Перечислите основные последствия внедрения ПО (программного обеспечения) в производство.

2. Что такое жизненный цикл программы? Перечислите основные «за» и «против» этого термина.

3. Что такое постановка? Перечислите основные подстадии постановки.

4. Кратко охарактеризуйте содержание результатов постановки.

5. Требования, цели, спецификации?

6. Кто выполняет постановку? Дайте желаемый состав группы выполняющей постановку.

7. Цели? На какие вопросы, прежде всего, должны дать ответ цели?

8. Спецификации? Что должно в них входить?

9. Чем различаются процессы внешнего и внутреннего проектирования?

10. Внешнее проектирование? Кто, когда, зачем?

11. Внутреннее проектирование? Кто, когда, зачем?

12. Величина проекта? На что она влияет?

13. Производительность труда программиста? Различия в программах опытного программиста и новичка.

14. Критерии правильности программы? Что такое хорошая программа?

15. Что такое разработка?

16. Что такое архитектура ПО?

17. Концепция уровней абстракции.

18. Достоинства «концепции уровней абстракции».

19. Проектирование сверху вниз.

20. Проектирование структур данных.

21. Распределение стоимости разработки ПО по технологическим стадиям.

22. Сопровождение ПО: кратко охарактеризовать технологическую стадию.

23. Распределение ошибок по стадиям.

24. Стоимость ликвидации ошибок: распределение по стадиям.