Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 840
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
678
ЧАСТЬ
VI Системные вопросы
Вы улучшите отношения с заказчиком Если частая интеграция влияет на мо- ральное состояние разработчиков, то она также оказывает влияние и на моральное состояние заказчика. Клиенты любят видеть признаки прогресса, а инкрементная интеграция предоставляет им такую возможность достаточно часто.
Системные модули тестируются гораздо полнее Интеграция начинается на ранних стадиях проекта. Вы интегрируете каждый класс по мере его готов- ности, а не ожидая одного внушительного мероприятия по интеграции в конце разработки. Программист тестирует классы в обоих случаях, но в качестве эле- мента общей системы они используются гораздо чаще при инкрементной, чем при поэтапной интеграции.
Вы можете создать систему за более короткое время Если интеграция тщательно спланирована, вы можете проектировать одну часть системы в то время, когда другая часть уже кодируется. Это не уменьшает общее число человеко-часов, требуемых для полного проектирования и кодирования, но позволяет выполнять часть работ параллельно, что является преимуществом в тех случаях, когда время имеет критическое значение.
Инкрементная интеграция поддерживает и поощряет другие инкрементные стра- тегии. Преимущества инкрементного подхода в отношении интеграции — лишь верхушка айсберга.
29.3. Стратегии инкрементной интеграции
При поэтапной интеграции вам не нужно планировать порядок создания ком- понентов проекта. Все компоненты интегрируются одновременно, поэтому вы можете разрабатывать их в любом порядке — главное, чтобы они все были готовы к часу Х.
При инкрементной интеграции вы должны планировать более аккуратно. Боль- шинство систем требует интеграции некоторых компонентов перед интеграцией других. Так что планирование интеграции влияет на планирование конструиро- вания — порядок, в котором конструируются компоненты, должен обеспечивать порядок, в котором они будут интегрироваться.
Стратегии, определяющие порядок интеграции, различны по форме и размерам, и ни одна из них не будет являться оптимальной во всех случаях. Наилучший подход к интеграции меняется от проекта к проекту, и лучшим решением будет то, что будет соответствовать конкретным требованиям конкретного проекта.
Знание делений на методологической шкале поможет вам представить варианты возможных решений.
Нисходящая интеграция
При нисходящей интеграции класс на вершине иерархии пишется и интегрируется первым. Вершина иерархии — это главное окно, управляющий цикл приложения, объект, содержащий метод
main() в программе на Java, функция WinMain() в про- граммировании для Microsoft Windows или аналогичные. Для работы этого верхнего класса пишутся заглушки. Затем, по мере интеграции классов сверху вниз, классы заглушек заменяются реальными (рис. 29-5).
ГЛАВА
29 Интеграция
679
Рис. 29-5. При нисходящей интеграции вы создаете те классы, которые находятся
на вершине иерархии, первыми, а те, что внизу, — последними
При нисходящей интеграции интерфейсы между классами нужно задать очень тща- тельно. Самые трудные для отладки не те ошибки, что влияют на отдельные классы, а те, что проявляются из-за незаметного взаимодействия между классами. Аккуратное определение интерфейсов может уменьшить проблему. Спецификация интерфейсов не относится к интеграции, однако проверка того, что интерфейсы были спроекти- рованы правильно, является ее задачей.
В дополнение к преимуществам, получаемым от любого типа инкрементной инте- грации, нисходящая интеграция позволяет относительно просто протестировать управляющую логику программы. Все классы на вершине иерархии выполняются большое количество раз, поэтому концептуальные проблемы и ошибки проекти- рования проявляются достаточно быстро.
Другое преимущество нисходящей интеграции в том, что при тщательном плани- ровании вы получите работающую систему на ранних стадиях проекта. Если часть, реализующая пользовательский интерфейс, находится на вершине иерархии, вы можете быстро получить работающую базовую версию интерфейса, а деталями заняться потом. Моральное состояние пользователей и программистов выиграет от раннего получения работающей версии.
Нисходящая инкрементная интеграция также позволяет начать кодирование до того, как все детали низкоуровневого проектирования завершены. Когда все раз- делы проекта проработаны достаточно подробно, можно начинать реализовывать и интегрировать все классы, стоящие на более высоких уровнях иерархии, не ожидая, когда будут расставлены все точки над «i».
Несмотря на все эти преимущества, чистый вариант нисходящей интеграции ча- сто содержит недостатки, с которыми вы вряд ли захотите мириться. Нисходящая интеграция в чистом виде оставляет на потом работу со сложными системными интерфейсами. Если они содержат много дефектов или имеют проблемы с произ- водительностью, вы, вероятно, хотели бы получить их задолго до конца проекта. Не так редко встречается ситуация, когда низкоуровневая проблема всплывает на самый верх системы, что приводит к высокоуровневым изменениям и снижает выгоду от раннего начала работы по интеграции. Эту проблему можно минимизировать посредством тщательного и раннего тестирования во время разработки и анализа производительности классов, из которых состоят системные интерфейсы.
680
ЧАСТЬ
VI Системные вопросы
Другая проблема чистой нисходящей интеграции в том, что при этом нужен це- лый самосвал заглушек. Масса классов более низкого уровня еще не разработана, а значит, на промежуточных этапах интеграции потребуются заглушки. Их проблема в том, что, как тестовый код, они с большей вероятностью содержат ошибки, чем тщательно спроектированный промышленный код. Ошибки в новых заглушках, используемых в новом классе, сводят на нет цель инкрементной интеграции, со- стоящую в том, чтобы ограничить источник ошибок одним новым классом.
Кроме того, нисходящую интеграцию практически невоз- можно реализовать в чистом виде. Если подходить к ней буквально, то надо начинать с вершины (назовем ее Уровнем
1), а затем интегрировать все классы следующего уровня
(Уровня 2). Когда вы полностью интегрируете классы Уров- ня 2, и не раньше, вы начинаете интегрировать классы Уров- ня 3. Жесткость чистой нисходящей интеграции абсолютно деспотична. Сложно представить кого-нибудь, кто стал бы использовать нисходящую интеграцию в чистом виде. Большинство применяет гибридный подход, такой как интеграция сверху вниз с разбиением на разделы.
И, наконец, вы не можете использовать нисходящую интеграцию, если набор клас- сов не имеет вершины. Во многих интерактивных системах понятие «вершины» субъективно. В одних системах вершиной является пользовательский интерфейс, в других — функция
main().
Хорошей альтернативой нисходящей интеграции в чистом виде может стать под- ход с вертикальным секционированием (рис. 29-6). При этом систему реализуют сверху вниз по частям, возможно, по очереди выделяя функциональные области и переходя от одной к другой.
Рис. 29-6. В качестве альтернативы строгого продвижения сверху вниз можно
выполнять интеграцию сверху вниз, разбив систему на вертикальные слои
Хотя чистая нисходящая интеграция и неработоспособна, представление о ней поможет вам выбрать основной подход. Некоторые преимущества и недостатки, свойственные нисходящему подходу в чистом виде, точно так же, но менее очевидно относятся и к более свободным нисходящим методикам, таким как интеграция с вертикальным секционированием, так что их следует иметь в виду.
Перекрестная ссылка Нисходя- щая интеграция не имеет ниче- го общего, кроме названия, с нисходящим проектированием
(о нем см. подраздел «Нисхо- дящий и восходящий подходы к проектированию» раздела 5.4).
ГЛАВА
29 Интеграция
681
Восходящая интеграция
При восходящей интеграции вы пишете и интегрируете сначала классы, находя- щиеся в низу иерархии. Добавление низкоуровневых классов по одному, а не всех одновременно — вот что делает восходящую интеграцию инкрементной стратегией.
Сначала вы пишете тестовые драйверы для выполнения низкоуровневых классов, а затем добавляете эти классы к тестовым драйверам, пристраивая их по мере готовности. Добавляя класс более высокого уровня, вы заменяете классы драйве- ров реальными. На рис. 29-7 показан порядок, в котором происходит интеграция классов при восходящем подходе.
Рис. 29-7. При восходящей интеграции классы, находящиеся в низу иерархии, объе-
диняются в начале, а находящиеся на вершине иерархии — в конце
Восходящая интеграция дает некоторые преимущества, свойственные инкремент- ной стратегии. Она ограничивает возможные источники ошибок единственным классом, добавляемым в данный момент, поэтому ошибки легко обнаружить. Ин- теграция начинается на ранних стадиях проекта. Кроме того, при этом довольно рано реализуются потенциально ненадежные системные интерфейсы. Поскольку системные ограничения часто определяют саму возможность достижения целей, поставленных перед проектом, то применение данного подхода может быть оправданным для того, чтобы убедиться, что система удовлетворяет всему набору требований.
Главная проблема восходящей интеграции в том, что она оставляет напоследок объединение основных, высокоуровневых интерфейсов системы. Если на верхних уровнях существуют концептуальные проблемы проектирования, конструирование не выявит их, пока все детали не будут реализованы. Если проект понадобится значительно изменить, часть низкоуровневой разработки может оказаться не- нужной.
Восходящая интеграция требует, чтобы проектирование всей системы было завершено до начала интеграции. Иначе в код низкого уровня могут глубоко внедриться неверные предпосылки проектирования, что поставит вас в затруд- нительное положение, в котором вам придется проектировать высокоуровневые классы так, чтобы обойти проблемы в низкоуровневых классах. А позволив низ- коуровневым деталям реализации управлять дизайном высокоуровневых классов, вы нарушите принципы сокрытия информации и объектно-ориентированного проектирования. Проблемы интеграции классов более высокого уровня покажутся каплей в море по сравнению с проблемами, которые вы получите, если не за-
682
ЧАСТЬ
VI Системные вопросы вершите проектирование высокоуровневых классов до начала низкоуровневого кодирования.
Как и нисходящую, восходящую интеграцию в чистом виде используют редко — вместо нее можно применять гибридный подход, реализующий секционную интеграцию (рис. 29-8).
Рис. 29-8. В качестве альтернативы продвижения исключительно снизу вверх
вы можете выполнять восходящую интеграцию посекционно. Это размывает разли-
чие между восходящей и функционально-ориентированной интеграцией,
о которой речь пойдет ниже
Сэндвич-интеграция
Проблемы с нисходящей и восходящей интеграциями в чистом виде привели к тому, что некоторые эксперты стали рекомендовать сэндвич-подход (Myers, 1976).
Сначала вы объединяете высокоуровневые классы бизнес-объектов на вершине иерархии. Затем добавляете классы, взаимодействующие с аппаратной частью, и широко используемые вспомогательные классы в низу иерархии. Эти высоко- и низкоуровневые классы — хлеб для сэндвича.
Напоследок вы оставляете классы среднего уровня — мясо, сыр и помидоры для сэндвича. Если вы вегетарианец, они могут представлять собой тофу и проро- щенные бобы, хотя автор сэндвич-интеграции ничего не сообщает на этот счет
— возможно, он не мог говорить с набитым ртом (рис. 29-9).
Рис. 29-9. При сэндвич-интеграции вы сначала объединяете верхний уровень
и широко используемые классы нижнего уровня, оставив средний уровень напоследок
ГЛАВА
29 Интеграция
683
Этот подход позволяет избежать жесткости чистых нисходящих и восходящих вариантов интеграции. Сначала интегрируются классы, вызывающие наибольшее беспокойство, при этом потенциально снижается количество лесов, которые могут вам понадобиться. Это реалистичный и практичный подход. Следующий вариант аналогичен данному, но в нем делается акцент на другом.
Риск-ориентированная интеграция
Риск-ориентированную интеграцию, которую также называют «интеграцией, начиная с самых сложных частей» (hard part first integration), похожа на сэндвич-интеграцию тем, что пытается избежать проблем, присущих нисходящей или восходящей ин- теграциям в чистом виде. Кроме того, в ней также есть тенденция к объединению классов верхнего и нижнего уровней в первую очередь, оставляя классы среднего уровня напоследок. Однако суть в другом.
При риск-ориентированной интеграции (рис. 29-10) вы определяете степень риска, связанную с каждым классом. Вы решаете, какие части системы будут са- мыми трудными, и реализуете их первыми. Опыт показывает, что это относится к интерфейсам верхнего уровня, поэтому они часто присутствуют в начале списка рисков. Системные интерфейсы, обычно расположенные внизу, тоже представляют опасность, поэтому они также находятся в числе первых в этом списке рисков.
Кроме того, вам может быть известно, что какие-то классы в середине иерархии могут также создавать трудности. Возможно, это класс, реализующий сложный для понимания алгоритм или к которому предъявляются повышенные требования по производительности. Такие классы тоже могут быть обозначены как имеющие повышенный риск, и их интеграция должна происходить на ранних стадиях.
Оставшаяся несложная часть кода может подождать. Какие-то из этих классов позже могут оказаться сложнее, чем вы предполагали, но это неизбежно.
Рис. 29-10. При риск-ориентированной интеграции вы начинаете работу с тех
классов, которые, по вашему мнению, могут доставить наибольшие трудности, бо-
лее простые классы вы реализуете позже
684
ЧАСТЬ
VI Системные вопросы
Функционально-ориентированная интеграция
Еще один поход — интеграция одной функции в каждый момент времени. Под
«функцией» понимается не нечто расплывчатое, а какое-нибудь поддающееся определению свойство системы, в которой выполняется интеграция. Если вы пишете текстовый процессор, то функцией может считаться отображение подчеркиваний, или автоматическое форматирование документа, или еще что-либо подобное.
Когда интегрируемая функция превышает по размерам отдельный класс, то «еди- ница приращения» инкрементной интеграции становится больше отдельного класса. Это немного снижает преимущество инкрементного подхода в том плане, что уменьшает вашу уверенность об источнике новых ошибок. Однако если вы тщательно тестировали классы, реализующие эту функцию, перед интеграцией, то это лишь небольшой недостаток. Вы можете использовать стратегии инкрементной интеграции рекурсивно, сформировав сначала из небольших кусков отдельные свойства, а затем инкрементно объединив их в систему.
Обычно процесс начинается с формирования скелета, поскольку он способен поддерживать остальную функциональность. В интерактивной системе такой изначальной опцией может стать система интерактивного меню. Вы можете прикреплять остальную функциональность к той опции, которую интегрировали первой (рис. 29-11).
Рис. 29-11. При функционально-ориентированной интеграции вы работаете
с группами классов, представляющими собой отдельные функции, поддающиеся
определению, которые часто, но не всегда состоят из нескольких классов
Компоненты добавляются в «дерево функциональности» — иерархический набор классов, реализующих отдельную функцию. Интеграцию выполнять легче, если функции относительно независимы. Например, классы, относящиеся к разной функциональности, могут вызывать один и тот же код низкоуровневых библиотек, но не использовать общий код среднего уровня. (Общие низкоуровневые классы не показаны на рис. 29-11.)
Функционально-ориентированная интеграция имеет три основных преимущества.
Во-первых, она позволяет обойтись без лесов практически везде, кроме низкоуров- невых библиотечных классов, Какие-то заглушки могут понадобиться скелету, или же некоторые его части просто могут быть неработоспособными, пока не будут
ГЛАВА
29 Интеграция
685
добавлена конкретная функциональность. Однако когда каждая функция будет до- бавлена к скелету, дополнительных лесов не потребуется. Поскольку вся функции изолированы друг от друга, в них содержится весь необходимый ей код.
Второе главное преимущество состоит в том, что каждая новая функция расширяет функциональность. Это наглядно показывает, что проект постоянно продвигается вперед. Кроме того, создается работоспособное ПО, которое можно представить заказчикам для оценки или сдать в эксплуатацию раньше намеченного срока, реализовав меньшую функциональность, чем планировалось изначально.
Третье преимущество в том, что функционально-ориентированная интеграция хорошо сочетается с объектно-ориентированным проектированием. Как пра- вило, можно легко провести соответствие между объектами и функциями, что делает функционально-ориентированную интеграцию естественным выбором для объектно-ориентированных систем.
Придерживаться чистой функционально-ориентированной интеграции так же сложно, как и нисходящей и восходящей интеграций в чистом виде. Обычно часть низкоуровневого кода должна быть реализована прежде, чем можно будет добавлять какую-либо функциональность.
Т-образная интеграция
Последний подход, который часто упоминается в связи с проблемами нисходя- щей и восходящей методик, называется «Т-образной интеграцией». При таком подходе выбирается некоторый вертикальный слой, который разрабатывается и интегрируется раньше других. Этот слой должен проходить сквозь всю систему от начала до конца и позволять выявлять основные проблемы в допущениях, сде- ланных при проектировании системы. Реализовав этот вертикальный участок (и устранив все связанные с этим проблемы), можно разрабатывать основную канву системы (например, системное меню для настольного приложения). Этот подход часто комбинируют с риск-ориентированной и функционально-ориентированной интеграциями (рис. 29-12).
Рис. 29-12. При Т-образной интеграции вы создаете и интегрируете вертикаль-
ный срез системы, чтобы проверить архитектурные допущения.
После этого вы создаете и интегрируете основную горизонталь системы,
чтобы предоставить каркас для разработки остальной функциональности