Книга: Искусство программирования для Unix

4.3.1. Сравнение нисходящего и восходящего программирования

4.3.1. Сравнение нисходящего и восходящего программирования

Первое направление является восходящим от конкретной проблемы к абстракции и представляет собой разработку исходя из специфических операций в предметной области, которые необходимо реализовать. Например, если разрабатывается программно-аппаратное обеспечение для дискового накопителя, то некоторыми низкоуровневыми примитивами могут быть "подвод головки к физическому блоку", "чтение физического блока", "запись в физический блок", "переключение светодиодного индикатора диска" и другие.

Другое направление является нисходящим, от абстракции к конкретным функциям, от высокоуровневых спецификаций, описывающих проект в целом или логику приложения, вниз к отдельным операциям. Следовательно, при проектировании программного обеспечения для контроллера запоминающего устройства большой емкости, который может оперировать несколькими различными типами носителей, можно начинать с определения абстрактных операций, таких как "поиск логического блока", "чтение логического блока", "запись логического блока", "переключение индикатора активности". Данные операции отличались бы от операций аппаратного уровня с подобными названиями тем, что они были бы предназначены в качестве общих для различных видов физических устройств.

Два описанных выше примера могут представлять два конструкторских подхода для одного и того же семейства аппаратного обеспечения. Выбор решения в подобных ситуациях представляется таким: абстрагировать аппаратное обеспечение (так чтобы объекты инкапсулировали реальные элементы внешнего мира, а программа просто представляла собой список операций по их обработке), либо организовать программу на основе некоторой поведенческой модели (а затем внедрить действительные аппаратные манипуляции, которые осуществляют эту модель в потоке поведенческой логики).

Аналогичный выбор обнаруживается во многих других ситуациях. Предположим, что разрабатывается программа цифрового музыкального синтезатора (MIDI sequencer). Организовывать такой код можно вокруг его верхнего уровня (упорядочение дорожек) или вокруг нижнего уровня (переключение фрагментов или выборок и управление генераторами колебаний).

Существует весьма точный способ анализа данных различий. Он заключается в том, чтобы выяснить, организована ли конструкция вокруг ее главного событийного цикла (стремящегося к тесной связи с высокоуровневой логикой приложения) или вокруг служебной библиотеки всех операций, которые может вызывать главный цикл. Проектировщик, разрабатывающий программу сверху вниз, начнет с обдумывания ее основного событийного цикла, а специфические события внедрит позднее. Проектировщик, работающий снизу вверх, начнет с обдумывания инкапсуляции специфических задач, а позднее соединит их в некую логическую последовательность.

В качестве более крупного примера рассмотрим разработку Web-браузера. Верхним уровнем конструкции Web-браузера является спецификация ожидаемого поведения данной программы: какие типы URL-адресов (http: или ftp:, или file:) она интерпретирует, какие виды изображений она способна визуализировать, допускает ли она использование языков Java или JavaScript и с какими ограничениями и т.д. Уровень реализации, который соответствует данному верхнему уровню, является основным событийным циклом программы. В каждой итерации цикл ожидает, накапливает и координирует действия пользователя (такие как нажатие Web-ссылки или ввод символа в поле).

Однако Web-браузер для решения поставленных перед ним задач вынужден вызывать большой набор основных примитивов. Одна группа примитивов занята установкой сетевых соединений, отправкой данных по ним, а также получением ответов. В другую группу входят операции GUI-инструментария, который используется браузером. Третья группа может быть занята механическим преобразованием полученных HTML-документов из текстовой формы в объектное дерево документа.

Важно то, с какой стороны этого набора начинается разработка, поскольку уровень на противоположной стороне, весьма вероятно, будет ограничен первоначальным выбором. В частности, если программа разрабатывается исключительно сверху вниз, то программист может оказаться в неудобном положении, когда примитивы предметной области, которые необходимы логике приложения, не совпадают с теми, которые фактически можно реализовать. С другой стороны, если программа разрабатывается исключительно снизу вверх, то может оказаться, что приходится проделывать большой объем работы, не соответствующей логике приложения, или просто проектируется "штабель кирпичей", в то время как необходимо "построить здание".

С момента возникновения полемики по поводу структурного программирования в 60-х годах, начинающих программистов, как правило, учат, что верный подход заключается в проектировании сверху вниз. То есть в поэтапном усовершенствовании, где на абстрактном уровне определяется, для чего предназначена данная программа, и постепенном заполнении пустот реализации до тех пор, пока не будет образован точно работающий код. Нисходящий подход становится хорошей практикой, когда выполняются три предусловия: (а) можно с большой точностью определить, для решения каких задач предназначена данная программа, (b) значительные изменения спецификации в ходе реализации маловероятны и (с) большая свобода выбора на низком уровне, каким образом программа будет выполнять свои функции.

Данные условия наиболее часто выполняются в программах, расположенных сравнительно близко к пользователю и высоко в стеке программ, т.е. в прикладном программировании. Однако даже в этом случае указанные предусловия часто не выполняются. Невозможно заранее определить "правильный" режим работы текстового редактора или графической программы до тех пор, пока пользовательский интерфейс не пройдет тестирование среди конечных пользователей. Исключительно нисходящее программирование часто характеризуется чрезмерным вложением трудозатрат в код, который придется удалить за ненадобностью и перестроить, поскольку интерфейс не проходит проверку в реальных условиях.

Для того чтобы обезопасить себя, программисты пытаются использовать оба подхода — описывают абстрактную спецификацию как нисходящую логику приложения и собирают множество низкоуровневых примитивов предметной области в функциях или библиотеках с тем, чтобы в случае изменения высокоуровневой конструкции их можно было использовать повторно.

Unix-программисты наследуют традицию, которая является центральной в системном программировании, где низкоуровневыми примитивами являются операции аппаратного уровня, которые имеют постоянный характер и чрезвычайно важны. Следовательно, "благодаря приобретенному инстинкту" они более склонны к восходящему программированию.

Независимо от того является программист системным или нет, восходящий подход также может выглядеть более привлекательно при программировании исследовательским способом, когда пытаются получить контроль над феноменами аппаратного или программного обеспечения или реальными феноменами, которые еще не полностью понятны. Восходящее программирование предоставляет время и пространство для уточнения нечеткой спецификации. Кроме того, восходящее программирование "апеллирует к естественной человеческой лени" программистов: когда требуется удалить часть кода и перестроить его, при работе в нисходящем направлении приходится удалять более крупные фрагменты, чем при работе в восходящем направлении.

Таким образом, создание реального кода склоняется к использованию как нисходящего, так и восходящего подходов. Нередко нисходящий и восходящий код является частью одного и того же проекта. В таком случае возникает необходимость использования связующих уровней.

Оглавление книги


Генерация: 1.225. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз