Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по
Скачать 5.88 Mb.
|
ЧАСТЬ VI Системные вопросы IEEE Std 1045%1992, Standard for Software Productivity Metrics. IEEE Std 1062%1998, Recommended Practice for Software Acquisition. IEEE Std 1540%2001, Standard for Software Life Cycle Processes — Risk Management. IEEE Std 828%1998, Standard for Software Configuration Management Plans IEEE Std 1490%1998, Guide — Adoption of PMI Standard — A Guide to the Project Management Body of Knowledge. Ключевые моменты Хорошая практика кодирования может быть достигнута путем внедрения стан# дартов или более ловкими способами. Управление конфигурацией при правильном применении делает работу про# граммистов проще. Это особенно касается контроля изменений. Правильная оценка ПО — сложная задача. Ключ к успеху — использование нескольких подходов, уточнение оценок по мере продвижения проекта и при# менение накопленных данных при выполнении оценки. Измерения — это ключ к успешному управлению конструированием. Вы все# гда сможете найти способы измерить любой аспект проекта, что будет лучше, чем полное отсутствие измерений. Точное измерение — ключ к точному со# ставлению плана, контролю качества и улучшению процесса разработки. Программисты и менеджеры — в первую очередь люди, и они лучше всего работают тогда, когда к ним относятся по#человечески. ГЛАВА 28 Управлениена конструированием 673 Г Л А В А 2 9 Интеграция Содержание 29.1. Важность выбора подхода к интеграции 29.2. Частота интеграции — поэтапная или инкремент- ная? 29.3. Стратегии инкрементной интеграции 29.4. Ежедневная сборка и дымовые тесты Связанные темы Тестирование во время разработки: глава 22 Отладка: глава 23 Управление конструированием: глава 28 Термин «интеграция» относится к такой операции в процессе разработки ПО, при которой вы объединяете отдельные программные компоненты в единую си- стему. В небольших проектах интеграция может занять одно утро и заключаться в объединении горстки классов. В больших — могут потребоваться недели или месяцы, чтобы связать воедино весь набор программ. Независимо от размера за- дач в них применяются одни и те же принципы. Тема интеграции тесно переплетается с вопросом последовательности конструи- рования. Порядок, в котором вы создаете классы или компоненты, влияет на по- рядок их интеграции: вы не можете интегрировать то, что еще не было создано. Последовательности интеграции и конструирования имеют большое значение. В этой главе мы рассмотрим оба вопроса с точки зрения интеграции. 29.1. Важность выбора подхода к интеграции В инженерных отраслях, отличных от разработки ПО, важность правильной ин- теграции хорошо известна. На северо#западном побережье Тихого океана, где я живу, столкнулись с драматическими последствиями плохой интеграции, когда футбольный стадион Университета Вашингтон частично обрушился во время строительства (рис. 29#1). http://cc2e.com/2985 CC2_Part6_ch29_2010.indd 673 22.06.2010 11:58:58 674 ЧАСТЬ VI Системные вопросы Рис. 29'1. Навес над футбольным стадионом Вашингтонского университета об- рушился, не выдержав собственного веса во время строительства. Он скорее всего был бы достаточно прочным после завершения работ, но строительство велось в неправильном порядке — налицо ошибка интеграции Не имеет значения, что после завершения строительства стадион был бы доста- точно прочным — он должен был быть таким и на других этапах работы. Если вы создаете и интегрируете ПО в неправильном порядке, его сложнее кодировать, сложнее тестировать и сложнее отлаживать. Если ничего не работает до того, как заработает все вместе, можно предположить, что проект никогда не будет завер- шен. Он также может рухнуть под собственным весом во время конструирования: количество дефектов может оказаться непреодолимым, прогресс — невидимым, а сложность — чрезмерной, даже если законченный продукт был бы вполне ра- ботоспособен. Поскольку интеграция выполняется после того, как разработчик завершил свое тестирование, и одновременно с системным тестированием, ее иногда считают операцией, относящейся к тестированию. Однако она достаточно сложна, и поэтому ее следует рассматривать как независимый вид деятельности. Аккуратная интеграция обеспечивает: упрощенную диагностику дефектов; меньшее число ошибок; меньшее количество «лесов»; раннее создание первой работающей версии продукта; уменьшение общего времени разработки; лучшие отношения с заказчиком; улучшение морального климата; увеличение шансов завершения проекта; более надежные оценки графика проекта; более аккуратные отчеты о состоянии; CC2_Part6_ch29_2010.indd 674 22.06.2010 11:59:44 ГЛАВА 29 Интеграция 675 лучшее качество кода; меньшее количество документации. Интеграцию часто недооценивают, несмотря на ее важность, и именно поэтому я посвятил ей отдельную главу. 29.2. Частота интеграции — поэтапная или инкрементная? Интеграция программ выполняется посредством поэтапного или инкрементного подхода. Поэтапная интеграция За исключением последних нескольких лет поэтапная интеграция была нормой. Она состояла из хорошо определенных этапов, перечисленных ниже. 1. «Модульная разработка»: проектирование, кодирование, тестирование и отладка каждого класса. 2. «Системная интеграция»: объединение классов в одну огромную систему. 3. «Системная дезинтеграция» [спасибо Мейлиру Пейдж#Джонсу (Meilir Page#Jones) за это остроумное замечание]: тестирование и отладка всей системы. Проблема поэтапной интеграции в том, что, когда классы в системе впервые соединяются вместе, неизбежно возникают новые проблемы и их причины могут быть в чем угодно. Поскольку у вас масса классов, которые никогда раньше не ра- ботали вместе, виновником может быть плохо протестированный класс, ошибка в интерфейсе между двумя классами или ошибка, вызванная взаимодействием двух классов. Все классы находятся под подозрением. Неопределенность местонахождения любой из проблем сочетается с тем фактом, что все эти проблемы вдруг проявляют себя одновременно. Это заставляет вас иметь дело не только с проблемами, вызванными взаимодействием классов, но и другими ошибками, которые трудно диагностировать, так как они взаимодей- ствуют. Поэтому поэтапную интеграцию называют еще «интеграцией большого взрыва» (рис. 29#2). Рис. 29'2. Поэтапную интеграцию также называют интеграцией «большого взрыва» — и заслуженно! CC2_Part6_ch29_2010.indd 675 22.06.2010 11:59:45 676 ЧАСТЬ VI Системные вопросы Поэтапную интеграцию нельзя начинать до начала последних стадий проекта, когда будут разработаны и протестированы все классы. Когда классы наконец будут объединены и проявится большое число ошибок, программисты тут же ударятся в паническую отладку вместо методического определения и исправления ошибок. Для небольших программ — нет, а для крошечных — поэтапная интеграция может быть наилучшим подходом. Если программа состоит из двух#трех классов, поэтапная интеграция может сэкономить ваше время, если вам повезет. Но в большинстве случаев другой подход будет лучше. Инкрементная интеграция При инкрементной интеграции вы пишете и тестируете маленькие участки программы, а затем комбинируете эти кусочки друг с другом по одному. При таком подходе — по одному элементу за раз — вы выполняете перечисленные далее действия. 1. Разрабатываете небольшую, функциональную часть си- стемы. Это может быть наименьшая функциональная часть, самая сложная часть, основная часть или их комбинация. Тщательно тестируете и отлаживаете ее. Она послужит скелетом, на котором будут наращиваться мускулы, нервы и кожа, составляющие остальные части системы. 2. Проектируете, кодируете, тестируете и отлаживаете класс. 3. Прикрепляете новый класс к скелету. Тестируете и отлаживаете соединение скелета и нового класса. Убеждаетесь, что эта комбинация работает, прежде чем переходить к добавлению нового класса. Если дело сделано, повторяете процесс, начиная с п. 2. У вас может возникнуть желание интегрировать большие модули, чем отдельный класс. Например, если компонент был тщательно протестирован и каждый из его классов прошел мини#интеграцию, вы можете интегрировать весь компонент, и это все еще будет инкрементная интеграция. По мере того, как вы добавляете новые куски, система разрастается и ускоряется, как разрастается и ускоряется снежный ком, катящийся с горы (рис. 29#3). Рис. 29'3. Инкрементная интеграция дает проекту движущую силу и напоминает снежный ком, катящийся с горы Перекрестная ссылка О метафо- рах, подходящих для инкремент- ной интеграции, см. подразделы «Метафора жемчужины: мед- ленное приращение системы» и «Строительная метафора: по- строение ПО» раздела 2.3. CC2_Part6_ch29_2010.indd 676 22.06.2010 11:59:46 ГЛАВА 29 Интеграция 677 Преимущества инкрементной интеграции Инкрементный подход имеет массу преимуществ перед традиционным поэтапным подходом независимо от того, какую инкрементную стратегию вы используете: Ошибки можно легко обнаружить Когда во время инкрементной интеграции возникает новая проблема, то очевидно, что к этому прича# стен новый класс. Либо его интерфейс с остальной частью программы неправилен, либо его взаимодействие с ранее интегрированными классами при- водит к ошибке. В любом случае вы точно знаете, где искать проблему (рис. 29#4). Более того, поскольку вы сталкиваетесь с меньшим числом проблем одновременно, вы уменьшаете риск того, что несколько ошибок будут взаимодействовать или маскировать друг друга. Чем больше интерфейсных ошибок может возникнуть, тем больше преимуществ от инкрементной интеграции получат ваши проекты. Учет ошибок в одном проекте показал, что 39% составляли ошибки межмодульных интерфейсов (Basili и Perricone, 1984). Поскольку разработчики многих проектов проводят до 50% времени за отладкой, максимизация эффективности отладки пу- тем упрощения поиска ошибок дает выигрыш в качестве и производительности. Рис. 29'4. При поэтапной интеграции вы объединяете так много компонентов одновременно, что тяжело понять, где находится ошибка. Она может быть в любом компоненте или их соединениях. При инкрементной интеграции ошибка обычно таится либо в новом компоненте, либо в месте соединения нового компонен- та и остальной системы В таком проекте система раньше становится работоспособной Когда код интегрирован и способен выполняться, даже если система еще не пригодна к использованию, это выглядит так, будто это скоро произойдет. При инкремент- ной интеграции программисты раньше видят результаты своей работы, поэтому их моральное состояние лучше, чем в том случае, когда они подозревают, что их проект может никогда не сделать первый вдох. Вы получаете улучшенный мониторинг состояния При частой интеграции реализованная и нереализованная функциональность видна с первого взгляда. Менеджеры будут иметь лучшее представление о состоянии проекта, видя, что 50% системы уже работает, а не слыша, что кодирование «завершено на 99%». CC2_Part6_ch29_2010.indd 677 22.06.2010 11:59:46 678 ЧАСТЬ VI Системные вопросы Вы улучшите отношения с заказчиком Если частая интеграция влияет на мо- ральное состояние разработчиков, то она также оказывает влияние и на моральное состояние заказчика. Клиенты любят видеть признаки прогресса, а инкрементная интеграция предоставляет им такую возможность достаточно часто. Системные модули тестируются гораздо полнее Интеграция начинается на ранних стадиях проекта. Вы интегрируете каждый класс по мере его готов- ности, а не ожидая одного внушительного мероприятия по интеграции в конце разработки. Программист тестирует классы в обоих случаях, но в качестве эле- мента общей системы они используются гораздо чаще при инкрементной, чем при поэтапной интеграции. Вы можете создать систему за более короткое время Если интеграция тщательно спланирована, вы можете проектировать одну часть системы в то время, когда другая часть уже кодируется. Это не уменьшает общее число человеко#часов, требуемых для полного проектирования и кодирования, но позволяет выполнять часть работ параллельно, что является преимуществом в тех случаях, когда время имеет критическое значение. Инкрементная интеграция поддерживает и поощряет другие инкрементные стра- тегии. Преимущества инкрементного подхода в отношении интеграции — лишь верхушка айсберга. 29.3. Стратегии инкрементной интеграции При поэтапной интеграции вам не нужно планировать порядок создания ком- понентов проекта. Все компоненты интегрируются одновременно, поэтому вы можете разрабатывать их в любом порядке — главное, чтобы они все были готовы к часу Х. При инкрементной интеграции вы должны планировать более аккуратно. Боль- шинство систем требует интеграции некоторых компонентов перед интеграцией других. Так что планирование интеграции влияет на планирование конструиро- вания — порядок, в котором конструируются компоненты, должен обеспечивать порядок, в котором они будут интегрироваться. Стратегии, определяющие порядок интеграции, различны по форме и размерам, и ни одна из них не будет являться оптимальной во всех случаях. Наилучший подход к интеграции меняется от проекта к проекту, и лучшим решением будет то, что будет соответствовать конкретным требованиям конкретного проекта. Знание делений на методологической шкале поможет вам представить варианты возможных решений. Нисходящая интеграция При нисходящей интеграции класс на вершине иерархии пишется и интегрируется первым. Вершина иерархии — это главное окно, управляющий цикл приложения, объект, содержащий метод main() в программе на Java, функция WinMain() в про- граммировании для Microsoft Windows или аналогичные. Для работы этого верхнего класса пишутся заглушки. Затем, по мере интеграции классов сверху вниз, классы заглушек заменяются реальными (рис. 29#5). CC2_Part6_ch29_2010.indd 678 22.06.2010 11:59:51 ГЛАВА 29 Интеграция 679 Рис. 29'5. При нисходящей интеграции вы создаете те классы, которые находятся на вершине иерархии, первыми, а те, что внизу, — последними При нисходящей интеграции интерфейсы между классами нужно задать очень тща- тельно. Самые трудные для отладки не те ошибки, что влияют на отдельные классы, а те, что проявляются из#за незаметного взаимодействия между классами. Аккуратное определение интерфейсов может уменьшить проблему. Спецификация интерфейсов не относится к интеграции, однако проверка того, что интерфейсы были спроекти- рованы правильно, является ее задачей. В дополнение к преимуществам, получаемым от любого типа инкрементной инте- грации, нисходящая интеграция позволяет относительно просто протестировать управляющую логику программы. Все классы на вершине иерархии выполняются большое количество раз, поэтому концептуальные проблемы и ошибки проекти- рования проявляются достаточно быстро. Другое преимущество нисходящей интеграции в том, что при тщательном плани- ровании вы получите работающую систему на ранних стадиях проекта. Если часть, реализующая пользовательский интерфейс, находится на вершине иерархии, вы можете быстро получить работающую базовую версию интерфейса, а деталями заняться потом. Моральное состояние пользователей и программистов выиграет от раннего получения работающей версии. Нисходящая инкрементная интеграция также позволяет начать кодирование до того, как все детали низкоуровневого проектирования завершены. Когда все раз- делы проекта проработаны достаточно подробно, можно начинать реализовывать и интегрировать все классы, стоящие на более высоких уровнях иерархии, не ожидая, когда будут расставлены все точки над «i». Несмотря на все эти преимущества, чистый вариант нисходящей интеграции ча- сто содержит недостатки, с которыми вы вряд ли захотите мириться. Нисходящая интеграция в чистом виде оставляет на потом работу со сложными системными интерфейсами. Если они содержат много дефектов или имеют проблемы с произ- водительностью, вы, вероятно, хотели бы получить их задолго до конца проекта. Не так редко встречается ситуация, когда низкоуровневая проблема всплывает на самый верх системы, что приводит к высокоуровневым изменениям и снижает выгоду от раннего начала работы по интеграции. Эту проблему можно минимизировать посредством тщательного и раннего тестирования во время разработки и анализа производительности классов, из которых состоят системные интерфейсы. CC2_Part6_ch29_2010.indd 679 22.06.2010 11:59:51 680 ЧАСТЬ VI Системные вопросы Другая проблема чистой нисходящей интеграции в том, что при этом нужен це- лый самосвал заглушек. Масса классов более низкого уровня еще не разработана, а значит, на промежуточных этапах интеграции потребуются заглушки. Их проблема в том, что, как тестовый код, они с большей вероятностью содержат ошибки, чем тщательно спроектированный промышленный код. Ошибки в новых заглушках, используемых в новом классе, сводят на нет цель инкрементной интеграции, со- стоящую в том, чтобы ограничить источник ошибок одним новым классом. Кроме того, нисходящую интеграцию практически невоз- можно реализовать в чистом виде. Если подходить к ней буквально, то надо начинать с вершины (назовем ее Уровнем 1), а затем интегрировать все классы следующего уровня (Уровня 2). Когда вы полностью интегрируете классы Уров# ня 2, и не раньше, вы начинаете интегрировать классы Уров- ня 3. Жесткость чистой нисходящей интеграции абсолютно деспотична. Сложно представить кого#нибудь, кто стал бы использовать нисходящую интеграцию в чистом виде. Большинство применяет гибридный подход, такой как интеграция сверху вниз с разбиением на разделы. И, наконец, вы не можете использовать нисходящую интеграцию, если набор клас- сов не имеет вершины. Во многих интерактивных системах понятие «вершины» субъективно. В одних системах вершиной является пользовательский интерфейс, в других — функция main(). Хорошей альтернативой нисходящей интеграции в чистом виде может стать под- ход с вертикальным секционированием (рис. 29#6). При этом систему реализуют сверху вниз по частям, возможно, по очереди выделяя функциональные области и переходя от одной к другой. Рис. 29'6. В качестве альтернативы строгого продвижения сверху вниз можно выполнять интеграцию сверху вниз, разбив систему на вертикальные слои Хотя чистая нисходящая интеграция и неработоспособна, представление о ней поможет вам выбрать основной подход. Некоторые преимущества и недостатки, свойственные нисходящему подходу в чистом виде, точно так же, но менее очевидно относятся и к более свободным нисходящим методикам, таким как интеграция с вертикальным секционированием, так что их следует иметь в виду. Перекрестная ссылка Нисходя- щая интеграция не имеет ниче- го общего, кроме названия, с нисходящим проектированием (о нем см. подраздел «Нисхо- дящий и восходящий подходы к проектированию» раздела 5.4). CC2_Part6_ch29_2010.indd 680 22.06.2010 11:59:51 ГЛАВА 29 Интеграция 681 Восходящая интеграция При восходящей интеграции вы пишете и интегрируете сначала классы, находя- щиеся в низу иерархии. Добавление низкоуровневых классов по одному, а не всех одновременно — вот что делает восходящую интеграцию инкрементной стратегией. Сначала вы пишете тестовые драйверы для выполнения низкоуровневых классов, а затем добавляете эти классы к тестовым драйверам, пристраивая их по мере готовности. Добавляя класс более высокого уровня, вы заменяете классы драйве- ров реальными. На рис. 29#7 показан порядок, в котором происходит интеграция классов при восходящем подходе. Рис. 29'7. При восходящей интеграции классы, находящиеся в низу иерархии, объе- диняются в начале, а находящиеся на вершине иерархии — в конце Восходящая интеграция дает некоторые преимущества, свойственные инкремент- ной стратегии. Она ограничивает возможные источники ошибок единственным классом, добавляемым в данный момент, поэтому ошибки легко обнаружить. Ин- теграция начинается на ранних стадиях проекта. Кроме того, при этом довольно рано реализуются потенциально ненадежные системные интерфейсы. Поскольку системные ограничения часто определяют саму возможность достижения целей, поставленных перед проектом, то применение данного подхода может быть оправданным для того, чтобы убедиться, что система удовлетворяет всему набору требований. Главная проблема восходящей интеграции в том, что она оставляет напоследок объединение основных, высокоуровневых интерфейсов системы. Если на верхних уровнях существуют концептуальные проблемы проектирования, конструирование не выявит их, пока все детали не будут реализованы. Если проект понадобится значительно изменить, часть низкоуровневой разработки может оказаться не- нужной. Восходящая интеграция требует, чтобы проектирование всей системы было завершено до начала интеграции. Иначе в код низкого уровня могут глубоко внедриться неверные предпосылки проектирования, что поставит вас в затруд- нительное положение, в котором вам придется проектировать высокоуровневые классы так, чтобы обойти проблемы в низкоуровневых классах. А позволив низ- коуровневым деталям реализации управлять дизайном высокоуровневых классов, вы нарушите принципы сокрытия информации и объектно#ориентированного проектирования. Проблемы интеграции классов более высокого уровня покажутся каплей в море по сравнению с проблемами, которые вы получите, если не за- CC2_Part6_ch29_2010.indd 681 22.06.2010 11:59:52 682 ЧАСТЬ VI Системные вопросы вершите проектирование высокоуровневых классов до начала низкоуровневого кодирования. Как и нисходящую, восходящую интеграцию в чистом виде используют редко — вместо нее можно применять гибридный подход, реализующий секционную интеграцию (рис. 29#8). Рис. 29'8. В качестве альтернативы продвижения исключительно снизу вверх вы можете выполнять восходящую интеграцию посекционно. Это размывает разли- чие между восходящей и функционально%ориентированной интеграцией, о которой речь пойдет ниже Сэндвич-интеграция Проблемы с нисходящей и восходящей интеграциями в чистом виде привели к тому, что некоторые эксперты стали рекомендовать сэндвич#подход (Myers, 1976). Сначала вы объединяете высокоуровневые классы бизнес#объектов на вершине иерархии. Затем добавляете классы, взаимодействующие с аппаратной частью, и широко используемые вспомогательные классы в низу иерархии. Эти высоко# и низкоуровневые классы — хлеб для сэндвича. Напоследок вы оставляете классы среднего уровня — мясо, сыр и помидоры для сэндвича. Если вы вегетарианец, они могут представлять собой тофу и проро- щенные бобы, хотя автор сэндвич#интеграции ничего не сообщает на этот счет — возможно, он не мог говорить с набитым ртом (рис. 29#9). Рис. 29'9. При сэндвич%интеграции вы сначала объединяете верхний уровень и широко используемые классы нижнего уровня, оставив средний уровень напоследок CC2_Part6_ch29_2010.indd 682 22.06.2010 11:59:52 ГЛАВА 29 Интеграция 683 Этот подход позволяет избежать жесткости чистых нисходящих и восходящих вариантов интеграции. Сначала интегрируются классы, вызывающие наибольшее беспокойство, при этом потенциально снижается количество лесов, которые могут вам понадобиться. Это реалистичный и практичный подход. Следующий вариант аналогичен данному, но в нем делается акцент на другом. Риск-ориентированная интеграция Риск#ориентированную интеграцию, которую также называют «интеграцией, начиная с самых сложных частей» (hard part first integration), похожа на сэндвич#интеграцию тем, что пытается избежать проблем, присущих нисходящей или восходящей ин- теграциям в чистом виде. Кроме того, в ней также есть тенденция к объединению классов верхнего и нижнего уровней в первую очередь, оставляя классы среднего уровня напоследок. Однако суть в другом. При риск#ориентированной интеграции (рис. 29#10) вы определяете степень риска, связанную с каждым классом. Вы решаете, какие части системы будут са- мыми трудными, и реализуете их первыми. Опыт показывает, что это относится к интерфейсам верхнего уровня, поэтому они часто присутствуют в начале списка рисков. Системные интерфейсы, обычно расположенные внизу, тоже представляют опасность, поэтому они также находятся в числе первых в этом списке рисков. Кроме того, вам может быть известно, что какие#то классы в середине иерархии могут также создавать трудности. Возможно, это класс, реализующий сложный для понимания алгоритм или к которому предъявляются повышенные требования по производительности. Такие классы тоже могут быть обозначены как имеющие повышенный риск, и их интеграция должна происходить на ранних стадиях. Оставшаяся несложная часть кода может подождать. Какие#то из этих классов позже могут оказаться сложнее, чем вы предполагали, но это неизбежно. Рис. 29'10. При риск%ориентированной интеграции вы начинаете работу с тех классов, которые, по вашему мнению, могут доставить наибольшие трудности, бо- лее простые классы вы реализуете позже CC2_Part6_ch29_2010.indd 683 22.06.2010 11:59:53 684 ЧАСТЬ VI Системные вопросы Функционально-ориентированная интеграция Еще один поход — интеграция одной функции в каждый момент времени. Под «функцией» понимается не нечто расплывчатое, а какое#нибудь поддающееся определению свойство системы, в которой выполняется интеграция. Если вы пишете текстовый процессор, то функцией может считаться отображение подчеркиваний, или автоматическое форматирование документа, или еще что#либо подобное. Когда интегрируемая функция превышает по размерам отдельный класс, то «еди- ница приращения» инкрементной интеграции становится больше отдельного класса. Это немного снижает преимущество инкрементного подхода в том плане, что уменьшает вашу уверенность об источнике новых ошибок. Однако если вы тщательно тестировали классы, реализующие эту функцию, перед интеграцией, то это лишь небольшой недостаток. Вы можете использовать стратегии инкрементной интеграции рекурсивно, сформировав сначала из небольших кусков отдельные свойства, а затем инкрементно объединив их в систему. Обычно процесс начинается с формирования скелета, поскольку он способен поддерживать остальную функциональность. В интерактивной системе такой изначальной опцией может стать система интерактивного меню. Вы можете прикреплять остальную функциональность к той опции, которую интегрировали первой (рис. 29#11). Рис. 29'11. При функционально%ориентированной интеграции вы работаете с группами классов, представляющими собой отдельные функции, поддающиеся определению, которые часто, но не всегда состоят из нескольких классов Компоненты добавляются в «дерево функциональности» — иерархический набор классов, реализующих отдельную функцию. Интеграцию выполнять легче, если функции относительно независимы. Например, классы, относящиеся к разной функциональности, могут вызывать один и тот же код низкоуровневых библиотек, но не использовать общий код среднего уровня. (Общие низкоуровневые классы не показаны на рис. 29#11.) Функционально#ориентированная интеграция имеет три основных преимущества. Во#первых, она позволяет обойтись без лесов практически везде, кроме низкоуров- невых библиотечных классов, Какие#то заглушки могут понадобиться скелету, или же некоторые его части просто могут быть неработоспособными, пока не будут CC2_Part6_ch29_2010.indd 684 22.06.2010 11:59:54 ГЛАВА 29 Интеграция 685 добавлена конкретная функциональность. Однако когда каждая функция будет до- бавлена к скелету, дополнительных лесов не потребуется. Поскольку вся функции изолированы друг от друга, в них содержится весь необходимый ей код. Второе главное преимущество состоит в том, что каждая новая функция расширяет функциональность. Это наглядно показывает, что проект постоянно продвигается вперед. Кроме того, создается работоспособное ПО, которое можно представить заказчикам для оценки или сдать в эксплуатацию раньше намеченного срока, реализовав меньшую функциональность, чем планировалось изначально. Третье преимущество в том, что функционально#ориентированная интеграция хорошо сочетается с объектно#ориентированным проектированием. Как пра- вило, можно легко провести соответствие между объектами и функциями, что делает функционально#ориентированную интеграцию естественным выбором для объектно#ориентированных систем. Придерживаться чистой функционально#ориентированной интеграции так же сложно, как и нисходящей и восходящей интеграций в чистом виде. Обычно часть низкоуровневого кода должна быть реализована прежде, чем можно будет добавлять какую#либо функциональность. Т-образная интеграция Последний подход, который часто упоминается в связи с проблемами нисходя- щей и восходящей методик, называется «Т#образной интеграцией». При таком подходе выбирается некоторый вертикальный слой, который разрабатывается и интегрируется раньше других. Этот слой должен проходить сквозь всю систему от начала до конца и позволять выявлять основные проблемы в допущениях, сде- ланных при проектировании системы. Реализовав этот вертикальный участок (и устранив все связанные с этим проблемы), можно разрабатывать основную канву системы (например, системное меню для настольного приложения). Этот подход часто комбинируют с риск#ориентированной и функционально#ориентированной интеграциями (рис. 29#12). Рис. 29'12. При Т%образной интеграции вы создаете и интегрируете вертикаль- ный срез системы, чтобы проверить архитектурные допущения. После этого вы создаете и интегрируете основную горизонталь системы, чтобы предоставить каркас для разработки остальной функциональности CC2_Part6_ch29_2010.indd 685 22.06.2010 11:59:55 686 ЧАСТЬ VI Системные вопросы Краткий итог методик интеграции Восходящая, нисходящая, сэндвич, риск#ориентированная, функционально#ори- ентированная, Т#образная — не кажется ли вам, что эти названия придумываются на ходу? Так и есть. Ни один из этих подходов не является жесткой процедурой, которой вы должны методично следовать, начиная с шага 1 и заканчивая шагом 47, а затем объявить задание выполненным. Как и другие подходы к проектирова- нию ПО, они скорее эвристические, чем алгоритмические. Поэтому вместо того, чтобы безусловно следовать какой#то процедуре, вам придется разработать свою уникальную стратегию, подходящую именно вашему проекту. 29.4. Ежедневная сборка и дымовые тесты Какую бы стратегию интеграции вы и выбрали, хорошим подходом к разработке ПО является «ежедневная сборка и дымовые тесты» (daily build and smoke test). Ежедневно каждый файл компилируется, компонуется и собирается в выполняемую программу. После чего прогоняется «дымовой тест» — относительно простая проверка, определяющая, «дымится» ли продукт во время выполнения 1 Этот простой процесс дает несколько преимуществ. Он уменьшает риск низкого качества, который связан с риском неуспешной или неправильной интеграции. Проверяя «на дым» весь код ежедневно, вы не позволяете проблемам с качеством получить контроль над проектом. Вы приводите систему в понятное, правильное состояние и сохраняете ее в таком виде. Вы просто не позволяете ей ухудшаться до такой степени, когда могут возникнуть проблемы с качеством, отнимающие много времени. Этот процесс также поддерживает упрощенную диагностику ошибок. Когда про- дукт собирается и тестируется каждый день, легко засечь, почему его работа в определенный день была нарушена. Если проект работал на 17#й день, а на 18#й перестал, то нечто, происшедшее между этими двумя сборками, и нарушило ра- боту продукта. Он улучшает моральный климат. Осознание того, что продукт работает, дает по- трясающий положительный заряд. Практически не имеет значения, что он делает. Разработчики будут рады, даже если продукт будет рисовать прямоугольник на экране! При ежедневных сборках с каждым днем все большая часть продукта на- чинает работать, и это поддерживает дух на высоте. У частой интеграции есть побочный эффект: она выявляет такие проблемы, какие в противном случае могли бы незаметно накапливаться и неожиданно проявиться в конце проекта. Такое накопление скрытых результатов может стать занозой в конце проекта и потребовать для устранения недели или месяцы. Команды, не применяющие ежедневных сборок, иногда считают, что они могут снизить эф- фективность их работы до скорости улитки. На самом же деле ежедневные сборки 1 Термин «дымовой тест» появился в электронике. Разработчики на определенном этапе включают испытываемое устройство в сеть и смотрят, не задымится ли оно. — Прим. перев. Дополнительные сведения Боль- шая часть этого материала по- заимствована из главы 18 книги «Rapid Development» (Mc Con- nell, 1996). Если вы ее читали, можете переходить к разделу «Не прерывная интеграция». CC2_Part6_ch29_2010.indd 686 22.06.2010 11:59:55 ГЛАВА 29 Интеграция 687 распределяют работу в проекте более равномерно, и команда просто получает более аккуратную картину того, как быстро продвигается проект. Создавайте сборку ежедневно Основной смысл ежедневной сборки заключен в слове «ежедневная». Как говорит Джим Маккарти, рассматривайте ежедневную сборку как пульс проекта (McCarthy, 1995). Пульса нет — проект мертв. Менее метафорично Майкл Кьюсумано и Ричард Селби определяют ежедневную сборку как синхроимпульс проекта (Cusumano and Selby, 1995). Части кода могут немного рассинхронизироваться между этими импульсами, но каждый раз во время импуль- са код должен приводиться в соответствие. Когда вы настаиваете на достаточно частых импульсах, вы предотвращаете полное рассогласование частей проекта. Некоторые организации выполняют сборки еженедельно, а не ежедневно. Проблема в том, что, если сборка на какой#то неделе была испорчена, вам может понадобить- ся несколько недель, прежде чем вы получите следующую хорошую сборку. Когда такое случается, вы теряете практически все преимущества частых сборок. Проверяйте правильность сборок Чтобы процесс ежедневных сборок действо- вал, собираемое ПО должно быть работоспособным. В противном случае сборка считается испорченной, и ее исправление становится приоритетной задачей. Каждый проект задает свои стандарты того, что считается «сломанной сборкой». Устанавливаемый стандарт должен быть достаточно строгим, чтобы не допускать серьезных дефектов, но и достаточно снисходительным, чтобы пренебрегать простейшими ошибками, способными парализовать движение вперед, если им уделять чрезмерное внимание. «Хорошая» сборка должна как минимум: успешно компилировать все файлы, библиотеки и другие компоненты; успешно компоновать все файлы, библиотеки и другие компоненты; не содержать серьезных дефектов, препятствующих запуску программы или делающих ее выполнение непредсказуемым, — иначе говоря, хорошая сборка должна проходить дымовой тест. Выполняйте дымовые тесты ежедневно Дымовой тест испытывает всю систему от начала до конца. Он не должен быть всесторонним, но должен быть способен выявить основные проблемы. Этот тест должен быть достаточно тщатель- ным, чтобы прошедшая его сборка могла считаться стабильной для выполнения более основательного тестирования. Ежедневная сборка не имеет большого значения без дымового теста. Этот тест как часовой, который охраняет от появления проблем, снижающих качество продукта и препятствующих интеграции. Без него ежедневные сборки становятся просто длительными упражнениями, позволяющими лишь убедиться в наличии еже#дневной безошибочной компиляции. Поддерживайте актуальность дымового теста Дымовой тест должен развиваться по мере развития системы. Поначалу он может проверять что#то простое, скажем, может ли система говорить «Hello, World». По мере разработки системы дымовой тест становится более изощренным. Если выполнение первой проверки может быть делом нескольких секунд, то с ростом системы дымовой тест может удлиниться до 10 минут, часа или больше. Если дымовой тест не под- CC2_Part6_ch29_2010.indd 687 22.06.2010 11:59:56 688 ЧАСТЬ VI Системные вопросы держивается в актуальном состоянии, то ежедневная сборка может стать само- обманом: разрозненный набор тестовых данных создает ложную уверенность в качестве продукта. Автоматизируйте ежедневную сборку и дымовой тест Поддержка сборок может отнимать много времени. Автоматизация процессов ежедневной сборки и тестирования позволяет убедиться, что код собирается и проверка выполняется. Без автоматизации выполнять сборку и дымовой тест непрактично. Организуйте группу, отвечающую за сборки В большинстве проектов надзор за ежедневными сборками и поддержание актуальности дымовых тестов становится достаточно большой задачей, чтобы стать заметной частью чьей#то работы. В больших проектах эти задачи могут обеспечить полную занятость для нескольких человек. Например, при выпуске первой версии Microsoft Windows NT группа, ответственная за сборки, состояла из четырех человек, работающих полный рабочий день (Zachary, 1994). Вносите исправления в сборку, только когда имеет смысл это делать… Отдельные разработчики обычно пишут код не настолько быстро, чтобы делать значимый вклад в систему ежедневно. Им следует работать над фрагментом кода и интегрировать его, когда у них будет некая совокупность кода в целостном со- стоянии — обычно раз в несколько дней. ...но не откладывайте внесение исправлений надолго Старайтесь реги- стрировать код не слишком редко. Существует вероятность, что разработчик на- столько запутается в списке изменений, что потребуется исправить практически каждый файл в системе. Это снижает ценность ежедневной сборки. Остальная часть команды будет продолжать использовать преимущества инкрементной ин- теграции, а этот отдельный разработчик — нет. Если программист откладывает регистрацию изменений более, чем на пару дней, можно считать, что его работа подвергается риску. Как указывает Кент Бек, частая интеграция иногда заставляет разбивать конструирование отдельной функции на несколько частей. Такие на- кладные расходы — приемлемая цена за снижение рисков интеграции, улучшение видимости состояния, улучшение контролепригодности и прочие преимущества частой интеграции (Beck, 2000). Требуйте, чтобы разработчики проводили дымовое тестирование своего кода перед его добавлением в систему Программисты должны тестировать собственный код, прежде чем добавить его к сборке. Разработчик может это сде- лать, создав частную сборку системы на собственной машине и протестировав ее самостоятельно. Или он может передать частную сборку «партнеру по тестиро- ванию» — тестировщику, который сосредоточится на коде этого разработчика. В обоих случаях цель в том, чтобы убедиться, что новый код пройдет «проверку на дым», прежде чем ему будет позволено влиять на остальные части системы. Создайте область промежуточного хранения кода, который следует добавить к сборке Успешность ежедневной сборки частично зависит от зна- ния, какая сборка хорошая, а какая — нет. При тестировании собственного кода у разработчиков должна быть возможность использовать систему, о которой точно известно, что она исправна. CC2_Part6_ch29_2010.indd 688 22.06.2010 11:59:56 ГЛАВА 29 Интеграция 689 Большинство групп решает эту проблему с помощью создания области промежу- точного хранения кода, который, по мнению разработчиков, готов к добавлению к сборке. Новый код помещается в эту область, создается новая сборка, и, если ее состояние признано удовлетворительным, новый код перемещается в основное хранилище. В небольших и средних проектах система управления версиями может обеспе- чить такую функцию. Разработчики регистрируют свой код в этой системе. Тот, кто хочет использовать заведомо хорошую сборку, просто устанавливает флаг даты в свойствах файла системы управления версиями. Значение этого флага сообщит системе, что следует извлекать файлы, основываясь на дате последней правильной сборки. В больших проектах или там, где используется слишком простое ПО управления версиями, создавать область промежуточного хранения приходится выполнять вручную. Автор нового кода посылает письмо в группу, ответственную за сборку, сообщая, где можно найти новые файлы, предназначенные для регистрации в системе. Или группа заводит «область данных для регистрации» на файловом сервере, где разработчики размещают новые версии своих исходных файлов. От- ветственная за сборку группа принимает на себя обязательства по регистрации нового кода в системе управления версиями после того, как удостоверится, что этот код не нарушит сборку. Назначьте наказание за нарушение сборки Большинство групп, применяющих ежедневную сборку, назначает наказание за ее нарушение. Сделайте ясным с само- го начала, что поддержание работоспособности сборок — одна из приоритетных задач проекта. Испорченная сборка должна быть исключением, а не правилом. Настаивайте на том, чтобы разработчики, испортившие сборку, прекращали всю работу до тех пор, пока ее не восстановят. Если сборка ломается слишком часто, трудно всерьез относиться к вопросу поддержания ее работоспособности. Забавное наказание поможет сделать акцент на этом приоритете. В некоторых группах на дверь офиса «сосунка», испортившего сборку, вешают леденцы, которые должны висеть до тех пор, пока проблема не будет исправлена. В других группах провинившиеся должны надевать козлиные рога или вносить по пять долларов в специальный фонд. Другие проекты устанавливают более существенное наказание. Разработчики наиболее заметных проектов в Microsoft, например, Windows 2000 и Microsoft Office, на последних стадиях проектов должны носить с собой пейджеры. Если они разрушают сборку, их вызывают для ее починки, даже если дефект обнаружен в три часа утра. Выпускайте сборки по утрам В некоторых группах пришли к выводу, что предпочтительней создавать сборку ночью, проводить дымовой тест рано утром и выпускать новые сборки утром, а не ближе к вечеру. Утреннее тестирование и выпуск сборок имеют несколько преимуществ. Во#первых, если вы выпускаете сборку утром, тестировщики могут работать в этот день со свежей сборкой. Если вы выпускаете сборку после обеда, тестировщики чувствуют себя обязанными запустить автоматические тесты перед уходом с ра- боты. Если сборка задерживается, что случается довольно часто, им приходится CC2_Part6_ch29_2010.indd 689 22.06.2010 11:59:56 690 ЧАСТЬ VI Системные вопросы оставаться после работы, чтобы запустить свои тесты. Поскольку в таких задержках нет их вины, процесс сборки становится деморализующим. Заканчивая сборку утром, вы получаете более надежный доступ к разработчикам, со сборкой возникли проблемы. Во время рабочего дня программисты находятся в офисе. Вечером они могут быть где угодно. Даже если разработчикам выданы пейджеры, не всегда легко их найти. Возможно, было бы круче начинать дымовой тест в конце дня и при возникнове- нии проблем вызывать людей на работу посреди ночи, но это усложняет работу команды, приводит к бесполезной трате времени — в результате же вы больше потеряете, чем приобретете. Создавайте сборку и проводите дымовой тест даже в экстремальных условиях Когда сроки начинают поджимать, ежедневные сборки могут пока- заться излишней роскошью. На самом деле все наоборот. В стрессовых условиях дисциплина программистов ухудшается. Под давлением обстоятельств они при- бегают к таким методам ускорения конструирования, которые не использовали бы в менее критичных ситуациях. Они рецензируют и тестируют собственный код менее тщательно, чем обычно. Код стремится к состоянию энтропии быстрее, чем это происходит при меньшем стрессе. На этом фоне ежедневные сборки ужесточают дисциплину и поддерживают на плаву критические проекты. Для каких проектов применять ежедневную сборку? Некоторые разработчики считают, что проводить ежедневные сборки непрактич- но, так как их проекты слишком велики. Но, вероятно, в одном из самых сложных недавних программных проектов успешно использовалась практика ежедневных сборок. К моменту выпуска Microsoft Windows 2000 состояла из примерно 50 миллионов строк кода, распределенных по десяткам тысяч исходных файлов. Полная сборка занимала 19 часов на нескольких машинах, но разработчики Win- dows 2000 оказались в состоянии проводить сборки каждый день. Они не стали помехой — напротив, команда Windows 2000 видит одну из причин успеха в еже#дневных сборках. Чем больше проект, тем большую важность имеет инкре- ментная интеграция. При рассмотрении 104 проектов в США, Индии, Японии и Европе выяс# нилось, что только 20–25% из них используют ежедневные сборки в на# чале или середине процесса разработки (Cusumano et al., 2003). Таким образом, в них имеются широкие возможности для улучшения. Непрерывная интеграция Некоторые авторы расценивают ежедневные сборки как аргумент в пользу вы- полнения непрерывной интеграции (Beck, 2000). В большинстве публикаций под «непрерывной» понимается «по крайней мере ежедневная» интеграция (Beck, 2000), что мне кажется обоснованным. Но порой я встречаю людей, понимающих слово «непрерывная» буквально: они стремятся выполнять интеграцию каждого изменения с самой последней сборкой каждые пару часов. Думаю, для большинства проектов действительно непрерывная интеграция выходит за рамки разумного. CC2_Part6_ch29_2010.indd 690 22.06.2010 11:59:56 ГЛАВА 29 Интеграция 691 В свободное время я руковожу работой дискуссионной группы, главных технических менеджеров таких компаний, как Ama zon.com, Boeing, Expe# dia, Microsoft, Nordstrom и др. Опрос показал, что никто из руководителей не считает непрерывную интеграцию предпочтительней ежедневной. В средних и больших проектах иногда имеет смысл допустить рассинхронизацию кода на короткое время. Разработчики часто нарушают синхронизацию, когда делают масштабные изменения. Через какое#то время они могут синхронизировать про- ект снова. Ежедневные сборки позволяют проектной команде организовать точки согласования достаточно часто. Если изменения синхронизируются ежедневно, нет нужды делать это непрерывно. Контрольный список: интеграция Стратегия интеграции Определяет ли стратегия оптимальный порядок, в кото- ром должны интегрироваться подсистемы, классы и методы? Скоординирован ли порядок интеграции с порядком конструирования, чтобы классы были подготовлены к интеграции вовремя? Приводит ли стратегия к упрощению диагностики дефектов? Позволяет ли стратегия сократить использование «подпорок» до миниму- ма? Имеет ли выбранная стратегия преимущества перед другими подходами? Хорошо ли определены интерфейсы между компонентами? (Определение интерфейсов не является задачей интеграции, а проверка правильности их определения — является.) Ежедневная сборка и дымовые тесты Выполняется ли сборка проекта достаточно часто (в идеале каждый день), чтобы поддерживать инкрементную интеграцию? Выполняется ли дымовой тест для каждой сборки так, что вам становится известно, находится ли сборка в рабочем состоянии? Автоматизированы ли сборка и дымовой тест? Часто ли разработчики регистрируют свой код: не превышает ли время между исправлениями день или два? Поддерживается ли дымовой тест в соответствии с кодом, расширяясь при его расширении? Редко ли происходит нарушение работоспособности сборки? Выполняете ли вы сборку и дымовой тест даже в экстремальных обстоя- тельствах? Дополнительные ресурсы Вопросам, обсуждаемым в этой главе, посвящены следующие публикации. Интеграция Lakos, John. Large%Scale C++ Software Design. Boston, MA: Addison#Wesley, 1996. Лей- кос доказывает, что «физическое представление» системы — иерархия файлов, каталогов и библиотек — заметно влияет на способность команды разработчиков http://cc2e.com/2992 http://cc2e.com/2999 CC2_Part6_ch29_2010.indd 691 22.06.2010 11:59:56 692 ЧАСТЬ VI Системные вопросы скомпилировать ПО. Если вы не обращаете должного внимания на физическое представление, то время сборки проекта может стать довольно долгим, что сведет на нет преимущества частой интеграции. Лейкос пишет о C++, но выводы, от- носящиеся к «физическому представлению» актуальны, и для проектов на других языках. Myers, Glenford J. The Art of Software Testing. New York, NY: John Wiley & Sons, 1979. В этой классической книге о тестировании интеграция рассматривается как опе- рация тестирования. Инкрементный подход McConnell, Steve. Rapid Development. Redmond, WA: Microsoft Press, 1996. В главе 7 (Lifecycle Planning) «Планирование жизненного цикла» подробно рассмотрены плюсы и минусы более гибких и менее гибких моделей жизненного цикла. В гла- вах 20, 21, 35 и 36 обсуждаются конкретные модели жизненных циклов, поддер- живающие инкрементный подход в разной степени. Глава 19 содержит описание « проектирования с поддержкой изменений» (designing for change) — ключевой методики, необходимой для поддержки интерактивной и инкрементной моделей разработки. Boehm, Barry W. «A Spiral Model of Software Development and Enhancement». Computer, May 1988: 61–72. В этой статье Бом описывает свою «спиральную модель» разра- ботки ПО. Он предлагает эту модель в качестве подхода к управлению рисками в программном проекте, поэтому статья больше касается разработки в целом, чем конкретно интеграции. Бом — один из выдающихся экспертов в области мас#штабных вопросов разработки ПО, и доходчивость его объяснений отражает глубину его понимания темы. Gilb, Tom. Principles of Software Engineering Management. Wokingham, England: Addison#Wesley, 1988. Главы 7 и 15 содержат исчерпывающее обсуждение эволю- ционной поставки — одного из первых подходов к инкрементной разработке. Beck, Kent. Extreme Programming Explained: Embrace Change.Reading, MA: Addi son#Wesley, 2000. Эта книга содержит более современное, лаконичное и евангеличе#ское пред- ставление большинства идей, приведены в книге Гилба. Лично я отдаю предпочте- ние глубокому анализу Гилба, но некоторые читатели могут посчитать изложение Бека более доступным или применимым непосредственно к тому типу проекта, над которым они работают. Ключевые моменты Последовательность конструирования и интеграционный подход влияют на порядок, в котором классы проектируются, кодируются и тестируются. Порядок интеграции снижает затраты на тестирование и упрощает отладку. Инкрементная интеграция имеет несколько вариантов, и, помимо совсем три- виальных проектов, любой из них лучше, чем поэтапная интеграция. CC2_Part6_ch29_2010.indd 692 22.06.2010 11:59:56 ГЛАВА 29 Интеграция 693 Лучший интеграционный подход для каждого конкретного проекта — обычно сочетание нисходящего, восходящего, риск#ориентированного и других инте- грационных подходов. Т#образная интеграция и интеграция с вертикальным секционированием часто дают хорошие результаты. Ежедневные сборки могут уменьшить проблемы с интеграцией, улучшить мо- ральный климат среди разработчиков и предоставить полезную информацию, касающуюся управления проектом. CC2_Part6_ch29_2010.indd 693 22.06.2010 11:59:56 694 ЧАСТЬ VI Системные вопросы Г Л А В А 3 0 Инструменты программирования Содержание 30.1. Инструменты для проектирования 30.2. Инструменты для работы с исходным кодом 30.3. Инструменты для работы с исполняемым кодом 30.4. Инструменты и среды 30.5. Создание собственного программного инструментария 30.6. Волшебная страна инструментальных средств Связанные темы Инструменты для управления версиями: раздел 28.2 Инструменты для отладки: раздел 23.5 Инструменты для облегчения тестирования: раздел 22.5 Современные инструменты сокращают время конструирования. Передовой набор инструментов (и знание этих средств) позволяет повысить производительность более, чем на 50% (Jones, 2000; Boehm et al., 2000). Программный инструмента# рий также позволяет уменьшить объем однообразной, монотонной работы. Собака, может, и лучший друг человека, но хорошие инструменты — луч# шие друзья программиста. Как уже давно выяснил Барри Бом, 20% ин# струментария используются приблизительно в 80% случаев (1987b). Если вы не знакомы с каким#то из наиболее полезных инструментов, вы упускаете шанс облегчить себе жизнь. В этой главе мы поговорим об инструментах для конструирования (об инстру# ментах, применяемых в процессе определения требований, управления, а также на протяжении всего цикла разработки, см. раздел «Дополнительные ресурсы» в конце главы). Темой нашего разговора будут не конкретные средства, а их разно# видности. Некоторые инструменты настолько широко распространены, что упо# минаются по имени, но номера версий, имена продуктов и компаний меняются так быстро, что информация о большинстве из них оказалась бы устаревшей до того, как высохли бы чернила на этих страницах. http://cc2e.com/3084 |