Абстрактность
Важным достижением было изобретение "языка высокого уровня". Это опять была программа-переводчик, но более мощная. Высокоуровневые языки делают возможным для программистов записывать выражения вида:
X = Y ( 456 / A ) - 2
которые сильно похожи на алгебраические. Благодаря языкам высокого уровня инженеры, а не только странноватые бит-жокеи, смогли начать писать программы. BASIC и FORTRAN -- примеры высокоуровневых языков.
Очевидно, что языки высокого уровня мощнее, чем ассемблеры, поскольку каждая инструкция может компилироваться в десятки машинных команд. Но что более важно, эти языки уничтожают линейное соответствие между исходным текстом и результирующим машинным кодом.
Реальные инструкции зависят от каждого "выражения" в исходном тексте, взятом как единое целое. Операторы вроде + и = сами по себе не имеют смысла. Они -- просто часть сложной символики, которая зависит от синтаксиса и позиции оператора в тексте.
Это нелинейное, зависящее от синтаксиса соответствие между исходным текстом и реальным (обектным) кодом обычно рассматривается как неоценимый вклад в прогресс методологии программирования. Но, как мы увидим впоследствии, такой подход неизбежно предоставляет больше ограничений, чем свободы.
Автоматическая передача данных
Во-вторых, передача данных происходит сама собой. Механизм, производящий такой эффект -- это стек данных Форта. Форт автоматически загружает числа на стек; слова, требующие на входе числа, автоматически снимают их оттуда; слова, выдающие на выходе значения, автоматически кладут их на стек. Слова ПОЛОЖИТЬ-НА-СТЕК и ВЗЯТЬ-СО-СТЕКА в Форте на высоком уровне не существуют.
Таким образом, мы можем написать:
: ВЫПОЛНИТЬ ПОЛУЧИТЬ-С ПРЕОБРАЗОВАТЬ-в-D ВЫДАТЬ-D ;
где ПОЛУЧИТЬ-С считывает "С" и оставляет его на стеке, ПРЕОБРАЗОВАТЬ-в-D берет "С" со стека, преобразует и оставляет на стеке как "D". Наконец, ВЫДАТЬ-D берет "D" со стека и записывает его. Форт скрывает акт передачи данных в нашем коде, позволяя сконцентрироваться на функциональных шагах преобразования информации.
Вследствие использования стека для передачи данных слова могут вкладываться в другие слова. Любое слово может положить числа на стек и взять их назад, не нарушая поток данных между вышестоящими словами (разумеется, если оно не забирает или оставляет на стеке какие-нибудь неожиданные значения). Таким образом, стек обеспечивает структурированное, модульное программирование, в то же время предоставляя простой механизм передачи локальных аргументов.
Форт убирает из наших программ детали о том, `как` вызываются слова и `как` передаются данные. Что же остается ? Остаются только слова, описывающие нашу задачу.
Имея слова, мы можем полностью воспользоваться рекомендациями Парнаса -- разбивать задачу в соответствии с частями, которые могут измениться, и формировать каждый "модуль" из стольких маленьких функций, сколько их потребуется для упрятывания информации об этом модуле. На Форте мы можем написать для этого столь много слов, сколько для этого нужно, независимо от простоты каждого из них.
Строка из типичной для Форта задачи может выглядеть так:
20 ПОВЕРНУТЬ ВЛЕВО БАШНЯ-ТАНКА
Немногие другие языки могут вдохновить Вас на сочетание подпрограммы с именем ВЛЕВО, простo модификатором, с подпрограммой БАШНЯ-ТАНКА, просто именем части аппаратуры.
Поскольку слово Форта легче запустить, чем подпрограмму (просто по имени, без специального вызова), программа на Форте может быть разбита на большее количество слов, нежели обычная -- на подпрограммы.
Автоматические вызовы
Во-первых, вызовы производятся автоматически. Нет необходимости писать CALL ОВСЯНКА, достаточно просто ОВСЯНКА. В Форте определение ОВСЯНКА "знает", какого типа словом оно является и какую процедуру надо вызвать для исполнения себя.
Таким образом переменные и константы, системные функции, утилиты, так же, как и определенные пользователем команды или структуры данных, могут быть "вызваны" просто по имени.
Функциональная мощность
Первый фактор -- нечто, называемое "функциональной мощностью" -- выражает единообразие назначения всего внутри модуля. Если все эти выражения в совокупности могут быть представлены как выполняющие единую задачу, то они являются функционально ограниченными (связными).
В общем случае можно сказать, являются ли выражения в модуле функционально ограниченными, отвечая на следующие вопросы: первый: можно ли описать их назначение одной фразой ? Если нет, то модуль, скорее всего, не ограничен функционально. Далее дать ответ на следующие четыре вопроса:
Является ли описание составным предложением ? Встречаются ли в нем слова -- описатели времени, такие, как "сначала", "затем", "потом" и т.д. ? Используется ли после глагола существительное общего или неспециального назначения ? Есть ли в нем слова типа "инициализировать", предполагающие выполнение множества различных функций одновременно ?
Если Вы ответите "да" на один из этих вопросов, то перед Вами некоторое менее связное построение, нежели функциональный модуль. Слабые формы связи:
`Совпадающая связность` (выражения встречаются несколько раз в одном модуле) `Логическая связность` (в модуле содержится несколько родственных функций и необходим флаг или параметр для решения о том, какую конкретно выполнять) `Связность по времени` (имеется группа выражений, исполняющихся одновременно, например, инициализация, но не имеющих иной связи) `Коммуникационная связность` (в модуле содержится группа выражений, работающих с одним и тем же набором данных) `Последовательная связность` (когда результат одного выражения служит входными данными для следующего)
Наш модуль "приготовление-овсянки" демонстрирует функциональную связность, поскольку его можно представить как единое целое, несмотря даже на то, что он состоит из нескольких подчиненных задач.
Философия Форта
Форт является языком и операционной системой. Но это не все: он также и воплощение философии. Обычно философию не рассматривают как нечто, отдельное от Форта. Она не предшествовала Форту и не описывалась где-либо вне рассуждений о Форте, и даже не имеет другого имени, кроме как "Форт".
Что она такое ? Как ее можно применять для решения задач программирования ?
Перед тем, как ответить на эти вопросы, давайте сделаем 100 шагов назад и изучим некоторые из важнейших философий, развитых учеными-компьютерщиками на протяжении многих лет. Проследив траекторию этих достижений, мы сравним -- и отделим -- Форт от этих принципов в программировании.
Иерархическое проектирование по принципу "вход-обработка-выход"
Третья составляющая структурированной разработки касается процесса проектирования. Разработчикам рекомендуется использовать подход "сверху-вниз", но уделять при этом меньше немедленного внимания на управляющие структуры. "Разработка принятия решений" может подождать до более детализированной проработки модулей. Вместо этого на ранней стадии следует концентрировать усилия на иерархии внутри программы (какие модули вызывают какие модули) и на передаче данных от модуля к модулю.
Рис.1-6. Представление структурной диаграммы, из статьи "Структурированная разработка". (Structured Design, IBM Systems Journal).
+-----------+ | ВЫПОЛНИТЬ | +--+----+---+ | | +-------+---------------|----+--+-------------+-----------+ |... |5 |6 ...| 7| ...| +---|--------+ +---|-------|-------+ +--|-------+ | ПОЛУЧИТЬ С | | ПРЕОБРАЗОВАТЬ в D | | ВЫДАТЬ D | +----+-------+ +-------------------+ +---+------+ | | +---+--------+ +---+--------+ |3 |4 |8 |9 +----|-----+ +----|------+ +-------|---+ +-----|--+ | ПОЛУЧИТЬ | | ПРЕОБРАЗ. | | ПРЕОБРАЗ. | | ВЫДАТЬ | | B | | в C | | в E | | E | +----+-----+ +-----------+ +-----------+ +-+------+ | | +-+---------+ +-------------+---+ |1 |2 |10 |11 +--|------+ +--|------------+ +------|--------+ +------|---+ | СЧИТАТЬ | | ПРЕОБРАЗОВАТЬ | | ПРЕОБРАЗОВАТЬ | | ЗАПИСАТЬ | | | | в В | | в F | | | +---------+ +---------------+ +---------------+ +----------+
+--------------+ | вход | выход | |------+-------| 1 | | A | 2 | A | B | 3 | B | | 4 | B | C | 5 | C | | 6 | C | D | 7 | D | | 8 | D | E | 9 | E | | 10 | E | F | 11 | F | | +------+-------+
Для того, чтобы помочь разработчикам думать в этом направлении, было предложено графическое представление, названное "структурными диаграммами". (Несколько измененная форма так называемых "иерархических диаграмм"). Эти диаграммы состоят из двух частей: иерархической схемы и таблицы входов-выходов.
На рис. 1-6 показаны эти две части. Главная программа, названная "выполнить", состоит из трех подчиненных модулей, которые, в свою очередь, вызывают другие модули, изображенные под ними. Как видно, при проектировании увеличивается внимание к преобразованию входных данных в выходные.
Числа в иерархической диаграмме соответствуют строкам в таблице входов-выходов. В точке 1 (модуль СЧИТАТЬ) выходом является величина А. В точке 2 (модуль ПРЕОБРАЗОВАТЬ-в-В), на вход подается А, выходом является В.
Быть может, наибольшим вкладом такого подхода является осознание того, что решения о передаче управления не должны доминировать в проекте. Как мы убедимся, поток управления -- это поверхностный аспект проблемы. Мизерные изменения в исходных требованиях могут существенно изменить структуры управления в программе и потребовать многих лет ее углубленного "перекапывания". Но, если проект программы ориентирован на что-то другое, например, на потоки данных, то изменения в планах не будут столь разрушительными.
Форт часто характеризуется как необычный,
Форт часто характеризуется как необычный, совершенно непохожий на любой другой популярный язык программирования -- как по своей структуре, так и по философии. Однако Форт включает в себя многие из принципов, которыми щеголяют большинство современных языков. Структурированная разработка, модульность и упрятывание информации -- среди ключевых слов сегодняшнего дня.
Некоторые новейшие языки близко подходят к духу Форта. Язык С, например, как и Форт, дает возможность программисту определять новые функции либо на С, либо на ассемблере. И, как и Форт, большая часть С определена в терминах функций.
Но Форт расширяет концепции модульности и упрятывания информации в большей степени, чем любой другой современный язык. Форт даже скрывает способ, которым вызываются слова и способ, по которому передаются локальные аргументы.
Результирующий код становится концентрированной смесью слов, чистейшим выражением абстрактного замысла. Как результат, Форт-программисты обычно продуктивнее, и пишут более плотные, эффективные и лучше управляемые программы.
Форт может не быть единственным возможным языком. Но я думаю, что подобный язык, если такая вещь возможна, был бы ближе к Форту, чем любой другой современный язык.
Язык проектирования
Форт -- язык для проектирования. Воспитаннику традиционной компьютерной науки такое утверждение кажется противоречивым. "Нельзя проектировать с помощью языка, с его помощью реализуют. Проектирование предваряет реализацию."
Опытные Форт-программисты с этим не соглашаются. Вы можете писать на Форте абстрактный, проектный код и все равно имеете возможность проверить его в любой момент, применяя преимущества разбиения на лексиконы. При продолжении разработки компонент может быть легко переписан на уровне ниже компонентов, которые его используют. Вначале слова могут печатать числа на Вашем терминале вместо управления шаговыми двигателями. Они могут печатать свои собственные имена только для того, чтобы сообщить Вам о своем выполнении. Они вообще могут ничего не делать.
Используя такую технологию, Вы можете писать простую, но проверяемую версию Вашей задачи, а затем успешно изменять и улучшать ее до тех пор, пока не достигнете своей цели.
Другим фактором, делающим возможным проектирование в коде, является то, что Форт, как и некоторые новейшие языки, сокращает последовательность разработки "редактирование- компиляция- тестирование- редактирование- компиляция- тестирование". Вследствие постоянной обратной связи среда окружения становится партнером в созидательном процессе. Программист, использующий обычный язык, редко может достичь того продуктивного образа мышления, которое присуще артистам, если ничто не мешает течению их творческого процесса.
По этим причинам Форт-программисты тратят меньше времени на планирование, чем их коллеги классического толка -- праведники планирования. Для них отсутствие такового кажется безрассудным и безответственным. Традиционные окружения вынуждают программистов планировать, поскольку традиционные языки не готовы к восприятию перемен.
К сожалению, человеческое предвидение ограничено даже при наилучших условиях. Слишком сильное планирование становится непродуктивным.
Конечно, Форт не исключает планирования. Он позволяет создавать прототипы. Конструирование прототипа -- лучший способ планирования, так же, как и макетирование в электронике.
В следующей главе мы увидим, что экспериментирование проявляет себя более надежным в деле приближения к истине, нежели стоительство догадок при планировании.
Литература
O.J. Dahl, E.W. Dijkstra, and C.A.R. Hoare, `Structured Programming`, London, Academic Press, 1972. Niklaus Wirth, "Program Development by Stepwise Refinement," `Communications of ACM`, 14, No.4(1971), 221-27. W.P. Stevens, G.J. Myers, and L.L. Constantine, "Structured Design," `IBM Systems Journal`, Vol.13, No.2, 1974. David L. Parnas, "On the Criteria To Be Used in Decomposing Systems into Modules," `Communications of the ACM`, December 1972. Barbara H. Liskov and Stephen N. Zilles, "Specification Techniques for Data Abstractions," `IEEE Transactions on Software Engineering`, March 1975. David L. Parnas, "Designing Software for Ease of Extension and Construction," `IEEE Transactions on Software Engineering`, March 1979. Dewey Val Shorre, "Adding Modules to FORTH," 1980 FORML Proceedings, p.71. Mark Bernstein, "Programming in the Laboratory," unpublished paper, 1983. James R. Bell, "Threaded Code", `Communications of ACM`, Vol.16, No.6, 370-72. Robert B.K. DeWar, "Indirect Threaded Code," `Communications of ACM`, Vol.18, No.6, 331. Peter M. Kogge, "An Architectual Trail to Threaded-Code Systems," `Computer`, March, 1982. Randy Dumse, "The R65F11 FORTH Chip," `FORTH Dimensions`, Vol.5, No.2, p.25.
Модульность
Существенное движение вперед произошло с внедрением "структурированного программирования", методологии, основанной на том, что, как показал опыт, большие задачи проще решаются, если рассматривать их как совокупность меньших задач [1]. Каждый такой кусочек называется `модулем`. Программы состоят из модулей внутри других модулей.
Структурированное программирование подавляет кашеобразность кода, поскольку процессы переходов прослеживаются только в пределах модуля. Нельзя перепрыгнуть из середины одного модуля в середину другого.
Например, на рис.1-2 показана блок-схема модуля под названием "приготовление завтрака", который состоит из четырех подмодулей. Внутри каждого подмодуля можно найти новый уровень сложности, которую вовсе не нужно показывать на нашем уровне.
Рис.1-2. Проект структурированной программы.
Приготовление завтрака | +------------------------|------------------------+ | +-----------+----------+ | | | Решение: Вы спешите? | | | +----------------------+ | | / \ | | да нет | | +-----------/--------------+ +--\-----------+ | | | Остановиться на холодной | | Сварить яйца | | | | овсянке | | | | | +-------------------\------+ +--/-----------+ | | \ / | | +-\---------/---+ | | | Вымыть посуду | | | +-------|-------+ | +-----------------------------|-------------------+ |
Решение о переходе внутри нашего модуля принимается при выборе между модулем "холодная овсянка" и модулем "яйца", но линии переходов входят только в наружный модуль.
Структурированное программирование имеет три преимущества:
Каждая программа представляется линейной последовательностью содержательных функций, называемых `модулями`. Каждый модуль имеет ровно один вход и ровно один выход. Каждый модуль состоит из одной или нескольких функций, каждая из которых имеет также ровно один вход и ровно один выход и сама может рассматриваться как модуль. Модуль может содержать:
операции или другие модули; структуры принятия решений (выражения типа ЕСЛИ ТО); структуры для организации циклов.
Смысл модулей, имеющих "один вход, один выход", состоит в том, что Вы можете вынуть их, изменить их начинку и вставить обратно без развинчивания остальных соединений в программе. Это означает, что Вы можете попробовать каждый кусок по отдельности. Такое возможно когда Вы точно знаете что имеется при входе в модуль и что наблюдается после выхода из него.
В "приготовлении завтрака" Вы либо останавливаетесь на овсянке, либо варите яйца, но не одновременно. А потом Вы обязательно моете посуду. (Насколько мне известно, некоторые программисты обходят этот последний модуль, переезжая на новую квартиру каждые три месяца.)
Структурированное программирование было изначально задумано как подход к проектированию. Модули были воображаемыми обектами, которые существовали в голове программиста или разработчика и не были частями реального кода. Когда техника структурированного программирования применяется к неструктурированным языкам типа Бейсика, результат получается похожим на то, что показано на рис.1-3.
Рис.1-3. Структурированное программирование на неструктурированном языке.
10 ИНСТРУКЦИЯ 20 ИНСТРУКЦИЯ ' Решить - спешим? 30 ЕСЛИ Н=ВЕРНО ТО ПЕРЕЙТИ К 80 ' если да, то на 80 40 ИНСТРУКЦИЯ 50 ИНСТРУКЦИЯ ' Варка яиц 60 ИНСТРУКЦИЯ 70 ПЕРЕЙТИ К 110 ' на 110 80 ИНСТРУКЦИЯ 90 ИНСТРУКЦИЯ ' Приготовление овсянки 100 ИНСТРУКЦИЯ 110 ИНСТРУКЦИЯ ' Мытье посуды 120 ИНСТРУКЦИЯ
Мощность
Программирование на языке ассемблера характеризуется соответствием "один-в-один" между каждой командой, которую набивает программист, и командой, которую исполняет процессор.
На практике программисты обнаружили, что они часто повторяют одинаковую `последовательность` инструкций вновь и вновь для того, чтобы делать одно и то же в различных частях программы. Было бы приятно завести имена, представляющие собой каждую из таких обычных последовательностей.
Это пожелание было удовлетворено "макроассемблером", более сложным ассемблером, который мог распознавать не только нормальные инструкции, но также специальные имена ("макро"). Для каждого из них макроассемблер транслирует пять или десять машинных команд, представленных этим именем, так, как будто программист написал их все полностью.
Но высокоуровневый ли это язык ?
В нашем кратком историческом обзоре было замечено, что традиционные языки высокого уровня оторвались от ассемблерных языков, устранив не только соответствие `один в один` между командами и машинными операциями, но и соответствие `линейное`. Очевидно, Форт имеет первое отличие, но что касается второго, то порядок слов, используемых в определении, совпадает с порядком, в котором эти команды компилируются.
Выводит ли это Форт из рядов языков высокого уровня ? Перед тем, как ответить, давайте исследуем преимущества Форт-подхода.
Вот что по этому поводу есть сказать у изобретателя Форта Чарльза Мура:
Вы определяете каждое слово так, что ЭВМ знает его значение. Способ, которым она это знает, состоит в том, что при вызове исполняется некоторый последовательный код. Компьютер предпринимает действия сразу по каждому слову. Он не отправляет слово на хранение и не держит его в уме на будущее.
В философском смысле, я думаю, это означает, что машина "понимает" слово. Она понимает слово DUP, быть может, лучше вашего, поскольку в ее мозгах никогда не возникает сомнение по поводу того, что DUP означает.
Связь между словами, имеющими смысл для Вас и имеющими смысл для компьютера, глубока. ЭВМ становится средством для связи между человеческим существом и концепцией.
Одним из преимуществ соответствия между исходным текстом и машинным кодом является огромное упрощение компилятора и интерпретатора. Такое упрощение улучшает работу многих частей системы, как это будет видно из дальнейшего.
С точки зрения методологии программирования, преимущество Форт-подхода состоит в том, что `новые` слова и `новые` синтаксисы могут легко добавляться. Нельзя говорить, что Форт `ищет` слова -- он находит слова и исполняет их. Если Вы добавляете новые слова, Форт с тем же успехом найдет и исполнит их. Нет различия между существующими словами и теми, которые добавили Вы.
Более того, такая "расширяемость" подходит к любым типам слов, а не только к словам-действиям. К примеру, Форт позволяет Вам добавлять новые `компилирующие` слова -- такие, как IF и THEN, которые обеспечивают структурированный поток управления.
Поскольку современные языки программирования дают несколько иное толкование выражения "упрятывание информации", нам придется внести ясность. От чего, от кого мы прячем информацию ? Новейшие традиционные языки (такие как Модула-2) напрягают свои силы для обеспечения упрятывания в модуле информации о его внутренних алгоритмах и структурах данных от других модулей. Целью является достижение независимости модуля (минимальной связности). Создается впечатление, что модули стараются атаковать друг друга как враждебные антитела. Или по-другому, что злобные банды модулей-мародеров выходят на большую дорогу грабить драгоценное семейство структур данных. Это `не` то, чем озабочены мы. Мы понимаем упрятывание информации просто как средство минимизации эффектов от возможного изменения проекта методом локализации тех вещей, которые могут измениться, внутрь каждого компонента. Форт-программисты обычно предпочитают держать свою программу под личным контролем и не использовать технику физического упрятывания структур данных. (Несмотря на это, великолепно простой способ -- всего в три строки исходного текста -- добавления к Форту Модула-подобных модулей был предложен Дьювеем Валь Шорром [7].) Мы обсуждали "модули" только как абстрактные объекты. Но любые высокоуровневые языки имеют аппарат, позволяющий кодировать модули проекта как модули реального кода -- отдельные куски, которым можно дать имена и "вызывать" из других кусков кода. Эти куски называются подпрограммами, процедурами или функциями, в зависимости от языка программирования и способа реализации. Предположим, мы написали "приготовление овсянки" в виде подпрограммы. Это могло бы выглядеть как-нибудь вроде: процедура приготовление-овсянки взять чистую тарелку открыть коробку с овсянкой насыпать овсянки открыть молоко налить молоко взять ложку конец Мы можем также написать и "варку-яиц", и "мытье-посуды" в виде подпрограмм. В этом случае можно определить "приготовление-завтрака" как простую программу, которая вызывает эти подпрограммы: процедура приготовление-завтрака переменная С: булевская (означает спешку) `проверить наличие спешки` если С=истина, то вызвать приготовление-овсянки иначе вызвать варку-яиц конец вызвать мытье-посуды конец Фраза "вызвать приготовление-овсянки" заставляет выполниться подпрограмму с таким именем. Когда выполнение заканчивается, управление возвращается назад в вызывающую программу в точку, следующую за вызовом. Подпрограммы повинуются законам структурированного программирования. Как можно видеть, эффект при использовании подпрограмм такой же, как если бы тело этих подпрограмм присутствовало бы в вызывающем модуле. Но, в отличие от кода, производимого макроассемблером, подпрограмма может быть скомпилирована где угодно в памяти, после чего на нее можно просто ссылаться. Не обязательно компилировать ее внутри реального кода главной программы (см. рис. 1-5). Рис.1-5. Главная программа и подпрограмма в памяти. Главная программа |-----------------------| ____ |_______________________| / \/ Подпрограмма |_______________________| / +-----------------------+ |_______________________| / | Приготовление-овсянки | | вызвать |/ |_______________________| | приготовление-овсянки | |_______________________| |_______________________|\ |_______________________| |_______________________| \ |_______________________| | | \_____/ Один из подходов, существенно опирающихся на использование подпрограмм, называется "постепенной детализацией" [2]. Идея состоит в том, что Вы начинаете с написания скелетной версии программы, использующей естественные имена процедур и структур данных. Затем Вы пишете версии для каждой из именованных процедур. Процесс продолжается в сторону большего уровня детализации до тех пор, пока процедуры не смогут быть выражены непосредственно на компьютерном языке. При каждом шаге программист должен принимать решения об используемых алгоритмах и о типах структур данных, которые обрабатываются этими алгоритмами. Такие решения должны приниматься параллельно. Если выбранный путь оказался непригодным, программист должен набраться мужества вернуться назад насколько требуется и заново проделать работу. Обратите внимание, что при постепенной детализации Вы не можете реально запустить какую-либо часть программы до тех пор, пока не будут написаны ее компоненты самого нижнего уровня. Обычно это значит, что программу нельзя проверить до тех пор, пока она не будет полностью закончена. Заметьте также: постепенная детализация заставляет Вас прорабатывать все детали структур управления на данном уровне перед переходом на следующий уровень вниз. Парнас предлагает два критерия разбиения: возможное (хотя пока и незапланированное) использование и возможное (хотя и незапланированное) изменение. Этот новый взгляд на "модуль" отличается от традиционного. Такой "модуль" -- собрание кусочков, обычно очень маленьких, которые вместе прячут информацию о некоторой стороне проблемы. Двое других авторов описывают ту же идею по-другому, используя выражение "абстрагирование данных" [5]. Они приводят в пример стековую структуру. Стековый "модуль" состоит из процедур для очистки стека, отправки значения в стек, снятия значения оттуда и определения пустоты стека. Этот "многопроцедурный" модуль скрывает информацию о том как оргазизован стек от остальных частей программы. При этом он считается единым модулем потому, что его процедуры взаимозависимы. Нельзя изменить способ загрузки значения в стек без изменения способа снятия его оттуда. Слово `использовать` играет важную роль в этой концепции. Парнас пишет в своей более поздней статье [6]: Системы, достигнувшие определенной степени "элегантности" ... сделали это за счет "использования" одними частями системы других ... Если существует такое иерархическое построение, то каждый уровень представляет собой проверяемый и способный быть использованным подраздел системы ... Проектирование иерархии "использования" может быть одним из главных направлений приложения сил. Расчленение системы на независимо вызываемые подпрограммы должно производиться параллельно с решениями об "использованиях", поскольку они влияют друг на друга. Разработка, при которой модули группируются в соответствии с потоком передачи управления, не готова к быстрым изменениям в проекте. Структура, построенная в соответствии с иерархией управления, является поверхностной. Готовность к восприятию изменений может обеспечить проект, в котором модули формируются по признаку объединения потенциально изменяемых вещей. Наличие большого набора простых слов делает простым использование техники, которую мы назовем "программирование на уровне компонентов". Для пояснения давайте вначале прoсмотрим те объединения, которые мы неопределенно описали как "части, которые могут быть изменены". В типовой системе почти все может быть подвержено изменениям: устройства ввода/вывода, такие, как терминалы и принтеры, интерфейсы типа микросхем ПЛМ, операционные системы, любые структуры данных или их представление, любые алгоритмы и т.д. Вопрос: "Как можно минимизировать вклад каждого из подобных изменений ? Как определить наименьший набор изменяемых вещей для обеспечения требуемых перемен ?" Ответом является: "наименьший набор взаимовлияющих структур данных и алгоритмов, которые разделяют знание о том, как они в совокупности работают." Мы будем называть такое объединение "компонентом". Компонент является ресурсом. Он может быть частью аппаратуры, например, ПЛМ или аппаратным стеком. Или компонент может быть программным ресурсом, таким, как очередь, словарь или программный стек. Все компоненты включают в себя объекты данных и алгоритмы. Не имеет значения, физический ли это объект данных (такой, как аппаратный регистр) или абстрактный (как вершина стека или поле в базе данных). Безразлично, описан ли алгоритм в машинном коде или проблемно-ориентированными словами типа ОВСЯНКА или ЯЙЦА. Рисунок 1-7 сопоставляет результаты структурированной разработки с результатами разработки на уровне компонентов. Вместо `модулей` под названиями ЧИТАТЬ-ЗАПИСЬ, РЕДАКТИРОВАТЬ-ЗАПИСЬ и ПИСАТЬ-ЗАПИСЬ мы сосредоточены на `компонентах`, описывающих структуру записей, реализующих систему команд редактора и обеспечивающих процедуры чтения/записи. Что мы сделали ? Мы ввели новый этап в процесс разработки: разбили на компоненты во время `проектирования`, затем описали последовательность, иерархию и линию вход-обработка-выход при `реализации`. Да, это еще один шаг, однако мы получили дополнительное измерение для проведения разреза -- не только по слоям, но и `в клеточку`. Несмотря на то, что производительность не является главной темой этой книги, начинающий в Форте должен быть убежден в том, что преимущества языка не являются чисто философскими. В целом Форт превышает все другие высокоуровневые языки по скорости работы, возможностям и компактности. Здесь имеются два соображения: размер корневой Форт-системы и размеры скомпилированных задач. Ядро Форта является чрезвычайно гибким. Для встроенных применений часть Форта, необходимая для запуска программы, может уместиться всего в 1 Кбайт. В полной инструментальной среде многозадачная Форт-система с интерпретатором, компилятором, ассемблером, редактором, операционной системой и другими утилитами поддержки занимает около 16 Кбайт. При этом остается много места для задач. (А некоторые Форты на новых процессорах имеют 32-х разрядную адресацию, что позволяет писать невообразимо большие программы.) Точно так же скомпилированные Форт-программы имеют очень маленький размер -- обычно меньше эквивалентных программ на ассемблере. Причиной опять же является шитый код. Каждая ссылка на предварительно определенное слово, независимо от его мощности, использует всего два байта. Одной из наиболее впечатляющих новых областей применения Форта является производство Форт-кристаллов, таких как Форт-микропроцессор Rockwell R65F11 [12]. На кристалле имеются не только аппаратные средства, но также исполняемая часть языка Форт и операционной системы для сложных применений. Только архитектура Форта и его компактность делают возможным создание микропроцессоров, основанных на Форте. Как приступать к разработке подобных модулей ? Методология, называемая "разработкой сверху-вниз", утверждает, что модули следует строить, начиная с самого общего, главного и далее прорабатывать до уровня самых мелких модулей. Последователи такого подхода могут засвидетельствовать возникновение позорно огромных потерь времени в результате ошибок в планировании. На горьком опыте они познали, что попытки корректировать программу после того, как она была написана -- такая практика известна как "наложение заплат" -- подобны попытке запереть двери конюшни после того, как лошадь уже понесла. Поэтому как контрмеру они предлагают следующее официальное правило программирования сверху-вниз: Не писать ни строчки текста до тех пор, пока план не проработан до мельчайших деталей. Вследствие таких трудностей при внесении изменений в однажды написанные программы, все упущения в проекте должны быть устранены на стадии предварительного планирования. В противном случае могут быть затрачены человеко-года усилий по написанию кода, который потом нельзя использовать. Второй догмат структурированной разработки говорит о "сцеплении", меры того, как модули влияют на поведение других модулей. Сильное сцепление считается плохим тоном. В наихудшем случае один модуль изменяет код внутри другого модуля. Опасна даже передача управляющих флагов другим модулям для управления их функциями. Приемлемой формой сцепления является связь через данные, которая предполагает передачу данных (не управляющей информации) из одного модуля в другой. Даже в этом случае системы гораздо легче строить, если интерфейсы передачи данных между модулями по возможности упрощены. Когда данные могут быть доступны со стороны многих модулей (например, глобальные переменные), говорят о сильном сцеплении между модулями. Если программисту нужно изменить один из них, велика опасность того, что другие продемонстрируют "побочные эффекты". Самый надежный способ сцепления данных -- это передача локальных переменных в качестве переметров от одного модуля к другому. В результате вызывающий модуль говорит подчиненному: "Я хочу, чтобы ты использовал данные, которые я загрузил в переменные с именами X и Y, а ответ от тебя я ожидаю в переменной по имени Z. Никто другой больше не использует эти переменные". Как мы уже говорили, обычные языки, поддерживающие подпрограммы, имеют тщательно проработанные методы передачи аргументов от одного модуля к другому. В доисторические программные времена, когда компьютеры были еще динозаврами, простой факт того, что некий гений создал программу, которая правильно работает, вызывал великое удивление. По мере роста цивилизованности ЭВМ это удивление слабело. Руководители желали большего от программистов и их программ. В то время, как стоимость аппаратуры устойчиво падала, стоимость программного обеспечения взмывала ввысь. Для программы было уже недостаточно хорошо просто правильно выполняться. Она должна была быть разработана быстро и обладать легкостью в управлении. Новое качество наряду с корректностью стало важнейшим. Это недостающее качество было названо "элегантностью". В даном разделе мы проследим историю инструментария и технологий, предназначенных для написания более элегантных программ. Несмотря на то, что Форт -- интерпретирующий язык, он исполняет скомпилированный код. Поэтому он работает примерно в десять раз быстрее, чем интерпретирующий Бейсик. Форт оптимизирован для исполнения слов с помощью техники, известной как "шитый код" [9],[10],[11]. Плата за разбиение на модули, состоящие из очень маленьких кусочков кода, сравнительно невелика. Он не работает так же быстро, как ассемблерный код, поскольку внутренний интерпретатор (который обрабатывает список адресов, составляющих каждое определение через двоеточие) может отнимать до 50% времени исполнения слов-примитивов, в зависимости от типа процессора. Но для больших задач Форт очень близко подходит к скорости ассмеблера. Вот три причины: Первая и главная, Форт прост. Использование им стека данных значительно снижает затраты по производительности на передачу аргументов от слова к слову. В большинстве языков передача аргументов между модулями -- одна из основных причин, по которым применение подпрограмм ограничивает их производительность. Второе, Форт позволяет Вам определять слова либо на высоком уровне, либо на машинном языке. В любом случае нет необходимости в специальной вызывающей последовательности. Вы можете писать новое определение на высоком уровне и, убедившись в его правильности, переписать его на ассемблере без изменения какого-либо использующего его кода. В типичной задаче, быть может, 20% кода будет использоваться 80% времени. Только наиболее часто используемые, критичные ко времени алгоритмы нуждаются в машинном кодировании. Форт-система сама во многом реализована на ассемблерных определениях, так что Вам нужно будет воспользоваться аасемблером лишь для нескольких специфических слов. Третье, программы на Форте имеют тенденцию быть лучше спроектированными, чем те, что написаны целиком на ассемблере. Форт-программисты извлекают выгоду из способностей языка к созданию прототипов и испытывают несколько алгоритмов перед тем, как выбрать наиболее подходящий к их потребностям. Поскольку Форт поддерживает изменения, он может также быть назван языком оптимизации. Форт не гарантирует быстроту исполнения. Он лишь дает программисту творческую среду, в которой можно разрабатывать быстродействующие программы. К середине конца 70-х компьютерная индустрия уже перепробовала все описанные нами концепции и все равно оставалась несчастной. Цена поддержки программного обеспечения -- сохранения его функциональности перед лицом возможных изменений -- выливалась более чем в половину его общей стоимости, в некоторых случаях доходя до девяноста процентов ! Все соглашались, что эти издержки можно обычно отнести к неполному анализу программ или плохому замыслу разработчиков. Однако было очевидно, что что-то не так с самим структурированным программированием. Когда проекты появлялись с опозданием, некомплектными или некачественными, разработчики жаловались на то, что все предвидеть невозможно. Ученые мужи прилагали все больше усилий к проекту. "В следующий раз мы все продумаем лучше". К этому времени возникла новая философия, описанная в статье, названной "Структурированная разработка" [3]. Один из ее принципов приводится ниже: Простота -- главный показатель, по которому рекомендуется выбирать среди альтернативных проектов для обеспечения снижения времени на отладку и модификацию. Уровень простоты может быть улучшен за счет разбиения системы на отдельные куски так, чтобы каждый из них мог рассматриваться, применяться, утверждаться и изменяться с минимальным влиянием на или изменениями в других частях системы. Разбиение задачи на простые модули должно было облегчить написание, изменение и понимание программ. Но на какой основе производить разбиение данного конкретного модуля ? Статья "Структурированная разработка" выделяет три фактора при проектирования модулей. Следующий шаг вперед, вдохновленный использованием стуктурированных программ -- структурированные языки программирования. Они содержат специальные операторы для управления процессом в составе своих наборов команд, что делает возможным написание программ, имеющих более модульный вид. Таким языком является Паскаль, изобретенный Никлаусом Виртом для обучения студентов принципам структурированного программирования. На рисунке 1-4 показано, как этот тип языка позволяет переписать программу "приготовление завтрака". Рис.1-4. Использование структурированного языка. ИНСТРУКЦИЯ ИНСТРУКЦИЯ Решение - спешим? ЕСЛИ ДА, ТО ИНСТРУКЦИЯ ИНСТРУКЦИЯ Варка яиц ИНСТРУКЦИЯ ИНАЧЕ ИНСТРУКЦИЯ ИНСТРУКЦИЯ Приготовление овсянки ИНСТРУКЦИЯ ДАЛЬШЕ ИНСТРУКЦИЯ Мытье посуды ИНСТРУКЦИЯ Языки структурированного программирования имеют управляющие структурные операторы типа ЕСЛИ и ТО для подчеркивания модульности в организации передачи управления. Как Вы можете заметить, отступы в тексте программы важны для ее читабельности, хотя все равно все инструкции внутри модуля написаны полностью вместо замены модуля его именем (например, "приготовление-овсянки"). Законченная программа может занимать десять страниц, с оператором ИНАЧЕ на странице пять. Большинство компьютеров используют нечто большее, чем просто список инструкций для своей работы. Они также производят проверку различных условий и затем "скачки" в соответствующие части программы в зависимости от результата. Они также производят многократное "зацикливание" на одних и тех же участках кода, обычно контролируя при этом момент выхода из цикла. Как ассемблер, так и высокоуровневый язык обеспечивают возможности для переходов и циклов. В ассемблерах мы используем команды типа "jump" ("прыжок"), в некоторых языках высокого уровня пользуемся конструкциями типа "GO TO" ("перейти к"). Когда эти возможности используются в сильной степени, программы начинают становиться похожими на такую же неразбериху, как на рисунке 1-1. Рис.1-1. Неструктурированный код, использующий инструкции типа "jump" или "GOTO". ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПЕРЕХОД ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД Этот подход, до сих пор широко представленный в таких языках, как Фортран и Бейсик, создает трудности при написании и трудности при внесении изменений. При такой "кашеобразной" манере написания программ невозможно протестировать отдельный участок кода или найти почему выполняется что-то, что выполняться не должно. Трудности с кашевидными программами привели к открытию "блок-схем". Это были нарисованные карандашом и ручкой картинки, показывающие "течение" процесса, которые использовались программистом в качестве шпаргалки для понимания создаваемого кода. К несчастью, программист вынужден был осуществлять переход от кода к диаграмме и наоборот вручную. Многие программисты осознали бесполезность старомодных диаграмм. В работе [4], опубликованной еще в 1972 году, д-р Дэвид Л. Парнас показал, что критерием для разбиения на модули должны быть не шаги в процессе, а куски информации, которые, возможно, будут меняться. Модули должны использоваться для сокрытия такой информации. Давайте рассмотрим эту важную идею об "упрятывании информации": предположим, Вы пишете Руководство по делопроизводству для своей компании. Вот его фрагмент: Отдел продаж принимает заказ посылает синюю копию в архив оранжевую копию на склад Джей подшивает оранжевую копию в красный скоросшиватель на своем столе и производит упаковку. Все согласны, что эта процедура корректна, и Ваше руководство распространяется для всех в компании. А потом Джей увольняется, а приходит Мэрилин. Новые копии приказов имеют зеленую и желтую обложки вместо синей и оранжевой. Красный скоросшиватель переполняется и уступает место черному. Все Ваше руководство становится устаревшим. Вы могли бы избежать устаревания, применяя слово "упаковщик" вместо имени "Джей", словосочетания "архивная копия" и "складская копия" вместо "синей" и "оранжевой" и т.д. Этот пример иллюстрирует мысль о том, что для сохранения корректности перед лицом возможных изменений произвольные детали должны быть исключены из процедур. Они могут быть при необходимости описаны отдельно. К примеру, каждую неделю или около того отдел кадров может издавать список работников и их должностей, так что каждый при необходимости может узнать имя упаковщика из единого для всех источника. При изменении кадрового состава этот список должен будет меняться. Такая техника очень важна при написании программного обеспечения. Почему же работающая уже программа должна быть когда-нибудь изменена ? По любой из миллиона причин. Вам может понадобиться запустить ее на новом оборудовании, программа должна быть изменена только для того, чтобы приспособиться к этому оборудованию. Ей, может быть, необязательно быть чрезвычайно быстрой или мощной для того, чтобы удовлетворить использующих ее людей.
Мы уже отмечали две особенности Форта, обеспечивающие использование описанной методологии -- автоматические вызовы и автоматическую передачу данных. Третья особенность позволяет описывать структуры данных внутри компонента в терминах предварительно описанных компонентов. Эта особенность -- прямой доступ к памяти. Предположим, что мы определяем переменную ЯБЛОКИ: VARIABLE ЯБЛОКИ Мы можем записать число в эту переменную для указания того, сколько яблок имеется в текущий момент: 20 ЯБЛОКИ ! Мы можем распечатать содержимое переменной: ЯБЛОКИ ? 20 ok ~~~~~~~ Мы можем увеличить ее содержимое на единицу: 1 ЯБЛОКИ +! (Новичок может изучить механизм работы этих фраз по .) Слово ЯБЛОКИ имеет единственную функцию: положить на стек `адрес` в памяти, где хранится количество яблок. О количестве можно думать как о "вещи", в то время как о словах, устанавливающих количество, считывающих или увеличивающих его -- как о "действиях". Форт удобно отделяет "вещи" от "действий", поскольку разрешает передачу адресов через стек и имеет команды "разыменования" и "загрузки". Мы обсуждали важность проектирования по признаку того, что может измениться. Предположим, мы написали множество кода, использующего переменную ЯБЛОКИ. И теперь, в одиннадцатом часу, обнаруживаем, что необходимо отслеживать два различных типа яблок -- красных и зеленых! Не стоит опускать руки, лучше вспомнить функцию слова ЯБЛОКИ: давать адрес. Если нам нужно два различных количества, ЯБЛОКИ могут давать два различных адреса, в зависимости от того, о каком типе яблок мы говорим. Так мы можем определить более сложную версию слова ЯБЛОКИ, как показано ниже: VARIABLE ЦВЕТ ( указатель на текущую переменную) VARIABLE КРАСНЫЕ ( количество красных яблок) VARIABLE ЗЕЛЕНЫЕ ( количество зеленых яблок) : КРАСНЫЙ ( тип яблок - красные) КРАСНЫЕ ЦВЕТ ! ; : ЗЕЛЕНЫЙ ( тип яблок - зеленые) ЗЕЛЕНЫЕ ЦВЕТ ! ; : ЯБЛОКИ ( -- адр текущей яблочной переменной) ЦВЕТ @ ; Форт может делать все, что могут другие языки -- но обычно проще. На нижнем уровне почти все Форт-системы имеют ассемблеры. Они поддерживают структурные операторы передачи управления для организации проверки условий и циклов, использующих технику структурированного программирования. Они обычно позволяют Вам писать подпрограммы обработки прерываний -- Вы можете даже при желании писать их тело в высокоуровневом коде. Форт может быть написан для работы под управлением любой операционной системы, такой как RT-11, CP/M или MS-DOS -- или, для тех, кто это предпочитает, Форт может быть написан как самодостаточная операционная система со своими драйверами терминала и дисков. С помощью кросс-компилятора Форта или целевого компилятора вы можете создавать новые Форт-системы для того же или для разных компьютеров. Поскольку Форт написан на Форте, Вы имеете невиданную возможность переписывать операционную систему в зависимости от нужд Вашей задачи. Или Вы можете переместить различные версии задачи на ряд систем. (*) - игра слов: Форт (FORTH) означает "вперед" (англ.) (здесь и далее примечания переводчика). В этом разделе мы рассмотрим основные свойства языка Форт и сравним их со свойствами традиционных методологий. Вот пример текста на Форте: : ЗАВТРАК СПЕШИМ? IF ОВСЯНКА ELSE ЯЙЦА THEN МЫТЬЕ ; Он по структуре идентичен процедуре "приготовление-завтрака" на стр.8. (Если Вы -- новичок в Форте, обратитесь к за объяснениями.) Слова СПЕШИМ?, ОВСЯНКА, ЯЙЦА и МЫТЬЕ также заданы (что наиболее вероятно) как определения через двоеточие. Здесь Форт демонстрирует все положительные качества, изученные нами: мнемонические обозначения, абстракцию, мощность, структурированные операторы передачи управления, сильную функциональную ограниченность, небольшую степень связности и модульность. Но, кроме модульности, подчеркнем еще то, что, быть может, является наиболее важной заслугой Форта: Мельчайшим атомом программы на Форте является не модуль или подпрограмма или процедура, а "слово". Далее, отсутствуют понятия подпрограмм, главных программ, утилит или операторов, вызываемых по отдельности. Все в Форте есть слова. Перед тем, как мы изучим важность среды, основанной на словах, давайте остановимся на двух новшествах Форта, которые делают это возможным. Первые программы для ЭВМ выглядели как-то вроде: 00110101 11010011 11011001 Программисты вводили их, устанавливая ряды переключателей в положение "вкл." для единиц и "выкл." для нулей. Эти значения были "машинными инструкциями" для ЭВМ, и каждая заставляла ее производить некие элементарные операции типа "переместить содержимое регистра А в регистр Б" или "добавить содержимое регистра В к содержимому регистра А". Это оказалось несколько скучноватым. Скука -- мачеха изобретения, поэтому некоторые умные программисты осознали, что машину и саму можно затставить помочь им. Так они написали программу, которая переводила легкозапоминаемые аббревиатуры в труднозапоминаемые последовательности битов. Новый язык выглядел примерно так: MOV B,A ADD C,A JNC REC1 Переводчик (транслятор) программ был назван `аcсемблером`, а новый язык -- `языком ассемблера`. Каждая инструкция "собирала" ("ассемблировала") соответствующую последовательность битов для себя при сохранении точного соотношения между ассемблерной инструкцией и машинной командой. Но ведь имена программистам запоминать легче. По этой причине новые инструкции были названы `мнемониками`. Как мы видели, Форт интегрирует аспекты проектирования с вопросами реализации и поддержки. В результате этого упоминание о "типичном цикле разработки" звучит примерно так же, как и упоминание о "типичном шуме". Однако любой подход лучше, чем отсутствие подхода и, разумеется, некоторые из подходов разработаны лучше других. Вот цикл разработки, представляющий некий "средний" из наиболее успешных способов, применяемых в программных проектах: `Анализ` Установка требований и ограничений Построение концептуальной модели решения Оценка цены/графика работ/производительности `Конструирование` Предварительная разработка Детальная проработка Реализация `Использование` Оптимизация Устранение ошибок и отладка Поддержка В этой книге мы рассматриваем первые шесть стадий цикла, делая акцент на анализе, проектировании и реализации. В Форт-проекте перечисленные фазы появляются на нескольких уровнях. Глядя на проект в самой широкой перспективе, можно сказать, что каждый из трех шагов может занимать месяц или более. Одна стадия сменяет другую, как времена года. Но Форт-программисты применяют те же самые фазы при определении каждого слова. В этом случае цикл повторяется в течение минут. Разработка программы с таким быстрым повторением программного цикла известна как использование "итеративного подхода". Итеративный подход был красноречиво описан Кимом Харрисом [1]. Он начинает с определения научного метода: ... бесконечный цикл открытий и улучшения. Он вначале изучает естественную систему и собирает сведения о ее поведении. Затем наблюдения моделируются для выработки теории об естественной системе. Далее инструменты анализа применяются к модели, что позволяет выдать предсказания о поведении реальной системы. Проводятся эксперименты с тем, чтобы сравнить истинное поведение с предсказанным. Природная система вновь изучается, и модель пересматривается. `Целью` метода является выработка модели, которая в точности предсказывает все обозримое поведение естественной системы. Любой, кто скажет Вам, что существует некоторое определенное количество фаз в цикле разработки программного обеспечения, будет глупцом. И все же ...
Вам также не составляет труда добавить оператор выбора по множеству вариантов или циклическую структуру со множеством выходов, если они Вам понадобятся или, что тоже важно, убрать их, если они не нужны.
В противоположность этому любой язык, в котором для понимания выражения важен порядок слов, должен "знать" все слова и все их допустимые комбинации. Шансы на то, что в них предусмотрены все удобные для Вас комбинации, малы. Язык таков, каким его создали, Вам нельзя расширить его знания.
Исследователи в лабораториях называют гибкость и расширяемость Форта среди его наиболее важных преимуществ для их работы. Можно разрабатывать лексиконы для сокрытия информации об огромном разнообразии тестового оборудования, присоединенного к компьютеру. Когда такая работа проделана более опытным программистом, исследователи могут использовать свой "программный инструментарий" маленьких слов для написания простых экспериментальных программ. При появлении нового оборудования добавляются новые лексиконы.
Марк Бернстейн описал проблему использования готовой целевой библиотеки процедур в лаборатории [8]: "Компьютер, а не пользователь, доминирует в эксперименте". Но, как он пишет, с помощью Форта "компьютер действительно подвигает ученых на изменение, исправление и улучшение программного обеспечения, для экспериментирования и изучения особенностей своего оборудования. Инициатива снова становится прерогативой исследователя."
Тех, кто упорствует в том, что Форт нельзя назвать языком высокого уровня, последний снабжает дополнительными аргументами. В то время, когда мощная проверка на синтаксис и типы данных становится одним из главных направлений в современных языках программирования, Форт вообще почти не производит синтаксический анализ. Предоставляя ту гибкость и свободу, которую мы описали, он не может указать Вам, что Вы собирались написать КРАСНЫЙ ЯБЛОКИ вместо ЯБЛОКИ КРАСНЫЙ. Ведь Вы сами придумали такой синтаксис !
Зато Форт более чем искупает это упущение, позволяя Вам компилировать каждое определение по отдельности и в течение считанных секунд.Вы обнаруживаете свою ошибку достаточно быстро -- когда Ваше определение не срабатывает. Кроме того, при желании Вы можете добавить в свои определения подходящие синтаксические проверки.
Кисть артиста не может защитить его от ошибки, художник сам будет судить об этом. Сковорода повара и рояль композитора остаются простыми и производительными. Зачем же позволять языку программирования пытаться быть умнее Вас ?
Так является ли Форт высокоуровневым языком ? По вопросу проверки синтаксиса он не проходит. По вопросу уровня абстрагирования и мощности он кажется языком `безграничного` уровня -- поддерживающим все -- от манипуляции с битами в порту вывода до задач бизнеса.
Решаете Вы. (Форту все равно.)
От кого прятать ?
Подпрограммы
Годами ученые-компьютерщики совершенствовались в искусстве использования многочисленных маленьких подпрограмм в сильно разветвленных, протяженных программах. Они могут быть написаны и отлажены независимо друг от друга. Это облегчает повторное использование ранее написанных программ, и так легче распределять части работы между различными программистами. Короткие куски проще продумывать и проверять их правильность.
Когда подпрограммы компилируются в отдельных частях памяти и вызываются ссылками на них, можно использовать одну и ту же подпрограмму много раз без излишнего расходования места на повторы кода подпрограммы. Так разумное использование подпрограмм может уменьшать размеры кода.
К сожалению, при этом имеется проигрыш в скорости исполнения. Проблему создает необходимость сохранения содержимого регистров перед переходом на подпрограмму и их восстановления при возвращении оттуда. Еще больше времени требуют невидимые, но существенные участки кода, необходимые для передачи параметров в и из подпрограммы.
Существенен также сам способ вызова и передачи параметров подпрограмме. Для автономного тестирования подпрограммы приходится писать специальные тестовые программы для ее вызова.
По этой причине ученые рекомендуют умеренное использование подпрограмм. На практике они обычно получаются довольно большими, от половины до целой страницы текста в длину.
Постепенная детализация
Поверхностность структуры
Программирование на уровне компонентов
Рис.1-7. Структурированная разработка против разработки на уровне компонентов.
Последовательное/иерархическое проектирование:
+-----------------+ | обновить-запись | +-----------------+ | +---------------+--------------------+ | | | +-------|--------+ +----|-----------------+ +|--------------+ | СЧИТАТЬ-ЗАПИСЬ | | РЕДАКТИРОВАТЬ-ЗАПИСЬ | | ПИСАТЬ-ЗАПИСЬ | +----------------+ +----------------------+ +---------------+
Разработка по компонентам:
: ОБНОВИТЬ-ЗАПИСЬ ЗАПИСЬ ЧИТАТЬ РЕДАКТИРОВАТЬ ЗАПИСЬ ПИСАТЬ ; | \ | / | | \_______|___________/__ | | _______________|__________/ \ | | / | \ | +------|-/--+ +-----|----+ +----\----|-----+ | структура | | редактор | | программы | | записей | | | | записи/чтения | +-----------+ +----------+ +---------------+
Представим себе, что после того, как программа была написана, нам потребовалось изменить структуру записи. При последовательном, иерархическом проектировании это затронет все три модуля. При проектировании на уровне компонентов изменение коснется лишь компонента, описывающего структуру записи. Ничто другое, использующее этот компонент, не должно знать о перемене.
В дополнение к перечисленному преимущество такой схемы состоит в том, что программисты в группе могут получить в разработку индивидуальные компоненты с меньшей взаимозависимостью. Принцип программирования компонентов применим к руководству группой так же, как и к разработке программ.
Мы будем называть набор слов для описания компонента "лексиконом". (Одно из значений слова "лексикон" -- "набор слов, относящийся к определенному кругу интересов".) Лексикон -- это Ваш наружный интерфейс с компонентами (рис. 1-8).
Рис.1-8. Kомпонент описывается лексиконом.
ВЕЩЬ ДУХАЙКИ ВЕРТЕТЬ РАЗДВИГАТЬ ДЕЛО ТЕМА ПИХАТЬ ЦЕПЛЯТЬ ТОВАР ЧТОЭТО | КРУТИТЬ / | | \ / / | | \ / | +-|--\--------/----/--|-----------|--\---------/-----|------+ | |Объекты данных | | Алгоритмы (действия) | | | (вещи) / | ... \ | | | | \ * ------ ---> /| | | | /\ +---+ / \ |----| |
В данной книге слово "лексикон" относится только к тем словам компонента, которые используются по имени вне этого компонента. Компонент может содержать также определения, написанные исключительно для поддержки видимого снаружи лексикона. Мы будет называть вспомогательные определения "внутренними" словами.
Лексикон дает логические эквиваленты объектам данных и алгоритмам в форме имен. Лексикон вуалирует структуры данных и алгоритмы компонентов -- "как оно работает". Он представляет миру только "концептуальную модель" компонента, описанную простыми словами -- "что оно делает".
Эти слова затем становятся языком для описания структур данных и алгоритмов компонентов, написанных на более высоком уровне. "Что" для одного компонента становится "как" для высших компонентов.
Написанная на Форте задача состоит только из компонентов. Рисунок 1-9 показывает, как может быть разбита робототехническая задача.
Можно даже сказать, что лексикон -- это специализированный компилятор, написанный специально для поддержки кода высокоуровневой программы наиболее эффективным и надежным способом.
Между прочим, сам по себе Форт не поддерживает компоненты. Ему это не нужно. Компоненты -- это продукты разбиения программы ее проектировщиком. (В то же время Форт имеет "блоки" -- небольшие порции массовой памяти для хранения исходных текстов. Компонент обычно может быть написан в пределах одного или двух экранов Форта.)
Рис.1-9. Полная программа состоит из компонентов.
+--------------+ | Сварить кофе | +-/---------\--+ / \ / \ Лексикон Лексикон процессов перемещений робота робота / \ Лексикон Лексикон переключателей считывания / показаний Лексикон датчиков шагового / двигателя / \ / Корневой язык (Форт) \ / +---\------------/---+ | Реальный компьютер | +--------------------+
Важно понять, что лексикон может использоваться любым или всеми компонентами высших уровней. Ни один нормальный компонент `не` прячет свои компоненты поддержки, как это часто случается при послойном подходе к разработке.
Вместо этого каждый лексикон волен использовать все команды, определенные до него. Команда для движения робота опирается на корневой язык, со всеми его переменными, константами, операторами работы со стеком, математикой и др. так же сильно, как и на любой другой компонент.
Важным результатом такого подхода является то, что полная задача использует единый синтаксис, который легко выучить и соблюдать. Именно поэтому я использую слово "лексикон", а не "язык". У языков уникальные синтаксисы.
Доступность команд также значительно облегчает процесс тестирования и отладки. Интерактивность Форта позволяет программисту набирать и тестировать примитивные команды типа
ПРАВЫЙ ПЛЕЧО 20 ПОВЕРНУТЬ
"снаружи" так же просто, как и более мощные типа
ЛЕВЫЙ КОФЕЙНИК
В то же время программист может (при желании) намеренно запретить доступ конечного пользователя к любым командам, включая сам Форт, после того, как программа закончена.
Новая Форт-методология проясняется. Программирование на Форте состоит в расширении корневого языка в сторону приложения, определении новых команд, которые прямо описывают проблему.
Языки программирования, созданные специально для опеределенных применений, таких как робототехника, производственный контроль, статистика и т.д., известны как "проблемно-ориентированные". Форт -- это программные средства для `создания` проблемно-ориентированных языков. (Последняя фраза -- быть может, самое краткое из возможных описаний Форта.)
На самом деле Вам не стоит писать каких-либо серьезных задач на Форте; как язык, он просто недостаточно мощен. Вам `следует` писать на Форте свои собственные языки (лексиконы) для моделирования Вашего понимания проблемы, на которых Вы можете элегантно описать ее решение.
Производительный язык
Размеры
Разработка "с вершины"
Сцепление
Сказание об истории элегантности программ
Скорость
Структурированная разработка
Удобство написания
Управляемость
Упрятывание информации
Большинство групп разработчиков обнаруживают, что пишут "семейства" программ, то есть много версий родственных программ для конкретного поля применений, все являющиеся вариантами одной ранней версии.
Для обеспечения принципа упрятывания информации определенные детали программы должны быть сведены в единое место, и каждый полезный кусок информации должен встречаться один раз. Программы, игнорирующие эту максиму, виновны в избыточности. В то время как избыточность аппаратуры (резервные ЭВМ и т.д.) могут сделать систему более безопасной, информационная избыточность наоборот, опасна.
Любой знающий программист скажет Вам, что число, которое предположительно может измениться в будущих версиях программы, должно быть определено как "константа" и встречаться в программах в виде ссылки на свое имя, а не в виде значения. Например, количество колонок, представляющих ширину бумаги в Вашем принтере, должно быть представлено константой. Даже в языках ассемблера имеются конструкции типа "EQU" и метки для связи адресов и битовых масок с именами.
Любой хороший программист также применит принцип упрятывания информации при разработке подпрограмм, стремясь к тому, чтобы каждый модуль знал как можно меньше о содержимом других модулей. Сегодняшние языки программирования, такие, как C, Модула-2 и Эдисон, применяют этот подход в архитектуре своих процедур.
Но Парнас проводит свою идею значительно дальше. Он предлагает распространить ее на алгоритмы и структуры данных. Упрятывание информации -- а не структура принятия решений или иерархия вызовов -- вот что должно лежать в основе проектирования !
Упрятывание конструкции структур данных
Рис.1-10. Смена косвенного указателя.
+------+ | ЦВЕТ | +------+ КРАСНЫЙ | ~~~~~~~ +---------+ | +---------+ | КРАСНЫЕ |
+------+ | ЦВЕТ | +------+ ЗЕЛЕНЫЙ | ~~~~~~~ +---------+ | +---------+ | КРАСНЫЕ | ------>| ЗЕЛЕНЫЕ | +---------+ +---------+
Мы переопределили ЯБЛОКИ. Теперь они дают содержимое переменной по имени ЦВЕТ. ЦВЕТ -- указатель: либо на переменную КРАСНЫЕ, либо на переменную ЗЕЛЕНЫЕ. Последние две переменных и являются действительными хранилищами для количеств.
Если мы сначала говорим КРАСНЫЙ, то можно использовать ЯБЛОКИ по отношению к красным яблокам, если говорим ЗЕЛЕНЫЙ, то -- по отношению к зеленым (рис. 1-10).
Нам не понадобилось изменять синтаксис чего-либо в наработанном коде, использующем ЯБЛОКИ. Мы так же говорим
20 ЯБЛОКИ !
и
1 ЯБЛОКИ +!
Взгляните опять на то, что мы сделали. Мы изменили описание слова ЯБЛОКИ с описания переменной на определение через двоеточие, никак не повлияв на метод его использования. Форт позволяет нам скрыть детали того, как определено слово ЯБЛОКИ, от использующего его кода. То, что представляется "вещью" с точки зрения старых определений, в действительности является "действием" (определением через двоеточие) внутри компонента.
Форт вдохновляет на использование абстрактных типов данных, позволяя определять структуры данных в терминах компонентов нижнего уровня. Только Форт, исключающий вызовы (вида CALL) в процедурах, позволяющий передавать адреса и данные через стек и предоставляющий прямой доступ к памяти с помощью слов @ и ! может предложить такой уровень упрятывания информации.
Форт мало заботится о том, является ли что-либо структурой данных или алгоритмом. Это дает нам, программистам, невероятную свободу в создании тех частей речи, которые нам нужны для описания наших задач.
Я стараюсь думать о каждом слове, возвращающем адрес (например, ЯБЛОКИ) как о "существительном" независимо от способа, которым оно определено. Слово, производящее очевидное действие -- это "глагол".
Такие слова, как КРАСНЫЙ и ЗЕЛЕНЫЙ в нашем примере, могут быть названы только "прилагательными", поскольку они изменяют функцию слова ЯБЛОКИ. Фраза
КРАСНЫЙ ЯБЛОКИ ?
отличается от фразы
ЗЕЛЕНЫЙ ЯБЛОКИ ?
Слова Форта могут служить также наречиями и предлогами. Мало смысла в том, чтобы определить, какой частью речи является конкретное слово, поскольку Форту в любом случае все равно. Нам нужно лишь порадоваться легкости описания задачи в естественных выражениях.
Возможности
Взгляд назад, вперед и на форт
Запоминаемость
Девять фаз цикла программирования
Затем Харрис применяет научный метод к циклу разработки программ, проиллюстрированному на рис. 2-1:
1. Проблема анализируется для выявления функций, требуемых для ее решения. 2. Принимаются решения о том, как достигнуть реализации этих функций с использованием доступных ресурсов. 3. Пишется программа, с помощью которой предпринимается попытка реализации проекта. 4. Программа проверяется на предмет правильности реализации функций.
Рис.2-1. Итеративный подход к циклу разработки программ, из статьи "Философия Форта" Кима Харриса. ("The FORTH Philosophy," by Kim Harris, Dr. Dobb's Journal.)
НОВЫЕ ТРЕБОВАНИЯ _____ФУНКЦИИ_____ НОВЫЕ ОГРАНИЧЕНИЯ ~|~~~~~~~~~~~~~~ / \ ~~~~~~~~~~~~~|~~~ | / \ | | +------------+----+ +---\------------+ | --->| АНАЛИЗ ПРОБЛЕМЫ | | ПРОЕКТИРОВАНИЕ |
М-р Харрис добавляет:
Разработка программ на Форте в первую очередь направлена на поиск самого простого решения данной проблемы. Это достигается реализацией выбранных частей задачи раздельно и игнорированием возможно большего числа ограничений. Затем вводятся одно или несколько ограничений, и программа модифицируется.
Превосходным свидетельством в пользу модели проектирования вида разработка/проверка является эволюция. От протоплазмы к головастику и к человеку, каждый вид на этом пути состоит из функциональных, жизнеспособных существ. Создатель явно не выглядит проектировщиком "сверху-вниз".
------------------------------------------------------------ СОВЕТ Начинайте с простого. Пусть оно заработает. Поймите, что именно Вы пытаетесь получить. Добавляйте сложность понемногу, насколько это необходимо для выполнения требований и ограничений. Не бойтесь начинать сначала, запутавшись в мусоре. ------------------------------------------------------------
ОБЬЕМ ПЛАНИРОВАНИЯ
В девяти фазах, перечисленных в начале этой главы, мы выделили пять шагов, предпринимаемых `перед` "разработкой". Раньше в Главе 1 мы увидели, что чрезмерное увлечение планированием одновременно и сложно, и бесцельно.
Ясно, что нельзя предпринимать важный программный проект - независимо от языка - без некоторого количества планирования. Какое же количество оптимально?
----------------------------------------------------------------
Многие Форт-программисты выражали глубокую признательность дотошному подходу к планированию Дейва Джонсона. Джонсон работает супервизором в фирме Moore Products Co. в Спрингсхаузе, штат Пенсильвания. Фирма специализируется на приложениях промышленной автоматизации и управлении процессами. Дейв использует Форт с 1978 года.
Вот как он описывает свой подход:
По сравнению со многими другими пользователями Форта я полагаю, что необходим более формальный подход. Я научился этому, пройдя тяжелый путь. Моя недисциплинированность в первые годы ныне как призрак посещает меня.
Мы используем два инструмента для начала проектирования новых продуктов: функциональное описание и проектное описание. Наш отдел продаж и применений выдает функциональное описание в соответствии с контрактом заказчика.
Когда мы договорились о том, что мы собираемся делать, функциональное описание приходит в наш отдел. С этого момента мы прорабатываем проект и выдаем проектное описание.
До этого момента наш подход не отличается от программирования на любом другом языке. Но с Фортом мы подходим к проектированию несколько по-другому. На Форте не надо проделывать 95% проектной работы до начала кодирования, достаточно 60% перед входом в итеративный процесс.
Типичный проект может состоять в функциональном усовершенствовании одного из наших продуктов. К примеру, у нас есть интеллектуальный терминал с дисководами и нам нужно реализовать определенные протоколы для связи с другим устройством. Проектирование протоколов, связи с дисплеями, интерфейса с оператором и т.д. может занять несколько месяцев. Функциональное описание занимает месяц; проектное описание берет месяц; кодирование отнимает три месяца; сборка и проверка продлится еще месяц.
Это - типичный цикл. Один проект занимал почти два года, из которых шесть или семь месяцев - обоснованно.
Когда мы пять лет назад принялись за Форт, это было не так. Как только я получал функциональное описание, я немедленно начинал кодирование. Я использовал нечто среднее между разработкой сверху-вниз и снизу-вверх, в основном определяя структуру и, по мере необходимости, некоторые низкоуровневые слова, затем вновь возвращаясь к структуре. Причиной такого подхода было огромное давление в сторону демонстрации чего-нибудь начальству. Мы крутились, как заведенные, никогда не описывая того, что делали. Через три года нам понадобилось вернуться назад и пытаться модифицировать этот код, без какой-либо документации. Форт стал недостатком, поскольку позволил нам начать слишком рано. Было забавно зажигать огоньки и заставлять жужжать дисководы. Но мы не проходили через горнило проектной работы. Как я сказал, наши "свободные духи" вернулись к нам в виде призраков.
Ныне для новых программистов мы установили требование: полное проектное описание, которое определяет в деталях все высокоуровневые Форт-слова - задачи, которые Ваш проект должен выполнять. Нет больше чтения нескольких страниц функционального описания, ответа на них, чтения еще нескольких, ответа на них и т.д.
Ни один из живущих программистов не любит документирование. Имея предварительный проект, мы имеем возможность просмотреть все через несколько лет и вспомнить, что мы делали. Я должен заметить, что во время проектной фазы надо написать некоторое количество кода для проверки конкретных мыслей. Но этот код может и не быть частью законченного продукта. Идея состоит в том, чтобы обдумать Ваш проект.
----------------------------------------------------------------
Джонсон советует нам составлять полное проектное описание до начала кодирования, за исключением необходимых предварительных тестов. Следующее интервью поддерживает эту точку зрения и добавляет новые аргументы.
----------------------------------------------------------------
Джон Телеска стал независимым программистом-консультантом с 1976 года, специализируясь на традиционных для академического исследовательского оборудования задачах.
Ему нравится снабжать исследовательские инструменты "как раз тем, что способна дать новейшая технология". Телеска работает в Рочестере, штат Нью-Йорк:
Я вижу две фазы в процессе разработки программ. Первая состоит в том, чтобы убедиться в моем понимании проблемы. Вторая заключается в реализации, включая отладку, верификацию и т.д.
Моя цель в фазе первой - операционное описание. Я начинаю с описания задачи, и по мере работы над ним получаю операционное описание - мое понимание того, как проблема преобразуется в решение. Чем лучше понимание, тем полнее решение. Я добиваюсь завершенности; чувства того, что больше нет вопросов, на которых нет ответа на бумаге.
Я понял, что чем больше времени в каждом проекте я трачу на первую фазу, тем больше беспокойство большинства моих клиентов. Ограничивающим фактором является то количество необходимого для этого периода времени, на которое мне удастся убедить клиента. Потребители обычно не знают характеристик работы, которую они хотят получить. И у них нет денег - или у них нет чувства того, что они есть - для траты на хорошие описания. Частью моей работы является убедить их в том, что не иметь их в конце концов обойдется дороже и по деньгам, и по времени.
Часть первой фазы тратится на изучение возможных решений. При написании спецификаций всплывают неопределенности. Я пытаюсь быть настолько неопределенным по отношению к неопределенностям, насколько это возможно. Например, им хочется собирать 200000 отсчетов в секунду с определенной точностью. Я в первую очередь должен проверить, что это вообще возможно на имеющимся у них оборудовании. При этом мне нужно для проверки возможностей написать заплатку из кода.
Другим аргументом в пользу описаний является прикрытие самого себя. В случае, если задача работает по спецификации, но не полностью удовлетворяет потребителя, то за это несет ответственность он. Если ему захочется большего, то мы должны договориться заново. Но я думаю, разработчик отвечает за то, чтобы сделать все возможное для выдачи операционного описания, которое будет работать к удовлетворению покупателя.
Я думаю, что есть консультанты, уступающие давлению клиента и ограничивающие время, затрачиваемое на описания из страха потерять работу. Но в таких ситуациях ни для кого не бывает счастливого конца.
----------------------------------------------------------------
Мы вернемся к интервью с Телеской через мгновение.
ОГРАНИЧЕНИЯ ПЛАНИРОВАНИЯ
Опыт научил нас намечать свой путь перед началом кодирования. Но у планирования есть и определенные ограничения. Следующие интервью дают различные оценки для количества планирования.
----------------------------------------------------------------
Несмотря на предпочтение Телеской хорошо спланированного проектирования, он рекомендует выбирать между подходом сверху-вниз и снизу-вверх в зависимости от ситуации:
В двух недавних проектах, содержавших много технической стыковочной работы, я проделал все снизу-вверх. Я перемолол кипу бумаг с данными и техническими описаниями мелких щелей операционной системы, с которой я имел дело. Большую часть времени я чувствовал себя потерянным, не понимая, зачем я вообще взялся за эту работу. Затем я достиг некоторой критической массы известного вида и начал составлять вместе маленькие программы, что приводило к возникновению маленьких вещей. Я продолжал, снизу-вверх, пока не достиг уровня целевого приложения.
Мой дух проектировщика "сверху-вниз" был напуган этой процедурой. Но я столько раз наблюдал себя успешно проходящим через этот процесс, что не могу сбросить его со счетов по какой-нибудь педагогической причине. И всегда бывает эта трудная стадия, которую, кажется, не преодолеет никакое количество линейного мышления. Программирование кажется гораздо более интуитивным, чем мы, им занимающиеся, склонны говорить друг другу. Я думаю, что когда задача порождает такое чувство потерянности, мне надо работать снизу-вверх. Если я ощущаю себя на своей территории, то тогда, по-видимому, изберу более традиционный, книжный подход.
----------------------------------------------------------------
А вот еще одна точка зрения:
----------------------------------------------------------------
Во время нашего интервью Майкл Старлинг из фирмы Union Carbide делал последние штрихи в двух программах, касающихся конфигурируемых пользователем лабораторных и производственных систем автоматизации. Для опытной фабричной системы Старлинг разработал как аппаратуру, так и программное обеспечение в соответствии с известными требованиями; на лабораторной системе он также сам определял требования.
Его усилия увенчались полным успехом. Для одного проекта новая система стоит лишь 20% от цены аналогичной системы и требует дней, а не месяцев, для установки и конфигурирования.
Я спросил его, какую технику он применял для поддержки проекта.
Для обеих задач требовалось большое количество проектирования. Тем не менее я не следовал традиционным методам анализа. Я осуществил следующие шаги: Во-первых, я четко отследил ограничения задачи. Во-вторых, я определил, какими должны быть минимальные функциональные куски - программные подсистемы. В-третьих, я сделал каждый кусочек, составил их вместе, и система стала работать.
Затем я спросил пользователей: "Это удовлетворяет вашим требованиям?" В чем-то оно не удовлетворяло, причем таким образом, каким ни пользователи, ни разработчики проекта не могли предугадать.
К примеру, разработчики не осознавали, что начальное описание не предусматривало приятные, ориентированные на человеческое восприятие графические картинки. При работе с интерактивной графикой первой версии пользователи применяли фиксированные масштабы графики и получали странные картинки.
Поэтому, даже уже после того, как был разработан базовый алгоритм графики, мы осознали, что необходимо было делать автоматическое масштабирование. Мы вернулись назад, изучили, как человеческие существа чертят графики, и написали функцию нижнего уровня, которая подсчитывают данные по осям X и Y и как они разместятся на графике. После этого мы осознали, что не все взятые данные будут представлять интерес для экспериментаторов.
Поэтому мы добавили возможность увеличения отдельных кусков.
Такой итеративный подход позволил получить более четкий и лучше продуманный код. Мы выявили основную линию целей и построили минимальную систему, отвечающую известным требованиям пользователя. Затем мы покопали наш программистский опыт для его улучшения и установили, что из нужного заказчики позабыли при составлении спецификации. Пользователи, в основном, новых идей не придумали. Это сделали программисты, и они отделяют эти идеи от идей пользователей. Постановка задачи оказалась улицей с двусторонним движением. В некоторых случаях они получали такие вещи, о которых и не думали, что они возможны на таком маленьком компьютере - например, применение цифровых фильтров и процессоров сигналов к данным.
Одним из свойств Форта, сделавших возможным такой подход, является то, что примитивы легко тестируются. Требуется некоторый опыт работы с Фортом, чтобы получать от этого преимущества. Ребята с традиционным воспитанием хотят написать за своим столом десять страниц кода, потом сесть и ввести их, и ожидают, что это будет работать.
Вот вкратце мой подход: я пытаюсь установить, что нужно пользователям, но в то же время осознаю неполность этих сведений. Затем я держу их вовлеченными в проект во время реализации, поскольку они должны выполнять роль экспертов. Когда они видят результат, это им приятно, поскольку известно, что их идеи использованы.
Итеративный подход позволяет достичь наивысших результатов при создании хорошего решения для реальной проблемы. Он может не всегда дать Вам объявленную заранее стоимость программного обеспечения. Путь решения может зависить от Ваших приоритетов. Запомните:
Хорошее Быстрое Дешевое
Выбирайте любые два качества!
----------------------------------------------------------------
Как говорит Старлинг, Вы как не знаете толком, что делаете, до тех пор, пока один раз это не пройдете. Мой опыт показывает, что наилучший способ написать программу - это написать ее дважды. Выкиньте первую версию, приняв ее за набросок.
----------------------------------------------------------------
Питер Кожж входит в основной технический состав подразделения федеральных систем фирмы IBM, Освего, штат Нью-Йорк:
Одним из ключевых преимуществ, которые я нахожу в Форте, является то, что он позволяет мне быстро создавать прототипы задачи без колокольного звона и свистопляски, зачастую с существенными ограничениями, однако в виде, достаточном для запуска "человеческого интерфейса" из подручных средств.
Когда я строю прототип, я делаю это с твердой уверенностью в том, что не использую ни строчки из текста прототипа в конечной программе. Эта вынужденная "переделка" почти всегда приводит к значительно более простым и более элегантным законченным программам, даже если последние написаны на чем-то, отличном от Форта.
----------------------------------------------------------------
Каковы наши выводы? В окружении Форта планирование необходимо. Но оно должно быть коротким. Тестирование и построение прототипов - наилучшие пути для понимания того, что именно действительно нужно.
Одно слово в предостережение руководителям проектов: если Вы наблюдаете за любым опытным Форт-программистом, то не надо беспокоиться насчет того, что он тратит слишком много времени на планирование. Так что следующий совет имеет две версии:
------------------------------------------------------------ СОВЕТ Для новичков в Форте (с "традиционным" воспитанием): Сокращайте до минимума фазу анализа. Для приверженцев Форта (без "традиционной" подготовки): Воздерживайтесь от кодирования столь долго, сколько сможете выдержать. ------------------------------------------------------------
Или, как мы упоминали в первой главе:
------------------------------------------------------------ СОВЕТ Планируйте изменения (проектируя компоненты, которые могут быть изменены). ------------------------------------------------------------
Или просто:
------------------------------------------------------------ СОВЕТ Делайте прототипы. ------------------------------------------------------------
ФАЗА АНАЛИЗА
В оставшейся части этой главы мы обсудем фазу анализа. Анализ - это организованный путь к пониманию и документированию того, что должна делать программа.
Для простой программы, которую Вы пишете для себя меньше чем за час, фаза анализа может занять около 250 микросекунд. В другой крайности некоторые проекты займут много человеко-годов. В таком проекте фаза анализа определяет успех всего проекта.
Мы указывали на три части фазы анализа:
1. Установка требований и ограничений 2. Построение концептуальной модели решения 3. Оценка цены/графика работ/производительности
Давайте вкратце опишем каждую часть:
УСТАНОВКА ТРЕБОВАНИЙ.
Первым шагом является выяснение того, что должна делать задача. Покупатель или вообще тот, кто хочет иметь систему, должен предоставить "описание требований". Это - честный документ, перечисляющий минимальные возможности конечного продукта.
Аналитик может также делать дальнейшие пробы, проводя беседы и рассылая вопросники пользователям.
УСТАНОВКА ОГРАНИЧЕНИЙ.
Следующиий шаг заключается в определении ограничивающих факторов. Насколько важна скорость? Сколько доступно памяти? Как быстро нужно получить разработку?
Независимо от утонченности нашей технологии, программисты всегда будут биться об ограничения. Возможности системы необъяснимым образом уменьшаются со временем. Дисководы с двойной плотностью записи, однажды послужившие ответом на мои молитвы, ныне не удовлетворяют меня. Двусторонние, с двойной плотностью дисководы, которые я заведу следующими, покажутся безграничными - на время. Я слышал жалобы на тесноту от людей с 10-ю мегабайтовыми жесткими дисками.
Где бы ни ощущалась нехватка чего-либо - а она всегда будет - следует делать компромиссы. Лучше использовать фазу анализа для противостояния большинству ограничений и принятия решений о том, какие нужны компромиссы.
С другой стороны, во время анализа Вы `не` должны принимать во внимание другие типы ограничений, преодолевая их постепенно во время реализации по типу того, как растирают комки в тесте.
Во время анализа следует принимать во внимание те типы ограничений, которые могут повлиять на подход в целом. Отложить следует те, которые могут быть учтены во время итеративного улучшения спланированного программного проекта.
Как мы слышали в предыдущих интервью, выявление `аппаратных` ограничений часто требует написания некоторого количества пробного кода и испытаний. Выявление ограничений со стороны `покупателя` обычно сводится к заданию ему вопросов или получению письменных обзоров. "Насколько быстро Вам необходимо то-то и то-то, в размере от одного до десяти?" и т.д.
ПОСТРОЕНИЕ КОНЦЕПТУАЛЬНОЙ МОДЕЛИ РЕШЕНИЯ.
Концептуальная модель - это воображаемое решение проблемы. Это - взгляд на то, как система `должна` работать. Это - ответ на все требования и ограничения.
Если определение требований звучит как "нечто, на чем можно стоять при покраске потолка", то описанием концептуальной модели будет "устройство, стоящее свободно (так, что можно красить в середине комнаты), с несколькими ступенями, отделенными друг от друга одинаковыми интервалами (так, что можно забираться вверх и вниз) и имеющее маленькую полку на вершине (для банки с краской)".
Концептуальная модель, однако - это не совсем проект. Проект начинается с описания того, как система `в действительности` работает. В проекте должен был бы начать появляться образ лестницы со ступеньками.
Форт несколько размывает это различие, поскольку все определения пишутся в концептуальных терминах, используя лексиконы компонентов нижних уровней. На самом деле, позже в этой главе мы будем использовать `псевдокод` на Форте для описания реализаций концептуальной модели.
Несмотря на это, полезно делать различие. Концептуальная модель более гибка, чем проект. Легче войти в рамки требований и ограничений в модели, чем в проекте.
------------------------------------------------------------ СОВЕТ Старайтесь построить солидную концептуальную модель перед началом проектирования. ------------------------------------------------------------
Анализ состоит из расширения описания требований до уровня концептуальной модели. Технология включает двухстороннюю связь с покупателем в попытках достичь успеха в описании модели.
Как и весь цикл разработки, в фазе анализа лучше всего итеративный подход. Каждое новое требование будет пытаться добавить что-то в Вашу мысленную модель. Ваша работа состоит в жонглировании всеми этими требованиями и ограничениями до тех пор, пока Вы не сплетете основу, которая им удовлетворяет.
Рисунок 2-2 иллюстрирует итеративный подход к фазе анализа. Последний шаг является одним из наиболее важных: показать задокументированную модель покупателю. Используйте любые необходимые средства связи - диаграммы, таблицы или картинки - для доведения Вашего понимания до заказчика и получайте необходимую обратную связь. Даже если Вы проделаете весь цикл сто раз, эти усилия не потеряют свою ценность.
Рис.2-2. Итеративный подход к анализу.
ПОЖЕЛАНИЯ ЗАКАЗЧИКА ____ТРЕБОВАНИЯ___ ~|~~~~~~~~~~~~~~~~~ / \ | / \ | +------------+-----+ +---\----------+ --->| ПОСТАВИТЬ ЗАДАЧУ | | ВЗВЕСИТЬ | +---------/--------+ | ТРЕБОВАНИЯ И | / | ОГРАНИЧЕНИЯ | ОЦЕНКА/ПРИЕМКА +--------+-----+ ЗАКАЗЧИКА / \ УТОЧНЕННЫЕ ТРЕБОВАНИЯ +---------+----------+ +-----------/------------+ | ПРОДЕМОНСТРИРОВАТЬ | | ОПИСАТЬ КОНЦЕПТУАЛЬНУЮ | | МОДЕЛЬ ЗАКАЗЧИКУ | | МОДЕЛЬ | +------------\-------+ +--------+---------------+ \ / \_ЗАДОКУМЕНТИРО-_/ ВАННАЯ МОДЕЛЬ
В следующих двух разделах мы исследуем три вида техники для определения и описания концептуальной модели:
1. определение интерфейсов 2. определение правил 3. определение структур данных.
ОПРЕДЕЛЕНИЕ ИНТЕРФЕЙСОВ
------------------------------------------------------------ СОВЕТ Первое и самое важное: концептуальная модель должна описывать интерфейсы системы. ------------------------------------------------------------
----------------------------------------------------------------
Телеска:
"Описание" обычно имеет дело с понятием ЧТО. В самом лучшем случае оно должно показывать то, на что система будет похожа для пользователя - можно назвать это руководством пользователя.
Мне кажется, что я пишу больше замечаний по взаимодействию с человеком - как это выглядит снаружи - чем по той части, которая в действительности выполняет работу. К примеру, я составляю полное описание обработки ошибок для того, чтобы показать, что происходит при определенных событиях. К несчастью, эта часть заодно отнимает и большую часть времени при реализации.
В настоящее время я работаю над твердотельным таймером для промышленной стиральной машины. В данном случае интерфейс с пользователем не так сложен. Что сложно, так это интерфейс со стиральной машиной, в котором я должен придерживаться требований заказчика и той документации, которую он может предоставить.
Интерфейс - это руки и ноги для продукта. На ранней стадии я не делаю различия между аппаратурой и программным обеспечением. Они оба могут взаимозаменяться при реализации.
Процесс проектирования аппаратуры и процесс проектирования программ аналогичны. Путь, по которому я разрабатываю аппаратуру - это представление ее в образе черного ящика. Передняя панель - это вход и выход. То же самое можно сделать и с программным обеспечением.
Я использую всякие приемы, диаграммы и т.п. для того, чтобы показать покупателю, как выглядят входы/выходы, использую описания того, что продукт должен делать. Но, параллельно, в воображении, я представляю как он должен быть реализован. Вычисляю, насколько эффективно я могу это сделать. Так что для меня это не черный ящик, это серый ящик. Проектировщик должен быть способен видеть внутри черных ящиков.
При проектировании системы получаются различные модули, я стараюсь сделать их взаимосвязь настолько рациональной и настолько маленькой, насколько возможно. Но всегда где-то находишь, а где-то теряешь, поскольку изменяешь идеалу с компромиссами.
Для самого документа я использую ДПД [диаграммы потоков данных, которые мы обсудим позже] и любые другие виды представления, которые я могу продемонстрировать клиентам. Я показываю им сколько возможно много диаграмм для пояснения своего понимания.
Я обычно не использую их, когда дело доходит до реализации. Текст должен быть полноценен даже и без ссылок на диаграммы.
----------------------------------------------------------------
------------------------------------------------------------ СОВЕТ Решайте вопросы обработки ошибок и исключительных случаев заранее, при определении интерфейсов. ------------------------------------------------------------
Верно то, что при написании программ для себя программист зачастую может сконцентрироваться на правильности работы кода при `нормальных` условиях и побеспокоиться об обработке ошибок позже. Однако при работе на других обработка ошибок должна быть проработана в первую очередь. Эта та область, которая часто выпадает из поля зрения начинающих программистов.
Причина важности принятия решений по обработке ошибок на этой стадии состоит в широком спектре методов их обработки. Ошибка может быть:
* игнорируемая * устанавливающая флаг индикации ошибки при продолжении процесса * немедленно останавливающая процесс * вызывающая процедуру для корректировки задачи и продолжения работы.
Простор для возникновения серьезной коммуникационной бреши открывается в том случае, если уровень сложности при обработке ошибок заранее не отслежен. Очевидно, что этот выбор чрезвычайно сильно отражается на проекте и реализации задачи.
------------------------------------------------------------ СОВЕТ Разрабатывайте концептуальную модель, представляя себе, как данные проходят, и какие действия над ними производятся в частях модели. ------------------------------------------------------------
Дисциплина, называемая `структурный анализ` [2], предлагает некоторые способы описания интерфейсов таким образом, что их легко поймут Ваши клиенты. Один из таких способов, упоминавшихся Телеской, называется "диаграммой потоков двнных" (ДПД).
Диаграмма потоков данных, типа изображенной на рис. 2-3, подчеркивает преобразования данных при их прохождении через систему. Круги представляют собой "преобразования", функции, воздействующие на информацию.
Стрелки показывают входы и выходы преобразований.
Рис.2-3. Диаграмма потоков данных.
-->\ (из производственной линии) \ ЗАПРОС НА ЗАПРОС НА МАТЕРИАЛЫ ЗАКУПКУ \ / \ \ +-----------+/ +\--------------------+ \| ПРОВЕРИТЬ + | ПОЛУЧИТЬ РАЗРЕШЕНИЕ | \ НАЛИЧИЕ | | НА ЗАКУПКУ | +------+----+ +------+--------------+
Диаграмма живописует застывший момент в работе системы. Она оставляет без внимания инициализацию, структуры циклов и другие детали программирования, которые зависят от времени.
При использовании ДПД достигаются три плюса:
Во-первых, они дают сведения заказчику при помощи простых, прямых терминов. Если он соглашается с содержимым Вашей диаграммы потоков данных, то Вы знаете, что понимаете проблему.
Во-вторых, они помогают Вам думать в терминах логических "ЧТО", без углубления в процедурные "КАК", что согласуется с философией упрятывания информации, обсужденной нами в предыдущей главе.
В-третьих, они фокусируют Ваше внимание на интерфейсах к системе и между модулями.
Несмотря на это, Форт-программисты редко используют ДПД иначе как для заказчика. Форт способствует Вашему мышлению в терминах концептуальной модели, а скрытое использование стека данных в Форте делает передачу данных между модулями настолько простой, что ее обычно можно принимать как само собой разумеющуюся. Это происходит потому, что Форт, будучи использован правильно, приближается к уровню функционального языка.
Даже для тех, кто всего несколько дней знаком с Фортом, простые определения содержат по крайней мере столько же смысла, сколько диаграммы:
: ЗАПРОС ( количество наименование -- ) ПОД-РУКОЙ? IF ВЫДАТЬ ELSE ПЕРЕАДРЕСОВАТЬ THEN ; : ПЕРЕАДРЕСОВАТЬ ЗАВЕРЕНО? IF ПОКУПКА THEN ; : ПОКУПКА АРХИВНАЯ КОПИЯ СКЛАДСКАЯ КОПИЯ ЗАКУПОЧНАЯ КОПИЯ ;
Это - псевдокод на Форте. Не было сделано никаких попыток конкретизировать, какие величины в действительности передаются через стек, поскольку это - детали реализации. Стековый комментарий для слова ЗАПРОС использован только для указания двух видов данных, необходимых для начала процесса.
(Если бы я проектировал эту задачу, я бы посоветовал, чтобы пользовательский интерфейс был представлен словом НУЖНО со следующим синтаксисом:
НУЖНО 50 ПОДШИПНИКИ
НУЖНО преобразует число в величину на стеке, переводит строку ПОДШИПНИКИ в номер запасной части, также оставляя его на стеке, затем вызывает ЗАПРОС. Такая команда могла бы быть определена только на самом удаленном от нижнего уровне.)
----------------------------------------------------------------
Джонсон из фирмы Moore Products Co. сказал несколько слов насчет Форт-псевдокода:
IBM использует строго документированный PDL (ЯПП - язык проектирования программ). Мы также используем PDL, хотя и называем его FDL, т.е. язык проектирования на Форте. Наверное, расточительно иметь все эти стандарты, однако, если Вы знакомы с Фортом, то Форт сам может быть языком проектирования. Надо только отбросить так называемые "шумовые" слова: C@, DUP, OVER и т.п. и показывать только основной поток. Большинство работающих на Форте делают это интуитивно. Мы же делаем это целенаправленно.
----------------------------------------------------------------
Во время одного из наших интервью я спросил Мура, использовал ли он какой-нибудь вид диаграмм для планирования концептуальной модели или кодировал прямо на Форте. Вот его ответ:
Концептуальной моделью `является` Форт. Я годами учился думать в этом духе.
Может ли кто-нибудь научиться так думать?
У меня есть нечестное преимущество. Я закодировал свой стиль программирования, и другие люди восприняли его. Я был поражен тем, что это случилось. И я чувствую, что это приятное преимущество, поскольку ведь это мой стиль другие пытаются скопировать. Могут ли они научиться думать так же, как я? Мне кажется, что могут. Это - всего лишь вопрос практики, а у меня практики больше.
----------------------------------------------------------------
ОПРЕДЕЛЕНИЕ ПРАВИЛ
Большая часть наших усилий при описании задачи концентрируется вокруг описания интерфейса. Но некоторые задачи требуют, чтобы Вы также определили набор правил для их решения.
Любое программирование опирается на правила. Обычно они настолько просты, что почти не играет роли, как Вы их выражаете: "если кто-либо нажмет кнопку, то зазвонит колокольчик".
Однако некоторые задачи содержат правила настолько сложные, что их нельзя выразить в нескольких фразах. Несколько формальных приемов могут быть Вам полезны для понимания и документирования таких сложных правил.
Вот пример. Имеющиеся требования описывают систему для вычисления платы за дальние телефонные переговоры. Вот объяснения заказчика о структуре платежей. (Я сам это придумал; не имею никакого представления о том, как в действительности телефонная компания вычисляет эти тарифы, знаю только, что всегда приходится переплачивать.)
Все тарифы исчисляются по-минутно, в соответствии с расстоянием в милях, плюс постоянная плата. Постоянная плата для прямого вызова по рабочим дням между 8 утра и 5 вечера составляет .30 (0.3 доллара) за 1-ю минуту и .20 за каждую последующую; кроме того, каждая минута увеличивается на .12 за 100 миль. Постоянная плата в будни между 5 вечера и 11 вечера составляет .22 за первую минуту и .15 за каждую дополнительную; плата за расстояние - .10 на минуту за 100 миль. Постоянная плата для прямых вызовов поздно в будни от 11 вечера или в любое время в субботу, воскресенье или праздники составляет .12 за первую минуту и .09 для каждой следующей; наценка за расстояние на минуту .06 за 100 миль. Если вызов требует помощи оператора, постоянная плата увеличивается на .90 назависимо от времени.
Это описание составлено на старом добром русском языке, и оно крайне многословно. Его трудно отслеживать и, смахивая на чердак, заваленный старым хламом, оно может даже содержать несколько ошибок.
При возведении концептуальной модели такой системы мы должны описать структуру платежей однозначным, удобным образом. Первым шагом на пути к расчистке завала является вычленение независимых кусков информации - следуя применению правила ограниченной связности. Мы может сильно улучшить этот текст, разбив его на утверждения.
Во-первых, это правило времени дня:
Вызовы в будни между 8 утра и 5 вечера идут по "полной" оплате. Вызовы в будни между 5 вечера и 11 вечера идут по "среднему" тарифу. Вызовы в будни от 11 вечера или по субботам, воскресеньям и праздникам оплачиваются по "низшему" тарифу.
Затем следует сама структура платежей, которая должна быть описана в терминах "тарифа за первую минуту", "тарифа за дополнительную минуту", "тарифа за расстояние" и "тарифа за работу оператора".
------------------------------------------------------------ СОВЕТ Разделите фрукты. (Не путайте яблоки с апельсинами.) ------------------------------------------------------------
Однако эти эпистолярные утверждения все еще трудно читать. Системные аналитики используют несколько способов для упрощения таких выражений: структурированный английский (русский) язык, деревья решений и таблицы решений. Давайте изучим каждую из этих технологий и прикинем их полезность в среде Форта.
СТРУКТУРИРОВАННЫЙ АНГЛИЙСКИЙ.
Структурированный английский (у нас - русский) - это вид структурированного псевдокода, при котором наше выражение для платежей будет читаться как-то вроде:
IF полный тариф IF прямой вызов IF первая минута .30 + .12/100миль ELSE ( дополн. минута) .20 + .12/100миль ENDIF ELSE ( оператор) IF первая минута 1.20 + .12/100миль ELSE ( дополн. минута) .20 + .12/100миль ENDIF ENDIF ELSE ( не полный тариф) IF средний тариф IF прямой вызов IF первая минута .22 + .10/100миль ELSE ( дополн. минута) .15 + .10/100миль ENDIF ELSE ( оператор) IF первая минута 1.12 + .10/100миль ELSE ( дополн. минута) .15 + .10/100миль ENDIF ENDIF ELSE ( низкий тариф) IF прямой вызов IF первая минута .12 + .06/100миль ELSE ( дополн. минута) .09 + .06/100миль ENDIF ELSE ( оператор) IF первая минута 1.02 + .06/100миль ELSE ( дополн. минута) .09 + .06/100миль ENDIF ENDIF ENDIF ENDIF
Это совершенно ужасно. Это трудно читать, еще труднее понимать и труднее всего писать.
И, мало того, совершенно бесполезно для реализации. Я более не хочу об этом даже говорить.
ДЕРЕВО РЕШЕНИЙ.
Рисунок 2-4 представляет телефонные тарифы при помощи дерева решений. Дерево решений - простейший из всех метод "прослеживания" результата при некоторых условиях. По этой причине он может оказаться наилучшим представлением для демонстрации заказчику.
Рис.2-4. Пример дерева решений.
1-я мин. .30 + .12/100м / Прям.вызов / \ / Доп.мин. .20 + .12/100м Полный тариф / \ 1-я мин. 1.20 + .12/100м / \ / / Оператор / \ / Доп.мин. .20 + .12/100м | | 1-я мин. .22 + .10/100м | / | Прям.вызов | / \ | / Доп.мин. .15 + .10/100м ТАРИФЫ --- Средний тариф | \ 1-я мин. 1.12 + .10/100м | \ / | Оператор | \ | Доп.мин. .15 + .10/100м | \ 1-я мин. .12 + .06/100м \ / \ Прям.вызов \ / \ \ / Доп.мин. .09 + .06/100м Низкий тариф \ 1-я мин. 1.02 + .06/100м \ / Оператор \ Доп.мин. .09 + .06/100м
К сожалению, по дереву решений трудно "двигаться назад" для определения того, какие определенные условия приводят к каким результатам. Это затрудняет поиск путей для упрощения задачи. Дерево закрывает тот факт, что дополнительные минуты стоят одинаково, независимо от помощи оператора. Этот факт не виден на дереве.
ТАБЛИЦА РЕШЕНИЙ.
Таблица решений, описанная далее, дает наиболее приемлемое графическое представление сложных правил для программиста, и, возможно, также и для заказчика. Рисунок 2-5 показывает нашу структуру платежей в форме таблицы решений.
На рис.2-5 присутствуют три измерения: тарифная скидка, присутствие оператора и начальная/дополнительная минута.
Рис.2-5. Таблица решений.
+-----------------|-----------------|-----------------+ | ПОЛНЫЙ ТАРИФ | СРЕДНИЙ ТАРИФ | НИЗКИЙ ТАРИФ | |-----------------|-----------------|-----------------| | 1-я м. | доп.м. | 1-я м. | доп.м. | 1-я м. | доп.м. | ---------|--------|--------|--------|--------|--------- Прямой | .30 + | .20 + | .22 + | .15 + | .12 + | .09 + | вызов |.12/100м|.12/100м|.10/100м|.10/100м|.06/100м|.06/100м| ---------|--------|--------|--------|--------|--------- Опера- | 1.20 + | .20 + | 1.12 + | .15 + | 1.02 + | .09 + | тор |.12/100м|.12/100м|.10/100м|.10/100м|.06/100м|.06/100м| |--------+--------|--------+--------|--------+--------|
Описание задачи более чем в двух измерениях может быть немного замысловатым. Как можно видеть, эти дополнительные измерения могут быть нарисованы на бумаге как подизмерения внутри наружных. Все условия этих подизмерений появляются внутри каждого из условий наружных измерений. Как мы увидим, в программе может быть легко реализовано любое количество измерений.
Все описанные нами приемы заставляют Вас анализировать, какие условия соответствуют каким измерениям. Во время такого разбиения применяются два правила:
Первое, все элементы каждого измерения должны быть взаимно исключающими. Вы не должны ставить "1-ю минуту" в то же измерение, что и "прямой вызов", поскольку они не взаимоисключают друг друга.
Второе, в каждом измерении должны быть перечислены все возможности. Если бы существовал иной тариф для звонков с 2 до 2.05 ночи, то таблицу пришлось бы увеличить.
Но наши таблицы решений имеют свои индивидуальные преимущества. Таблица решений не только хорошо читается клиентом, но также помогает разработчику несколькими способами:
`Преобразуемость в реальный код`. Это особенно верно для Форта, в котором таблицы решений легко реализуются в форме, очень близкой к рисунку.
`Способность к отслеживанию логики в обратную сторону`. Найдите условие и смотрите, какие факторы его создали.
`Более ясное графическое представление`. Таблицы решений служат лучшим инструментом для понимания как для аналитика, так и для разработчика.
В отличие от деревьев решений, таблицы решений группируют вместе `результаты` с помощью осмысленной графики. Визуализация идей помогает в понимании проблем, особенно тех, которые слишком сложны для выражения линейным образом.
К примеру, на рисунке 2-5 ясно видно, что плата за дополнительные минуты не зависит от вмешательства оператора. С осознанием этого мы можем нарисовать упрощенную таблицу, как показано на рис. 2-6.
Рис.2-6. Упрощенная таблица решений.
+-----------------|-----------------|-----------------+ | ПОЛНЫЙ ТАРИФ | СРЕДНИЙ ТАРИФ | НИЗКИЙ ТАРИФ | |-----------------|-----------------|-----------------| | 1-я м. | доп.м. | 1-я м. | доп.м. | 1-я м. | доп.м. | ---------|--------|--------|--------|--------|--------- Прямой | .30 + | | .22 + | | .12 + | | вызов |.12/100м| .20 + |.10/100м| .22 + |.06/100м| .09 + | ---------| |--------| |--------| | Опера- | 1.20 + |.12/100м| 1.12 + |.10/100м| 1.02 + |.06/100м| тор |.12/100м| |.10/100м| |.06/100м| | |--------+--------|--------+--------|--------+--------|
Легко так увлечься каким- нибудь аналитическим инструментом, что позабыть о самой задаче. Аналитик должен не только извлечь все возможности проблемы до N-го измерения, как рекомендуют некоторые из виденных мною авторов по структурному анализу. Такой подход только увеличивает количество участвующих деталей. Решающий задачу должен также пытаться упростить ее.
------------------------------------------------------------ СОВЕТ Вы не понимаете проблему до тех пор, пока не можете упростить ее. ------------------------------------------------------------
Если целью анализа является не только понимание, но и упрощение, тогда нам, быть может, следует проделать дополнительную работу.
Наша пересмотренная таблица решений (рис. 2-6) показывает, что плата за милю зависит только от того, является ли тариф полным, средним или низким. Другими словами, в таблице играет роль только одно из трех измерений. Что случится, если мы разобьем таблицу на две, как на рис.2-7?
Рис.2-7. Разбиение таблицы решений.
+-----------------|-----------------|-----------------+ | ПОЛНЫЙ ТАРИФ | СРЕДНИЙ ТАРИФ | НИЗКИЙ ТАРИФ | |-----------------|-----------------|-----------------|
ПЛАТА ЗА СОЕДИНЕНИЕ
| 1-я м. | доп.м. | 1-я м. | доп.м. | 1-я м. | доп.м. | ---------|--------|--------|--------|--------|--------- Прямой | .30 | | .22 | | .12 | | вызов | | | | | | | ---------| .20 |--------| .15 |--------| .09 | Опера- | 1.20 | | 1.12 | | 1.02 | | тор | | | | | | |
ПЛЮС ПЛАТА ЗА РАССТОЯНИЕ
|-----------------|-----------------|-----------------| | .12/100миль | .10/100миль | .06/100миль | +-----------------+-----------------+-----------------+
Теперь мы получаем ответ из комбинации просмотра таблицы с вычислениями. Формула для по-минутной платы может быть выражена как определение на псевдоФорте:
: ПО-МИНУТНЫЙ-ТАРИФ ( -- плата-за-минуту) ПЛАТА-ЗА-СОЕДИНЕНИЕ ПЛАТА-ЗА-РАССТОЯНИЕ + ;
Знак "+" теперь появляется один раз в определении, а не девять раз в таблице.
Приняв принцип вычисления как следующий шаг, отметим (или вспомним из первоначального описания проблемы), что оператор просто добавляет одноразовую плату в размере .90 к суммарной.
В этом случае плата за оператора не является функцией ни одного из трех измерений. Это наиболее точно может быть выражено в виде "логического расчета"; т.е. функции, комбинирующей логику с арифметикой:
: ?ПОМОЩЬ ( плата-за-прямой-вызов -- суммарная-плата) ОПЕРАТОР? IF .90 + THEN ;
(Но помните, такая плата относится только к первой минуте.)
Это дает нам упрощенную таблицу, показанную на рисунке 2-8, и большую степень использования арифметических выражений.
Рис.2-8. Таблица решений без изображения вмешательства оператора.
+-----------------|-----------------|-----------------+ | ПОЛНЫЙ ТАРИФ | СРЕДНИЙ ТАРИФ | НИЗКИЙ ТАРИФ | |-----------------|-----------------|-----------------|
ПЛАТА ЗА СОЕДИНЕНИЕ
| | | | 1-я | .30 | .22 | .12 | минута | | | | |-----------------|-----------------|-----------------| Дополн. | | | | минута | .20 | .15 | .09 | | | | |
ПЛЮС ПЛАТА ЗА РАССТОЯНИЕ
| | | | | .12/100миль | .10/100миль | .06/100миль | +-----------------+-----------------+-----------------+
Давайте вернемся к нашему определению "ПО-МИНУТНЫЙ-ТАРИФ":
: ПО-МИНУТНЫЙ-ТАРИФ ( -- плата-за-минуту) ПЛАТА-ЗА-СОЕДИНЕНИЕ ПЛАТА-ЗА-РАССТОЯНИЕ + ;
Углубимся в правила вычисления платы за соединение и платы за расстояние.
Плата за соединение зависит от того, первая идет минута или последующая. Поскольку имеется два вида по-минутной оплаты, быть может, было бы проще переписать ПО-МИНУТНЫЙ-ТАРИФ как два разных слова.
Давайте положим, что мы построим компонент, который получает соответствующие величины из таблицы. Слово 1МИНУТА будет давать плату за первую минуту; +МИНУТЫ - за каждую дополнительную. Работа обоих слов будет зависеть от времени дня для получения полного, среднего или низкого тарифа.
Теперь мы можем определить пару слов для замены одного слова ПО-МИНУТНЫЙ-ТАРИФ:
: ПЕРВАЯ ( -- плата) 1МИНУТА ?ПОМОЩЬ ПЛАТА-ЗА-РАССТОЯНИЕ + ; : ЗА-ДОПОЛНИТЕЛЬНУЮ ( -- плата) +МИНУТЫ ПЛАТА-ЗА-РАССТОЯНИЕ + ;
Каково правило для платы за расстояние? Оно очень простое.
Это плата за сотню миль, помноженная на расстояние (в сотнях миль). Договоримся, что мы можем составить слово ПЛАТА-ЗА-100МИЛЬ, которое будет выдавать эту величину из таблицы:
: ПЛАТА-ЗА-РАССТОЯНИЕ ( -- плата) #МИЛИ @ ПЛАТА-ЗА-100МИЛЬ * ;
Наконец, если нам известно общее число минут в вызове, можно вычислить общую плату за вызов:
: СУММА ( -- суммарная-плата) ПЕРВАЯ ( плата за 1-ю минуту) ( #минут) 1- ( дополнительные минуты) ЗА-ДОПОЛНИТЕЛЬНУЮ * ( умножить на их стоимость) + ; ( сложить вместе)
Мы выразили правила для данной задачи через комбинацию простых таблиц и логических вычислений.
(Некоторые замечания в конце этого примера: мы написали нечто, очень похожее на действительную программу на Форте. Но это - всего-навсего псевдокод. Мы остерегались манипуляций со стеком, предполагая, что величины как-то попадают на стек в тех местах, в которых это показано комментариями. Мы также использовали чрезвычайно длинные имена, поскольку они должны быть удобочитаемы для заказчика. В реальной программе предпочтительны короткие имена - см. главу 5.)
Мы развернем законченную программу для этого примера в главе 8.
ОПРЕДЕЛЕНИЕ СТРУКТУР ДАННЫХ
По завершению определения интерфейсов, а иногда и правил, порой возникает необходимость также определить и некоторые структуры данных. Мы будем рассматривать не здесь вопросы реализации таких структур, но лишь описание их концептуальной модели.
Если, например, Вы автоматизируете библиотечную картотеку, то основная часть Ваших усилий будет касаться разработки логической структуры данных. Вам нужно решить, какую информацию следует хранить для каждой книги: название, имя автора, тему и т.д. Эти "атрибуты" будут составлять "сущность" (набор связанных записей) под названием КНИГИ. Далее Вам надо уточнить, какие другие структуры данных потребуются пользователям для эффективного поиска в списке КНИГИ. Вам может понадобиться другая сущность, состоящая из имен авторов в алфавитном порядке вместе с "указателями атрибутов" тех книг, которые написал каждый автор.
Повлияют на концептуальную модель структуры данных также некоторые ограничения. В примере с библиотечным каталогом Вам нужно знать не только `какая` информация интересует пользователей, но и как долго они расчитывают `ожидать` ее.
Например, пользователи могут запросить список тем по годам их публикаций - скажем, все по дамской галантерее между 1900 и 1910 годом. Если они хотят получать информацию молниеносно, то Вам следует проводить список по годам и темам. Если они могут ждать сутки, Вы можете позволить компьютеру перерыть все подряд книги в библиотеке.
ДОСТИЖЕНИЕ ПРОСТОТЫ
------------------------------------------------------------ СОВЕТ Пусть будет просто. ------------------------------------------------------------
Во время первых решающих шагов к пониманию проблемы не забывайте про старое высказывание:
Если дано два решения проблемы, то правильное - более простое.
Это особенно верно для разработки программ. Более простое решение часто труднее найти, но если это случилось, то оно:
* легче для понимания * легче для реализации * легче для изменения и отладки * легче для поддержки * более компактно * более эффективно * доставляет больше удовольствия
----------------------------------------------------------------
Одним из наиболее рьяных защитников простоты является Мур:
Надо иметь чувство размера задачи. Как много потребуется кода для ее реализации? Один блок? Три? Я думаю, что это очень полезный инструмент для разработки. У Вас должно быть внутреннее чувство того, тривиальная или важная это задача, сколько времени и сил Вы можете на нее потратить.
Когда Вы закончили, оглянитесь и скажите: "Обоснованно ли я подошел к решению этой проблемы?" Если Ваше решение занимает шесть экранов, может оказаться, что Вы использовали кувалду для того, чтобы убить комара. Ваш мысленный образ непропорционален важности задачи.
Я видел программы физиков-ядерщиков с сотнями тысяч строк на Фортране. Что бы ни делал этот код, это не оправдывает сотни тысяч строк кода.
Видимо, их авторы чрезмерно обобщили задачу. Они решили большую задачу, подмножество в которой составляют их собственные нужды. Они пренебрегли принципом того, что решение должно отвечать задаче.
----------------------------------------------------------------
------------------------------------------------------------ СОВЕТ Обобщенность обычно приводит к сложности. Не обобщайте свое решение более, чем это необходимо; вместо этого предусматривайте возможность изменения. ------------------------------------------------------------
----------------------------------------------------------------
Мур продолжает:
Если перед Вами поставлена задача, Вы можете написать ее решение. После того, как Вы это сделали и обнаружили в нем некоторые неприятности, можете вернуться назад и изменить задачу, получив в конце концов более простое решение.
Существует такой вид оптимизации устройств - минимизация числа вентилей в схеме, где можно использовать преимущества "безразличных" ситуаций. Они возникают или вследствие того, что данный случай на практике не встретится, или потому что действительно все равно. Однако исходные данные зачастую пишутся людьми, ничего не понимающими в программировании. Разработчик может аккуратно описать все случаи, но при этом не сказать Вам, программисту, какие из них действительно важны.
Если Вы вольны вернуться назад и поспорить с ним и использовать преимущества "безразличных" случаев, то можете получить более простое решение.
Возьмем инженерное приложение, такое, как 75-тонный пресс, работающий с металлическим порошком и выдавливающий разные штуки. Некто желает установить компьютер для управления клапанами в тех местах, где сейчас применяется гидравлическое управление. Какого типа спецификацию получите Вы от инженера? Наиболее вероятно, что датчики были расположены с точки зрения электомеханических удобств. Теперь же их могли расположить где-нибудь еще, но где - инженер позабыл. Если Вам хочется получить объяснения, то Вы должны приблизиться к реальному миру и удалиться от их модели этого мира.
Другим примером может послужить алгоритм ПИД (пропорционального интегрирования и дифференцирования) для сервоприводов. У Вас есть один термин для интегрирования, один - для дифференцирования и один - для сглаживания. Вы комбинируете 30% интегрирования с 10%-ми дифференцирования или что-то вроде этого. Но ведь это - всего-навсего цифровой фильтр. Он был удобен во времена аналоговой техники для того, чтобы можно было отбросить некоторые выражения из области цифровой фильтрации и заявить: "Это - интегратор, а то - дифференциатор. Я сделаю это с помощью конденсатора, а то - с помощью катушки индуктивности". И вновь авторы спецификаций будут моделировать аналоговое решение проблемы, которое моделировало электромеханическое решение и будут находиться за несколько моделей от реальности. На самом деле Вы можете заменить все это с помощью двух или трех коэффициентов в цифровом фильтре для получения куда более чистого, простого и эффективного решения.
----------------------------------------------------------------
------------------------------------------------------------ СОВЕТ Вернитесь к тому, что представляла собой задача до того, как заказчик пытался ее решить. Используйте "безразличные" ситуации. ------------------------------------------------------------
----------------------------------------------------------------
Мур продолжает:
Иногда возможности для упрощения сразу не видны. Вот, кстати, проблема увеличения изображения на цифровом графическом дисплее, к примеру, для САПР. На экране имеется картинка, и Вы хотите увеличить ее кусок для рассматривания деталей. Я реализовывал это так: Вы подводили курсор к интересующей позиции, затем нажимали кнопку, и изображение увеличивалось до тех пор, пока у Вас не образовывалось окно желаемого размера. Так я делал всегда. До тех пор, пока не осознал, что это глупо. Мне никогда не нужно было менять увеличение с таким мелким шагом.
Поэтому вместо предвижения курсора на один пиксел за раз я сделал перепрыгивание сразу, скажем, на десять позиций.
И вместо плавного увеличения размера окна я сделал скачкообразное увеличение. У Вас нет больше выбора масштаба. Увеличение производится в четыре раза. Промежуточные размеры не представляют интереса, прыжки же можно делать сколько угодно раз.
Безжалостно квантуя разные вещи, Вы можете облегчить работу с ними, сделать их более надежными и простыми.
----------------------------------------------------------------
------------------------------------------------------------ СОВЕТ Для упрощения - квантуйте. ------------------------------------------------------------
----------------------------------------------------------------
Мур подводит итоги:
Это большая смелость - взять и сказать: "Вы на самом деле не то имели в виду" или "Вы не будете возражать, если я изыму эту страницу и заменю ее таким вот выражением?" Это вызывает раздражение. От Вас хотят, чтобы Вы делали то, что Вам сказано.
ЛяФарр Стюарт пошел этим путем, когда перепроектировал Форт [3]. Ему не нравился буфер входного потока, поэтому он сделал Форт без него и понял, что буфер ему не нужен. Если Вы можете улучшить постановку задачи, следует не упускать такую возможность. Гораздо приятнее перепроектировать мир, нежели реализовывать его "в лоб".
----------------------------------------------------------------
Опытные программисты учат быть тактичными и проводить свой подход так, чтобы не чувствовалось угрозы: "Какими могли бы быть последствия от замены этого на то?" и т.д.
Еще один путь к упрощению проблемы:
------------------------------------------------------------ СОВЕТ Упрощая, не доставляйте беспокойства пользователям. ------------------------------------------------------------
Предположим, Вы проектируете часть текстового процессора, которая показывает каталог хранимых документов на экране, один документ в строке. Задумано, что пользователь будет подводить курсор к имени документа, а затем вводить однобуквенную команду для выбранного действия - "П" для печати, "Р" для редактирования и т.д.
Вначале кажется приемлемым позволить пользователю перемещать курсор по всему экрану. Это означает, что те места, где уже имеется текст, должны быть защищены от порчи. Это приводит к концепции "защищенных полей" и специальной обработки. Более простой подход привязывает курсор к определенным полям, возможно с использованием инверсии цвета для того, чтобы выделить для пользователя величину доступного поля.
Другой случай возникает, когда задача предлагает пользователю ввести числовое значение. Зачастую такие задачи не делают проверку ввода до тех пор, пока Вы не нажмете , после чего система ответит сообщением об ошибке типа "неверное число". Столь же просто - скорее всего, проще - проверять каждую нажатую клавишу и просто не позволять появляться нецифровым символам.
------------------------------------------------------------ СОВЕТ Для упрощения извлекайте выгоду из знания того, что возможно. ------------------------------------------------------------
----------------------------------------------------------------
Майкл ЛаМанна, Форт-программист из Лонг-Айленда, штат Нью-Йорк, комментирует:
Я всегда пытаюсь проектировать задачу на самом мощном процессоре, на который могу наложить руку. Если у меня есть выбор между разработкой на системе с процессором 68000 либо с процессором 6809, то я буду делать ее на первой системе. Процессор сам по себе настолько мощен, что берет на себя заботу о множестве деталей, которые иначе мне пришлось бы решать самому.
Если мне затем придется вернуться и переписать задачу для более простого процессора, то это нормально. По крайней мере, я не терял своего времени напрасно.
----------------------------------------------------------------
Небольшое предупреждение: Если Вы используете существующий компонент для упрощения своего прототипа, не позволяйте этому компоненту влиять на Ваш проект. Нежелательно, чтобы проект зависел от внутреннего устройства компонента.
СОБЛЮДЕНИЕ БЮДЖЕТА И ГРАФИКА
Другим важным аспектом фазы анализа является распределение стоимости.
Опять же, этот процесс сложнее, чем может показаться. Если Вы не знаете проблему до тех пор, пока ее не решите, то как, спрашивается, вы можете узнать, сколько времени она отнимет?
Тщательное планирование обязательно, поскольку все всегда длится дольше, чем Вы расчитываете. У меня по этому поводу есть теория, основанная на законах вероятности:
------------------------------------------------------------ СОВЕТ Среднее время доработки задачи "за пару часов" составляет около 12-ти часов. ------------------------------------------------------------
Представьте себе следующий сценарий: Вы находитесь посередине разработки большой программы, когда неожиданно Вас осеняет добавить туда относительно маленькую функцию. Вы думаете, что это займет часа два, поэтому, без дальнейшего планирования, Вы ее делаете. Полагаем: два часа на время кодирования. Время проектирования Вы не считаете, поскольку постигли необходимость - и реализацию - функции в мгновение ока при работе над задачей. Поэтому Вы кладете два часа.
Но допустим следующие возможности:
1. Ваша реализация содержит ошибку. Через два часа это не работает. Поэтому Вы проводите еще два часа на переделку. (Всего 4 часа.) 2. ИЛИ еще до реализации Вы поняли, что Ваш начальный проект не работал бы. Вы тратите два часа на перепроектирование. `Эти` два часа считаются. Плюс два часа на кодирование. (Всего 4 часа.) 3. ИЛИ Вы реализуете первый проект до того, как понимаете, что он неработоспособен. Поэтому Вы перепроектируете (еще два часа) и перекодируете (еще два). (Всего 6 часов.) 4. ИЛИ Вы кодируете первый вариант, находите ошибку, переписываете код, находите слабость в проекте, перепроектируете, перекодируете, находите ошибку в новом коде, перекодируете опять. (Всего 10 часов.)
Вы видите, как растет снежный ком?
5. Теперь Вам надо задокументировать новую функцию. Добавьте два часа к вышеуказанному. (Всего 12 часов.) 6. После того, как Вы потратитли что-то между 2-мя и 12-ю часами, устанавливая и отлаживая свою новую функцию, Вы неожиданно обнаруживаете, что элемент Y в Вашей задаче перестал работать.
Хуже всего то, что Вы не понимаете, почему. Вы проводите два часа за чтением дампов памяти, пытаясь выяснить причину. Когда она найдена, Вы тратите целых 12 дополнительных часов, переделывая элемент Y. (Всего 26 часов.) Затем Вам надо задокументировать синтаксические изменения по элементу Y. (Всего 27 часов.)
Это в сумме составляет более трех человеко-дней. Конечно, редко бывает так, чтобы все эти несчастья навалились на Вас сразу, однако все решительно `против` того, чтобы быть таким легким, как кажется.
Как можно улучшить свои шансы по правильной оценке временных запросов? На эту тему написано много хороших книг, отмечу `Мифический человеко-месяц` Фредерика П. Брукса, Мл. [4]. У меня есть мало что добавить к этому кладезю знаний, за исключением некоторых личных наблюдений.
1. Не занимайтесь целым. Разбейте проблему на минимально возможные куски, затем посчитайте время на каждый кусок. Сумма частей всегда больше того, что Вы дали бы на целое. (Целое кажется меньшим, чем сумма его частей.) 2. При определении кусков отделяйте те из них, которые Вы настолько хорошо понимаете, что можете рискнуть угадать их содержимое, от тех, для которых этого сделать нельзя. Для второй категории обрисуйте пределы затрат. 3. Немного психологии: всегда предлагайте Вашему клиенту разные варианты. Клиенты `любят` варианты. Если вы скажете: "Это обойдется Вам в 6000 долларов", клиент наверное ответит: "Я реально потратил бы 4000". Это ставит Вас в такое положение, при котором надо либо соглашаться, либо оставаться без работы.
Однако, если Вы скажете: "У Вас есть выбор: за 4000 долларов я заставлю это `проходить` через обруч. За 6000 я заставлю это `прыгать` через обруч. За 8000 я сделаю так, что оно будет `танцевать` через обруч с флагами, разбрасывая конфетти и распевая "Шумел камыш". Большинство заказчиков сойдутся на прыжках через обруч.
------------------------------------------------------------ СОВЕТ Все отнимает больше времени, чем Вы думаете, включая размышления. ------------------------------------------------------------
СМОТРИНЫ ДЛЯ КОНЦЕПТУАЛЬНОЙ МОДЕЛИ
Последний прямоугольник на нашей итеративной аналитической карусели обозначен как "Демонстрация модели заказчику". С помощью выделенных в этой главе инструментов эта работа должна быть легко выполнима.
При документировании требований помните, что спецификации сходны со снежным человеком. Ныне они могут быть заморожены, но будут двигаться, скользить и растаивать когда пригреет. Избрали ли Вы диаграммы потоков данных или прямой Форт-псевдокод, готовьтесь к великой оттепели, не забывая применять концепции ограниченной связности.
Покажите задокументированную модель покупателю. Когда заказчик, наконец, удовлетворен, Вы готовы к следующему большому шагу: созданию проекта!
ЛИТЕРАТУРА
1. Kim Harris, "The FORTH Philosophy," `Dr. Dobb's Journal`, Vol. 6, Iss. 9, No. 59 (Sept. 81), pp. 6-11. 2. Victor Weinberg, `Structured Analysis`, Englewood Cliffs, N.J.: Prentice-Hall, Inc., 1980. 3. LaFarr Stuart, "LaFORTH", 1980 FORML Proceedings, p. 78. 4. Frederick P. Brooks, Jr., `The Mythical Man-Month`, Reading, Massachusetts, Addison-Wesley, 1975.
Анализ