По мере все более интенсивного использования компонентной технологии при разработке программных приложений, становится очевидной необходимость пересмотра стратегии сопровождения ПО. Данная статья дает обзор основных проблем, связанных с сопровождением компонентного программного обеспечения.
Считающиеся сегодня общепризнанными принципы и процессы сопровождения и поддержки программного обеспечения вырабатывались в свое время в предположении, что при разработке систем применяются "строительные блоки" - подпрограммы или процедуры, чей исходный код доступен. Типичными составляющими процесса сопровождения (как правило, предполагающим модификацию кода) считались "анализ влияний" (impact analysis), который позволяет установить и отследить, как различные части системы взаимодействуют между собой; и регрессионное тестирование, которое предназначено для проверки, корректно ли работает модернизированная система с тестовыми примерами, разработанными для более ранних версий системы. При этом, проведение одного только анализа влияний часто было достаточно для вывода о том, оказывают ли воздействие произведенные в одной процедуре изменения на другую процедуру, и соответственно, есть ли смысл эту другую процедуру тестировать. Такой подход, гарантировавший тестирование только модернизированных и зависимых от них процедур, позволял поддерживать целостность системы без дополнительных издержек. Однако, эта традиционная методология, полагающаяся на доступность и видимость исходного кода, оказывается недостаточной для эффективного сопровождения систем, разработанных с использованием компонентной технологии.
Уже вполне очевидно, что компонентная технология, сочетающаяся с объектно-ориентированным проектированием, приведет к радикальному изменению принципов построения и сопровождения систем. ПО, построенное на базе "componentware", может быть коммерческим "коробочным" (commercial off-the-shelf - COTS), свободно-копируемым ("public domain"), свободно-распространяемым ("freeware" и "shareware") и, наконец, типа "copyleft". Когда все эти компоненты инкорпорируются в систему, ее сопровождение, подразумевающее неизбежную эволюцию становится серьезной проблемой - прежде всего по той простой причине, что исходный код частично или полностью недоступен. Например, большинство приложений, построенных для Windows NT, будут так или иначе использовать библиотеку Microsoft Foundation Classes (MFC). Фактически это означает, что при создании такого приложения, приходится как бы работать в одной команде с сотнями разработчиков из Microsoft. Однако, когда вы ответственны за сопровождение приложения, обнаруживается, что в команде, кроме вас, никого нет!
Итак, очевидно, что технология сопровождения компонентных приложений должна весьма отличаться от традиционной, причем это касается всех так или иначе вовлеченных в общий процесс сторон. Так, если вы - поставщик компонентов, то необходимо думать о сопровождении блока исходного кода, используемого во многих разных приложениях ваших клиентов, а не только в одном специфическом приложении. Так как каждое приложение может в процессе своей эволюции выдвигать свои требования к задействованным компонентам, то становится затруднительным так модифицировать компоненты, чтобы это удовлетворило всех.
Если вы - интегратор компонентов, то нужно мыслить в терминах технологий, работающих на сопровождение систем как единого целого. Если компоненты - это "черные ящики", то их видимость ограничена документацией, описывающей функциональность и условия работы. Ясно, что сопровождение систем, собранных из "черных ящиков", приводит к проблемам качественно иного уровня, даже по сравнению с известными трудностями поддержки "чужого" кода.
Современная тенденция все более широкого использования ОО языков и технологий компонентной программной инженерии приводит к тому, что разработка ПО становится все менее творческим занятием, приобретая зато все больше черт традиционного производства. Соответственно, главным занятием разработчиков становится не столько кодирование, сколько проектирование и интеграция. Что касается принципов производства, то компонентные технологии, очевидно, уменьшат значимость ориентированных на разработку моделей зрелости, зато потребуют моделей, ориентированных на выстраивание надежных процессов приобретения компонентов. В конечном итоге, будет резко сокращено время доставки продукта на рынок, и потребители получат возможность большего выбора за меньшую цену. Однако, пожать в полной мере плоды основанного на компонентных технологиях прорыва будет возможно лишь при условии надлежащего решения проблем сопровождения ПО.
Коммерческое ("COTS") ПО
Сопровождение систем, в которых присутствуют "COTS" - компоненты, может стать истинным кошмаром по следующим основным причинам:
- замороженная функциональность;
- модернизация компонентов (добаленная функциональность или исправленная ошибка в компоненте: и то, и другое, возможно, улучшая компонент, как отдельную единицу, может привести к возникновению его несовместимости с другими в рамках всей системы);
- "Троянский конь";
- ПО типа "COTS", содержащее дефекты или оказавшееся ненадежным;
- сложное или имеющее дефекты промежуточное ПО, такое как "оберточное" ПО (wrappers), выступающее в качестве посредника (broker) между COTS-компонентом и ПО, построенным специально для данного приложения.
Замороженная функциональность
С данной проблемой приходится иметь дело в случае, если поставщик компонентов либо прекратил эту деятельность вообще, либо перестал развивать данные компоненты. Приложения с "замороженными" компонентами могут стать несопровождаемыми, поскольку их модернизация делается затруднительной. Если условия функционирования приложения таковы, что компоненты требуют периодической модернизации (например в случае такого компонента, как парсер, когда изменяется язык, с которым он работает), то возникают серьезные проблемы, не имеющие простого решения [1]. Впрочем, у разработчика может быть следующий выбор:
- попытаться реализовать необходимую функциональность самому;
- приобрести компонент с нужной функциональностью у другого поставщика;
- приобрести у поставщика компонента исходный код и далее поддерживать его самостоятельно.
Однако, если вы не эксперт в той предметной области, с которой связан компонент, то реализовать первую из перечисленных альтернатив будет сложно. Вторая возможность сработает лишь при условии, что найдется другой поставщик, способный поставить компонент с необходимой функциональностью. Если же такового нет, а написать компонент "с нуля" нереалистично, то останется только приобретать исходный код. Чтобы не иметь проблем с правами на этот код, необходимо заключить соответствующее лицензионное соглашение. Однако и при этом, если имеющийся персонал не способен поддерживать чужой код, то результат может быть печальным. Останется только нанять в качестве консультантов специалистов, которые когда-то этот код писали. При этом могут весьма увеличиться издержки.
Несовместимость модернизированных компонентов
Поставщики модернизируют и сопровождают свои компоненты, следуя прежде всего запросам наиболее важных для них клиентов, как нынешних, так и потенциальных. Если же вы почему-либо не попали в это привилегированное сословие, а необходимые именно вам изменения или исправления не совпадают с тем, чего требуют другие потребители, то в один прекрасный день вы можете обнаружить, что модернизированный компонент несовместим с оставшейся частью системы.
В сущности, проблема несовместимости сходна с проблемой "замороженной функциональности". Перед вами встанет все тот же вопрос: отказаться от предлагаемого компонента и создавать свой, или искать на рынке другого поставщика? Если предположить, что окажется нереально и создать компонент собственного производства, и приобрести его где-либо еще, то у вас останется еще одна возможность решить проблему - построить промежуточную "обертку" (wrapper), в которую можно так "завернуть" компонент, что проявления его несовместимости не будут через эту обертку видны. Однако, что при этом останется от компонента? Поэтому одно лишь такое "укутывание" компонента может оказаться неэффективным; может быть необходимым и переписывание того слоя ПО, который "склеивает" модернизированные компоненты с оставшейся частью системы.
Если же ни "обертывание", ни написание "склеивающего" слоя не решают проблем, возникших после модернизации компонента, то останется выполнение его "де-модернизации" (downgrading) - процедуры, которую иногда называют деинсталляцией (uninstalling). Деинсталляция может быть трудновыполнимым процессом, так как не всегда легко "обратить" все шаги, выполненные при модернизации. Тем не менее, подобно деинсталляции операционной системы или приложения, деинсталляции компонентов, видимо, суждено стать процессом, часто используемым при сопровождении ПО. Очевидно, будут необходимы средства автоматической деинсталляции компонентов. Хотя, 100% автоматизации этого процесса достичь вряд ли удастся, и кое-что придется делать "вручную".
Интересно, что необходимость в обратной модернизации могла бы и исчезнуть, если бы мы были способны определять заранее совместимость модернизированных компонентов. Это нелегкая проблема, однако уже есть несколько подходов к сертификации совместимости COTS-компонентов [2].
Опасения появления несовместимости после модернизации компонентов возникли не на пустом месте. За исключением редких случаев, исправления ошибок в компоненте без компрометации всего кода почти всегда требует замены компонента с проверенной и протестированной функциональностью на компонент, чье надлежащее поведение еще должно быть доказано. Какова же мораль? Очевидно, следует всегда быть готовым к де-инсталляции.
"Троянские кони"
Таким термином принято в мире ПО обозначать компоненты, которые были умышленно запрограммированы так, что в какой-то момент начинает проявляться их "злокачественное" поведение. Типичным примером такого рода поведения может быть, например, внезапное переключение из режима, в котором компонент уничтожал файлы из определенного каталога, на уничтожение системных файлов.
Попытка локализовать Троянского коня статически - путем чтения кода, либо пропусканием его через парсер, может не привести к успеху. За скобками остается контекст, в котором может проявиться зловредная сущность кода: ведь наиболее эффективный способ замаскировать ненадлежащее поведение - это сделать его чувствительным к динамически изменяющемуся контексту. Например, команда "уничтожить все файлы" может иметь катастрофические последствия именно в том случае, если она изменяет свой контекст на системный каталог и удаляет системные файлы. Если же имя каталога, к которой приложимо действие по удалению файла, устанавливается во время исполнения, то методы статической идентификации окажутся мало полезными.
Когда происходит замена нового компонента на место работающего, то избежать его возможного нехорошего поведения становится проблемой: это сложно даже при наличии исходного кода, а без такового - практически невозможно. Некоторые гарантии безопасности могут обеспечить известные и обладающие надлежащей репутацией производители, но и здесь можно нарваться на мину. Например, в 1994 г. компания Adobe выпустила Photoshop 3.0, забыв удалить "time bomb", которая автоматически приводила к завершению работы программы. Как выяснилось, это был рудимент стадии бета-тестирования, который просто забыли убрать [3].
Обнаружение ненадлежащего поведения компонента требует отслеживания всех его запросов к операционной системе с проверкой контекста каждого запроса. Опять же можно при этом использовать программу-"обертку", но полной гарантии она не обеспечит - процесс в типичном случае включает многочисленные вызовы и проверки контекста, и "обертка" с большой вероятностью отфильтрует те запросы, которые как раз допустимы, в то же время разрешив запросы, которые следовало бы запретить.
Ненадежные компоненты
Проблемы несовместимости модернизированных компонентов и ненадежных "коробочных" компонентов различны, но взаимосвязаны. Сегодня не существует общепринятого стандарта, в соответствии с которым компоненты тестируются и сертифицируются на надежность. Имеются подходы к оценке и измерению характеристик процесса разработки, такие как Модель Зрелости ПО (Capability Maturity Model - CMM), ISO и другие. Однако, хороший процесс еще вовсе не гарантирует хорошего ПО [4] (и даже если принять, что такие гарантии есть, COTS-производители вполне могут исказить сведения о зрелости этих процессов).
Что касается моделей надежности ПО, то их за прошедшие годы было предложено немало [5], однако, они обычно опираются на носящие слишком общий характер предположения о среде, в которой предстоит функционировать системе, не учитывая той конкретики, которая может свести на нет обеспечиваемые этой моделью результаты. Например, предоставленные поставщиком сведения, касающиеся взаимозависимости компонентов, могут оказаться некорректными применительно к вашей вычислительной среде.
Итак, если в процессе сопровождения потребуется замена компонентов, то нужно иметь универсальный стандарт для оценки их взаимозависимости. Такой подход должен обеспечивать достаточно информации для того, чтобы можно было учесть специфику среды функционирования системы. Сейчас мы имеем стандартные способы оценки качества транзисторов и платим за них в соответствии с такой оценкой. Почему же с программными компонентами дело обстоит по-иному? Знание о воздействии компонента на поведение системы до его инкорпорирования в нее, несомненно, сделало бы сопровождение компонентных систем процессом, менее похожим на игру в рулетку.
"Оберточное" ПО
Промежуточное ПО - это общепринятый сегодня термин для обозначения программного обеспечения, которое связывает два отдельных программных пакета, выполняет "посреднические" функции в их взаимодействии между собой, либо позволяет получать новое качество от их совместного функционирования. Промежуточное ПО призвано отслеживать процесс взаимодействия компонентов и (если необходимо) его модифицировать. Всякий раз, когда нет полной информации относительно корректности взаимодействия компонента с другими частями системы, следует встраивать промежуточное ПО, способное гарантированно обеспечить нужное поведение.
"Оберточное" ПО (wrappers) - это вид промежуточного ПО, ограничивающее функциональность компонента. По-существу, оно играет роль посредника, который отбирает из всего набора результатов на выходе компонента те, что могут быть надлежащим образом восприняты другими компонентами, использующими эти результаты. Оберточная технология зародилась при создании систем, где была необходимость выделения критических с точки зрения надежности и безопасности функций. Оберточное ПО обычно ограничивает входную или выходную информацию, что изменяет функциональность компонента.
Проблема здесь одна: как узнать, от какого поведения следует защищаться? Если нет полной информации о компоненте, то трудно найти защиту от такого поведения. Производитель может помочь с такой информацией, но наилучший подход - это всестороннее тестирование компонента в системной среде. Именно комбинируя информацию от производителя с внутрифирменным тестированием, возможно создать более надежное оберточное ПО.
Оберточное ПО - это наиболее приемлемый способ разрешения проблем, связанных с несовместимостью, Троянскими конями и взаимозависимостью. Однако, оно само по себе может быть сложным, неполным и ненадежным, к тому же не обеспечивающим полной "защиты от дурака", особенно, если не совсем ясно, от какого поведения надо защищаться.
Некоммерческое ПО
Это ПО, производимое "третьей стороной", включает все разновидности, перечисленные во введении. Свободно-распространяемое ПО типа "shareware" обычно можно бесплатно скачать по Сети. Примером может служить архиватор WinZip производства Nico Mak Computing. При этом действует неявное соглашение: если продукт понравился и вы собираетесь продолжать его использование, то требуется послать производителю некоторую номинальную плату. Если вы не регистрируетесь и не платите, то по истечении некоторого установленного срока теряете право использования продукта.
Как и в случае с коммерческим "COTS" ПО, некоторые производители являются очень надежными и обеспечивают строгий контроль целостности ПО. Однако, сам характер распространения продукта таков, что не всегда ясен источник shareware-компонента. Например, в 1995 г. в Internet широко циркулировала "бета-версия" утилиты для сжатия информации в DOS PKZIP 3.0. Как многие убедились на собственном опыте, эта программа затирала их жесткий диск. К тому же исходный код обычно не распространяется вместе с продуктом, что приводит к тем же проблемам при сопровождении, что уже были рассмотрены в связи с COTS-компонентами.
То же самое можно сказать и о другой форме свободного распространения ПО - "freeware", которая подобна "shareware", за исключением того, что здесь не требуется вносить плату. Такого рода продукты обычно распространяются только в исполняемом формате. Примером может служить Dunce - "номеронабиратель" для Win95.
Свободно-копируемое ("public domain") ПО бесплатно распространяется в формате, включающем исходный код без ограничений на его использование; соответственно, он может быть модифицирован, если на то есть желание. Например, Sun Microsystems использовала BSD UNIX (public domain) для создания своих корпоративных операционных систем. Сопровождение такого ПО или использование его в своем продукте приводит к уже рассмотренным проблемам сопровождения кода, когда производитель его более не поддерживает.
"Copyleft" - это проект GNU, имеющий в виду то же свободно-копируемое ПО, но в несколько иной форме. Типичное уведомление о Сopyright лишает любого индивида права на изменение или распространение ПО; "copyleft" же, наоборот, является лицензионной схемой, которая устанавливает, что "любой, кто распространяет ПО, с изменениями или без, не имеет права ограничивать дальнейшую свободу копирования и изменения продукта". Чтобы эта схема действительно функционировала, GNU ставит знак Copyright на ПО и затем добавляет легитимные условия распространения, дающие каждому права на использование, модифицирование и распространение программного кода или любой программы, полученной с использованием этого кода, с оговоркой, что условия распространения останутся неизменными. Это приводит к тому, что сам код и свобода делать с ним все, что угодно, с юридической точки зрения неразделимы. В терминах сопровождения, "copyleft" подвержен тем же проблемам, что ассоциированы с сопровождением чужого исходного кода.
Возможность использования ПО третьей стороны сильно зависит от лицензионных ограничений. Если ПО поставляется в исполняемом формате, то возникают все присущие COTS-компонентам проблемы. Если обеспечивается исходный код, появляются вопросы, связанные с наличием экспертов в определенной предметной области.
Корпоративные хранилища компонентов
Корпоративными ("In-house") называют компоненты, которые хранятся в фирменном репозитории (библиотеке). Сопровождение требуется для исходного кода корпоративных компонентов двух видов: используемых в одной системе; хранящихся в библиотеке для повторного использования. Эти компоненты могут влиять на многие разрабатывающиеся в данной организации системы.
Архитектура репозитория
Процесс сопровождения всех систем, инкорпорирующих компоненты из репозитория, может весьма существенным образом зависеть от его архитектуры. Функционально структурированные репозитории вызывают больше проблем при сопровождении, так как они организуются в зависимости от типа информации. Как результат, приложения ассоциируются со всем массивом информации в репозитории, а не просто с информацией, относящейся к отдельным компонентам. Например, предположим, что мы имеем три компонента X, Y и Z, каждый из которых содержит следующие типы информации: анализ, проектирование, исходный код и тест. Тогда функционально структурированный репозиторий будет организован следующим образом:
исходный код: X | Y | Z анализ: X | Y | Z проектирование: X | Y | Z тест: X | Y | Z
Если в процессе сопровождения понадобилось модернизировать какой-либо компонент, то извлечь его из репозитория не так-то просто - необходимо найти и извлечь информацию, связанную с исходным кодом, анализом, проектированием и тестом для всех компонентов, а затем выделить из этих данных информацию о необходимом компоненте. В целом, здесь требуется избыточное количество памяти, и весь этот подход к архитектуре выглядит не слишком практичным.
Напротив, структура репозитория на основе классов информации организуется следующим образом:
X: анализ | проектирование |исходный код | тест Y: анализ | проектирование |исходный код | тест Z: анализ | проектирование |исходный код | тест
Такая архитектура позволяет легко извлекать отдельные компоненты и весь процесс их сопровождения становится менее обременительным.
Проблемы сопровождения
Сопровождение корпоративной библиотеки - непростая задача: вы должны гарантировать, что модификации, которым неизбежно будут подвергаться компоненты, не нарушат их совместимости в рамках всех приложений, в которые эти компоненты инкорпорированы. Повторное использование компонентов во многих приложениях также накладывает определенные ограничения: эти компоненты должны соответствовать общим требованиям набора спецификаций - в противном случае, повторное использование в разнообразном контексте станет невозможным. Возможным побочным, и нежелательным, эффектом такого рода ограничений может стать потеря некоторыми компонентами какой-либо специфики; компоненты же, носящие слишком общий характер, как правило, имеют невысокую эффективность, а потому их использование особого смысла не имеет.
Чтобы удовлетворить требованиям корпоративного повторного использования, большинство программных репозиториев используют целый ряд правил доступа. Адаптируя общепринятые в логистике правила модификации компонентов, программные репозитории способны обеспечить лучшую организацию работы по разработке ПО и его сопровождению. Однако, создание ПО в соответствии с жесткими правилами доступа может до некоторой степени подавлять творческую активность разработчика, который должен получать из библиотеки исходный код перед его модификацией и тестировать внесенные изменения перед реинтеграцией кода обратно в библиотеку. К тому же, когда разработчик получит необходимые ему компоненты, они сразу же блокируются для использования кем-либо еще - до тех пор, пока разработчик не положит их на место. При этом сопровождение других приложений, где эти компоненты также могут быть задействованы, оказывается затруднительным.
Если же обходиться без блокировки файлов, то возникают проблемы иного рода. Так, разработчик может весьма существенно изменить компонент, а его коллеги могут сделать то же самое; интеграция же всех изменений в единое работающее целое может стать просто кошмаром. Очевидно, необходима специальная дисциплина "управления модификациями" (revision control), без которой процесс сопровождения компонентов не может выполняться корректно.
Другой подход к сопровождению повторно-используемых компонентов известен под названием "продвижение" (promotion). В данном случае архитектура репозитория имеет три уровня, ориентированных на все приложения: разработка/сопровождение, тестирование и выпуск
Разработчик получает исходный код компонентов с уровня "разработка/сопровождение", делает необходимые изменения и затем помещает модернизированный код в свой персональный репозиторий. Только после того, как разработчик выполнил тестирование и убедился в корректной работе измененного компонента, он "продвигает" его обратно на уровень "разработка /сопровождение", где специально назначенный менеджер интегрирует эти изменения и продвигает на уровень "тестирование". В это же самое время, разработчики еще могут вновь получать код с уровня "разработка/сопровождение" и выполнять дополнительные модификации. После того, как будет выполнено тестирование приложений с измененными компонентами, менеджер продвигает программный продукт на уровень "выпуск", где он становится доступным для общего пользования.
Надлежащая архитектура репозитория и управление модификациями компонентов жизненно важны для успешного сопровождения программных систем, построенных из корпоративных компонентов. Также необходимы: всестороннее тестирование, подробная документация и трассируемость относительно спецификации компонента.
Заключение
Разработка ПО по компонентной технологии основана на принципе "разделяй и властвуй" - требования, которым должна отвечать большая система, распределяются по небольшим подсистемам, интегрируемым в одно целое. Успех же в конечном итоге зависит от качества используемых строительных блоков - программных компонентов, разработанных "на стороне". Мы видели, что необходим существенный пересмотр и модификация традиционных процессов по сопровождению ПО.
Пока же можно следовать следующим советам:
- если вы строите небольшую систему, то лучше не использовать компоненты;
- всегда имейте детальную документацию на компоненты и избегайте соблазна бесконечного добавления к ним каких-то незначительных и необязательных особенностей;
- используйте структуру репозитория на основе "классов информации" и "продвижения".
Если вы имеете несколько приложений, использующих один и тот же компонент, то имейте в виду ситуацию, когда одно из приложений может потребовать такой модификации компонента, которая нарушит целостность другого приложения. Поэтому, храните в репозитории несколько аналогичных компонентов: лучше иметь два компонента и два работающих приложения, чем один компонент и только одно приложение, которое работает.
Хочется надеяться, что в будущем сопровождение ПО будет сводиться к простой замене одного компонента на другой. Однако, как мы это видели на примере катастрофы при запуске спутника Ариан 5, даже повторное использование компонентов в схожей среде может не дать гарантий безопасности, особенно когда всестороннее тестирование не является частью рутинного процесса сопровождения.
Использование компонентной технологии - это путь к ускорению производства информационных систем. Однако, по сравнению с их предшественниками, компонентные системы труднее сертифицировать и убеждаться в корректности их работы. Если же этим вопросам не уделять надлежащего внимания, то сопровождение компонентных систем станет затруднительным, особенно имея в виду, что унаследованные системы требуют частого регрессионного тестирования. Самолеты, автомобили и строительные постройки функционируют в течение многих лет, подвергаясь простой модификации, связанной с заменой их составных частей. Думается, и программная индустрия придет к такой технологии.
Об авторе
Джеффри Воас (Jeffrey Voas) - один из учредителей компании Reliable Software Technologies (Sterling, VA, США; http://www.rstcorp.com ). Основанная в 1991 году, компания является ведущим производителем инновационных методов и инструментов для разработки и сопровождения программных приложений, требующих высокой надежности. Известный специалист в области безопасности, тактики информационных войн, сертификации и тестирования ПО, программных метрик и мобильного компьютинга, Д-р Воас опубликовал 125 статей и является соавтором двух книг: "Software Assesment: Reliability, Safety, Testability" (John Wiley&Sons, 1995) и "Software Fault Injection: Inoculating Programs Against Errors" (John Wiley&Sons, 1998). Д.Воас является также Исполнительным секретарем IEEE Reliability Society, членом Совета Директоров Центра National Software Studies, членом редколлегий нескольких журналов. В 1997 г. влиятельный Journal of Systems and Software назвал его шестым в списке пятнадцати ведущих исследователей в области Systems and Software Engineering.
Литература
- J.Voas, "COTS Software: The Economical Choice?", //IEEE Software, Mar. 1998, p. 16-19.
- J.Voas, "Certifying Off-the-Shelf Software Components", - Computer, June 1998, p. 53-59
- S.Garfunkel and G.Spafford, eds., "Practical Unix and Internet Security", Second Edition, - O'Reilly and Associates, Sebastopol, Calif., 1996.
- J.Voas, "Can Clean Pipes Produce Dirty Water", //IEEE Software, July 1997, p. 93-95
- C.V.Ramamoorthy and F.B.Bastani, "Software Reliability - Status and Perspectives", - IEEE Trans. Software Eng., July 1982, p. 543-571