|
Перевод с английского С.Н.Дмитренко
(Москва, 1993 г.)
В html перевел А.Черезов 1996 г.
СПИСОК ПРИМЕРОВ ПРОГРАММ ~~~~~~~~~~~~~~~~~~~~~~~~ ПРОГРАММА N СТРАНИЦЫ Яблоки 24 - 26 Телефонные тарифы Крошечный редактор Цвета Римские числа Рисование квадратиков Банкомат
Уже несколько лет я использую для программирования язык Форт. С первой же встречи с ним я был очарован и покорен его простотой, элегантностью и логичностью. И Форт пока ни разу не давал мне повода для разочарований. К сожалению, в нашей стране Форт знают и используют лишь считанные энтузиасты, чему в большой мере способствует отсутствие сколько-нибудь доступной литературы и программного обеспечения. Можно сказать много грустных слов о тенденциозности современной околотехнической литературы, о переориентации отечественных программистов и разработчиков с исследовательских и новаторских на чисто коммерческие работы, о складывающемся монополизме отнюдь не лучших (зато более хватких) производителей компьютеров и программного обеспечения и т.д. И все же, несмотря на эти признаки нарастающего вырождения в нашем, да и мировом компьютерном деле, я надеюсь, что подъем наступит, и мы будем его свидетелями и реализаторами.
Впрочем, когда я говорю о недоступности литературы по Форту, я несколько преувеличиваю. Несколько книг все же изданы, и среди них - несколько очень хороших. Я выделил бы такие:
Последняя из этих книг, на мой взгляд - прекрасный учебник по Форту (оригинальное название ее - "Starting FORTH", т.е. "Начала Форта"). Именно знакомство с ней побудило меня искать и вторую книгу того же автора, Лео Броуди, служащую логическим продолжением первой: "Thinking FORTH" (я перевел это название как "Способ мышления - Форт", хотя игру слов на русский язык точно перевести, кажется, невозможно). Мне не удалось найти ее перевода, и поэтому я обратился к копии оригинала. Впоследствии выяснилось, что в С.-Петербурге перевод этой книги имеется, издание же его (которое должно было последовать вслед за "Начальным курсом...") пока не состоялось из-за известных финансовых затруднений.
Мне кажется, книг, подобных "Thinking FORTH", в нашей стране не видели. По крайней мере, столь подробного и приближенного к подлинной практике описания процесса создания программного обеспечения - и при этом без традиционного наукообразия - мне раньше читать не доводилось. Даже если предположить, что лишь один я столь мало эрудирован, это все равно нисколько не может умалить интересность книги для любого увлеченного программиста - и необязательно только фортиста. Я просто не мог держать эту книгу только для себя - и рискнул выполнить собственный ее перевод для тех, кому английский оригинал недоступен.
Я не литератор и не профессиональный переводчик, поэтому приношу извинения за порою встречающиеся англицизмы и коряво построенные русские фразы. Будем считать этот вариант черновым и ознакомительным, я надеюсь только, что правильно передал смысл текста.
Перевод готовился мною намеренно в самом простом формате - в виде компьютерных ASCII-файлов. Это позволяет читать и распечатывать текст практически на любых компьютерах и принтерах (расчитывать на хорошо обеспеченных читателей пока не приходится). Платой за это служит исключение из текста многочисленных остроумных авторских иллюстраций и рисунков, которые слишком затруднительно было бы отображать алфавитно-цифровыми символами. Все же я постарался сохранить все существенные для понимания сути картинки.
Я старался держать перевод как можно ближе к тексту оригинала, однако все же позволил себе сделать несколько, на мой взгляд, существенных, правок и ремарок. В наибольшей степени это касается употребления термина "блок" вместо использовавшегося Броуди термина "экран" - это сделано в соответствии с рекомендациями из "Начального курса...", который вышел значительно позже имеющегося у меня оригинала "Thinking FORTH" (от 1984 года). Кроме того, с целью улучшения восприятия в книге переведены на русский язык имена слов, использующихся для иллюстраций и примеров. Кстати, следует иметь в виду, что лишь некоторые реальные Форт-системы (будучи рожденными вне России) допускают использование русских букв в именах определений (заметим, что для классических языков программирования о такой возможности даже речи нет). Пример Форт-системы, в которой можно использовать для имен любые символы - FORTH-83 V4.0 Undirect (&-C, piton DS @1992) для компьютеров класса PDP-11 (RT-11: ДВК, СМ, БК, УКНЦ и т.д.).
Надеюсь, что эта книга поможет Вам лучше понять суть работы программиста-практика, расширить свой кругозор и очистить мышление. Броуди высказывает множество интересных и порою спорных мыслей, поднимает темы, которые весьма актуальны и сегодня - особенно в связи с "непрограммистскими" тенденциями в программировании, переусложненностью современных систем и борьбой за сохранение контроля над такими системами. Интересно проследить точки соприкосновения и различия между модным сегодня объектно-ориентированным программированием (по-моему, способом улучшения понимания системы человеком путем внесения огромной избыточности в исполняющую систему или компилятор) и Форт-методом (достижения того же путем исключения избыточности и достижения простоты). Форт дает совершенно иное решение "вечных" проблем программирования, позволяет избавиться от страха перед большими системами и задачами. Мне кажется, распространению Форта мешают лишь два фактора: 1) воспитываемая сызмальства "классическая" методология мышления у программистов (мешающая в равной степени и внедрению объектно-ориентированной технологии) и 2) направленность Форта на создание "конечных" продуктов и систем, особенно - специализированных, и ограниченность (минимальность) имеющихся стандартов. Если вторая проблема вполне преодолима, то первая будет еще многие годы тянуть назад всю компьютерную науку (как она и тянет ее уже не первое десятилетие). Постарайтесь освободиться от консервативного скепсиса; не обязательно целиком принимать Форт или какие-то размышления Броуди, достаточно хотя бы усомниться в незыблемости устоявшихся и непреложности модных течений в искусстве программирования. Наградой послужит уже то, что Ваше мышление будет подготовлено к восприятию перемен, которые, несомненно, принесет нам XXI век.
С.Н.Дмитренко, 8 апреля 1993 г.
Программирование компьютеров может свести с ума. Другие профессии дают Вам прекрасные возможности наблюдать осязаемые результаты Ваших усилий. Часовщик может смотреть на свои зубчики и колесики, швея - на швы, ровно ложащиеся после каждого взмаха иглы. Но программист проектирует, строит и ремонтирует нечто воображаемое, призрачные механизмы, ускользающие от восприятия органами чувств. Наша работа происходит не в ОЗУ, не в программе-редакторе, а внутри нашей головы.
Построение моделей в воображении привлекает и доставляет удовольствие программисту. Как же лучше к этому подготовиться? Вооружиться самыми хорошими отладчиками, декомпиляторами и дизассемблерами? Они помогают, однако самые существенные из технологий и инструментов - умственные. Нам нужна последовательная и практическая методология для `мышления` на тему задач программирования. Это и составляет суть того, что я попытался выразить в моей книге. "Способ мышления ..." предлагается всем, кто заинтересован в написании программ для решения конкретных задач. Книга рассматривает вопросы проектирования и применения: принятие решений о том, что Вам нужно сделать, разработка компонентов системы и, наконец, построение системы.
В книге подчеркивается важность написания программ не просто работоспособных, но и надежных, логичных и выражающих наилучшее решение проблемы самыми простыми методами.
Несмотря на то, что описываемые здесь принципы могут быть применены к любому языку, я представил их в контексте языка Форт. Форт - это язык, операционная система, набор инструментов и философия. Это - идеальное средство для мышления, поскольку оно соответсвует тому способу, по которому работают наши головы. Думать на Форте значит думать просто, думать элегантно, думать гибко. Такое мышление `не` имеет запретительного характера, `не` сложно, `не` чрезмерно теоретизировано. Вам даже не нужно знать Форт для получения пользы от этой книги. Книга "Способ мышления - Форт" сочетает Форт-метод со многими принципами, выработанными современной компьютерной наукой. Союз между простотой Форта и традиционной дисциплиной анализа и стилистки даст Вам новый и лучший способ подхода к задачам программирования и окажет помощь во всех областях применения компьютеров.
Если Вы хотите узнать больше о Форте, другая моя книга - "Начальный курс программирования на языке Форт" - содержит сведения об этом языке. Кроме того, такие сведения приводятся в приложении А данной книги.
Несколько слов о плане этой книги. Первая глава посвящена основным соображениям, далее я провел книгу по основному циклу создания программного обеспечения: от начальных требований до внедрения. Приложения в конце включают обзор Форта для тех, кто с ним не знаком, тексты для нескольких описанных в книге программ, ответы на вопросы и свод соглашений по стилистике.
Многие мысли в этой книге не являются научными. Они основаны на субъективном опыте и наблюдениях за самим собой. По этой причине я привел в книге интервью с большим количеством профессионалов, работающих на Форте, и не все из них полностью согласны друг с другом или со мной. Все эти мнения могут изменяться изготовителем без специального уведомления. В книге вносятся также предложения, называемые "советами". Подразумевается, что им следует внимать лишь тогда, когда они соответствуют Вашей ситуации. В Форт-мышлении нет нерушимых правил. Для обеспечения возможно большего соответствия возможным Форт-системам все примеры программ в книге соответствуют стандарту Форт-83.
Личность, в сильной степени повлиявшая на эту книгу - это человек, придумавший Форт - Чарльз Мур. В дополнение к нескольким дням, проведенным за интервьюированием его для книги, я имел возможность понаблюдать его за работой. Он - хозяин своего дела, двигающийся в нем быстро и искусно так, как будто он физически реализует концептуальные модели внутри машины - строя, оттачивая, обыгрывая. Он обходится минимумом инструментов (результат продолжающейся борьбы против внутренней сложности) и немногими ограничениями, дополняющими те, которые накладываются его собственной технологией. Я надеюсь, что эта книга уловила что-то из его мудрости. Пользуйтесь!
Особая признательность всем добрым людям, которые отдали свое время и свои идеи этой книге, среди них: д-р Марк Бернстейн, Дональд Барджисс, Кери Кемпбелл, д-р Раймонд Десси, Том Даулинг, Майкл Хэм, Ким Харрис, Дейв Джонсон, д-р Питер Кожж, Майкл ЛаМанна, Чарльз Х. Мур, д-р Майкл Старлинг и Джон Телеска.
Спасибо также Джерри Бутеллю, Джеймсу Элфу, Джиму Флорною, фирме Moore Products Co. и Карен Нельсон.
Печально, что Майкл ЛаМанна покинул этот мир в то время, когда книга еще писалась. Его глубоко не хватает нам, тем, кто любил его.
Форт является языком и операционной системой. Но это не все: он также и воплощение философии. Обычно философию не рассматривают как нечто, отдельное от Форта. Она не предшествовала Форту и не описывалась где-либо вне рассуждений о Форте, и даже не имеет другого имени, кроме как "Форт".
Что она такое? Как ее можно применять для решения задач программирования?
Перед тем, как ответить на эти вопросы, давайте сделаем 100 шагов назад и изучим некоторые из важнейших философий, развитых учеными-компьютерщиками на протяжении многих лет. Проследив траекторию этих достижений, мы сравним - и отделим - Форт от этих принципов в программировании.
В доисторические программные времена, когда компьютеры были еще динозаврами, простой факт того, что некий гений создал программу, которая правильно работает, вызывал великое удивление. По мере роста цивилизованности ЭВМ это удивление слабело. Руководители желали большего от программистов и их программ.
В то время, как стоимость аппаратуры устойчиво падала, стоимость программного обеспечения взмывала ввысь. Для программы было уже недостаточно хорошо просто правильно выполняться. Она должна была быть разработана быстро и обладать легкостью в управлении. Новое качество наряду с корректностью стало важнейшим. Это недостающее качество было названо "элегантностью".
В даном разделе мы проследим историю инструментария и технологий, предназначенных для написания более элегантных программ.
Первые программы для ЭВМ выглядели как-то вроде:
00110101
11010011
11011001
Программисты вводили их, устанавливая ряды переключателей в положение "вкл." для единиц и "выкл." для нулей. Эти значения были "машинными инструкциями" для ЭВМ, и каждая заставляла ее производить некие приземленные операции типа "переместить содержимое регистра А в регистр Б" или "добавить содержимое регистра В к содержимому регистра А".
Это оказалось несколько скучноватым.
Скука - мачеха изобретения, поэтому некоторые умные программисты осознали, что машину и саму можно затставить помочь им. Так они написали программу, которая переводила легкозапоминаемые аббревиатуры в труднозапоминаемые последовательности битов. Новый язык выглядел примерно так:
MOV B,A ADD C,A JMC REC1
Переводчик (транслятор) программ был назван `аcсемблером`, а новый язык - `языком ассемблера`. Каждая инструкция "собирала" ("ассемблировала") соответствующую последовательность битов для себя при сохранении точного соотношения между ассемблерной инструкцией и машинной командой. Но ведь имена программистам запоминать легче. По этой причине новые инструкции были названы `мнемониками`.
Программирование на языке ассемблера характеризуется соответствием "один-в-один" между каждой командой, которую набивает программист, и командой, которую исполняет процессор.
На практике программисты обнаружили, что они часто повторяют одинаковую `последовательность` инструкций вновь и вновь для того, чтобы делать одно и то же в различных частях программы. Было бы приятно завести имена, представляющие собой каждую из таких обычных последовательностей.
Это пожелание было удовлетворено "макроассемблером", более сложным ассемблером, который мог распознавать не только нормальные инструкции, но также специальные имена ("макро"). Для каждого из них макроассемблер транслирует пять или десять машинных команд, представленных этим именем, так, как будто программист написал их все полностью.
Важным достижением было изобретение "языка высокого уровня". Это опять была программа-переводчик, но более мощная. Высокоуровневые языки делают возможным для программистов записывать выражения вида:
X = Y(456/A) - 2которые сильно похожи на алгебраические. Благодаря языкам высокого уровня инженеры, а не только странноватые бит-жокеи, смогли начать писать программы. Бейсик и Фортран - примеры высокоуровневых языков.
Очевидно, что языки высокого уровня мощнее, чем ассемблеры, поскольку каждая инструкция может компилироваться в десятки машинных команд. Но что более важно, эти языки уничтожают линейное соответствие между исходным текстом и результирующим машинным кодом.
Реальные инструкции зависят от каждого "выражения" в исходном тексте, взятом как единое целое. Операторы вроде + и = сами по себе не имеют смысла. Они - просто часть сложной символики, которая зависит от синтаксиса и позиции оператора в тексте.
Это нелинейное, зависящее от синтаксиса соответствие между исходным текстом и реальным (обектным) кодом обычно рассматривается как неоценимый вклад в прогресс методологии программирования. Но, как мы увидим впоследствии, такой подход неизбежно предоставляет больше ограничений, чем свободы.
Большинство компьютеров используют нечто большее, чем просто список инструкций для своей работы. Они также производят проверку различных условий и затем "скачки" в соответствующие части программы в зависимости от результата. Они также производят многократное "зацикливание" на одних и тех же участках кода, обычно контролируя при этом момент выхода из цикла.
Как ассемблер, так и высокоуровневый язык обеспечивают возможности для переходов и циклов. В ассемблерах мы используем команды типа "jump" ("прыжок"), в некоторых языках высокого уровня пользуемся конструкциями типа "GO TO" ("перейти к"). Когда эти возможности используются в сильной степени, программы начинают становиться похожими на такую же неразбериху, как на рисунке 1-1.
Рис.1-1. Неструктурированный код, использующий инструкции типа "jump" или "GOTO".
ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПЕРЕХОД ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ИНСТРУКЦИЯ ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД ПРОВЕРКА УСЛОВИЯ ПЕРЕХОД
Этот подход, до сих пор широко представленный в таких языках, как Фортран и Бейсик, создает трудности при написании и трудности при внесении изменений. При такой "кашеобразной" манере написания программ невозможно протестировать отдельный участок кода или найти почему выполняется что-то, что выполняться не должно.
Трудности с кашевидными программами привели к открытию "блок-схем". Это были нарисованные карандашом и ручкой картинки, показывающие "течение" процесса, которые использовались программистом в качестве шпаргалки для понимания создаваемого кода. К несчастью, программист вынужден был осуществлять переход от кода к диаграмме и наоборот вручную. Многие программисты осознали бесполезность старомодных диаграмм.
Существенное движение вперед произошло с внедрением "структурированного программирования", методологии, основанной на том, что, как показал опыт, большие задачи проще решаются, если рассматривать их как совокупность меньших задач [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 ИНСТРУКЦИЯ
Следующий шаг вперед, вдохновленный использованием стуктурированных программ - структурированные языки программирования. Они содержат специальные операторы для управления процессом в составе своих наборов команд, что делает возможным написание программ, имеющих более модульный вид. Таким языком является Паскаль, изобретенный Никлаусом Виртом для обучения студентов принципам структурированного программирования.
На рисунке 1-4 показано, как этот тип языка позволяет переписать программу "приготовление завтрака".
Рис.1-4. Использование структурированного языка.
ИНСТРУКЦИЯ ИНСТРУКЦИЯ Решение - спешим? ЕСЛИ ДА, ТО ИНСТРУКЦИЯ ИНСТРУКЦИЯ Варка яиц ИНСТРУКЦИЯ ИНАЧЕ ИНСТРУКЦИЯ ИНСТРУКЦИЯ Приготовление овсянки ИНСТРУКЦИЯ ДАЛЬШЕ ИНСТРУКЦИЯ Мытье посуды ИНСТРУКЦИЯ
Языки структурированного программирования имеют управляющие структурные операторы типа ЕСЛИ и ТО для подчеркивания модульности в организации передачи управления. Как Вы можете заметить, отступы в тексте программы важны для ее читабельности, хотя все равно все инструкции внутри модуля написаны полностью вместо замены модуля его именем (например, "приготовление-овсянки"). Законченная программа может занимать десять страниц, с оператором ИНАЧЕ на странице пять.
Как приступать к разработке подобных модулей? Методология, называемая "разработкой сверху-вниз", утверждает, что модули следует строить, начиная с самого общего, главного и далее прорабатывать до уровня самых мелких модулей.
Последователи такого подхода могут засвидетельствовать возникновение позорно огромных потерь времени в результате ошибок в планировании. На горьком опыте они познали, что попытки корректировать программу после того, как она была написана - такая практика известна как "наложение заплат" - подобны попытке запереть двери конюшни после того, как лошадь уже понесла.
Поэтому как контрмеру они предлагают следующее официальное правило программирования сверху-вниз:
Не писать ни строчки текста до тех пор, пока план не проработан до мельчайших деталей.
Вследствие таких трудностей при внесении изменений в однажды написанные программы, все упущения в проекте должны быть устранены на стадии предварительного планирования. В противном случае могут быть затрачены человеко-года усилий по написанию кода, который потом нельзя использовать.
Мы обсуждали "модули" только как абстрактные объекты. Но любые высокоуровневые языки имеют аппарат, позволяющий кодировать модули проекта как модули реального кода - отдельные куски, которым можно дать имена и "вызывать" из других кусков кода. Эти куски называются подпрограммами, процедурами или функциями, в зависимости от языка программирования и способа реализации.
Предположим, мы написали "приготовление овсянки" в виде подпрограммы. Это могло бы выглядеть как-нибудь вроде:
процедура приготовление-овсянки взять чистую тарелку открыть коробку с овсянкой насыпать овсянки открыть молоко налить молоко взять ложку конец
Мы можем также написать и "варку-яиц", и "мытье-посуды" в виде подпрограмм. В этом случае можно определить "приготовление-завтрака" как простую программу, которая вызывает эти подпрограммы:
процедура приготовление-завтрака переменная С: булевская (означает спешку) `проверить наличие спешки` если С=истина, то вызвать приготовление-овсянки иначе вызвать варку-яиц конец вызвать мытье-посуды конец
Фраза "вызвать приготовление-овсянки" заставляет выполниться подпрограмму с таким именем. Когда выполнение заканчивается, управление возвращается назад в вызывающую программу в точку, следующую за вызовом. Подпрограммы повинуются законам структурированного программирования.
Как можно видеть, эффект при использовании подпрограмм такой же, как если бы тело этих подпрограмм присутствовало бы в вызывающем модуле. Но, в отличие от кода, производимого макроассемблером, подпрограмма может быть скомпилирована где угодно в памяти, после чего на нее можно просто ссылаться. Не обязательно компилировать ее внутри реального кода главной программы (см. рис. 1-5).
Рис.1-5. Главная программа и подпрограмма в памяти.
Главная программа
|-----------------------| ____
|_______________________| / \/ Подпрограмма
|_______________________| / +-----------------------+
|_______________________| / | Приготовление-овсянки |
| вызвать |/ |_______________________|
| приготовление-овсянки | |_______________________|
|_______________________|\ |_______________________|
|_______________________| \ |_______________________|
| | \_____/
Годами ученые-компьютерщики совершенствовались в искусстве использования многочисленных маленьких подпрограмм в сильно разветвленных, протяженных программах. Они могут быть написаны и отлажены независимо друг от друга. Это облегчает повторное использование ранее написанных программ, и так легче распределять части работы между различными программистами. Короткие куски проще продумывать и проверять их правильность.
Когда подпрограммы компилируются в отдельных частях памяти и вызываются ссылками на них, можно использовать одну и ту же подпрограмму много раз без излишнего расходования места на повторы кода подпрограммы. Так разумное использование подпрограмм может уменьшать размеры кода.
К сожалению, при этом имеется проигрыш в скорости исполнения. Проблему создает необходимость сохранения содержимого регистров перед переходом на подпрограмму и их восстановления при возвращении оттуда. Еще больше времени требуют невидимые, но существенные участки кода, необходимые для передачи параметров в и из подпрограммы.
Существенен также сам способ вызова и передачи параметров подпрограмме. Для автономного тестирования подпрограммы приходится писать специальные тестовые программы для ее вызова.
По этой причине ученые рекомендуют умеренное использование подпрограмм. На практике они обычно получаются довольно большими, от половины до целой страницы текста в длину.
Один из подходов, существенно опирающихся на использование подпрограмм, называется "постепенной детализацией" [2]. Идея состоит в том, что Вы начинаете с написания скелетной версии программы, использующей естественные имена процедур и структур данных. Затем Вы пишете версии для каждой из именованных процедур. Процесс продолжается в сторону большего уровня детализации до тех пор, пока процедуры не смогут быть выражены непосредственно на компьютерном языке.
При каждом шаге программист должен принимать решения об используемых алгоритмах и о типах структур данных, которые обрабатываются этими алгоритмами. Такие решения должны приниматься параллельно.
Если выбранный путь оказался непригодным, программист должен набраться мужества вернуться назад насколько требуется и заново проделать работу.
Обратите внимание, что при постепенной детализации Вы не можете реально запустить какую-либо часть программы до тех пор, пока не будут написаны ее компоненты самого нижнего уровня. Обычно это значит, что программу нельзя проверить до тех пор, пока она не будет полностью закончена.
Заметьте также: постепенная детализация заставляет Вас прорабатывать все детали структур управления на данном уровне перед переходом на следующий уровень вниз.
К середине конца 70-х компьютерная индустрия уже перепробовала все описанные нами концепции и все равно оставалась несчастной. Цена поддержки программного обеспечения - сохранения его функциональности перед лицом возможных изменений - выливалась более чем в половину его общей стоимости, в некоторых случаях доходя до девяноста процентов!
Все соглашались, что эти издержки можно обычно отнести к неполному анализу программ или плохому замыслу разработчиков. Однако было очевидно, что что-то не так с самим структурированным программированием. Когда проекты появлялись с опозданием, некомплектными или некачественными, разработчики жаловались на то, что все предвидеть невозможно.
Ученые мужи прилагали все больше усилий к проекту. "В следующий раз мы все продумаем лучше".
К этому времени возникла новая философия, описанная в статье, названной "Структурированная разработка" [3]. Один из ее принципов приводится ниже:
Простота - главный показатель, по которому рекомендуется выбирать среди альтернативных проектов для обеспечения снижения времени на отладку и модификацию. Уровень простоты может быть улучшен за счет разбиения системы на отдельные куски так, чтобы каждый из них мог рассматриваться, применяться, утверждаться и изменяться с минимальным влиянием на или изменениями в других частях системы.
Разбиение задачи на простые модули должно было облегчить написание, изменение и понимание программ.
Но на какой основе производить разбиение данного конкретного модуля? Статья "Структурированная разработка" выделяет три фактора при проектирования модулей.
Первый фактор - нечто, называемое "функциональной мощностью" - выражает единообразие назначения всего внутри модуля. Если все эти выражения в совокупности могут быть представлены как выполняющие единую задачу, то они являются функционально ограниченными (связными).
В общем случае можно сказать, являются ли выражения в модуле функционально ограниченными, отвечая на следующие вопросы: первый: можно ли описать их назначение одной фразой? Если нет, то модуль, скорее всего, не ограничен функционально. Далее дать ответ на следующие четыре вопроса:
Если Вы ответите "да" на один из этих вопросов, то перед Вами некоторое менее связное построение, нежели функциональный модуль. Слабые формы связи:
Совпадающая связность (выражения встречаются несколько раз в одном модуле)
Логическая связность (в модуле содержится несколько родственных функций и необходим флаг или параметр для решения о том, какую конкретно выполнять)
Связность по времени (имеется группа выражений, исполняющихся одновременно, например, инициализация, но не имеющих иной связи)
Коммуникационная связность (в модуле содержится группа выражений, работающих с одним и тем же набором данных)
Последовательная связность (когда результат одного выражения служит входными данными для следующего)
Наш модуль "приготовление-овсянки" демонстрирует функциональную связность, поскольку его можно представить как единое целое, несмотря даже на то, что он состоит из нескольких подчиненных задач.
Второй догмат структурированной разработки говорит о "сцеплении", меры того, как модули влияют на поведение других модулей. Сильное сцепление считается плохим тоном. В наихудшем случае один модуль изменяет код внутри другого модуля. Опасна даже передача управляющих флагов другим модулям для управления их функциями.
Приемлемой формой сцепления является связь через данные, которая предполагает передачу данных (не управляющей информации) из одного модуля в другой. Даже в этом случае системы гораздо легче строить, если интерфейсы передачи данных между модулями по возможности упрощены.
Когда данные могут быть доступны со стороны многих модулей (например, глобальные переменные), говорят о сильном сцеплении между модулями. Если программисту нужно изменить один из них, велика опасность того, что другие продемонстрируют "побочные эффекты".
Самый надежный способ сцепления данных - это передача локальных переменных в качестве переметров от одного модуля к другому. В результате вызывающий модуль говорит подчиненному: "Я хочу, чтобы ты использовал данные, которые я загрузил в переменные с именами X и Y, а ответ от тебя я ожидаю в переменной по имени Z. Никто другой больше не использует эти переменные".
Как мы уже говорили, обычные языки, поддерживающие подпрограммы, имеют тщательно проработанные методы передачи аргументов от одного модуля к другому.
Третья составляющая структурированной разработки касается процесса проектирования. Разработчикам рекомендуется использовать подход "сверху-вниз", но уделять при этом меньше немедленного внимания на управляющие структуры. "Разработка принятия решений" может подождать до более детализированной проработки модулей. Вместо этого на ранней стадии следует концентрировать усилия на иерархии внутри программы (какие модули вызывают какие модули) и на передаче данных от модуля к модулю.
Рис.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 (модуль ПРЕОБРАЗОВАТЬ-в-В), на вход подается А, выходом является В.
Быть может, наибольшим вкладом такого подхода является осознание того, что решения о передаче управления не должны доминировать в проекте. Как мы убедимся, поток управления - это поверхностный аспект проблемы. Мизерные изменения в исходных требованиях могут существенно изменить структуры управления в программе и потребовать многих лет ее углубленного "перекапывания". Но, если проект программы ориентирован на что-то другое, например, на потоки данных, то изменения в планах не будут столь разрушительными.
В работе [4], опубликованной еще в 1972 году, д-р Дэвид Л. Парнас показал, что критерием для разбиения на модули должны быть не шаги в процессе, а куски информации, которые, возможно, будут меняться. Модули должны использоваться для сокрытия такой информации.
Давайте рассмотрим эту важную идею об "упрятывании информации": предположим, Вы пишете Руководство по делопроизводству для своей компании. Вот его фрагмент:
Отдел продаж принимает заказ посылает синюю копию в архив оранжевую копию на склад Джей подшивает оранжевую копию в красный скоросшиватель на своем столе и производит упаковку.
Все согласны, что эта процедура корректна, и Ваше руководство распространяется для всех в компании.
А потом Джей увольняется, а приходит Мэрилин. Новые копии приказов имеют зеленую и желтую обложки вместо синей и оранжевой. Красный скоросшиватель переполняется и уступает место черному.
Все Ваше руководство становится устаревшим. Вы могли бы избежать устаревания, применяя слово "упаковщик" вместо имени "Джей", словосочетания "архивная копия" и "складская копия" вместо "синей" и "оранжевой" и т.д.
Этот пример иллюстрирует мысль о том, что для сохранения корректности перед лицом возможных изменений произвольные детали должны быть исключены из процедур. Они могут быть при необходимости описаны отдельно. К примеру, каждую неделю или около того отдел кадров может издавать список работников и их должностей, так что каждый при необходимости может узнать имя упаковщика из единого для всех источника. При изменении кадрового состава этот список должен будет меняться.
Такая техника очень важна при написании программного обеспечения. Почему же работающая уже программа должна быть когда-нибудь изменена? По любой из миллиона причин. Вам может понадобиться запустить ее на новом оборудовании, программа должна быть изменена только для того, чтобы приспособиться к этому оборудованию. Ей, может быть, необязательно быть чрезвычайно быстрой или мощной для того, чтобы удовлетворить использующих ее людей. Большинство групп разработчиков обнаруживают, что пишут "семейства" программ, то есть много версий родственных программ для конкретного поля применений, все являющиеся вариантами одной ранней версии.
Для обеспечения принципа упрятывания информации определенные детали программы должны быть сведены в единое место, и каждый полезный кусок информации должен встречаться один раз. Программы, игнорирующие эту максиму, виновны в избыточности. В то время как избыточность аппаратуры (резервные ЭВМ и т.д.) могут сделать систему более безопасной, информационная избыточность наоборот, опасна.
Любой знающий программист скажет Вам, что число, которое предположительно может измениться в будущих версиях программы, должно быть определено как "константа" и встречаться в программах в виде ссылки на свое имя, а не в виде значения. Например, количество колонок, представляющих ширину бумаги в Вашем принтере, должно быть представлено константой. Даже в языках ассемблера имеются конструкции типа "EQU" и метки для связи адресов и битовых масок с именами.
Любой хороший программист также применит принцип упрятывания информации при разработке подпрограмм, стремясь к тому, чтобы каждый модуль знал как можно меньше о содержимом других модулей. Сегодняшние языки программирования, такие, как C, Модула-2 и Эдисон, применяют этот подход в архитектуре своих процедур.
Но Парнас проводит свою идею значительно дальше. Он предлагает распространить ее на алгоритмы и структуры данных. Упрятывание информации - а не структура принятия решений или иерархия вызовов - вот что должно лежать в основе проектирования!
Парнас предлагает два критерия разбиения:
а) возможное (хотя пока и незапланированное)
использование и
б) возможное (хотя и незапланированное) изменение.
Этот новый взгляд на "модуль" отличается от традиционного. Такой "модуль" - собрание кусочков, обычно очень маленьких, которые вместе прячут информацию о некоторой стороне проблемы.
Двое других авторов описывают ту же идею по-другому, используя выражение "абстрагирование данных" [5]. Они приводят в пример стековую структуру. Стековый "модуль" состоит из процедур для очистки стека, отправки значения в стек, снятия значения оттуда и определения пустоты стека. Этот "многопроцедурный" модуль скрывает информацию о том как оргазизован стек от остальных частей программы. При этом он считается единым модулем потому, что его процедуры взаимозависимы. Нельзя изменить способ загрузки значения в стек без изменения способа снятия его оттуда.
Слово `использовать` играет важную роль в этой концепции. Парнас пишет в своей более поздней статье [6]:
Системы, достигнувшие определенной степени "элегантности" ... сделали это за счет "использования" одними частями системы других ... Если существует такое иерархическое построение, то каждый уровень представляет собой проверяемый и способный быть использованным подраздел системы ... Проектирование иерархии "использования" может быть одним из главных направлений приложения сил. Расчленение системы на независимо вызываемые подпрограммы должно производиться параллельно с решениями об "использованиях", поскольку они влияют друг на друга.
Разаработка, при которой модули группируются в соответствии с потоком передачи управления, не готова к быстрым изменениям в проекте. Структура, построенная в соответствии с иерархией управления, является поверхностной.
Готовность к восприятию изменений может обеспечить проект, в котором модули формируются по признаку объединения потенциально изменяемых вещей.
В этом разделе мы рассмотрим основные свойства языка Форт и сравним их со свойствами традиционных методологий.
Вот пример текста на Форте:
: ЗАВТРАК
СПЕШИМ? IF ОВСЯНКА ELSE ЯЙЦА THEN МЫТЬЕ ;
Он по структуре идентичен процедуре "приготовление-завтрака" на стр.8. (Если Вы - новичок в Форте, обратитесь к приложению А за объяснениями.)
Слова СПЕШИМ?, ОВСЯНКА, ЯЙЦА и МЫТЬЕ также заданы (что наиболее вероятно) как определения через двоеточие.
Здесь Форт демонстрирует все положительные качества, изученные нами: мнемонические обозначения, абстракцию, мощность, структурированные операторы передачи управления, сильную функциональную ограниченность, небольшую степень связности и модульность. Но, кроме модульности, подчеркнем еще то, что, быть может, является наиболее важной заслугой Форта:
Мельчайшим атомом программы на Форте является не модуль или подпрограмма или процедура, а "слово".
Далее, отсутствуют понятия подпрограмм, главных программ, утилит или операторов, вызываемых по отдельности. `Все` в Форте есть слова.
Перед тем, как мы изучим важность среды, основанной на словах, давайте остановимся на двух новшествах Форта, которые делают это возможным.
Во-первых, вызовы производятся автоматически. Нет необходимости писать CALL ОВСЯНКА, достаточно просто ОВСЯНКА. В Форте определение ОВСЯНКА "знает", какого типа словом оно является и какую процедуру надо вызвать для исполнения себя.
Таким образом переменные и константы, системные функции, утилиты, так же, как и определенные пользователем команды или структуры данных, могут быть "вызваны" просто по имени.
Во-вторых, передача данных происходит сама собой. Механизм, производящий такой эффект - это стек данных Форта. Форт автоматически загружает числа на стек; слова, требующие на входе числа, автоматически снимают их оттуда; слова, выдающие на выходе значения, автоматически кладут их на стек. Слова ПОЛОЖИТЬ-НА-СТЕК и ВЗЯТЬ-СО-СТЕКА в Форте на высоком уровне не существуют.
Таким образом, мы можем написать:
: ВЫПОЛНИТЬ
ПОЛУЧИТЬ-С ПРЕОБРАЗОВАТЬ-в-D ВЫДАТЬ-D ;
где ПОЛУЧИТЬ-С считывает "С" и оставляет его на стеке, ПРЕОБРАЗОВАТЬ-в-D берет "С" со стека, преобразует и оставляет на стеке как "D". Наконец, ВЫДАТЬ-D берет "D" со стека и записывает его. Форт скрывает акт передачи данных в нашем коде, позволяя сконцентрироваться на функциональных шагах преобразования информации.
Вследствие использования стека для передачи данных слова могут вкладываться в другие слова. Любое слово может положить числа на стек и взять их назад, не нарушая поток данных между вышестоящими словами (разумеется, если оно не забирает или оставляет на стеке какие-нибудь неожиданные значения). Таким образом, стек обеспечивает структурированное, модульное программирование, в то же время предоставляя простой механизм передачи локальных аргументов.
Форт убирает из наших программ детали о том, `как` вызываются слова и `как` передаются данные. Что же остается? Остаются только слова, описывающие нашу задачу.
Имея слова, мы можем полностью воспользоваться рекомендациями Парнаса - разбивать задачу в соответствии с частями, которые могут измениться, и формировать каждый "модуль" из стольких маленьких функций, сколько их потребуется для упрятывания информации об этом модуле. На Форте мы можем написать для этого столь много слов, сколько для этого нужно, независимо от простоты каждого из них.
Строка из типичной для Форта задачи может выглядеть так:
20 ПОВЕРНУТЬ ВЛЕВО БАШНЯ-ТАНКА
Немногие другие языки могут вдохновить Вас на сочетание подпрограммы с именем ВЛЕВО, простo модификатором, с подпрограммой БАШНЯ-ТАНКА, просто именем части аппаратуры.
Поскольку слово Форта легче запустить, чем подпрограмму (просто по имени, без специального вызова), программа на Форте может быть разбита на большее количество слов, нежели обычная - на подпрограммы.
Наличие большого набора простых слов делает простым использование техники, которую мы назовем "программирование на уровне компонентов". Для пояснения давайте вначале прoсмотрим те объединения, которые мы неопределенно описали как "части, которые могут быть изменены". В типовой системе почти все может быть подвержено изменениям: устройства ввода/вывода, такие, как терминалы и принтеры, интерфейсы типа микросхем ПЛМ, операционные системы, любые структуры данных или их представление, любые алгоритмы и т.д.
Вопрос: "Как можно минимизировать вклад каждого из подобных изменений? Как определить наименьший набор изменяемых вещей для обеспечения требуемых перемен?"
Ответом является: "наименьший набор взаимовлияющих структур данных и алгоритмов, которые разделяют знание о том, как они в совокупности работают." Мы будем называть такое объединение "компонентом".
Компонент является ресурсом. Он может быть частью аппаратуры, например, ПЛМ или аппаратным стеком. Или компонент может быть программным ресурсом, таким, как очередь, словарь или программный стек.
Все компоненты включают в себя объекты данных и алгоритмы. Не имеет значения, физический ли это объект данных (такой, как аппаратный регистр) или абстрактный (как вершина стека или поле в базе данных). Безразлично, описан ли алгоритм в машинном коде или проблемно-ориентированными словами типа ОВСЯНКА или ЯЙЦА.
Рисунок 1-7 сопоставляет результаты структурированной разработки с результатами разработки на уровне компонентов. Вместо `модулей` под названиями ЧИТАТЬ-ЗАПИСЬ, РЕДАКТИРОВАТЬ-ЗАПИСЬ и ПИСАТЬ-ЗАПИСЬ мы сосредоточены на `компонентах`, описывающих структуру записей, реализующих систему команд редактора и обеспечивающих процедуры чтения/записи.
Что мы сделали? Мы ввели новый этап в процесс разработки: разбили на компоненты во время `проектирования`, затем описали последовательность, иерархию и линию вход-обработка-выход при `реализации`. Да, это еще один шаг, однако мы получили дополнительное измерение для проведения разреза - не только по слоям, но и `в клеточку`.
Рис.1-7. Структурированная разработка против разработки на уровне компонентов.
Последовательное/иерархическое проектирование:
+-----------------+ | обновить-запись | +-----------------+ | +---------------+--------------------+ | | | +-------|--------+ +----|-----------------+ +|--------------+ | СЧИТАТЬ-ЗАПИСЬ | | РЕДАКТИРОВАТЬ-ЗАПИСЬ | | ПИСАТЬ-ЗАПИСЬ | +----------------+ +----------------------+ +---------------+
Разработка по компонентам:
: ОБНОВИТЬ-ЗАПИСЬ ЗАПИСЬ ЧИТАТЬ РЕДАКТИРОВАТЬ ЗАПИСЬ ПИСАТЬ ; | \ | / | | \_______|___________/__ | | _______________|__________/ \ | | / | \ | +------|-/--+ +-----|----+ +----\----|-----+ | структура | | редактор | | программы | | записей | | | | записи/чтения | +-----------+ +----------+ +---------------+
Представим себе, что после того, как программа была написана, нам потребовалось изменить структуру записи. При последовательном, иерархическом проектировании это затронет все три модуля. При проектировании на уровне компонентов изменение коснется лишь компонента, описывающего структуру записи. Ничто другое, использующее этот компонент, не должно знать о перемене.
В дополнение к перечисленному преимущество такой схемы состоит в том, что программисты в группе могут получить в разработку индивидуальные компоненты с меньшей взаимозависимостью. Принцип программирования компонентов применим к руководству группой так же, как и к разработке программ.
Мы будем называть набор слов для описания компонента "лексиконом". (Одно из значений слова "лексикон" - "набор слов, относящийся к определенному кругу интересов".) Лексикон - это Ваш наружный интерфейс с компонентами (рис. 1-8).
Рис.1-8. Kомпонент описывается лексиконом.
ВЕЩЬ ДУХАЙКИ ВЕРТЕТЬ РАЗДВИГАТЬ
ДЕЛО ТЕМА ПИХАТЬ ЦЕПЛЯТЬ
ТОВАР ЧТОЭТО | КРУТИТЬ / |
| \ / / | | \ / |
+-|--\--------/----/--|-----------|--\---------/-----|------+
| |Объекты данных | | Алгоритмы (действия) |
| | (вещи) / | ... \ | |
| | \ * ------ ---> /| | |
| /\ +---+ / \ |----| | ---- |
| \/ +---+ *---* ------ \/ |
+-----------------------------------------------------------+
В данной книге слово "лексикон" относится только к тем словам компонента, которые используются по имени вне этого компонента. Компонент может содержать также определения, написанные исключительно для поддержки видимого снаружи лексикона. Мы будет называть вспомогательные определения "внутренними" словами.
Лексикон дает логические эквиваленты объектам данных и алгоритмам в форме имен. Лексикон вуалирует структуры данных и алгоритмы компонентов - "как оно работает". Он представляет миру только "концептуальную модель" компонента, описанную простыми словами - "что оно делает".
Эти слова затем становятся языком для описания структур данных и алгоритмов компонентов, написанных на более высоком уровне. "Что" для одного компонента становится "как" для высших компонентов.
Написанная на Форте задача состоит только из компонентов. Рисунок 1-9 показывает, как может быть разбита робототехническая задача.
Можно даже сказать, что лексикон - это специализированный компилятор, написанный специально для поддержки кода высокоуровневой программы наиболее эффективным и надежным способом.
Между прочим, сам по себе Форт не поддерживает компоненты. Ему это не нужно. Компоненты - это продукты разбиения программы ее проектировщиком. (В то же время Форт имеет "блоки" - небольшие порции массовой памяти для хранения исходных текстов. Компонент обычно может быть написан в пределах одного или двух экранов Форта.)
Рис.1-9. Полная программа состоит из компонентов.
+--------------+ | Сварить кофе | +-/---------\--+ / \ / \ Лексикон Лексикон процессов перемещений робота робота / \ Лексикон Лексикон переключателей считывания / показаний Лексикон датчиков шагового / двигателя / \ / Корневой язык (Форт) \ / +---\------------/---+ | Реальный компьютер | +--------------------+
Важно понять, что лексикон может использоваться любым или всеми компонентами высших уровней. Ни один нормальный компонент `не` прячет свои компоненты поддержки, как это часто случается при по-слойном подходе к разработке. Вместо этого каждый лексикон волен использовать все команды, определенные до него. Команда для движения робота опирается на корневой язык, со всеми его переменными, константами, операторами работы со стеком, математикой и др. так же сильно, как и на любой другой компонент.
Важным результатом такого подхода является то, что полная задача использует единый синтаксис, который легко выучить и соблюдать. Именно поэтому я использую слово "лексикон", а не "язык". У языков уникальные синтаксисы.
Доступность команд также значительно облегчает процесс тестирования и отладки. Интерактивность Форта позволяет программисту набирать и тестировать примитивные команды типа
ПРАВЫЙ ПЛЕЧО 20 ПОВЕРНУТЬ"снаружи" так же просто, как и более мощные типа
ЛЕВЫЙ КОФЕЙНИКВ то же время программист может (при желании) намеренно запретить доступ конечного пользователя к любым командам, включая сам Форт, после того, как программа закончена.
Новая Форт-методология проясняется. Программирование на Форте состоит в расширении корневого языка в сторону приложения, определении новых команд, которые прямо описывают проблему.
Языки программирования, созданные специально для опеределенных применений, таких как робототехника, производственный контроль, статистика и т.д., известны как "проблемно-ориентированные". Форт - это программные средства для `создания` проблемно-ориентированных языков. (Последняя фраза - быть может, самое краткое из возможных описаний Форта.)
На самом деле Вам не стоит писать каких-либо серьезных задач на Форте; как язык, он просто недостаточно мощен. Вам `следует` писать на Форте свои собственные языки (лексиконы) для моделирования Вашего понимания проблемы, на которых Вы можете элегантно описать ее решение.
Поскольку современные языки программирования дают несколько иное толкование выражения "упрятывание информации", нам придется внести ясность. От чего, от кого мы прячем информацию?
Новейшие традиционные языки (такие как Модула-2) напрягают свои силы для обеспечения упрятывания в модуле информации о его внутренних алгоритмах и структурах данных от других модулей. Целью является достижение независимости модуля (минимальной связности). Создается впечатление, что модули стараются атаковать друг друга как враждебные антитела. Или по-другому, что злобные банды модулей-мародеров выходят на большую дорогу грабить драгоценное семейство структур данных.
Это не то, чем озабочены мы. Мы понимаем упрятывание информации просто как средство минимизации эффектов от возможного изменения проекта методом локализации тех вещей, которые могут измениться, внутрь каждого компонента.
Форт-программисты обычно предпочитают держать свою программу под личным контролем и не использовать технику физического упрятывания структур данных. (Несмотря на это, великолепно простой способ - всего в три строки исходного текста - добавления к Форту Модула-подобных модулей был предложен Дьювеем Валь Шорром [7].)
Мы уже отмечали две особенности Форта, обеспечивающие использование описанной методологии - автоматические вызовы и автоматическую передачу данных. Третья особенность позволяет описывать структуры данных внутри компонента в терминах предварительно описанных компонентов. Эта особенность - прямой доступ к памяти.
Предположим, что мы определяем переменную ЯБЛОКИ: VARIABLE ЯБЛОКИ
Мы можем записать число в эту переменную для указания того, сколько яблок имеется в текущий момент:
20 ЯБЛОКИ !Мы можем распечатать содержимое переменной:
ЯБЛОКИ ? 20 okМы можем увеличить ее содержимое на единицу:
1 ЯБЛОКИ +!(Новичок может изучить механизм работы этих фраз по приложению А.)
Слово ЯБЛОКИ имеет единственную функцию: положить на стек адрес в памяти, где хранится количество яблок. О количестве можно думать как о "вещи", в то время как о словах, устанавливающих количество, считывающих или увеличивающих его - как о "действиях".
Форт удобно отделяет "вещи" от "действий", поскольку разрешает передачу адресов через стек и имеет команды "разыменования" и "загрузки".
Мы обсуждали важность проектирования по признаку того, что может измениться. Предположим, мы написали множество кода, использующего переменную ЯБЛОКИ. И теперь, в одиннадцатом часу, обнаруживаем, что необходимо отслеживать два различных типа яблок - красных и зеленых!
Не стоит опускать руки, лучше вспомнить функцию слова
ЯБЛОКИ: давать адрес. Если нам нужно два различных количества,
ЯБЛОКИ могут давать два различных адреса, в зависимости от
того, о каком типе яблок мы говорим. Так мы можем определить
более сложную версию слова ЯБЛОКИ, как показано ниже:
VARIABLE ЦВЕТ ( указатель на текущую переменную)
VARIABLE КРАСНЫЕ ( количество красных яблок)
VARIABLE ЗЕЛЕНЫЕ ( количество зеленых яблок)
: КРАСНЫЙ ( тип яблок - красные) КРАСНЫЕ ЦВЕТ ! ;
: ЗЕЛЕНЫЙ ( тип яблок - зеленые) ЗЕЛЕНЫЕ ЦВЕТ ! ;
: ЯБЛОКИ ( -- адр текущей яблочной переменной) ЦВЕТ @ ;
Рис.1-10. Смена косвенного указателя.
+------+ | ЦВЕТ | +------+ КРАСНЫЙ | ~~~~~~~ +---------+ | +---------+ | КРАСНЫЕ |\----- | ЗЕЛЕНЫЕ | +---------+ +---------+
+------+ | ЦВЕТ | +------+ ЗЕЛЕНЫЙ | ~~~~~~~ +---------+ | +---------+ | КРАСНЫЕ | ------/| ЗЕЛЕНЫЕ | +---------+ +---------+
Мы переопределили ЯБЛОКИ. Теперь они дают содержимое переменной по имени ЦВЕТ. ЦВЕТ - указатель: либо на переменную КРАСНЫЕ, либо на переменную ЗЕЛЕНЫЕ. Последние две переменных и являются действительными хранилищами для количеств.
Если мы сначала говорим КРАСНЫЙ, то можно использовать ЯБЛОКИ по отношению к красным яблокам, если говорим ЗЕЛЕНЫЙ, то - по отношению к зеленым (рис. 1-10).
Нам не понадобилось изменять синтаксис чего-либо в наработанном коде, использующем ЯБЛОКИ. Мы так же говорим
20 ЯБЛОКИ !и
1 ЯБЛОКИ +!Взгляните опять на то, что мы сделали. Мы изменили описание слова ЯБЛОКИ с описания переменной на определение через двоеточие, никак не повлияв на метод его использования. Форт позволяет нам скрыть детали того, как определено слово ЯБЛОКИ, от использующего его кода. То, что представляется "вещью" с точки зрения старых определений, в действительности является "действием" (определением через двоеточие) внутри компонента.
Форт вдохновляет на использование абстрактных типов данных, позволяя определять структуры данных в терминах компонентов нижнего уровня. Только Форт, исключающий вызовы (вида CALL) в процедурах, позволяющий передавать адреса и данные через стек и предоставляющий прямой доступ к памяти с помощью слов @ и ! может предложить такой уровень упрятывания информации.
Форт мало заботится о том, является ли что-либо структурой данных или алгоритмом. Это дает нам, программистам, невероятную свободу в создании тех частей речи, которые нам нужны для описания наших задач.
Я стараюсь думать о каждом слове, возвращающем адрес (например, ЯБЛОКИ) как о "существительном" независимо от способа, которым оно определено. Слово, производящее очевидное действие - это "глагол".
Такие слова, как КРАСНЫЙ и ЗЕЛЕНЫЙ в нашем примере, могут быть названы только "прилагательными", поскольку они изменяют функцию слова ЯБЛОКИ. Фраза
КРАСНЫЙ ЯБЛОКИ ?отличается от фразы
ЗЕЛЕНЫЙ ЯБЛОКИ ?Слова Форта могут служить также наречиями и предлогами. Мало смысла в том, чтобы определить, какой частью речи является конкретное слово, поскольку Форту в любом случае все равно. Нам нужно лишь порадоваться легкости описания задачи в естественных выражениях.
В нашем кратком историческом обзоре было замечено, что традиционные языки высокого уровня оторвались от ассемблерных языков, устранив не только соответствие `один в один` между командами и машинными операциями, но и соответствие `линейное`. Очевидно, Форт имеет первое отличие, но что касается второго, то порядок слов, используемых в определении, совпадает с порядком, в котором эти команды компилируются.
Выводит ли это Форт из рядов языков высокого уровня? Перед тем, как ответить, давайте исследуем преимущества Форт-подхода.
Вот что по этому поводу есть сказать у изобретателя Форта Чарльза Мура:
Вы определяете каждое слово так, что ЭВМ знает его
значение. Способ, которым она это знает, состоит в том,
что при вызове исполняется некоторый последовательный
код. Компьютер предпринимает действия сразу по каждому
слову. Он не отправляет слово на хранение и не держит его
в уме на будущее.
В философском смысле, я думаю, это означает, что машина
"понимает" слово. Она понимает слово DUP, быть может,
лучше вашего, поскольку в ее мозгах никогда не возникает
сомнение по поводу того, что DUP означает.
Связь между словами, имеющими смысл для Вас и имеющими
смысл для компьютера, глубока. ЭВМ становится средством
для связи между человеческим существом и концепцией.
Одним из преимуществ соответствия между исходным текстом и машинным кодом является огромное упрощение компилятора и интерпретатора. Такое упрощение улучшает работу многих частей системы, как это будет видно из дальнейшего.
С точки зрения методологии программирования, преимущество Форт-подхода состоит в том, что `новые` слова и `новые` синтаксисы могут легко добавляться. Нельзя говорить, что Форт `ищет` слова - он находит слова и исполняет их. Если Вы добавляете новые слова, Форт с тем же успехом найдет и исполнит их. Нет различия между существующими словами и теми, которые добавили Вы.
Более того, такая "расширяемость" подходит к любым типам слов, а не только к словам-действиям. К примеру, Форт позволяет Вам добавлять новые `компилирующие` слова - такие, как IF и THEN, которые обеспечивают структурированный поток управления. Вам также не составляет труда добавить оператор выбора по множеству вариантов или циклическую структуру со множеством выходов, если они Вам понадобятся или, что тоже важно, убрать их, если они не нужны.
В противоположность этому любой язык, в котором для понимания выражения важен порядок слов, должен "знать" все слова и все их допустимые комбинации. Шансы на то, что в них предусмотрены все удобные для Вас комбинации, малы. Язык таков, каким его создали, Вам нельзя расширить его знания.
Исследователи в лабораториях называют гибкость и расширяемость Форта среди его наиболее важных преимуществ для их работы. Можно разрабатывать лексиконы для сокрытия информации об огромном разнообразии тестового оборудования, присоединенного к компьютеру. Когда такая работа проделана более опытным программистом, исследователи могут использовать свой "программный инструментарий" маленьких слов для написания простых экспериментальных программ. При появлении нового оборудования добавляются новые лексиконы.
Марк Бернстейн описал проблему использования готовой целевой библиотеки процедур в лаборатории [8]: "Компьютер, а не пользователь, доминирует в эксперименте". Но, как он пишет, с помощью Форта "компьютер действительно подвигает ученых на изменение, исправление и улучшение программного обеспечения, для экспериментирования и изучения особенностей своего оборудования. Инициатива снова становится прерогативой исследователя."
Тех, кто упорствует в том, что Форт нельзя назвать языком высокого уровня, последний снабжает дополнительными аргументами. В то время, когда мощная проверка на синтаксис и типы данных становится одним из главных направлений в современных языках программирования, Форт вообще почти не производит синтаксический анализ. Предоставляя ту гибкость и свободу, которую мы описали, он не может указать Вам, что Вы собирались написать КРАСНЫЙ ЯБЛОКИ вместо ЯБЛОКИ КРАСНЫЙ. Ведь Вы сами придумали такой синтаксис!
Зато Форт более чем искупает это упущение, позволяя Вам компилировать каждое определение по отдельности и в течение считанных секунд. Вы обнаруживаете свою ошибку достаточно быстро - когда Ваше определение не срабатывает. Кроме того, при желании Вы можете добавить в свои определения подходящие синтаксические проверки.
Кисть артиста не может защитить его от ошибки, художник сам будет судить об этом. Сковорода повара и рояль композитора остаются простыми и производительными. Зачем же позволять языку программирования пытаться быть умнее Вас?
Так является ли Форт высокоуровневым языком? По вопросу проверки синтаксиса он не проходит. По вопросу уровня абстрагирования и мощности он кажется языком `безграничного` уровня - поддерживающим все - от манипуляции с битами в порту вывода до задач бизнеса.
Решаете Вы. (Форту все равно.)
Форт - язык для проектирования. Воспитаннику традиционной компьютерной науки такое утверждение кажется противоречивым. "Нельзя проектировать с помощью языка, с его помощью реализуют. Проектирование предваряет реализацию."
Опытные Форт-программисты с этим не соглашаются. Вы можете писать на Форте абстрактный, проектный код и все равно имеете возможность проверить его в любой момент, применяя преимущества разбиения на лексиконы. При продолжении разработки компонент может быть легко переписан на уровне ниже компонентов, которые его используют. Вначале слова могут печатать числа на Вашем терминале вместо управления шаговыми двигателями. Они могут печатать свои собственные имена только для того, чтобы сообщить Вам о своем выполнении. Они вообще могут ничего не делать.
Используя такую технологию, Вы можете писать простую, но проверяемую версию Вашей задачи, а затем успешно изменять и улучшать ее до тех пор, пока не достигнете своей цели.
Другим фактором, делающим возможным проектирование в коде, является то, что Форт, как и некоторые новейшие языки, сокращает последовательность разработки "редактирование- компиляция- тестирование- редактирование- компиляция- тестирование". Вследствие постоянной обратной связи среда окружения становится партнером в созидательном процессе. Программист, использующий обычный язык, редко может достичь того продуктивного образа мышления, которое присуще артистам, если ничто не мешает течению их творческого процесса.
По этим причинам Форт-программисты тратят меньше времени на планирование, чем их коллеги классического толка - праведники планирования. Для них отсутствие такового кажется безрассудным и безответственным. Традиционные окружения вынуждают программистов планировать, поскольку традиционные языки не готовы к восприятию перемен.
К сожалению, человеческое предвидение ограничено даже при наилучших условиях. Слишком сильное планирование становится непродуктивным.
Конечно, Форт не исключает планирования. Он позволяет создавать прототипы. Конструирование прототипа - лучший способ планирования, так же, как и макетирование в электронике.
В следующей главе мы увидим, что экспериментирование проявляет себя более надежным в деле приближения к истине, нежели стоительство догадок при планировании.
Несмотря на то, что производительность не является главной темой этой книги, начинающий в Форте должен быть убежден в том, что преимущества языка не являются чисто философскими. В целом Форт превышает все другие высокоуровневые языки по скорости работы, возможностям и компактности.
Несмотря на то, что Форт - интерпретирующий язык, он исполняет скомпилированный код. Поэтому он работает примерно в десять раз быстрее, чем интерпретирующий Бейсик.
Форт оптимизирован для исполнения слов с помощью техники, известной как "шитый код" [9],[10],[11]. Плата за разбиение на модули, состоящие из очень маленьких кусочков кода, сравнительно невелика.
Он не работает так же быстро, как ассемблерный код, поскольку внутренний интерпретатор (который обрабатывает список адресов, составляющих каждое определение через двоеточие) может отнимать до 50% времени исполнения слов-примитивов, в зависимости от типа процессора.
Но для больших задач Форт очень близко подходит к скорости ассмеблера. Вот три причины:
Первая и главная, Форт прост. Использование им стека данных значительно снижает затраты по производительности на передачу аргументов от слова к слову. В большинстве языков передача аргументов между модулями - одна из основных причин, по которым применение подпрограмм ограничивает их производительность.
Второе, Форт позволяет Вам определять слова либо на высоком уровне, либо на машинном языке. В любом случае нет необходимости в специальной вызывающей последовательности. Вы можете писать новое определение на высоком уровне и, убедившись в его правильности, переписать его на ассемблере без изменения какого-либо использующего его кода. В типичной задаче, быть может, 20% кода будет использоваться 80% времени. Только наиболее часто используемые, критичные ко времени алгоритмы нуждаются в машинном кодировании. Форт-система сама во многом реализована на ассемблерных определениях, так что Вам нужно будет воспользоваться аасемблером лишь для нескольких специфических слов.
Третье, программы на Форте имеют тенденцию быть лучше спроектированными, чем те, что написаны целиком на ассемблере. Форт-программисты извлекают выгоду из способностей языка к созданию прототипов и испытывают несколько алгоритмов перед тем, как выбрать наиболее подходящий к их потребностям. Поскольку Форт поддерживает изменения, он может также быть назван языком оптимизации.
Форт не гарантирует быстроту исполнения. Он лишь дает программисту творческую среду, в которой можно разрабатывать быстродействующие программы.
Форт может делать все, что могут другие языки - но обычно проще.
На нижнем уровне почти все Форт-системы имеют ассемблеры. Они поддерживают структурные операторы передачи управления для организации проверки условий и циклов, использующих технику структурированного программирования. Они обычно позволяют Вам писать подпрограммы обработки прерываний - Вы можете даже при желании писать их тело в высокоуровневом коде.
Форт может быть написан для работы под управлением любой операционной системы, такой как RT-11, CP/M или MS-DOS - или, для тех, кто это предпочитает, Форт может быть написан как самодостаточная операционная система со своими драйверами терминала и дисков.
С помощью кросс-компилятора Форта или целевого компилятора вы можете создавать новые Форт-системы для того же или для разных компьютеров. Поскольку Форт написан на Форте, Вы имеете невиданную возможность переписывать операционную систему в зависимости от нужд Вашей задачи. Или Вы можете переместить различные версии задачи на ряд систем.
Здесь имеются два соображения: размер корневой Форт-системы и размеры скомпилированных задач.
Ядро Форта является чрезвычайно гибким. Для встроенных применений часть Форта, необходимая для запуска программы, может уместиться всего в 1 КБайт. В полной инструментальной среде многозадачная Форт-система с интерпретатором, компилятором, ассемблером, редактором, операционной системой и другими утилитами поддержки занимает около 16 КБайт. При этом остается много места для задач. (А некоторые Форты на новых процессорах имеют 32-х разрядную адресацию, что позволяет писать невообразимо большие программы.)
Точно так же скомпилированные Форт-программы имеют очень маленький размер - обычно меньше эквивалентных программ на ассемблере. Причиной опять же является шитый код. Каждая ссылка на предварительно определенное слово, независимо от его мощности, использует всего два байта.
Одной из наиболее впечатляющих новых областей применения Форта является производство Форт-кристаллов, таких как Форт-микропроцессор Rockwell R65F11 [12]. На кристалле имеются не только аппаратные средства, но также исполняемая часть языка Форт и операционной системы для сложных применений. Только архитектура Форта и его компактность делают возможным создание микропроцессоров, основанных на Форте.
Форт часто характеризуется как необычный, совершенно непохожий на любой другой популярный язык программирования - как по своей структуре, так и по философии. Однако Форт включает в себя многие из принципов, которыми щеголяют большинство современных языков. Структурированная разработка, модульность и упрятывание информации - среди ключевых слов сегодняшнего дня.
Некоторые новейшие языки близко подходят к духу Форта. Язык С, например, как и Форт, дает возможность программисту определять новые функции либо на С, либо на ассемблере. И, как и Форт, большая часть С определена в терминах функций.
Но Форт расширяет концепции модульности и упрятывания информации в большей степени, чем любой другой современный язык. Форт даже скрывает способ, которым вызываются слова и способ, по которому передаются локальные аргументы.
Результирующий код становится концентрированной смесью слов, чистейшим выражением абстрактного замысла. Как результат, Форт-программисты обычно продуктивнее, и пишут более плотные, эффективные и лучше управляемые программы.
Форт может не быть единственным возможным языком. Но я думаю, что подобный язык, если такая вещь возможна, был бы ближе к Форту, чем любой другой современный язык.