Автор: M.L.Gassanenko
Дата публикации: 1995
оригинал статьи взят
http://www.forth.org.ru/~mlg/ef95/ef95-1-paper.txtПеревел:
mOlegРасширение механизма обработки исключений АннотацияВ статье рассматривается расширенный набор слов для обработки исключений и повторного выполнения вызвавшего исключение кода. Механизм повторного выполнения позволяет программисту сфокусироваться на логике процесса и уменьшить размер кода, отвечающего за повторные попытки. Синтаксис совместим с нотацией зафиксированной ANSI стандартом.
ВведениеОдна из наиболее важных областей применения форта - контроль аппаратуры присоединенной к центральному процессору. Ситуации, в которых имеется контроллер с собственными "интеллектом" и логикой, немного документации по нему, и некой специфичностью, узнаваемой в процессе работы, встречаются часто. Форт - мощное средство в этой области, потому что позволяет отражать логику контроллера (в реальности два уровня: уровень номеров портов и значений значимых команд, оба могут поддерживаться Фортом) и благодаря механизму исключений, а так же улучшений обсуждаемых ниже.
В последнее время память человека считается наиболее важным ресурсом, который приходится брать в расчет при создании программных систем. Человеческая память ненадежна и ограничена психологической константой 7+-2 (объектов, которые одновременно могут держаться в уме); в дальнейшем требования человеческой памяти могут обернуться в издержки времени, потраченного в поисках чего-либо забытого, и нахождении и исправлении ошибок сделанных благодаря ненадежности человеческой памяти. В дополнение, много людей чувствуют дискомфорт, когда им приходится держать в уме больше вещей, чем они могут. Мы может делить объекты, которые должны удерживаться в памяти на три группы:
1) вещи, забывание которых делает дальнейшую деятельность бессмысленной; обычно они не забываются (к примеру, цель деятельности);
2) вещи запоминание которых требуют больше человеческой памяти, чем просто удерживание их в уме (например, программист может пересчитать состояние стека из известного начального состояния и стековых операций, но, делая это он может забыть что-то другое);
3) вещи, запоминание которых не требует дополнительной человеческой памяти (как состояние стека указанное в комментариях).
К сожалению, бессмысленно считать объекты, которые могут удержаться в человеческой памяти, потому что их количество зависит от персональных способностей программиста. Все же, эти наблюдения остаются полезными, ведь позволяют нам говорить о простоте не только на интуитивном уровне.
ЦельНашей целью является создание прозрачной нотации для обработки ошибок и повторения попыток, то есть синтаксиса с минимальными требованиями к человеческой памяти (как для удержания в уме, так и вспоминания). Стандартный CATCH безусловно неудачен.
Нотация позволяющая думать в терминах успешности исполнения последовательностей контрольных команд, то есть последовательностей описанных в документации к контроллеру.
Так же существуют требования взаимонезависимости обработчиков исключения от циклов DO LOOP, позволяющих работать с индексами циклов. Это может не выглядеть важным, но, несомненно, это требование очень важно для удобной работы.
Чем меньше проблем с контроллером у вас возникнет, тем лучше, даже если эти проблемы тривиальны.
Требование совместимости со стандартным CATCH означает, что синтаксис должен позволять нам определять стандартный CATCH в его терминах и затем использовать обе нотации в одной программе.
Дополнительный анализИспользование специальных структур для организации повторных попыток выполнения понятно, потому что уменьшает требования к человеческой памяти.
Думанье в терминах последовательностей успешных операций (которые могут повторяться в случае неуспеха) проще, чем думанье в терминах циклов, которые выполняют операции до успешного завершения.
Ощущение, что что-то не так в структуре управления занимает человеческую память (программист думает, что это должно или может быть).
Стандартный CATCH вынуждает программиста разбивать его код минимум на два слова: одно для действий, которые могут не удаться, и другое способное повторять первое. Этот недостаток синтаксиса одновременно открывает другой системный недостаток: если мы используем одни и те же структуры управления (ветвления и циклы) для написания повторных попыток и логики процесса, эти два вида кода перемешаются, и программа станет менее читаемой. Если мы решим вторую проблему, представив специальную структуру управления для повторных попыток, мы так же сможем найти решения нежелательной факторизации кода. Во-первых, программист должен решать, как разбивать (факторизовать) код. Если язык принуждает программиста делать решение - это слабость языка. Ненужная фрагментация кода так же нехороша, как недостаточная. Во-вторых, CATCH способствует созданию слов, которые не должны исполняться самостоятельно, а должны использоваться в контексте их разыменования ([']) и передачи их CATCH . Эти определения по сути являются локальными метками, которые не значат самостоятельно ничего (пример: если мы не передадим такое определение в предполагаемом контексте, и оно вызовет THROW, прерывание не будет разрешено). В-третьих, CATCH является структурой управления, которая использует вспомогательную константу (вспомогательное слово - исполнимый токен). Большинство удачных (и поэтому популярных) структур управления не используют вспомогательных данных (границы циклов не являются вспомогательными данными - это контрольные данные).
СинтаксисПервый шаг будет разделением CATCH на CATCH( и )CATCH . Это позволит нам избежать вспомогательных определений, и использовать индексы вложенных циклов изнутри CATCH( ... )CATCH.
Вторым шагом будет внедрение специального механизма повторов неудавшихся действий. Это решит проблему смешения нормальных структур управления и логики повторов.
Результирующий синтаксис должен выглядеть как-то так:
TRY
CATCH(
.... \ код, который может не удаться
....
....
)CATCH \ кладет код ошибки на стек
?DUP
IF
<какой-то анализ>
3 RETRY \ трехкратный повтор попыток исполнения действий
THROW \ затем выход
THEN
(обработчик исключений не обязательно должен заканчиваться словом THROW).
Стандартный CATCH может быть определен следующим образом:
: CATCH >R CATCH( R@ EXECUTE )CATCH R> DROP ;
и может использоваться вместе с TRY и RETRY :
TRY ['] <aux-name> CATCH
?DUP
IF .... 3 RETRY THROW
THEN
Замечу, что мы не можем писать
['] <aux-name> TRY CATCH
потому что исполнимый токен <aux-name> будет поглощен CATCH и не будет корректно восстановлен (хотя это может зависеть от реализации).
Вероятно, мы должны лучше определить слово, которое совмещает функциональность TRY и CATCH( :
: TRAP( POSTPONE TRY POSTPONE CATCH( ; IMMEDIATE
и, вероятно:
: )IFERR POSTPONE )CATCH POSTPONE ?DUP POSTPONE IF ; IMMEDIATE
: )CASEERR POSTPONE )CATCH POSTPONE CASE ; IMMEDIATE
СпецификацииCATCH( "catch-paren"
В режиме интерпретации действия не определены.
компиляция: ( C: -- catch-orig )
Положить место начала CATCH на стек контроля структур управления. Изменить поведение текущего определения следующим образом: действие не завершено до тех пор, пока не встретится закрывающий )CATCH.
Время исполнения: ( i*x -- i*x ) ( EX: -- catch-sys )
Положить фрейм исключения на стек исключений.
)CATCH "paren-catch"
В режиме интерпретации действие слова не определено.
В режиме компиляции: ( C: catch-orig -- )
добавить к поведению текущего определения действия, описанные ниже, затем разрешить (завершить) действия по созданию структуры управления.
В режиме исполнения: ( -- 0 ) ( EX: catch-sys -- )
Удалить фрейм исключения со стека исключений, оставить флаг Нуль на стеке данных.
Код, следующий за )CATCH может так же получить управления в результате выполнения THROW , в таком случае на вершине стека данных останется ненулевое значение.
THROW
( k*x 0 -- k*x ) | ( k*x n -- i*x n ; control transfer )
( EX: catch-sys -- )
Если любой бит флага отличен от нуля, удалить верхний кадр исключения, восстановить указатели всех стеков, какими они были на момент создания фрейма исключения соответствующего CATCH( , оставить на вершине стека данных флаг исключения, передать управление за соответствующий )CATCH, соответствующего CATCH( , который создал данный фрейм исключения.
TRY ( j*x -- j*x )
Заполнить последний пробный фрейм в стеке исключений. В любое время как минимум один фрейм попытки присутствует на стеке исключений. Установить счетчик попыток в нуль.
1) фрейм исключений содержит место для фрейма повторных попыток.
2) фрейм попыток включает информацию о глубинах всех стеков и счетчиков попыток.
RETRY ( +n -- | j*x ; control transfer )
"Повторно сделать до +n повторных попыток". Если +n больше, чем значение, определяющее максимальное количество попыток, увеличить счетчик попыток на единицу, установить глубины всех стеков так, чтобы они отражали сохраненное в фрейме исключения состояние, и передать управление в точку за TRY. В случае, если глубина какого либо стека меньше сохраненного значения, поведение не определено. Так же поведение не определено, если на вершине стека исключений отсутствует корректный фрейм повторной попытки.
*TRY устанавливает счетчик попыток в нуль.
ATTEMPT# ( -- n ) "attempt-number"
n - значение счетчика попыток, равно нулю после выполнения TRY , увеличивается после выполнения каждой RETRY .
Результат реализацииМы использовали отдельный стек исключений в следствии чего:
1) эта техника позволяет использовать индексы циклов внутри CATCH( ... )CATCH;
2) проще реализовать обсуждаемые конструкции.
Стек исключений не бывает пустым: там всегда зарезервировано место для фрейма повторных попыток. TRY сохраняет фрейм попыток в эту память, вероятно перезаписывая предыдущий фрейм попыток; это не изменяет указатель стека исключений. CATCH( кладет фрейм исключений в стек исключений и резервирует место для нового фрейма повторов.
ОбсуждениеНабор слов обработки исключений значительно упрощает программирования различных вариантов операций, которые могут не удаться.
Код совместим с ANS стандартными CATCH-THROW, то есть CATCH может быть определено через CATCH( и )CATCH и могут быть совместно использованы в одной программе.
С использованием CATCH( ... )CATCH определения естественно разделяются на две части: действия и реакции на ошибки. Обе части, тем не менее, стремятся раздуться. Обычные кандидаты для дробления кода: анализирующий ответ устройства код и, вероятно, вызывающий исключения; код следующий за )CATCH анализирующий код ошибки; действия реинициализации; значащая последовательность команд устройства.
Способность использовать индексы вложенных циклов изнутри CATCH( ... )CATCH очень полезно, особенно в начале разработки.
**********************************************************RETRY позволяет программисту избавиться от вспомогательных структур управления и заниматься логикой работы процесса. С другой стороны, это сама по себе интересная структура:
INITIALIZE
TRY CATCH( ... )CATCH
IF
2 RETRY
REINITIALIZE-ALL
6 RETRY
THROW
THEN
делает две дополнительных попытки без полной реинициализации, и затем делает еще четыре попытки с полной реинициализацей. Следующий пример показывает использование структуры TRY ... RETRY для определения параметров аппаратуры:
rate1 TO the-rate
TRY CATCH(
<try-data-transfer>
)CATCH
IF
rate2 TO the-rate
1 RETRY
rate3 TO the-rate
2 RETRY
rate3 TO the-rate
3 RETRY
THROW
THEN
Поскольку THROW - это нелокальный возврат, RETRY - нелокальный цикл. Код, ответственный за повторы может быть удален, впоследствии RETRY восстановит указатели стеков во время передачи управления на код, следующий за TRY.
Важной точкой (применимой в основном к FORTH-83 системам) является возможность перехвата исключений текстовым интерпретатором, иначе Форт потеряет интерактивное естество. Как последнее средство, определения вида:
: T CATCH( ' EXECUTE )CATCH ." errcode=" . ;
может помочь.
Хотя структура CATCH известна давно, и была кандидатом для включения в стандарт на протяжении длительного периода (19 июня 1991 уже был определен синтаксис), другие механизмы обработки исключений появлялись[2,3]. Персональные контакты автора показали, что ANS-евый CATCH, впитанный некоторыми программистами, использован для создания их собственных механизмов обработки исключений. Мы должны отметить, что большинство виденных нами механизмов исключений в Форте используют фиксированную нотацию (различные виды скобок, окружающих действия, которые должны быть повторены) охотнее, чем передачу вспомогательного исполнимого токена перехватчику исключений, подобному CATCH.
Мы можем предположить, что развитие механизмов обработки ошибок в Форте продолжится.
ВыводыПредставленное расширение набора слов обработки исключений включает механизм перехвата ошибок и повторного выполнения попыток. Механизм повтора попыток позволяет программисту заниматься логикой процесса и уменьшает количество кода, ответственного за повторные попытки. Код становится прозрачнее с момента использования специальных структур управления для повтора попыток; такой тип кода распознается беглым взглядом. Синтаксис совместим со стандартной нотацией: зафиксированный стандартом CATCH может быть определен с помощью CATCH( ... )CATCH и оба варианта могут использоваться совместно.
Литература:
[1] American National Standard for Information Systems - Programming Languages - Forth. American National Standard Institute, Inc., 1994.
[2] Rodriguez, B.J., "A Forth Exception Handler," SIGForth, Vol. 1, Summer'89, p.11-13.
[3] Wejgaard, W., "TRY: A Simple Exception Handler," proc. of the euroFORML'91 conf., 4 p.
**********************************************************из текста вырезан непонятный для меня кусок, который выглядит в тексте бессмысленно на мой взгляд mOleg
In an application source file of 1300 lines, 34.5K bytes, 260 empty lines, 105 comment lines, 126 colon definitions, 22 CODE definitions, 27 VALUEs, 20 constants, 6 VARIABLEs, 31 CREATE definitions, 11 DEFERs (total: 243 definitions in 935 lines), 16 DO loops (used in 14 definitions) and 18 CATCHes (used in 12 definitions) there are 2 definitions where indices of enclosing loop are used, and (to compare with) 2 definitions where DO loops are used insideCATCH(...)CATCH (one of these 2 loops is used just for a delay). TRY is used 17 times, RETRY is used 22 times, THROW is used 37 times. There are 18 BEGIN loops, 85 IF statements and 1 CASE statement. This main file does not contain the whole application source: there are also some utility files. The application programms a controller and deals with a timer.