Системное программирование Джексона в перспективе
Введение
СПД – это метод разработки программ. Эти истоки лежат в системах обработки данных, которые появились в 1960-х, когда надежные, относительно недорогие и достаточно мощные компьютеры впервые стали общедоступными. Фундаментальным понятием в СПД является поток последовательный поток данных. Первоначально это понятие появилось и было обосновано благодаря последовательным файлам на магнитной ленте, которые являлись характерной чертой обработки данных в 60-х годах, но быстро стало ясно, что у него было более широкое прикладное значение. Сегодня метод разработки СПД является ценным для таких приложений, как встроенное программное обеспечение и обработка сетевых протоколов. СПД возникло из попыток небольшой группы людей из консалтинговой компании по обработке данных улучшить практику программирования и сделать свои программы более надежными и легкими для понимания и модификации. В 1971 это стало основным продуктом очень небольшой компании Michael Jackson Systems Limited, которая предлагала услуги по разработке, обучающие курсы, консультации и, с 1975 года, программное обеспечение для поддержки разработок СПД для программ на COBOL. Название СПД – «Структурно Программирование Джексона» ¾ было предложено шведским лицензиатом компании в 1974 году. В мире коммерции IBM признало название «Структурное Программирование» в начале 1970-х, и компания Yourdon Inc начала предлагать курсы по «Структурному Дизайну» приблизительно в 1974 году. Отличительное название было коммерческой необходимостью. Также было технически необходимо выбрать отличительное и запатентованное название: метод СПД очень сильно отличался от своих конкурентов.
Системы Обработки Данных в 1960-е года.
Системы обработки данных начала и середины 1960-х в основном занимались обработкой последовательных файлов на магнитной пленке. Надежные лентопротяжные устройства стали широкодоступными и повсеместно используемыми в конце 1950-х; сменные дисковые накопители стали впервые доступными, когда IBM предложил дисковый накопитель модели 1311 в 1963 году. Дисковый накопитель был очень ограниченным и дорогим средством по сравнению с пленкой. По ценам 1965 года упаковка дисковых накопителей 1311 модели стоила около 200 долларов и имела емкость в 2 миллиона символов; 2400 футовая Бабина стоила около 7 долларов и имела емкость в 20 миллионов и 60 миллионов символов. Для больших файлов, которые могли содержать миллионы записей, пленка была единственным реалистичным выбором. Большинство систем обработки данных имели большие файлы. Так как пленка является по своей сути последовательным средством, обновление одной записи мастер-файла могло быть выполнено только через чтение целого файла и копирования исправленной записи на новую пленку. Этот очень медленный процесс был экономичным, и только если нужно было обновить много записей, поэтому системы записи на магнитную ленту были почти всегда неизбежно системами пакетной обработки. Транзакции – например полученные платежи – записывались ежедневно или еженедельно на файл с транзакциями на пленке. Файл с транзакциями был затем упорядочен в той же самой последовательности, как и файл мастер-записей, например, счета клиентов, к которым должны были применятся транзакции; и затем использовался в программе пакетного обновления для того, чтобы произвести новую версию мастер-файла, чью записи отражали результаты транзакций. Необходимо было всегда обрабатывать мастер-файл полностью и производить новую версию полностью, даже если пакет содержал транзакции для очень небольшого количества мастер-записей. Обработка файла, занимающего одну полную магнитную ленту могла занять час или более; некоторые мастер-файлы занимали дюжины лент. Хуже того, могло было быть несколько мастер-файлов, предназначенных для обработки – например, клиенты, заказы, счета и продукция. Файл с транзакциями затем полностью упорядочивался в различные последовательности различных мастер-файлов, выполняя программу пакетного обновления для каждого мастер-файла и перенося промежуточные результаты далее в следующую программу обновления в виде файла для передачи, который также требовалось упорядочить.
В целях сокращения времени обработки мастер-файлы при возможности объединяли. Например, заказы, вместо того, чтобы содержаться в своем мастер-файле, могли содержаться в мастер-файле клиентов, причем записи с заказами для каждого клиента следовали за записью клиента в совместном файле. В результате этих выборов появлялась база данных с иерархической структурой, содержащаяся на магнитной ленте: это был именно тот тип баз данных, для которых изначально была спроектирована система управления базами данных IMS, созданная IBM совместно с North American Rockwell приблизительно в 1966 году. [Blackman 98].
Основная Идея СПД
Общим недостатком проектирования в программах пакетного обновления была невозможность убедиться в том, что программа содержала правильно синхронизированные файлы, так как она нарушала их иерархические структуры. Операция чтения, выполненная в неправильной точке исполнения программы, могла читать за пределами записи, к которой должна была быть применима следующая транзакция. В результате происходила ошибочная обработка такой транзакции и, часто, последующих транзакций и мастер-файлов. Другим общим недостатком проектирования была невозможность учитывать пустые множества, например, клиента с невыполненными заказами. Как можно было разрабатывать программы без подобных недостатков? В коммерческом и промышленном программировании в 1960-х годах вопрос с разработкой программ в основном ставился в терминах «модульного программирования»: Что было лучшей декомпозицией для каждой отдельной программы? Основное внимание уделялось структуре декомпозиции, а не инкапсуляции. Конференция 1968 года [Barnett 68], посвященная модульному программированию, привлекла к участию Джорджа Мили, теоретика компьютерных систем, которые дал свое имя компьютерам Mealy.
Идеи комплексации и связанности Структурной Разработки [Stevens 74, Myers 76] оформились как подход к модульности: было заявлено, что хорошая разработка может быть достигнута, если сеть уверенность в том, что модули имеют высокую связанность и низкую степень комплексации.
Фундаментальная идея СПД была совершенно другой: структура программы должна быть продиктована структурой входных и выходных потоков данных [Jackson 75]. Если один из последовательных файлов, обрабатываемых программой, состоял из групп клиентов, за каждой группой, состоящей из записи клиента, следовало некоторое количество записей заказов, каждая из которых была или простым заказом или срочным заказом, тогда программа должна иметь такую же структуру: она должна иметь раздел, который обрабатывает файл, и подраздел, который обрабатывает запись клиента, и так далее. Последовательность выполнения этих разделов должна отражать последовательность записей и групп записей в файле. Части программы могли быть очень маленькими, или в общем не компилировались отдельно. Окончательная структура может быть представлена на диаграмме структуры СПД, как показано на рисунке 1. Эта диаграмма представляет собой древовидное представление регулярного выражения
(CustRecord ( SimpleOrder | UrgentOrder ) * ) *
в котором выражение и все его подвыражения помечены. Итерация показана звездочкой в итерируемом подвыражении; каждая альтернатива выбора показана кружком. Структура представляет собой одновременно структуру файла и структуру программы для обработки файла. В качестве структуры данных это можно озвучить следующим образом: «Файл состоит из нуля или больше групп Групп Клиентов. Каждая Группа Клиентов состоит из Записи Клиента, за которой следует Тело Группы Клиента. Каждое Тело Группы Клиента состоит из нуля или более Заказов. Каждый Заказ является или Простым Заказом или Срочным Заказом».
В качестве структуры программы это представляет собой следующее:
program =
{ /* process file */
while (another customer group) do
{ /* process customer group */
process customer record;
{ /* process customer group body /*
while (another order) do
{ /* process order */
if simple order then
{ /* process simple order */ }
else
{ /* process urgent order */ }
} } } }
Figure 1: Structure of a File
and of a Program
FILE
CUST
GROUP
*
CUST
RECORD
CUST
GBODY
* ORDER
o SIMPLE
ORDER
o URGENT
ORDER
Многопоточность
В соответствии с методом разработки программ СПД, структура программы должна отражать все структуры потоков данных, а не структура одного из них. И первые шаги тогда заключаются в том, чтобы определить структуру данных каждого файла, обрабатываемого программой, и затем сформировать структуру программы, которая
включает в себя их все. Подобная структура программы позволяет разработчику легко убедиться в том, что выполнение программы чередовать все прохождения файла правильно и
сохранит их правильно синхронизированными. Рисунок 2 показывает образец структуры программы, основанной на двух структурах данных. Для краткости мы взяли стилизованный и тривиальный пример. Программа обрабатывает входной файл IN и выходной файл OUT. Последовательные OPAIRs в OUT конструируются из последовательных IPAIRs из IN: то есть, IPAIRs и OPAIRs «соответствуют друг другу функционально». Точно так же записи d и e вычисляются из b и c записей соответственно: то есть записи b и d соотносятся, точно так же как записи c и e. В этой тривиальном примере легко видно, что структура программы включает в себя обе структуры файлов в точности. В более реалистичных примерах структура, содержащая все структуры файлов, может быть достигнута разрешимым переписыванием структур. Подобные переписывания показаны на рисунке 3.
Структура данных A1 может быть переписана как A2, а A2 как A3. Разрешимые переписывания сохраняют множество последовательностей листьев, которые определяются структурой: A1, A2 и А3 все определяют последовательность <B, C,D>. Они должны так же сохранять промежуточные узлы каждой структуры: A2 нельзя переписать как A1, так как узел CD был бы в этом случае потерянным. Окончательная структура программы должна иметь по крайней мере один компонент, который соотносится с каждым компонентом каждой структуры данных.
Рисунок 2: Структуры 2 файлов и структура программы
IN
IBODY a
IPAIR *
b c
OUT
f OBODY
OPAIR*
d e
IN&OUT
f I&OBODY a
* I&OPAIR
b&d c&e
Рисунок 3: Примеры Переписывания Регулярных Выражений
A1
C B D
A2
B
C D
CD
A3
BB
C D
CD
B B
o o
Þ Þ
Операции и условия
Основное преимущество структуры программы, которая включает в себя все структуры файлов с данными, соблюдающими соответствие между их частями, состоит в том, она обеспечивает очевидно правильное место в тексте программы для каждой операции, которая должна быть выполнена. Она так же проясняет условия, необходимые в качестве защиты в конструкциях итерации (loop) и выбора (if-else or case).
Операции, которые должны быть выполнены, являются проходящими файл операциями, такими как open, close, read и write, и другими операциями, которые вычисляют значения выходных записей из значений входных записей. Например, в тривиальной иллюстрации на рисунке 2 операции могут быть: open IN, read IN, close IN, open OUT, write OUT record, close OUT, d := f(b), и так далее. Каждая операция должна появляться в том компоненте программы, который обрабатывает данные операций. Операции read являются особым случаем. Если предположить, что входные файлы могут быть проанализированы через за один проход одной записи, то должна быть одна операция read, которая следует за операцией open в начале файла, и одна операция в конце каждого компонента, который полностью обрабатывает одну входную запись. Таким образом, для примера на рисунке 2 операции должны быть расположены так, как это показано на рисунке 4.
Соответствие программы и структуры данных, совместно с схемой прохода одной записи, облегчает определение условий итерации и выбора. Например, условием по итерации компонента I&OBODY является
while (another I&OPAIR)
которое легко переводится в
while (IN record is b)
в котором ‘IN record’ относится к записи, которая была прочитана на одну запись вперед и в настоящий момент находится в буфере IN buffer.
5 2
2,5 2,5
IN&OUT
f I&OBODY a
*
I&OPAIR
b&d c&e
3,6 2 1,4
7
Рисунок 4: Расположение Операций в Структуре Программы
1. open IN
2. read IN
3. close IN
4. open OUT
5. write OUT record
6. close OUT
7. d := f(b)
...
СЛОЖНОСТИ
Процедуры разработки метода должны близко соответствовать специфическим свойствам задачи, в решении которых они используются. Процедуры разработки базового СПД, как это уже было описано здесь, требуют, чтобы задача обладала по крайней мере этими двумя свойствами:
· Структуры данных входного и выходного файлов и соответствия среди их компонентов данных, являются такими, что единственная структура программы может включить их все; и
· каждый входной файл может быть недвусмысленно проанализирован путем прохода только по одной записи. Отсутствие необходимого свойства немедленно распознается через трудности в завершении части процедуры разработки СПД. Если структуры файла не соотносятся соответствующим образом, становиться невозможно разработать правильную структуру программы: эта сложность называется структурным несоответствием. Если входной файл не может быть проанализирован за один проход, невозможно записать все необходимые условия по итерациям и выбору в программе: это сложность распознавания.
Хотя эти сложности определяются в процессе выполнения основной процедуры разработки СПД, они не указывают на ограниченность СПД. Они указывают на неотъемлемые сложности задачи как таковой, которые нельзя проигнорировать и которыми необходимо как-то решать. В СПД они решаются с помощью дополнительных техник внутри метода СПД.
Структурные несоответствия
Существует три типа структурных несоответствий: чередуемые несоответствия, несоответствия в порядке, и граничные несоответствия.
В чередуемых несоответствиях группы данных, которые последовательно возникают в одной структуре, функционально соотносятся с группами, которые являются чередующимися в другой структуре. Например, входной файл программы может состоять из хронологически упорядоченных записей вызовов, сделанных на центральной телефонной станции; программа должна выдавать напечатанный отчет с выходными данными тех же самых звонков, расположенных хронологически внутри абонента. «Группы абонентов», которые последовательно возникают в напечатанном отчете являются чередующимися в входном файле
В несоответствиях в порядке соответствующие сущности данных по разному упорядочиваются в двух структурах. Например, входной файл содержит матричные элементы, расположенные в ряд, а требуемый выходной файл содержит те же элементы в колонках.
В граничных несоответствиях 2 структуры имеют соотносимые элементы, которые происходят в одинаковом порядке, но элементы по-разному сгруппированы в двух структурах. Границы 2 групп не синхронизированы. Граничные несоответствия на удивление обычны. Вот три хорошо известных примера:
· Календарь состоит из лет, каждый год состоит из определенного количества дней. В одной структуре дни могут быть сгруппированными по месяцам, в другой структуре по неделям. Здесь наблюдается граничное несоответствие: недели и месяцы нельзя синхронизировать.
· Глава напечатанной книги состоит из строчек текста. В одной структуре строчки могут быть сгруппированы в абзацы, но в другой структуре в страницы. Здесь также пограничное несоответствие, так как абзацы и страницы нельзя синхронизировать.
· Файл в системе обработки файлов низкого уровня состоит из записей различной длины, каждая из которых состоит от 2 до 2000 битов. Записи должны храниться последовательно в фиксированных блоках по 512 битов. Здесь наблюдается граничное несоответствие: границы записей нельзя синхронизировать в границах блоков.
Сложности, происходящие из-за граничного несоответствия, являются очень реальными. Несоответствие между неделями и месяцами вызывает бесконечные проблемы в бухгалтерии: в 1923 Лига Наций организовала Специальную Комиссию по Проблемам Реформы Календаря для того, чтобы определить, можно ли решить проблему несоответствия благодаря введению нового календаря [Achelis 59]. Несоответствие между записями и блоками повлияло на первоначальное программное обеспечение для обработки файлов OS/360 от IBM: «метод доступа», который мог обрабатывать несоответствия – программное обеспечение, которое поддерживало «охваченные записи» – оказался самым сложным для разработки и он был предоставлен последним. Техника СПД по работе с структурными несоответствиями заключается в том, чтобы разложить первоначальную программу на две и более, и эти программы должны сообщаться через промежуточные структуры данных.
Граничное несоответствие, например, требует разложения на две программы, которые сообщаются через промежуточный последовательный поток. Структура последовательного потока основывается на ‘highest common factor’ двух несоответствующих друг другу структур. Например, в бухгалтерских программах может быть граничное несоответствие между входным файлом, который структурирован по месяцам и выходным файлом, структурированным по неделям.
Рисунок 5 показывает структуры данных.
The solution to the difficulty is to decompose the program into two: one program
handles the Months, producing an intermediate file that is input to a second
program that handles the Weeks. For the second program the intermediate file
structure must have no Month component: any necessary information from the
MonthHeader record must therefore be encoded in the OneDay records of the
intermediate file.
Monthly
Data
One
Month
Month
Body
One
Day
*
*
Month
Header
Weekly
Data
One
Week
Week
Body
One
Day
*
*
Week
Header
Week
Total
7
4
4
Figure 5: Structures Exhibiting a Boundary Clash
RECOGNITION DIFFICULTIES
A recognition difficulty is present when an input file can not be unambiguously
parsed by single look-ahead. Sometimes the difficulty can be overcome by looking
ahead two or more records; sometimes a more powerful technique is necessary.
The two cases are illustrated in Figure 6. The structure on the left can be parsed by
looking ahead three records: the beginning of an AGroup is recognised when the
third of the lookahead records is an A. But the structure on the right can not be
parsed by any fixed look-ahead. The JSP technique needed for this structure is
backtracking.
The JSP procedure for the backtracking technique has three steps:
· First, the recognition difficulty is simply ignored. The program is designed, and
the text for the AGroup and BGroup components is written, as usual. No
condition is written on the Group selection component. The presence of the
difficulty is marked only by using the keywords posit and admit in place of if and
else.
· Second, a quit statement is inserted into the text of the posit AGroup component
at each point at which it may be detected that the Group is, in fact, not an
AGroup. In this example, the only such point is when the B record is
encountered. The quit statement is a tightly constrained form of GO TO: its
meaning is that execution of the AGroup component is abandoned and control
jumps to the beginning of the admit BGroup component.
· Third, the program text is modified to take account of side-effects: that is, of the
side-effects of operations executed in AGroup before detecting that the Group was
in fact a BGroup.
Y * Y * read read read read read read
Y Z B Y Z A
File
Group*
AGroup
o
open read;
read;
read
BGroup
o
YY Z B YY Z A
File
Group*
AGroup
o
BGroup
o
Figure 6: Structures Requiring Multiple
Read-ahead and Backtracking
PROGRAM INVERSION
The JSP solution to structure clash difficulties seems, at first sight, to add yet more
sequential programs and intermediate files to systems already overburdened with
time-consuming file processing. But JSP provides an implementation technique ¾
program inversion ¾ that both overcomes this obstacle and offers other important
positive advantages.
The underlying idea of program inversion is that reading and writing sequential
files on tape is only a specialised version of a more general form of communication.
In the general form programs communicate by producing and consuming
sequential streams of records, each stream being either unbuffered or buffered
according to any of several possible regimes. The choice of buffering regime is, to a
large extent, independent of the design of the communicating programs. But it is
not independent of their scheduling. Figure 7 shows three possible
implementations of the same system.
In the upper diagram two ‘main’ programs, P and Q, communicate by writing and
reading an intermediate tape file F. First P is run to completion; then F is rewound;
then Q is run to completion. In the lower left diagram the intermediate tape file has
been eliminated: the ‘main’ program Q has been inverted with respect to F. This
inversion converts Q into a subroutine Q¢, invoked by P whenever P requires to
produce a record of F. Q¢ functions as a ‘consume next F record’ procedure.
Similarly, in the lower right diagram the ‘main’ program P has been inverted with
respect to F. This inversion converts P into a subroutine P¢, invoked by Q whenever
Q requires to consume a record of F. P¢ functions as a ‘produce next F record’
procedure. Both inversions interleave execution of P and Q as tightly as possible:
each F record is consumed as soon as it has been produced.
Two or more programs may be inverted in one system. Inverting Q with respect to
F, and P with respect to E, gives a subroutine P¢¢ that uses the lower-level
subroutine Q¢; the function of the two together is to consume the next record of E,
producing whatever records of G can then be constructed.
Inversion has an important effect on the efficiency of the system. First, it eliminates
the storage space and device costs of the intermediate tape file. It eliminates the
time required by the device to write and read each record of F, and also the ‘rewind
time’ to reposition the newly written file for reading: for a magnetic tape file this
P E Q F G
P E
Q’ G P’ E
Q G
Figure 7: Using Inversion to Eliminate
an Intermediate File
may be many minutes for one reel. Second, it makes each successive record of G
available with the least possible delay: each G record is produced as soon as P has
consumed the relevant records of E.
Inversion also has an important effect on the program designer’s view of a
sequential program. The ‘main program’ P, the subroutine ‘P inverted with respect
to F’, and the subroutine ‘P inverted with respect to E’, are seen as identical at the
design level. This is a large economy of design effort. The JSP-COBOL
preprocessor that supports JSP design for COBOL programs allows the three to
differ only in their declarations of the files E and F.
The effect on the program designer’s view goes deeper than an economy of design
effort. An important example of the distinction between design and implementation
is clarified; procedures with internal state can be designed as if they were main
programs processing sequential message streams; and JSP design becomes
applicable to all kinds of interactive systems, and even to interrupt handlers.
Program inversion also suggests the modelling of real-world entities by objects with
time-ordered behaviours: this is the basis of the eventual enlargement of JSP to
handle system specification and design [Jackson 83].
A PERSPECTIVE VIEW OF JSP
Although JSP was originally rooted in mainframe data processing, it has been
applied effectively in many environments. For applying JSP, the necessary problem
characteristic is the presence of at least one external sequential data stream, to
provide a given data structure that can and should be used as the basis of the
program structure. Many programs have this characteristic. For example:
· a program that processes a stream of EDI messages;
· an automobile cruise control program that responds to the changing car state
and the driver’s actions;
· a program that justifies text for printing;
· a file handler that responds to invoked operations on the file;
· a program that generates HTML pages from database queries.
JSP was developed in the commercial world, often in ignorance of work elsewhere.
Some of the JSP ideas were reinventions of what was already known, while others
anticipated later research results. The JSP relationship between data structures and
program structure is essentially the relationship exploited in parsing by recursive
descent [Aho 77]. Some of the early detailed JSP discussion of recognition
difficulties dealt with aspects that were well known to researchers in formal
languages and parsing techniques. The idea of program inversion is closely related
to the Simula [Dahl 70] concept of semi-coroutines, and, of course, to the later Unix
notion of pipes as a flexible implementation of sequential files. There was also one
related program design approach in commercial use: the Warnier method [Warnier
74] based program structure on the regular data structure of one file. Program
decomposition into sequential processes communicating by a coroutine-like
mechanism was discussed in a famous early paper [Conway 63]; it was also the basis
of a little-known development method called Flow-Based Programming [Morrison
94].
The central virtues of JSP are two. First, it provides a strongly systematic and
prescriptive method for a clearly defined class of problem. Essentially, independent
JSP designers working on the same problem produce the same solution. Second,
JSP keeps the program designer firmly in the world of static structures to the
greatest extent possible. Only in the last step of the backtracking technique, when
dealing with side-effects, is the JSP designer encouraged to consider the dynamic
behaviour of the program. This restriction to designing in terms of static structures
is a decisive contribution to program correctness for those problems to which JSP
can be applied. It avoids the dynamic thinking ¾ the mental stepping through the
program execution ¾ that has always proved so seductive and so fruitful a source
of error.
ACKNOWLEDGEMENTS
The foundations of JSP were laid in the years , when the author was
working with Barry Dwyer, a colleague in John Hoskyns and Company, an English
data processing consultancy. Many of the underlying ideas can be traced back to
Barry Dwyer’s contributions in those years. Refining the techniques, and making
JSP more systematic and more teachable in commercial courses, was the work of the
following four years in Michael Jackson Systems Limited.
The JSP-COBOL preprocessor was designed by the author and Brian Boulter, a
colleague in Michael Jackson Systems Limited. Brian Boulter was responsible for
most of the implementation.
Many other people contributed to JSP over the years ¾ John Cameron, Tony
Debling, Leif Ingevaldsson, Ashley McNeile, Hans Naegeli, Dick Nelson (who
introduced the name ‘JSP’), Bo Sanden, Peter Savage, Mike Slavin and many
others. A partial bibliography of JSP can be found in [Jackson 94].
Daniel Jackson read a draft of this paper and made several improvements.
REFERENCES
[Achelis 59] Elisabeth Achelis; The Calendar for the Modern Age; Thomas Nelson and Sons,
1959.
[Aho 77] A V Aho and J D Ullman; Principles of Compiler Design; Addison-Wesley, 1977.
[Barnett 68] Barnett and Constantine eds; Modular Programming: Proceedings of a National
Symposium; Information & Systems Press, 1968.
[Blackman 98] K R Blackman; IMS celebrates thirty years as an IBM product; IBM Systems
Journal, Volume 37 Number 4, 1998.
[Conway 63] Melvin E. Conway; Design of a Separable Transition-Diagram Compiler;
Communications of the ACM Volume 6 Number 7, 1963.
[Dahl 70] O-J Dahl, B Myhrhaug and K Nygaard; SIMULA-67 Common Base Language.
Technical Report Number S-22, Norwegian Computer Centre, Oslo, 1970.
[Jackson 75] M A Jackson; Principles of Program Design; Academic Press, 1975.
[Jackson 83] M A Jackson; System Development; Prentice-Hall International, 1983.
[Jackson 94] Michael Jackson; Jackson Development Methods: JSP and JSD; in Encyclopaedia of
Software Engineering, John J Marciniak ed, Volume I; John Wiley and Sons, 1994.
[Morrison 94] J Paul Morrison; Flow-Based Programming: A New Approach to Application
Development; Van Nostrand Reinhold, 1994.
[Myers 76] Glenford J. Myers; Software Reliability: Principles & Practices; Wiley, 1976.
[Stevens 74] W P Stevens, G J Myers, and L L Constantine; Structured Design; IBM Systems
Journal Volume 13 Number 2, 1974.
[Warnier 74] Jean-Dominique Warnier; Logical Construction of Programs; H E Stenfert
Kroese, 1974, and Van Nostrand Reinhold, 1976.
Michael Jackson
11th April 2001


