Л.БРОУДИ. СПОСОБ МЫШЛЕНИЯ - ФОРТ. часть 6 ГЛАВА 6 --------------------------------------------------------------- - 174 - ГЛАВА 6. Ф Р А Г М Е Н Т А Ц И Я ~~~~~~~~~~~~~~~~~~~~~~~ ---------------------------------------------------------------- В этой главе мы продолжим наше исследование фазы реализации, сосредоточившись на этот раз на фрагментации. Декомпозиция и фрагментация - суть разные части единого целого. И то, и другое включает в себя разбиение и организацию. Декомпозиция применяется при предварительном проектировании, в то время, как фрагментация - при детализированной проработке и реализации. Поскольку каждое определение через двоеточие отражает то, какие были приняты решения при фрагментации, владение хорошей техникой этого процесса является едва ли не самым важным умением для Форт-программиста. Так что же это такое? Фрагментация означает организацию кода в полезные куски. Для того, чтобы они оказались полезными, часто необходимо отделить фрагменты, которые могут быть использованы повторно, от тех, которые встречаются однократно. Первые становятся новыми определениями. Вторые преобразуются в аргументы или параметры для определений. Такое разделение обычно называют "факторизацией". Первая часть это главы будет посвящена обсуждению различных приемов для такой "факторизации". Другой стороной фрагментации является принятие решений о том, сколь много надо оставить внутри, а сколько вынести наружу определения. Во второй части будут приведены критерии для разумной и полезной фрагментации. ТЕХНИКА ФАКТОРИЗАЦИИ ~~~~~~~~~~~~~~~~~~~~ Если модуль кажется почти, но не совсем полезным с точки зрения еще какого-нибудь места в системе, попробуйте найти и выделить полезную подфункцию. Оставшаяся часть модуля может быть перенесена в вызывающую часть (из `Структурированного проектирования` [1]). - 175 - Разумеется, "полезная подфункция" становится вновь вычлененным определением. А что такое "не совсем полезная"? Это зависит от того, что она собой представляет. ФАКТОРИЗАЦИЯ ДАННЫХ. Проще всего выделить данные, за что следует благодарить Фортов стек данных. К примеру, для вычисления двух третей от 1000, мы пишем 1000 2 3 */ Для определения слова, вычисляющего две трети от `любого` числа, мы отделяем аргумент от определения: : ДВЕ-ТРЕТИ ( n1 -- n2 ) 2 3 /* ; Когда данные должны применяться в `середине` полезного выражения, нам приходится использовать манипуляции со стеком. К примеру, для того, чтобы расположить по центру 80-знаковой строки экрана текст длиной в 10 символов, можно написать: 80 10 - 2/ SPACES Однако текст не всегда бывает длиной по 10 символов. Для придания полезности такой фразе при любой строке, следовало бы отделить длину, написав: : ЦЕНТРОВАТЬ ( длина -- ) 80 SWAP - 2/ SPACES ; Стек данных может также использоваться и для передачи адресов. Поэтому то, что выделено, может быть `указателем` на данные, вместо самих данных. Данные же могут быть числами и даже строками, но все равно будут таким образом отделены благодаря использованию стека. Иногда различие оказывается функцией, которая, однако, может быть легко приведена к числу, передаваемому через стек. К примеру: Сегмент 1: ВИЛЛИ НИЛЛИ ПУДИНГ ПИРОГ AND Сегмент 2: ВИЛЛИ НИЛЛИ 8 * ПУДИНГ ПИРОГ AND Как можно факторизовать операцию "8 *"? Оставляя "*" во фрагменте, и передавая ему единицу или восьмерку: : НОВАЯ ( n -- ) ВИЛЛИ НИЛЛИ * ПУДИНГ ПИРОГ AND ; Сегмент 1: 1 НОВАЯ Сегмент 2: 8 НОВАЯ - 176 - (Конечно, если ВИЛЛИ или НИЛЛИ меняют состояние стека, Вам понадобится добавить подходящие стековые операторы.) Если операция производит сложение, то ее можно обойти, передавая фрагменту ноль. ------------------------------------------------------------ СОВЕТ Для простоты пытайтесь представить различия в похожих фрагментах как числовые (по значениям или адресам), вместо того, чтобы представлять их как процедурные. ------------------------------------------------------------ ВЫДЕЛЕНИЕ ФУНКЦИЙ. С другой стороны, различие иногда представляется `только` функцией. Дано: Сегмент 1: ВЗДОР-А ВЗДОР-Б ВЗДОР-В ВЗДОР-Г ВЗДОР-Д ВЗДОР-Е Сегмент 2: ВЗДОР-А ВЗДОР-Б ПЕРЕВОРОТ ВЗДОР-Г ВЗДОР-Д ВЗДОР-Е Неправильный подход: : ВЗДОРЫ ( t=делать-ВЗДОР-В | f=делать-ПЕРЕВОРОТ -- ) ВЗДОР-А ВЗДОР-Б IF ВЗДОР-В ELSE ПЕРЕВОРОТ THEN ВЗДОР-Г ВЗДОР-Д ВЗДОР-Е ; Сегмент 1: TRUE ВЗДОРЫ Сегмент 2: FALSE ВЗДОРЫ Более подходящий вариант: : ВЗДОР-АБ ВЗДОР-А ВЗДОР-Б ; : ВЗДОР-ГДЕ ВЗДОР-Г ВЗДОР-Д ВЗДОР-Е ; Сегмент 1: ВЗДОР-АБ ВЗДОР-В ВЗДОР-ГДЕ Сегмент 2: ВЗДОР-АБ ПЕРЕВОРОТ ВЗДОР-ГДЕ ------------------------------------------------------------ СОВЕТ Не передавайте управляющих флагов нижестоящим словам. ------------------------------------------------------------ А почему нет? Во-первых, Вы требуете от Вашей исполняемой задачи принятия ненужного решения - того, ответ на который Вам и так ясен при программировании - и этим снижаете эффективность. - 177 - Во-вторых, терминология не соответствует концептуальной модели. Что означают TRUE ВЗДОРЫ (правильные вздоры) в противоположность FALSE ВЗДОРЫ (неправильным вздорам)? ФАКТОРИЗАЦИЯ КОДА ИЗ СТРУКТУР УПРАВЛЕНИЯ. Остерегайтесь повторений в обеих ветвях выражений типа IF THEN ELSE. К примеру: ... ( с) DUP BL 127 WITHIN IF EMIT ELSE DROP ASCII . EMIT THEN ... Этот фрагмент печатает ASCII-символ, кроме тех случаев, когда этот символ - управляющий, в этом случае печатается точка. В любом случае выполняется слово EMIT. Следует выделить EMIT из структуры управления, например: ... ( с) DUP BL 127 WITHIN IF DROP ASCII . THEN EMIT ... Хуже всего обстоит дело, когда различие между двумя определениями проявляется как функция внутри структуры, что делает выделение частей фрагмента невозможным. В такой ситуации используйте стековые аргументы, переменные или даже векторизацию. Как можно использовать векторизацию, мы покажем в разделе главы 7, названном "Использование DOER/MAKE". Вот напоминание на случай факторизации кода из циклов DO LOOP: ------------------------------------------------------------ СОВЕТ При факторизации содержимого циклов DO LOOP в отдельное определение переработайте код таким образом, чтобы слово I (индекс) не употреблялось внутри такого определения, но передавалось ему через стек. ------------------------------------------------------------ ФАКТОРИЗАЦИЯ САМИХ СТРУКТУР УПРАВЛЕНИЯ. Вот два определения, отличающиеся внутренностью конструкции IF THEN: : АКТИВНЫЙ А Б OR В AND IF БЕСИТЬСЯ РЕЗВИТЬСЯ ПРЫГАТЬ THEN ; : ЛЕНИВЫЙ А Б OR В AND IF СИДЕТЬ ЕСТЬ СПАТЬ THEN ; - 178 - Условие и структура управления не отличаются; лишь события другие. Поскольку выделить IF в одно слово, а THEN в другое нельзя, проще всего факторизовать условие: : УСЛОВИЯ? ( -- ?) А Б OR В AND ; : АКТИВНЫЙ УСЛОВИЯ? IF БЕСИТЬСЯ РЕЗВИТЬСЯ ПРЫГАТЬ THEN ; : ЛЕНИВЫЙ УСЛОВИЯ? IF СИДЕТЬ ЕСТЬ СПАТЬ THEN ; При большом количестве повторений одного и того же условия и стуктуры управления можно даже выделить и то, и другое. Смотрите: : УСЛОВНО А Б OR В AND NOT IF R> DROP THEN ; : АКТИВНЫЙ УСЛОВНО БЕСИТЬСЯ РЕЗВИТЬСЯ ПРЫГАТЬ ; : ЛЕНИВЫЙ УСЛОВНО СИДЕТЬ ЕСТЬ СПАТЬ ; Слово УСЛОВНО может - в зависимости от условия - изменять поток управления таким образом, что остальные слова в каждом определении исполнены не будут. У такого подхода есть и определенные недостатки. Мы будем обсуждать такую технику - за и против - в 8-й главе. Менее жестокие примеры факторизации структур управления основываются на выражениях типа CASE, устраняющие вложенные IF THEN ELSE, и множественные выходы из циклов (конструкции BEGIN WHILE WHILE WHILE ... REPEAT). Мы еще обсудим эти темы в главе 8. ФАКТОРИЗАЦИЯ ИМЕН. Хорошо также факторизовывать имена в случаях, когда они кажутся почти, но не совсем одинаковыми. Просмотрите следующий ужасающий пример кода, которые предназначен для инициализации трех переменных для каждого из восьми каналов: VARIABLE 0STS VARIABLE 1STS VARIABLE 2STS VARIABLE 3STS VARIABLE 4STS VARIABLE 5STS VARIABLE 6STS VARIABLE 7STS VARIABLE 0TNR VARIABLE 1TNR VARIABLE 2TNR VARIABLE 3TNR VARIABLE 4TNR VARIABLE 5TNR VARIABLE 6TNR VARIABLE 7TNR VARIABLE 0UPS VARIABLE 1UPS VARIABLE 2UPS VARIABLE 3UPS VARIABLE 4UPS VARIABLE 5UPS VARIABLE 6UPS VARIABLE 7UPS - 179 - : INIT-CH0 0 0STS ! 1000 0TNR ! -1 0UPS ! ; : INIT-CH1 0 1STS ! 1000 1TNR ! -1 1UPS ! ; : INIT-CH2 0 2STS ! 1000 2TNR ! -1 2UPS ! ; : INIT-CH3 0 3STS ! 1000 3TNR ! -1 3UPS ! ; : INIT-CH4 0 4STS ! 1000 4TNR ! -1 4UPS ! ; : INIT-CH5 0 5STS ! 1000 5TNR ! -1 5UPS ! ; : INIT-CH6 0 6STS ! 1000 6TNR ! -1 6UPS ! ; : INIT-CH7 0 7STS ! 1000 7TNR ! -1 7UPS ! ; : INIT-ALL-CHS INIT-CH0 INIT-CH1 INIT-CH2 INIT-CH3 INIT-CH4 INIT-CH5 INIT-CH6 INIT-CH7 ; Во-первых, имеется сходство между именами переменных, кроме того, сходство есть и в коде, используемом в словах INIT-CH. Вот улучшенный вариант. Одинаковые имена переменных были факторизованы в три структуры данных, а длинные процедуры в словах INIT-CH были вынесены в структуру DO LOOP: : МАССИВ ( #ячеек -- ) CREATE 2* ALLOT DOES> ( i -- 'ячейки) SWAP 2* + ; 8 МАССИВ STATUS ( #канала -- адр ) 8 МАССИВ TENOR ( " ) 8 МАССИВ UPSHOT ( " ) : УСТАНОВИТЬ 8 0 DO 0 I STATUS ! 1000 I TENOR ! -1 I UPSHOT ! LOOP ; Вот весь нужный нам код. Даже и в более невинных случаях создание маленькой структуры данных может сократить количество дополнительных имен. Имеется соглашение, по которому в Форте текст хранится в "строках со счетчиком" (т.е. с количеством символов, хранимом в первом байте). Любое слово, возвращающее "адрес строки", на самом деле дает начальный адрес, адрес счетного байта. Такая двухэлементная структура данных не только убирает необходимость в раздельных именах для строки и ее длины, но также облегчает перемещение такой строки в памяти, поскольку и строку, `и` ее счетчик можно скопировать сразу, одним словом CMOVE. Когда там и здесь Вам начинают попадаться разные ужасы, что-то можно скомбинировать, да и убрать их с глаз долой. ФАКТОРИЗАЦИЯ ФУНКЦИЙ В ОПРЕДЕЛЯЮЩИЕ СЛОВА. ------------------------------------------------------------ СОВЕТ Если последовательности определений содержат одинаковые функции, отличающиеся лишь подставляемыми данными, используйте определяющее слово. ------------------------------------------------------------ - 180 - Рассмотрите структуру этого кода (не беспокоясь об его назначении - позже такой пример Вам еще встретится): : ОТТЕНОК ( цвет -- цвет' ) 'СВЕТЛЫЙ? @ OR 0 'СВЕТЛЫЙ? ! ; : ЧЕРНЫЙ 0 ОТТЕНОК ; : СИНИЙ 1 ОТТЕНОК ; : ЗЕЛЕНЫЙ 2 ОТТЕНОК ; : ЖЕЛТЫЙ 3 ОТТЕНОК ; : КРАСНЫЙ 4 ОТТЕНОК ; : ФИОЛЕТОВЫЙ 5 ОТТЕНОК ; : КОРИЧНЕВЫЙ 6 ОТТЕНОК ; : СЕРЫЙ 7 ОТТЕНОК ; Такой подход корректен, однако менее эффективен с точки зрения занимаемой памяти, чем следующий, в котором используется определяющее слово: : ОТТЕНОК ( цвет -- ) CREATE , DOES> ( -- цвет) @ 'СВЕТЛЫЙ? @ OR 0 'СВЕТЛЫЙ? ! ; 0 ОТТЕНОК ЧЕРНЫЙ 1 ОТТЕНОК СИНИЙ 2 ОТТЕНОК ЗЕЛЕНЫЙ 3 ОТТЕНОК ЖЕЛТЫЙ 4 ОТТЕНОК КРАСНЫЙ 5 ОТТЕНОК ФИОЛЕТОВЫЙ 6 ОТТЕНОК КОРИЧНЕВЫЙ 7 ОТТЕНОК СЕРЫЙ (Суть определяющих слов объясняется в книге "Начальный курс программирования ...", в главе 11). Используя определяющее слово, мы экономим память, поскольку каждому двоеточечному определению необходим адрес слова EXIT для выхода. (При определении восьми слов использование определяющего слова экономит 14 байтов для 16-ти битового Форта.) Кроме того, в определениях через двоеточие каждая ссылка на числовой литерал требует компиляции ссылки на слово LIT (или literal), то есть еще по 2 байта на определение. (Если числа 1 и 2 - это предварительно определенные константы, то за это приходится расплачиваться еще 10-ю байтами - итого 24 байта.) С точки зрения читабельности определяющее слово делает абсолютно ясным то обстоятельство, что все вводимые с его помощью цвета принадлежат к одному семейству слов. Однако самая большая сила определяющих слов проявляется тогда, когда множество определений разделяют одно и то же поведение `во время компиляции`. Эта тема будет предметом для обсуждения в последующем разделе, "Факторизация во время компиляции". - 181 - КРИТЕРИИ ДЛЯ ФРАГМЕНТАЦИИ ~~~~~~~~~~~~~~~~~~~~~~~~~ Теперь, вооружившись техникой факторизации, давайте обсудим некоторые критерии разбиения определений в Форте. Вот они: 1. Ограничение размера определения 2. Ограничение повторов в коде 3. Оптимизация имен 4. Упрятывание информации 5. Упрощение командного интерфейса ------------------------------------------------------------ СОВЕТ Пусть определения будут короткими. ------------------------------------------------------------ ---------------------------------------------------------------- Мы задали Муру вопрос: "Какой длины должно быть определение на Форте?" Слово должно быть длиной в одну строку. Это цель. Когда у Вас имеется много слов, каждое из которых в своем роде полезно - быть может, лишь для отладки или апробирования, но причина для его существования обязательно есть - тогда Вы ощущаете, что ухватили самую сущность проблемы и это - те слова, которые ее выражают. Короткие слова дают хорошее качество такого ощущения. ---------------------------------------------------------------- Беглый осмотр одной из написанных Муром программ показал, что в среднем на определение у него приходится по семь ссылок, в том числе и на числа, и на слова. Эти определения замечательно коротки. (Реально его код 50 на 50 состоит из одно- и двухстрочных определений.) Психологические тесты показали, что человеческое сознание может сосредоточиться только на семи, плюс-минус двух, вещах одновременно [2]. В то же время постоянно, днем и ночью, огромные ресурсы нашего ума заняты подсознательным сбором непрерывных потоков данных, наведением связей и группированием и решением задач. Даже если наше подсознание и знает вдоль и поперек все уголки задачи, наше ограниченное сознание может одновременно сочетать лишь семь вещей сразу. За этими пределами наша хватка теряет прочность. Короткие определения соответствуют возможностям нашего разума. - 182 - Многих Форт-программистов побуждает писать слишком длинные определения знание того, что заголовки занимают место в словаре. Чем крупнее разбиение, тем меньше имен и тем меньше памяти будет потрачено. Это правда, память будет использована, но вряд ли можно сказать, что нечто, помогающее Вам проверять, отлаживать и взаимодействовать со своим кодом - это "трата". Если у Вас большая задача, попытайтесь использовать по умолчанию ширину поля имени, равную трем, с возможностью переключения на полную длину во избежание коллизий. (Ширина - WIDTH - содержит предельное число символов, которое может храниться в поле имени каждого из заголовков в словаре.) Если задача все еще остается слишком большой, используйте Форт со множественными словарями на машине с расширенной памятью или, еще лучше, 32-х битный Форт на ЭВМ с 32-разрядной адресацией. Родственный страх возникает и от того, что чрезрезмерное дробление будет снижать производительность за счет слишком частого обращения к встроенному интерпретатору Форта. Опять-таки, правда то, что за каждый уровень вложенности приходится платить. Но обычно проигрыш за вложенность при правильном разбиении не заметен. Если же положение действительно так серьезно, правильным решением будет перевод некоторых частей на ассемблер. ------------------------------------------------------------ СОВЕТ Производите разбиение там, где чувствуете неуверенность в своем коде (где сложность достигает пределов понимания). ------------------------------------------------------------ Не позволяйте своему Я бравировать отношением "Я это превзойду!". Код на Форте никогда не должен вызывать чувство неуютной сложности. Факторизуйте! ---------------------------------------------------------------- Мур: Ощущение того, что дело идет к ошибке, является одним из поводов к расчленению. Всякий раз, встречая цикл DO LOOP двойной вложенности, Вы должны видеть указание на то, что что-то неверно, поскольку это будет трудно отлаживать. Почти всегда хорошо взять внутренний DO LOOP и сделать его словом. И, выделив слово для отладки, нет никакой причины внедрять его назад. Оно пригодилось вам вначале. Нет гарантии того, что оно опять Вам не понадобится. ---------------------------------------------------------------- - 183 - Вот еще одна сторона того же принципа: ------------------------------------------------------------ СОВЕТ Производите факторизацию в той точке, где кажется необходимым применение комментария. ------------------------------------------------------------ В частности, если Вы ощущаете, что нужно напомнить себе, то лежит на стеке, то это может оказаться тем самым моментом, когда хорошо "сделать перерыв". Предположим, у Вас написано: ... БАЛАНС DUP ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ( баланс) ПОКАЗАТЬ ... Ситуация начинается с вычисления баланса и заканчивается его распечаткой. Посередине же несколько строчек кода используют баланс для собственных нужд. Поскольку трудно сразу увидеть, что баланс все еще находится на стеке при вызове ПОКАЗАТЬ, программист вставляет стековую картинку. Такое решение обычно говорит о плохой факторизации. Лучше написать: : РЕВИЗОВАТЬ ( баланс -- ) ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ххх ; ... БАЛАНС DUP РЕВИЗОВАТЬ ПОКАЗАТЬ ... При этом не нужны внутренние стековые картинки. Более того, программист теперь получил пригодный для повторного использования, проверяемый набор определений. ------------------------------------------------------------ СОВЕТ Ограничивайте повторения кода. ------------------------------------------------------------ Другой причиной для факторизации является устранение повторяющихся фрагментов кода, что даже более важно, чем уменьшение размера определений. ---------------------------------------------------------------- Мур: Когда слово является частью чего-то, то оно может быть полезно для придания коду чистоты или для отладки, но - 184 - никогда оно не будет столь же хорошо, как то, которое всречается много раз. Всякое слово, которое встречается в коде лишь один раз, должно вызывать у Вас желание оценить его значимость. Неоднократно, когда программа становится слишком большой, я возвращаюсь назад и просматриваю ее в поисках фраз, бросающихся в глаза как кандидаты для вычленения. Компьютер не может этого сделать - слишком много вариантов. ---------------------------------------------------------------- При просмотре своей работы Вы часто находите идентичные фразы или короткие словосочетания, повторяющиеся несколько раз. При написании редактора у меня обнаружилась такая повторявшаяся часто фраза: ЭКРАН КУРСОР @ + Пскольку она встечалась во многих местах, я выделил ее в отдельное слово НА. Только от Вас зависит умение выделять изложенные по-разному, но функционально эквивалентные фрагменты, типа: ЭКРАН КУРСОР @ 1- + Слово 1- кажется выпадающим из фразы, выделенной в слово НА. На самом же деле это может быть записано как НА 1- С другой стороны: ------------------------------------------------------------ СОВЕТ Когда при факторизации часть кода дублируется, убедитесь в том, что выделенный код служит одной цели. ------------------------------------------------------------ Не выделяйте слепо повторения, которые могут не оказаться полезными. К примеру, в нескольких местах одной задачи я использовал фразу BLK @ BLOCK >IN @ + C@ Я превратил ее в слово БУКВА, поскольку она возвращала букву, на которую показывал интерпретатор. - 185 - В более поздней версии мне неожиданно пришлось писать: BLK @ BLOCK >IN @ + C! Я мог бы использовать существующее слово БУКВА, если бы не C@ на конце. Вместо того, чтобы повторять большую часть фразы в новой секции, я предпочел переделать факторизацию слова БУКВА для улучшения гибкости. После этого его использование приобрело вид БУКВА C@ или БУКВА C!. Такая перемена потребовала от меня поиска по листингу и замены всех появлений БУКВА на БУКВА C@. Но я ведь должен был сделать это с самого начала, отделив адрес буквы от операции по этому адресу. Аналогично нашему установлению о повторах кода: ------------------------------------------------------------ СОВЕТ Следите за повторами последовательностей. ------------------------------------------------------------ Если Вы ловите себя на том, что лезете назад по своей программе для копирования последовательности ранее использованных слов, то, быть может, Вы смешали общую идею со специфическим приложением. Часть копируемой Вами последовательности, должно быть, может быть выделена в качестве независимого определения для использования во всех подобных случаях. ------------------------------------------------------------ СОВЕТ Убедитесь в том, что можете подобрать имя тому, что выделяете. ------------------------------------------------------------ ---------------------------------------------------------------- Мур: Если у Вас есть что-то, чему Вы не в состоянии присвоить единое имя, не название с внутренними тире, а имя, то значит это - не четко сформированная концепция. Возможность присваивания имени является обязательной частью декомпозиции. Естественно, Вы должны глубоко понимать идею. ---------------------------------------------------------------- Сравните такой взгляд на вещи с критериями для декомпозиции модуля, на защиту которых становится структурное проектирование (глава 1). При таком подходе модуль должен был бы проявлять - 186 - "функциональную связность", которая может меняться при описании его функции единственным и не составным `предложением`. "Атом" Форта - `имя` - детализирован на порядок лучше. ------------------------------------------------------------ СОВЕТ Делайте факторизацию определений так, чтобы скрыть детали, которые мгут измениться. ------------------------------------------------------------ Важность упрятывания информации мы уже видели в предыдущих главах, особенно в отношении предварительного проектирования. Полезно вспомнить об этом критерии и на этапе разработки. Вот очень короткое определение, которое только то и делает, что упрятывает информацию: : >BODY ( acf -- apf ) 2+ ; Это определение позволяет превращать адрес поля кода (acf) в адрес поля параметров (apf) вне зависимости от действительного строения словарной статьи. Если бы Вы использовали 2+ вместо слова >BODY, то потеряли бы переносимость в случае, если когда-либо перешли бы на Форт-систему, в которой заголовки отделены от тел определений. (Это - одно из слов набора, предложенного Кимом Харрисом и включенного в список экспериментальных расширений в стандарт Форт-83 [3].) А вот группа определений, которую можно было бы использовать при написании редактора: : ЭКРАН ( -- а) SCR @ BLOCK ; : КУРСОР ( -- а) R# ; : НА ( -- а) ЭКРАН КУРСОР @ + ; Эти слова могут послужить основой для вычислений всех адресов, потребных для работы с текстом. Их использование полностью устраняет зависимость Ваших алгоритмов редактирования от Форт-блоков. Что же в этом хорошего? Если бы Вы решили (в процессе развития) создать буфер редактирования для предохранения разрушения блока при ошибках пользователя, то Вам достаточно было бы переопределить пару этих слов, может быть, вот так: CREATE ЭКРАН 1024 ALLOT VARIABLE КУРСОР Ваш остальной код может оставаться нетронутым. - 187 - ------------------------------------------------------------ СОВЕТ Выделяйте вычислительные алгоритмы из слов, индицирующих результаты. ------------------------------------------------------------ На самом деле этот вопрос должен выясняться при декомпозиции. Вот пример. Определенное ниже слово, которое читается как "от-людей-к-связям", вычисляет количество коммуникационных связей, возникающих между данным количеством людей в группе. (Руководителям программистских коллективов полезно знать - число связей колоссально возрастает с каждым новым членом команды.) : ЛЮДИ>СВЯЗИ ( #людей -- #связей ) DUP 1- * 2/ ; Это определение производит только вычисление. Вот "пользовательское определение", которое вызывает ЛЮДИ>СВЯЗИ для расчетов, а зетем печатает результат: : ЛЮДЕЙ ( #людей) ." = " ЛЮДИ>СВЯЗИ . ." связей" ; Это дает: 2 ЛЮДЕЙ = 1 связей ~~~~~~~~~~~ 3 ЛЮДЕЙ = 3 связей ~~~~~~~~~~~ 5 ЛЮДЕЙ = 10 связей ~~~~~~~~~~~~ 10 ЛЮДЕЙ = 45 связей ~~~~~~~~~~~~ Даже если Вы уверены, что собираетесь производить калькуляцию лишь один раз, только для распечатки, то, поверьте мне, Вы ошибаетесь. Вам обязательно придется вернуться к этому впоследствии и вычленить вычисляющую часть. Может быть, понадобится отображать всю информацию в выровненной справа колонке, или Вы захотите записать все результаты в базу данных - никогда не знаешь, что случится. Но придется выделить ее обязательно, поэтому лучше сделать это сразу. (Не беда, если даже несколько раз Вы сможете без этого обойтись.) Лучший пример - слово . (точка). Точка отлично годится в 99% случаев, но иногда оказывается, что она слишком много всего делает. Вот что она в действительности собой представляет (в Форте-83): : . ( n) DUP ABS 0 <# #S ROT SIGN #> TYPE SPACE ; - 188 - Но, предположим, Вам захотелось преобразовать число на стеке в строку ASCII и поместить ее в буфер для дальнейшей распечатки. Точка делает преобразование, но при этом сразу и печатает. Или, предположим, Вы желаете отформатировать печать игральных карт в виде 10Ч (для "десятки червей"). Нельзя использовать точку для печати 10, поскольку она выводит заключительный пробел. Вот более удачное разбиение, встречающееся в некоторых Форт-системах: : (.) ( n -- a #) DUP ABS 0 <# #S ROT SIGN #> ; : . ( n) (.) TYPE SPACE ; Другой пример неотделения выходной функции от вычислений можно найти в нашей собственной работе по вычислению римских чисел из главы 4. При примененном нами решении мы не можем записать полученную римскую цифру в буфер или даже выровнять ее по центру поля. (Лучше было бы использовать слово HOLD вместо EMIT.) Упрятывание информации может также служить причиной и для `не` разбиения. К примеру, если Вы выделяете фразу SCR @ BLOCK в определение : ЭКРАН SCR @ BLOCK ; то помните, что Вы это делаете только потому, что Вам может захотеться изменить положение экрана при редактировании. Не заменяйте вслепую все появления фразы на новое слово ЭКРАН, поскольку это определение может быть изменено, а ведь обязательно встретятся несколько мест, где Вам действительно нужно лишь SCR @ BLOCK. ------------------------------------------------------------ СОВЕТ Если повторяющийся фрагмент кода для некоторых случаев может измениться, а для других - нет, факторизуйте только те случаи, которые подвержены изменениям. Если фрагмент меняется более, чем одним образом, факторизуйте его в более, чем одно определение. ------------------------------------------------------------ Понимание того, где нужно упрятывать информацию, требует интуиции и опыта. Пройдя на своем веку множество изменений в проектах, Вы хорошо узнаете, какие вещи наиболее вероятным образом будут изменяться в будущем. - 189 - Впрочем, никогда нельзя предвидеть все. Было бы бесполезно даже пытаться, как мы увидим в последующем разделе под названием "Итеративный подход при реализации". ------------------------------------------------------------ СОВЕТ Упрощайте командный интерфейс, уменьшая количество команд. ------------------------------------------------------------ Может показаться парадоксальным, но хорошее расчленение может привести к образованию `меньшего количества` имен. В главе 5 мы видели, как шесть простых имен (ЛЕВЫЙ, ПРАВЫЙ, МОТОР, МАГНИТ, ПУСК, СТОП) выполняли работу восьми плохо отфакторизованных, многочленных имен. Вот другой пример: на одном предприятии, в котором недавно был внедрен Форт, я обнаружил в ходу два определения. Их назначение было чисто вспомогательным, просто для напоминания программисту, какой словарь - CURRENT, а какой - CONTEXT: : .CONTEXT CONTEXT @ 8 - NFA ID. ; : .CURRENT CURRENT @ 8 - NFA ID. ; Если Вы набирали .CONTEXT то система отвечала .CONTEXT FORTH ~~~~~~ (Они работали, - во всяком случае, на использовавшейся здесь системе - возвращаясь назад к полю имени определения словаря и распечатывая его.) Очевидное повторение кода бросилось мне в глаза в качестве признака плохой факторизации. Можно было бы выделить повторяющийся участок в третье определение: : .СЛОВАРЬ ( указатель) @ 8 - NFA ID. ; укорачивая при этом первоначальные определения до : .CONTEXT CONTEXT .СЛОВАРЬ ; : .CURRENT CURRENT .СЛОВАРЬ ; Но при таком подходе единственной разницей между определениями был бы используемый указатель. Поскольку частью хорошей факторизации является уменьшение, а вовсе не увеличение - 190 - количества определений, казалось логичным иметь всего одно такое определение и позволить ему получать в качестве аргумента либо слово CONTEXT, либо CURRENT. Применяя принципы выбора подобающих имен, я предложил: : ЕСТЬ ( адр) @ 8 - NFA ID. ; для синтаксиса CONTEXT ЕСТЬ ASSEMBLER ~~~~~~~~~~ или CURRENT ЕСТЬ FORTH ~~~~~~ Начальная предпосылка этому была в повторении кода, но конечный результат родился в результате попытки упрощения командного интерфейса. Вот другой пример. В IBM PC имеется четыре чисто текстовых режима отображения: 40 символов монохромно 40 символов в цвете 80 символов монохромно 80 символов в цвете В моей рабочей Форт-системе имеется слово MODE. Оно берет аргумент в пределах от 0 до 3 и соответственно устанавливает текстовый режим. Разумеется, фразы типа 0 MODE или 1 MODE нисколько не помогали мне запомнить, где какой режим. Поскольку при работе мне было нужно переключаться между этими режимами, то понадобилось заполучить набор слов для выполнения этой задачи. Слова заодно должны были устанавливать значение переменной, показывающей количество символов - 40 или 80. Вот самый прямолинейный путь для выполнения этих требований: : 40-Ч/Б 40 #СИМВОЛОВ ! 0 MODE ; : 40-ЦВЕТНОЙ 40 #СИМВОЛОВ ! 1 MODE ; : 80-Ч/Б 80 #СИМВОЛОВ ! 2 MODE ; : 80-ЦВЕТНОЙ 80 #СИМВОЛОВ ! 3 MODE ; Производя факторизацию для устранения повторов, мы приходим к такой версии: - 191 - : СИМВ-РЕЖИМ! ( #символов режим) MODE #СИМВОЛОВ ! ; : 40-Ч/Б 40 0 СИМВ-РЕЖИМ! ; : 40-ЦВЕТНОЙ 40 1 СИМВ-РЕЖИМ! ; : 80-Ч/Б 80 2 СИМВ-РЕЖИМ! ; : 80-ЦВЕТНОЙ 80 3 СИМВ-РЕЖИМ! ; Но, пытаясь уменьшить количество команд, а также следуя принципу нежелательности начинающихся с цифр и сложных, связанных через тире имен, мы обнаруживаем, что можем использовать количество символов как стековый аргумент и `вычислять` режим: : Ч/Б ( #символов) DUP #СИМВОЛОВ ! 20 / 2- MODE ; : ЦВЕТНОЙ ( #символов) DUP #СИМВОЛОВ ! 20 / 2- 1+ MODE ; Это дает нам такой синтаксис: 40 Ч/Б 40 ЦВЕТНОЙ 80 Ч/Б 80 ЦВЕТНОЙ Мы уменьшили количество команд с черырех до двух. И снова у нас образовался повторяющийся код. Если его выделить, то получится: : СИМВ-РЕЖИМ! ( #символов цвет?) SWAP DUP #СИМВОЛОВ ! 20 / 2- + MODE ; : Ч/Б ( #символов -- ) 0 СИМВ-РЕЖИМ! ; : ЦВЕТНОЙ ( #символов -- ) 1 СИМВ-РЕЖИМ! ; Мы пришли к более приятному синтаксису, при этом значительно сократив размеры объектного кода. Имея лишь две команды, как в приведенном примере, можно получить ограниченный выигрыш. При большем наборе команд прибыль возрастает геометрически. Наш последний пример является набором слов для представления цветов в конкретной системе. Имена типа СИНИЙ и КРАСНЫЙ использовать приличнее, чем числа. Одним из решений может быть определение: 0 CONSTANT ЧЕРНЫЙ 1 CONSTANT СИНИЙ 2 CONSTANT ЗЕЛЕНЫЙ 3 CONSTANT ЖЕЛТЫЙ 4 CONSTANT КРАСНЫЙ 5 CONSTANT ФИОЛЕТОВЫЙ 6 CONSTANT КОРИЧНЕВЫЙ 7 CONSTANT СЕРЫЙ 8 CONSTANT ТЕМНО-СЕРЫЙ 9 CONSTANT СВЕТЛО-СИНИЙ 10 CONSTANT СВЕТЛО-ЗЕЛЕНЫЙ 11 CONSTANT СВЕТЛО-ЖЕЛТЫЙ 12 CONSTANT СВЕТЛО-КРАСНЫЙ 13 CONSTANT СВЕТЛО-ФИОЛЕТОВЫЙ 14 CONSTANT СВЕТЛО-КОРИЧНЕВЫЙ 15 CONSTANT БЕЛЫЙ - 192 - Эти цвета могут использоваться с такими словами, как ФОН, ПЕРО, БОРДЮР: БЕЛЫЙ ФОН КРАСНЫЙ ПЕРО СИНИЙ БОРДЮР Однако такое решение требует введения 16-ти имен, многие из которых - сложно-составные. Есть ли путь к упрощению? Отмечаем, что цвета между 8 и 15 суть "светлые" версии цветов от 0 до 7. (На аппаратном уровне единственным различием между двумя этими наборами является установка бита "интенсивности".) Если мы выделим "светлость", то можем получить такое решение: VARIABLE 'СВЕТ? ( бит интенсивности?) : ЦВЕТ ( цвет) CREATE , DOES> ( -- цвет ) @ 'СВЕТ? @ OR 0 'СВЕТ? ! ; 0 ЦВЕТ ЧЕРНЫЙ 1 ЦВЕТ СИНИЙ 2 ЦВЕТ ЗЕЛЕНЫЙ 3 ЦВЕТ ЖЕЛТЫЙ 4 ЦВЕТ КРАСНЫЙ 5 ЦВЕТ ФИОЛЕТОВЫЙ 6 ЦВЕТ КОРИЧНЕВЫЙ 7 ЦВЕТ СЕРЫЙ : СВЕТЛО 8 'СВЕТ? ! ; При таком синтаксисе слово СИНИЙ само по себе будет возвращать на стеке "1", а фраза СВЕТЛО СИНИЙ будет давать "9". (Приставка-прилагательное СВЕТЛО устанавливает флаг, который используется цветами, а затем очищается.) При необходимости мы все равно можем определить: 8 ЦВЕТ ТЕМНО-СЕРЫЙ 14 ЦВЕТ ГУСТО-ЖЕЛТЫЙ И вновь этот подход привел нас к более приятному синтаксису и более короткому объектному коду. ------------------------------------------------------------ СОВЕТ Не расчленяйте ради расчленения. Используйте клише. ------------------------------------------------------------ - 193 - Фраза OVER + SWAP часто наблюдается в определениях. (Она преобразует адрес и счетчик в конечный и начальный адрес для цикла DO LOOP.) Другая столь же частая фраза - это 1+ SWAP (Она преобразует последовательность из начала-счета и конца-счета в последовательность конец-отсчета-плюс-один и начало-отсчета, требуемую для цикла DO LOOP.) Мало проку в выделении этих фраз в слова, такие, как ДИАПАЗОН (RANGE) (для первой фразы). ---------------------------------------------------------------- Мур: Часто встречающаяся фраза (OVER + SWAP) принадлежит к тому виду фраз, которые находятся на грани представимости их в виде полезных слов. Часто, если Вы определили что-то в виде слова, оказывается, что оно встречается лишь однажды. Если Вы на свою голову дали имя такой фразе, то Вам придется помнить, что именно делает ДИАПАЗОН. Манипуляции не видны из названия. OVER + SWAP имеет более значимую мнемоническую ценность, чем ДИАПАЗОН. ---------------------------------------------------------------- Я называю такие фразы "клише". Они сами по себе образуют исполненную значения функцию. Не нужно помнить, как работает такая фраза, а только что она делает. И не надо запоминать еще одно имя. ФАКТОРИЗАЦИЯ ПРИ КОМПИЛЯЦИИ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ В последнем разделе мы рассмотрели много способов для организации кода и данных с целью уменьшения избыточности. Мы можем также применить принцип ограничения избыточности для времени компиляции, позволяя Форту делать за нас черную работу. ------------------------------------------------------------ СОВЕТ Для получения максимальной управляемости снижайте избыточность даже при компиляции. ------------------------------------------------------------ - 194 - Предположим, в задаче требуется нарисовать девять квадратиков, как показано на рис. 6-1. Рис.6-1. Что нам нужно изобразить. ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** ******** Для работы нам нужны константы, представляющие такие значения, как размеры каждого квадратика, зазоры между ними и координаты вершины первого из них. Разумеется, мы можем определить: 8 CONSTANT ШИРИНА 5 CONSTANT ВЫСОТА 4 CONSTANT ПЕРЕУЛОК 2 CONSTANT УЛИЦА (Улицы идут на восток и запад, переулки простираются к северу и югу.) Теперь мы можем посчитать в уме левый отступ. Мы собираемся отцентрировать все эти квадратики на экране шириной в 80 колонок. Чтобы что-нибудь выровнять, нам надо вычесть его ширину из 80 и поделить пополам, получая при этом левый отступ. Для расчета общей ширины мы складываем: 8 + 4 + 8 + 4 + 8 = 32 - 195 - (три ширины и два переулка). (80 - 32) / 2 = 24. Так мы можем определить 24 CONSTANT ЛЕВЫЙ-ОТСТУП и проделать то же для верхнего-отступа. Однако что будет, если позже мы перепроектируем задачу, в результате чего ширина изменится или переулки расширятся? Нам придется вручную пересчитывать отступы. В среде Форта мы имеем возможность использовать всю силу языка даже при компиляции. Почему бы не позволить Форту самому проделать расчеты? ШИРИНА 3 * ПЕРЕУЛОК 2 * + 80 SWAP - 2/ CONSTANT ЛЕВЫЙ-ОТСТУП ШИРИНА 3 * УЛИЦА 2 * + 24 SWAP - 2/ CONSTANT ПРАВЫЙ-ОТСТУП ------------------------------------------------------------ СОВЕТ Если значение константы зависит от величины ранее введенной константы, используйте Форт для расчета этого значения. ------------------------------------------------------------ Все эти вычисления не производятся при работе задачи, так что они не влияют на скорость исполнения. Вот еще один пример. Рисунок 6-2 содержит код для определения слова, рисующего картинки. Слово РИСОВАТЬ ставит звездочку на каждой координате, указанной в таблице по имени ТОЧКИ. (Здесь слово XY позицирует курсор в позицию ( x y ), снимаемую со стека.) Рис.6-2. Еще один пример снижения избыточности при компиляции. : P ( x y -- ) C, C, ; CREATE ТОЧКИ 10 10 P 10 11 P 10 12 P 10 13 P 10 14 P 11 10 P 12 10 P 13 10 P 14 10 P 11 12 P 12 12 P 13 12 P 14 12 P HERE ТОЧКИ - ( /таблицу) 2/ CONSTANT #ТОЧЕК : @ТОЧКА ( i -- x y) 2* ТОЧКИ + DUP 1+ C@ SWAP C@ ; : РИСОВАТЬ #ТОЧЕК 0 DO I @ТОЧКА XY ASCII * EMIT LOOP ; Обратите внимание на строку, следующую сразу за списком точек: HERE ТОЧКИ - ( /таблицу) 2/ CONSTANT #ТОЧЕК - 196 - Фраза "HERE ТОЧКИ -" вычисляет количество байтов, занимаемых таблицей. Деля это значение на два, получаем число координат x-y в списке; это число становится константой #ТОЧЕК, используемой в качестве предела в цикле DO LOOP слова РИСОВАТЬ. Такая конструкция позволяет Вам добавлять или убирать точки из таблицы, не заботясь о том, сколько их получается. Форт подсчитывает это Вам. ФАКТОРИЗЦИЯ ПРИ КОМПИЛЯЦИИ С ПОМОЩЬЮ ОПРЕДЕЛЯЮЩИХ СЛОВ. Давайте исследуем набор подходов к одной и той же проблеме - определению группы связанных адресов. Вот первая попытка: HEX 01A0 CONSTANT БАЗОВЫЙ.АДРЕС.ПОРТА БАЗОВЫЙ.АДРЕС.ПОРТА CONSTANT ДИНАМИК БАЗОВЫЙ.АДРЕС.ПОРТА 2+ CONSTANT ПЛАВНИК-А БАЗОВЫЙ.АДРЕС.ПОРТА 4 + CONSTANT ПЛАВНИК-Б БАЗОВЫЙ.АДРЕС.ПОРТА 6 + CONSTANT ПОДСВЕТ DECIMAL Замысел правильный, но реализация его уродлива. Единственные элементы, меняющиеся от порта к порту - это числовые смещения и определяемые имена; все остальное повторяется. Такой повтор подсказывает применение определяющего слова. Следующий, более читабельный вариант, выделяет весь повторяющийся код в часть "does" определяющего слова: : ПОРТ ( смещение -- ) CREATE , DOES> ( -- 'порта ) @ БАЗОВЫЙ.АДРЕС.ПОРТА + ; 0 ПОРТ ДИНАМИК 2 ПОРТ ПЛАВНИК-А 4 ПОРТ ПЛАВНИК-Б 6 ПОРТ ПОДСВЕТ При таком решении мы производим расчет смещения во время `исполнения` всякий раз, когда вызываем эти имена. Было бы более эффективно производить вычисления во время компиляции, например, так: : ПОРТ ( смещение -- ) БАЗОВЫЙ.АДРЕС.ПОРТА + CONSTANT ; \ does> ( -- 'порта ) 0 ПОРТ ДИНАМИК 2 ПОРТ ПЛАВНИК-А 4 ПОРТ ПЛАВНИК-Б 6 ПОРТ ПОДСВЕТ - 197 - Мы здесь ввели определяющее слово ПОРТ, которое имеет уникальное поведение во время `компиляции` - а именно добавляет смещение к БАЗОВЫЙ.АДРЕС.ПОРТА и определяет константу. Мы можем даже пройти еще один шаг вперед. Предположим, что адреса всех портов отстоят друг от друга на 2 байта. В этом случае нет такой причины, по которой мы обязаны были бы определять эти смещения. Числовая последовательность 0 2 4 6 сама по себе избыточна. В следующей версии мы начинаем с того, что имеем на стеке БАЗОВЫЙ.АДРЕС.ПОРТА. Определяющее слово ПОРТ дублирует адрес, делает из него константу, а затем добавляет 2 к остающемуся на стеке адресу для использования следующим вызовом слова ПОРТ. : ПОРТ ( 'порта -- 'след-порта ) DUP CONSTANT 2+ ; \ does> ( -- 'порта ) БАЗОВЫЙ.АДРЕС.ПОРТА ПОРТ ДИНАМИК ПОРТ ПЛАВНИК-А ПОРТ ПЛАВНИК-Б ПОРТ ПОДСВЕТ DROP ( адрес-порта) Отметьте, что мы обязаны дать начальный адрес на стек перед определением первого порта, а затем, после окончания определения всех портов, вызвать DROP для удаления все еще остающегося на стеке адреса. И последнее замечание. Очень похоже, что базовый адрес порта может меняться, и поэтому должен быть определен в единственном месте. Это `не` означает, что его надо делать константой. Зная, что такой адрес не будет использоваться вне лексикона для имен портов, нисколько не будет хуже дать его здесь просто числом. HEX 01A0 ( базовый адрес портов) DECIMAL ПОРТ ДИНАМИК ПОРТ ПЛАВНИК-А ПОРТ ПЛАВНИК-Б ПОРТ ПОДСВЕТ DROP - 198 - ИТЕРАТИВНЫЙ ПОДХОД ПРИ РЕАЛИЗАЦИИ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ранее в этой книге мы описывали итеративный подход, уделяя особенное внимание его вкладу на стадии проектирования. Поскольку теперь мы рассматриваем реализацию, давайте убедимся, что этот подход в действительности используется и при написании кода. ------------------------------------------------------------ СОВЕТ Работайте каждый раз только над одной стороной задачи. ------------------------------------------------------------ Предположим, в нашу задачу входит рисование или стирание квадрата на заданной координате x-y. (Это та же задача, которую мы предлагали в разделе с названием "Факторизация при компиляции".) Вначале мы фокусируем свое внимание на рисовании квадратика, не думая о том, как его будем стирать. Вот к чему мы могли бы прийти: : СЛОЙ ШИРИНА 0 DO ASCII * EMIT LOOP ; : КВАДРАТ ( верх-левый-х верх-левый-у -- ) ВЫСОТА 0 DO 2DUP I + XY СЛОЙ LOOP 2DROP ; Проверив, что это работает правильно, мы переходим теперь к задаче использования этого кода для `стирания` квадратика. Решение просто: вместо того, чтобы вставлять внутрь программы ASCII * мы хотели бы заменять выдаваемый символ со звездочки на пробел. Это требует добавления переменной и некоторых удобочитаемых слов для установки содержимого этой переменной. Итак: VARIABLE ПЕРО : РИСОВАТЬ ASCII * ПЕРО ! ; : СТИРАТЬ BL ПЕРО ! ; : СЛОЙ ШИРИНА 0 DO ПЕРО @ EMIT LOOP ; Определение КВАДРАТа, так же, как и последующего кода, остается неизменным. Такой подход дает синтаксис ( х у ) РИСОВАТЬ КВАДРАТ или ( х у ) СТИРАТЬ КВАДРАТ - 199 - Переходя от прямо указанного числа к переменной, содержащей нужной значение, мы добавили уровень подвижности. В нашем случае мы его добавили "задним числом", увеличив уровень сложности слова СЛОЙ без утяжеления определения. Концентрируясь в данный момент времени на одной проблеме, Вы разрешаете каждое из измерений более эффективно. Если в Ваших размышлениях присутствует ошибка, проблему легче увидеть, если она не загорожена еще не испытанной, неопробованной стороной Вашего кода. ------------------------------------------------------------ СОВЕТ Не меняйте слишком многое за один раз. ------------------------------------------------------------ При редактировании программы - добавлении новой функции или исправлении чего-нибудь - часто хочется взять и подправить одновременно еще несколько других мест. Наш совет: так не делайте. В каждом своем цикле редактирования-компиляции производите столь мало изменений, сколько только можете. Не забывайте проверять результаты каждой ревизии перед продолжением. Поразительно, насколько часто бывает достаточно проделать всего три невинных исправления только для того, чтобы, перекомпилировав код, убедиться в том, что ничего не работает! Внесение изменений по одному за раз дает гарантии того, что, когда что-то перестанет работать, Вы будете знать причину. ------------------------------------------------------------ СОВЕТ Не пытайтесь слишком рано превзойти все пути для факторизации. ------------------------------------------------------------ Кое-кто удивляется, почему большинство Форт-систем не имеют определяющего слова МАССИВ (ARRAY). Причина состоит в вышеприведенном правиле. ---------------------------------------------------------------- Мур: Мне часто попадается тот класс вещей, которые называются массивами. Простейший массив просто добавляет ссылку к адресу и возвращает Вам назад адрес. Массив можно определить фразой CREATE X 100 ALLOT - 200 - и затем использовать X + Или можно сказать : X X + ; Одним из наиболее расстраивающих меня вопросов является такой: стоит ли создавать определяющее слово для некоторой структуры данных? Достаточно ли у меня будет оправданий для его существования? Я редко сразу представляю себе, понадобится ли мне более, чем один массив. Поэтому слово МАССИВ я не определяю. После того, как обнаруживается, что нужно два массива, вопрос становится спорным. Если нужно три, все ясно. Если только они не разные. И плохо дело, если они разные. Может понадобиться, чтобы массив выдавал свое содержимое. Может хотеться иметь байтовый массив или битовый массив. Может возникнуть желание проверять допустимые границы или помнить его текущую длину для того, чтобы наращивать его на конце. Стиснув зубы, я спрашиваю себя: "Обязан ли я представлять байтовый массив через двухбайтовый только для того, чтобы подогнать структуру данных к уже имеющемуся у меня слову?" Чем сложнее проблема, тем менее вероятно, что Вы найдете универсальную структуру данных. Число случаев, когда такая сложная структура может найти широкое применение, очень невелико. Одним из положительных примеров такой сложной организации данных является словарь Форта. Очень крепкая и подвижная структура. Она используется кругом в Форте. Но это - редкость. Если Вы останавливаетесь на том, чтобы определить слово МАССИВ, то совершаете этим акт декомпозиции. Вы выделяете концепцию массива изо всех слов, в которые впоследствии он войдет. И Вы переходите на следующий уровень абстракции. Построение уровней абстракции - это динамический процесс, Вам его не предугадать. ---------------------------------------------------------------- ------------------------------------------------------------ СОВЕТ Пусть сегодня это заработает. Оптимизируйте это завтра. ------------------------------------------------------------ - 201 - ---------------------------------------------------------------- Опять Мур. Во время этого интервью Мур завершал работу над проектированием Форт-компьютера одноплатного уровня с использованием доступных промышленных ИС. В качестве рабочего инструмента для проектирования платы он создал ее симулятор на Форте - для проверки ее логики: Сегодня утром я понял, что смешиваю описание микросхем с их расположением на плате. Это вполне подходит для моих сегодняшних целей, однако если я зайду на другую плату и захочу использовать для нее те же микросхемы, то для этого вещи сгруппированы плохо. Мне следовало бы факторизовать их по описаниям в одном месте и по использованию в другом. Тогда я получил бы язык описания микросхем. Хорошо. В то время, когда это делалось, я не был заинтересован в таком уровне оптимизации. Даже если бы прямо тогда мне пришла в голову эта мысль, мне, наверное, нужно было бы сказать: "Все в порядке, я сделаю это попозже", и затем идти вперед так, как я и шел. Оптимизация тогда не была для меня самым важным. Конечно, я пытаюсь как следует делать факторизацию. Но если хорошего пути не видно, я говорю: "Пусть оно хотя бы работает". Причина этого не в лени, а в знании того, что объявятся и другие вещи, которые повлияют на решение непредвиденным образом. Попытки оптимизировать все прямо сейчас глупы. Пока я не увижу перед собой полной картины, я не буду знать, где лежит оптимум. ---------------------------------------------------------------- Наблюдения, приведенные в этом разделе, не должны противопоставляться тому, что было сказано ранее об упрятывании информации и выделении элементов, подверженных изменениям. Хороший программист постоянно пытается балансировать между тягой к встраиванию способностей к изменениям внутрь и склонностью к проведению изменений позже, при необходимости. Принятие таких решений требует определенного опыта. Но, в качестве общего правила: ------------------------------------------------------------ СОВЕТ Противодействуйте вещам-которые-могут-меняться с помощью организации информации, а не наращивания сложности. Добавляйте сложность только при необходимости заставить работать текущую итерацию. ------------------------------------------------------------ - 202 - ИТОГИ ~~~~~ В этой главе мы обсуждали различную технику и критерии для факторизации. Мы изучали также итеративный подход в применении к фазе реализации. ЛИТЕРАТУРА ~~~~~~~~~~ 1. W.P. Stevens, G.J. Myers and L.L. Constantine, `IBM Systems Journal`, vol. 13, no. 2, 1974. Copyright 1974 by International Business Machines Corporation. 2. G.A. Miller, "The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information," `Psychol. Rev.`, vol. 63, pp. 81-97, Mar. 1956. 3. Kim R. Harris, "Definition Field Address Conversion Operators," `FORTH-83 Standard`, FORTH Standards Team.