- •Глава II
- •12. Предварительные замечания о процессе разработки программ
- •12.1. Жизненный цикл математического обеспечения
- •12. Предварительные замечания о процессе разработки программ
- •12.1. Жизненный цикл математического обеспечения
- •12.2. Анализ требований
- •12.3. Пример задачи
- •13.1. Обзор процесса проектирования
- •13.2.1. Вводный раздел
- •13.2.2. Разделы абстракций
- •13.8. Абстракция строки
- •13.9. Обзор и обсуждение
- •14. Этап перехода от проектирования к реализации
- •14.1. Оценка проекта
- •14.1.1. Корректность и эффективность
- •14.1.2. Структура
- •15.2. Выбор подхода
13.9. Обзор и обсуждение
Проектирование задачи formatосуществляется поэтапно. На каждом этапе мы выбираем новую цель и исследуем ее реализацию. В этом направлении решаются две основные задачи. Во-первых, мы изобретаем некоторые вспомогательные абстракции, полезные пря разработке целевой. Затем мы точно определяем свойства вспомогательных абстракций, а также описываем эти свойства в спецификациях.
Основная проблема при проектировании программы и требующая наибольшей активности, заключается в создании вспомогательных спецификаций. Основной метод, использованный и в данном случае, заключается в изучении структуры проблемы, исходя из которой создается структура самой програмы. Изучение структуры программы имеет форму создания списка задач. Однако мы не просто изобретаем структуру программы, выполняющую эти задачи. Список используется для концентрирования внимания на основных моментах проблемы и последующем создании абстракций, выполняющих необходимые функции.
В каждом случае абстракции создаются для сокрытия подробностей. Например, абстракция docскрывает подробности форматирования выходных данных, a doJineскрывает обработку и интерпретацию текста. Подобное «сокрытие» желательно по двум причинам. Это дает возможность управления модифицируемостью и сложностью. Мы структурируем задачу по степеням сложности, что позволяет ограничить число проблем на каждом шаге. Возможность модифицируемости позволяет нам изменять в дальнейшем мелкие подробности, не затрагивая других абстракций.
В процессе проектирования мы руководствуемся следующими факторами:
1.Знанием того, что абстракции уже доступны. Это включает в себя как абстракции, имеющиеся в языке программирования и в любой доступной библиотеке программ, так и уже определенные в процессе проектирования.
2.Знанием существующих алгоритмов и структур данных. Необходимо знать об уже существующих методах. Например, проектировщик должен знать об известных методах сортировки и поиска и не изобретать их заново.
3.Знание относящихся к этому вопросу и уже существующих программ. По мере углубления в процессе проектирования это д^-, знание становится все более полезным, поскольку даже принци- -уЦ пиально отличные друг от друга программы могут иметь схожие подпрограммы.
Например, наше решение о построчном вводе-выводе основано на знании того, что однопроходные алгоритмы, как правило, менее эффективны с точки зрения используемого объема памяти, чем
двухпроходные. Мы также отталкиваемся от имеющихся в языкеCtUтипов, производя выбор между ними и абстракциями данных, например между абстракцией строки и абстракцией слова или потоками и абстракцией вывода полученных данных. Мы не используем знание структур данных в форматировщике, однако можем сделать это в любой программе, которой необходимо хранить большие объемы данных. Например, при наличии большого числа форматирующих команд мы можем хранить информацию о них в таблице команд, позволяющую делать быстрый просмотр.
Наибольшее влияние на проектирование оказывают используемые типы. Разумеется, процедуры и итераторы также важны, однако они скорее появляются не независимо, а как операции типов. (В нашем случае в программе для форматировщика итераторы отсутствуют.) Мы определяем типы в четырех областях: вводе, выводе, внутренних структурах данных и индивидуальных элементах данных. Проект для задачи formatсодержит примеры двух из этих типов. Docпредставляет собой абстракцию для вывода, a lineесть абстрактная структура данных. Абстракция слова могла быть и абстрактным элементом, но мы решили, что в этом нет необходимости. Однако в будущем может потребоваться абстракция команды, особенно в том случае, если пользователям будет разрешено создавать свои собственные команды.
Инкапсуляция ввода и вывода позволяет нам осуществлять ввод или вывод в терминах абстрактных величин. Ввод часто выполняется при помощи процедур или итераторов, поскольку мы обычно просто считываем следующий элемент. Удобно использовать буферизацию, поддерживаемую потоками. Абстракция данных позволяет осуществить более гибкую буферизацию, а также ввести или вывести следующий элемент в удобный для нас момент. Мы используем в docоба этих свойства.
По мере создания проектов реализаций мы можем использовать наброски программ в качестве руководства для дальнейшего изучения абстракций. Эти наброски пишутся на естественном языке и содержат описания подзадач, которые необходимо разработать. Мы можем указать для каждой подзадачи, какая процедура, итератор или тип должны быть использованы и каковы должны быть аргументы. В отличие от списка задач, с которого начинается проектирование, в данном случае порядок создания подзадач весьма существен. Подобного рода набросок был сделан при изучении операции add_word.
При создании вспомогательных абстракций мы беспокоились только о концепциях: подробности и детали были определены недостаточно. На втором шаге проектирования уточняем эти подробности и документируем их посредством спецификаций. В процессе этой работы обычно обнаруживаются неучтенные ситуации, в особенности ситуации, приводящие к ошибке, и ситуации завер-
296 Глава 13
шения работы программы (например, окончание потока входных данных). Мы можем обнаружить пропущенные аргументы или результаты или обнаружить, что аргументы или результаты имеют? неправильный тип. Для типов данных мы может обнаружить пропущенные операции. Более того, если абстракция получилась слишком сложной, то мы можем обнаружить это при попытке написания спецификации. Сложная спецификация, естественно, приводит к попытке ее упрощения. Результатом обычно является более простая абстракция.
После создания точных спецификаций для вспомогательных абстракций мы документируем принятые решения в рабочем журнале проектировщика, расширяя диаграмму модульной зависимости, добавляя или расширяя определения вспомогательных абстракций и иногда записывая описание реализации целевой абстракции. Эта информация может быть опущена для процедур и итераторов, за исключением того случая, когда реализация нетривиальна. Для типов данных необходимо всегда приводить набросок реализации, инварианта представления и функции абстракции.
Мы еще не рассматривали вопрос о том, насколько подробным должен быть процесс проектирования. На каждом шаге анализ должен быть подробен до такой степени, чтобы дать возможность определить и специфицировать все вспомогательные абстракции. Большей детализации не требуется. Например, в абстракцииdo.nextJineмы не обсуждаем проблему сканирования, поскольку очевидно, что это легко может быть сделано при помощи операций с потоком. В противоположность этому проектирование абстракции docпроводилось очень подробно. В действительности в избыточных подробностях необходимости не было. Например, нам не нужно было анализировать подробности процесса создания страниц. Если бы мы не делали этого анализа, то получить набросок представления, инварианта представления и функции абстракции было бы гораздо легче. В таких случаях на этапе реализации приходится выполнять больше работы (локального характера), например для определения в представлении абстракции docточных значений для Нпепо и pageno,и, возможно, даже ввести и специфицировать внутреннюю процедуру outputJine.(Фактически, приведенные в приложении Б реализации docи lineиспользуют дополнительные внутренние программы.)
Как только цель изучена достаточно, мы можем перейти к следующему шагу и выбрать следующую цель. Сначала выдвигаются кандидаты: к ним относятся те абстракции, чьи реализации еще не были изучены, но для которых были проанализированы использующие их абстракции. После этого выбирается один из кандидатов. В этом выборе можно руководствоваться только рекомендациями, поскольку какие-либо правила отсутствуют. Например,
': Проектирование
•требование изучения в первую очередь абстракций, находящихся
-наодном уровне, расположенном выше других, отсутствует. Вместо этого необходимо руководствоваться здравым смыслом, преследуя цель завершить проектирование как можно быстрее. По этой причине в первую очередь мы анализируем абстракции, вызывающие сомнения, и даже изучаем их еще в незавершенном виде.
Если в процессе рассмотрения реализации какой-либо целевой абстракции мы обнаруживаем ошибку в самой абстракции, ее необходимо исправить до перехода к следующей целевой абстракции. Для выявления всех реализаций, подверженных влиянию данной ошибки, используются дуги на диаграмме модульной зависимости. Затем мы исправляем проект этих реализаций. В процессе исправлений могут быть обнаружены другие ошибки, которые также должны быть исправлены.
Например, предположим, что мы забыли передать аргумент отfrom_rightк line$justify.Эта ошибка может быть обнаружена в процессе принятия решения о том, что делать с дополнительными пробелами. Путем анализа диаграммы модульной зависимости мы можем обнаружить ту реализацию, которую необходимо пересмотреть. Поэтому мы возвращаемся назад, исправляем ошибку (возможно, что мы забыли включить соответствующую информацию в представление абстракции doc),исправляем спецификациюjustifyи продолжаем работу с реализацией для line.
Если кандидаты отсутствуют, то обычно на этом проектирование завершается. Имеется, однако, один особый случай. Если две или более абстракции взаимно рекурсивны, то ни одна из них не может быть кандидатом, поскольку каждая использует еще не исследованную абстракцию. При работе со взаимно рекурсивными абстракциями мы должны быть особенно осторожны. Одна из них должна быть выбрана в качестве кандидата и исследована первой. Поскольку этот кандидат используется другой, еще не изученной абстракцией, то в дальнейшем мы можем обнаружить, что ее поведение не соответствует требуемому. Эта проблема служит еще одним указанием на то, что взаимная рекурсия должна использоваться с осторожностью.
Результатом процесса проектирования является рабочий журнал проектировщика, содержащий полную диаграмму модульной зависимости, а также разделы для каждой абстракции. Важно отметить, что этот журнал содержит не только решения, но и обоснования к ним. В идеальном случае эта часть документации должна объяснять, какие проблемы решаются какой-либо конкретной структурой, а такжето, каких порождаемых исследованными в процессе проектирования структурами проблем удалось избежать.
Поскольку подобная документация сложна и требует много времени для своего создания, то ею часто пренебрегают. Однако на более поздних стадиях создания программного обеспечения
298 Глава 13
часто возникает ситуация, требующая пересмотра или изменения счруктуры проекта. Новое решение лучше всего может быть сделано лишь тем, кто полностью понимает весь проект. Документация дает возможность разобраться в этом вопросе не только одним оригинальным проектировщикам, но и тем из них, кто со временем уже многое забыл.
Отметим, что проектирование представляет собой процесс, распространяющийся сверху вниз; т. е. мы всегда решаем, каким образом достичь того, что мы хотим. Четко придерживаясь этой цели (требующейся нам программы), мы можем на пути к решению свободно использовать свою интуицию. Опыт написания программ развивает у программистов интуицию относительно того, что может быть реализовано и с какой эффективностью. Также расширяются представления о том, какие программные структуры подходят для решения различных задач. Эти знания оказывают существенное влияние на процесс проектирования сверху вниз.
Альтернативой проектированию сверху вниз является подход «снизу вверх». При этом разработчик отталкивается от того, что может быть реализуемо, двигаясь к тому, что должно быть реализовано. Подход снизу вверх удобен лишь для очень небольших программ. Для больших программ разрыв между доступным и желаемым весьма велик и должен быть устранен введением большого числа абстракций. Этот разрыв удобнее ликвидировать через подход сверху вниз, поскольку мы можем сосредоточить свое внимание на том, что менее всего понятно (требуемая программа), и воспользоваться тем, что .нам уже известно. При проектировании сверху вниз мы .интуитивно чувствуем, двигаемся ли мы в правильном направлении: вспомогательные абстракции легче реализовывать, чем целевую. При проектировании снизу вверх оценить успех работы гораздо труднее, поскольку труднее оценить, насколько мы близки к желаемому результату.
В действительности обычно используются оба подхода. Например, мы можем исследовать ключевые абстракции или недоопределенные абстракции даже в том случае, когда все их функции целиком не определены. Важно, однако, отметить, что проектирование должно начинаться сверху и что при этом необходимо избегать преждевременной реализации абстракций. Слишком ранняя реализация является чистой потерей времени, поскольку шансы на то, что все детали будут разработаны правильно, весьма малы.
13.10. Заключение
В этой главе рассматривалась задача проектирования программы. Проектирование начинается с модульной декомпозиции, основанной на выделении полезных абстракций. Мы обсудили то,
! Проектирование
как производится эта декомпозиция, и проиллюстрировали процесс проектирования на примере. Также был рассмотрен метод документирования с применением рабочего журнала проектировщика.
В качестве примера был использован простой форматировщик текста, а результирующая программа получилась небольшой, состоящей из шести модулей. Сам процесс проектирования был довольно нереалистичный, поскольку в ходе работы мы не делали ошибок. В реальной ситуации любой проект, даже простой программы, подобной форматировщику, состоит из последовательности приближений, в процессе которого вносится и исправляется множество ошибок. Тем не менее рассмотренные нами базовые методы по-прежнему применимы и даже к гораздо большим программам. Мы сами использовали их в больших программах.
Мы не беремся утверждать, что проект форматировщика является лучшим из возможных. В действительности цель процесса проектирования никогда не предполагает создание «лучшего» проекта. Вместо этого предполагается «адекватный» проект, удовлетворяющий поставленным целям и требованиям и имеющий приемлемую структуру. Этот вопрос будет рассмотрен в следующей главе^
Дополнительная литература
Bentley, Jon L., 1982. Writing Efficient Programs. Englewood Cliffs, N. J.: Prentice-Hall,
Hester, S. D., David L. Parnas, and D. F. Utter, 1981. Using documentation as a software design medium. Bell System Technical Journal 60 (8): 1941—1977.
Kernighan, Brian W., and P. L. Plauger, 1981. Software Tools in Pascal. Reading, Mass.: Addison-Wesley.
Parnas, David L., 1972. On the criteria to be used in decomposing systems into modules. Communications of the ACM 15 (12): 1053—1058.
Упражнения
13.1. Спроектируйте и реализуйте программу KWIC, спецификация которой приведена в гл. 12 и упражнении 1 гл. 12,
13.2. Разработайте и реализуйте программу xref, описанную в упражнении 2 гл. 12.
13.3. Организуйте группу из трех человек и реализуйте сравнительно большую программу, Одной из возможных может быть описанная в приложении В программа Trivicalc.