Бертран Мейер - Основы объектно-ориентированного программирования
[x]. Контрпример: Препроцессоры. Общепринятым способом расширения языка программирования, а иногда и преодоления его недостатков, является использование "препроцессора", принимающего входные данные в расширенном синтаксисе и отображающего их в стандартной для этого языка форме. Типичные препроцессоры для Fortran'а и C поддерживают графические примитивы, расширенные управляющие структуры или операции над базами данных. Однако обычно такие расширения не являются взаимно совместимыми; что не позволяет сочетать два таких препроцессора, и приходится выбирать между, например, графикой или базой данных.
Композиция не зависит от декомпозиции. Фактически эти критерии часто противоречат друг другу. Например, метод нисходящего проектирования, удовлетворяющий, как уже было показано, критерию декомпозиции, обычно приводит к созданию таких модулей, которые нелегко сочетать с модулями, полученными из других источников. При такой декомпозиции модули обычно тесно связаны с теми специфическими требованиями, которые привели к их разработке, и не могут быть приспособлены к использованию в других условиях. Метод нисходящего проектирования не дает рекомендаций по разработке модулей, удовлетворяющих общим требованиям. В нем нет средств такой разработки, он не позволяет ни избежать, ни хотя бы обнаружить программную избыточность модулей, получаемых в различных частях иерархии.
Как композиция, так и декомпозиция являются частью требований к модульному методу проектирования. Неизбежна смесь двух подходов к проектированию: сверху-вниз и снизу-вверх. На этот принцип дополнительности обратил внимание Рене Декарт почти четыре столетия тому назад, как видно из сопоставления двух правил его Рассуждений, приведенных в эпиграфе этой лекции.
Модульная Понятность
Метод удовлетворяет критерию Модульной Понятности, если он помогает получить такую программу, читая которую можно понять содержание каждого модуля, не зная текста остальных, или, в худшем случае, ознакомившись лишь с некоторыми из них.
Важность этого критерия следует из его влияния на процесс сопровождения программного продукта. Почти все действия по сопровождению программы, как неизбежные, так и не столь неизбежные, связаны с глубоким пониманием ее элементов. Метод едва ли может называться модульным, если тот, кто читает программный текст, не в состоянии понять его смысл.
Рис. 3.4. Понятность
Этот критерий, подобно четырем остальным, применим к модулям при описании системы на любом уровне: анализа, проектирования, реализации.
[x]. Контрпример: последовательные зависимости. Предположим, что некоторые модули спроектированы таким образом, что они будут правильно функционировать лишь при их запуске в определенном заранее предписанном порядке. Например, B может работать надлежащим образом лишь при запуске его после A и перед C, возможно потому, что эти модули предназначены для использования в "конвейере" Unix, упоминавшемся ранее: A | B | C. В таком случае, по-видимому, трудно понять как работает B, не понимая работу A и C.
В последующих лекциях критерий модульной понятности поможет при рассмотрении двух важных вопросов: как документировать многократно используемые компоненты и как их индексировать, чтобы разработчики программного продукта могли без труда обращаться к ним путем соответствующего запроса. В соответствии с этим критерием информация о компоненте, полезная для документирования или поиска, должна, насколько это возможно, содержаться в тексте самого компонента, тогда средства документирования, индексации или поиска смогут обработать этот компонент и получить требуемую информацию.
Наличие нужной информации в каждом компоненте предпочтительнее хранения ее где-либо в другом месте, например в базе данных для хранения информации о компонентах.
Модульная Непрерывность
Метод удовлетворяет критерию Модульной Непрерывности, если незначительное изменение спецификаций разработанной системы приведет к изменению одного или небольшого числа модулей.
Этот критерий непосредственно связан с критерием расширяемости. Как подчеркивалось в предыдущей лекции, внесение изменений является неотъемлемой частью процесса разработки программного продукта. Соответствующие требования к программе будут неминуемо изменяться в ходе разработки. Непрерывность означает, что небольшие изменения будут воздействовать только на отдельные модули в структуре системы, а не на всю систему.
Термин "непрерывность" предлагается по аналогии с понятием непрерывной функции в математическом анализе. Математическая функция является непрерывной, если (неформально) малое изменение аргумента приводит к пропорционально малому изменению результата. В нашем случае роль функции играет метод конструирования программного продукта, который может рассматриваться как механизм, получающий на входе спецификации и возвращающий в качестве результата систему, удовлетворяющую заданным требованиям:
Метод_конструирования_ПО: Спецификации -> Система
Рис. 3.5. Непрерывность
Этот математический термин введен здесь лишь по аналогии, поскольку не существует формального понятия размера спецификации и программы. Можно было бы ввести приемлемую меру для определения "небольших" или "больших" изменений программы, но дать подобное определение для спецификаций к программе это уже настоящая проблема. Однако если не претендовать на строгость, то такое интуитивно понятное определение будет соответствовать необходимому требованию к любому модульному методу.
[x]. Пример 1:именованные константы3.2). Разумный стиль не допускает в программе констант, заданных литералами. Вместо этого следует пользоваться именованными константами, значения которых даются в их определениях (constant в языках Pascal или Ada, макрокоманды препроцессоров в языке C, PARAMETER в языке Fortran 77, атрибуты констант в обозначениях этого курса). Если значение изменяется, то следует лишь внести единственное изменение в определение константы. Это простое, но важное правило является разумной мерой обеспечения непрерывности, потому что значения констант, несмотря на их название, довольно часто могут изменяться.
[x]. Пример 2: принцип Унифицированного Доступа. Еще одно правило требует единой нотации при вызове свойств объекта независимо от того, представляют они обычные или вычислимые поля данных.
[x]. Контрпример 1: использование физического представления информации. Метод, в котором разрабатываемые программы согласуются с физической реализацией данных, будет приводить к конструкциям, весьма чувствительным к незначительным изменениям окружения.
[x]. Контрпример 2: статические массивы. Такие языки, как Fortran или стандартный Pascal, в которых не допускаются динамические массивы, границы которых становятся известными лишь во время выполнения программы, существенно усложняют развитие системы.
Модульная Защищенность
Метод удовлетворяет критерию Модульной Защищенности, если он приводит к архитектуре системы, в которой аварийная ситуация, возникшая во время выполнения модуля, ограничится только этим модулем, или, в худшем случае, распространится лишь на несколько соседних модулей.
Вопрос об отказах и ошибках является основным в программной инженерии. Сейчас речь идет об ошибках периода исполнения программы, связанных с аппаратными прерываниями, ошибочными входными данными или исчерпанием необходимых ресурсов (например, из-за недостаточного объема памяти). Критерий защищенности направлен не на предотвращение или исправление ошибок, а на проблему, непосредственно связанную с модульностью - распространением ошибок в модульной системе.
Рис. 3.6. Нарушение защищенности
[x]. Пример: проверка достоверности входных данных в источнике. Метод, требующий от каждого модуля, вводящего данные, проверку их достоверности, пригоден для реализации модульной защищенности.3.3)
[x]. Контрпример: недисциплинированные (undisciplined) исключения. (Об обработке исключений см. лекцию 12) Такие языки как PL/I, CLU, Ada, C++ и Java поддерживают понятие исключения (exception). Исключение это ситуация, при которой программа не может нормально выполняться. Исключение "возбуждается" ("raised") некоторой командой модуля, и в результате операционной системе посылается специальный сигнал. Обработчик исключения (exception handler) может находиться в одном или нескольких модулях, расположенных в, возможно, удаленной части системы. Детали этого механизма отличаются в разных языках программирования; Ada или CLU являются более строгими в этом отношении, чем PL/I. Такие средства контроля ошибок позволяют отделить алгоритмы для обычных случаев от алгоритмов обработки ошибок. Но ими следует пользоваться осторожно, чтобы не нарушить модульную защищенность. В лекции 12, посвященной исключениям, рассматривается проектирование дисциплинированного (disciplined) механизма исключений, удовлетворяющего критерию защищенности.