10,49 €
«Совершенный софт» — это проверенный, структурированный и высокотехнологичный подход к разработке программного обеспечения. Множество компаний уже используют идеи Лёве в сотнях систем, но раньше эти мысли нигде не публиковались. Методология Лёве объединяет разработку систем и дизайн проектов, используя базовые принципы разработки ПО, корректные наборы инструментов и эффективные методы. Автор подробно описывает основы, на которых прокалываются многие архитекторы ПО, и показывает, как разложить систему на мелкие блоки или службы. Вы узнаете как вывести эффективный дизайн проекта из дизайна системы, как рассчитать время, необходимое на запуск проекта, его стоимость и риски, и даже как разработать несколько вариантов выполнения. Метод и принципы «совершенного софта» можно применять независимо от размера проекта, компании, технологии, платформы или отрасли. Цель этой книги — решение важнейших задач современной разработки ПО, требующих исправления программных систем и проектов, ваш карьерный рост и, возможно, изменение всей IT-индустрии. Рекомендации и знания, которые вы получите, сэкономят десятилетия вашего опыта и спасут многие проекты. Эта книга принесет большую пользу разработчикам, архитекторам, руководителям проектов или менеджерам на любом этапе карьеры.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 564
Veröffentlichungsjahr: 2023
Переводчик Е. Матвеев
Литературные редакторы М. Макурина, Е. Тихонова
Художник В. Мостипан
Корректоры С. Беляева, Н. Викторова
Джувел Лёве
Совершенный софт. — СПб.: Питер, 2020.
ISBN 978-5-4461-1621-8
© ООО Издательство "Питер", 2020
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Я посещал как мастер-класс для архитекторов, так и мастер-класс по планированию проектов. До этого я почти потерял всякую надежду, что мне когда-нибудь удастся понять, почему работа моей команды никогда не приводит к успешному завершению, и лихорадочно пытался найти работоспособное решение, которое бы остановило нашу безумную гонку на выживание. Мастер-классы открыли передо мной мир, в котором разработка ПО поднимается на уровень всех остальных инженерных дисциплин и организуется профессионально, предсказуемо и надежно, что приводит к разработке высококачественных программных продуктов в рамках срока и бюджета. Полученные знания были бесценными! От создания основательной, проработанной архитектуры, которая может устоять перед вечно изменяющимися требованиям пользователей, до внутренних подробностей планирования и направления проекта к успешному завершению — все это было представлено с непревзойденным профессионализмом и компетентностью. А если учесть, что каждое слово истины, которым Джувел поделился с нами на мастер-классах, было получено, проверено и подтверждено реальной жизнью, этот усвоенный опыт превратился в мощный комплекс знаний, абсолютно необходимый для каждого, кто стремится стать архитектором в области программных систем.
Россен Тотев (Rossen Totev), программный архитектор/руководитель проектов
Мастер-класс по планированию проектов стал событием, изменившим мою карьеру. Я вырос в среде, в которой ограничения по срокам и бюджетам нарушались патологически, так что возможность учиться у Джувела стала даром небес. Шаг за шагом он представлял компоненты и необходимые инструменты для правильного планирования проектов. Это позволяет держать под контролем затраты и сроки в динамической и даже хаотичной среде современной разработки программных продуктов. Джувел говорит, что вы вступаете в асимметричную войну против превышения затрат и сроков, а после его мастер-классов у вас появляется ощущение, что вступаете в рукопашный бой с пистолетом. Никакого волшебства в этом нет — только применение базовых инженерных и производственных доктрин в области ПО, но, вернувшись в офис, вы будете чувствовать себя настоящим волшебником.
Мэтт Роболд (Matt Robold), руководитель разработки ПО, West Covina Service Group
Фантастический опыт. Навсегда изменил мой образ мышления относительно того, как следует подходить к разработке программных продуктов. Я всегда знал, что какая-то часть моих ощущений относительно проектирования и программирования была правильной. Просто я не умел выразить ее словами, зато теперь эти слова у меня появились. Новые знания повлияли на мой подход не только к проектированию программных продуктов, но и к другим видам проектирования.
Ли Мессик (Lee Messick), ведущий архитектор
Программный проект, над которым я работаю, годами страдал от немыслимо жестких сроков. Попытки разобраться в методологиях разработки и сформировать правильный процесс казались напрасной тратой сил, потому что помимо неразумных требований заказчиков приходилось бороться с нежеланием руководства идти на какие-либо компромиссы. Я сражался на двух фронтах, и поражение казалось неизбежным. Мастер-класс предоставил четкое видение ситуации, о котором я никогда не подозревал. Я получил именно те знания, которые искал. Освоил фундаментальные методы, которые изменили мое понимание того, как работают программные проекты. Теперь у меня есть инструменты для эффективного управления моими проектами в потоке бесконечно изменяющихся требований. В мире хаоса этот мастер-класс навел порядок. Я бесконечно благодарен IDesign. Моя жизнь никогда не будет прежней.
Аарон Фридман (Aaron Friedman), архитектор
Жизнь изменилась. Я чувствую себя как хорошо настроенное пианино, на котором уже пару десятилетий копилась пыль.
Джордан Ян (Jordan Jan), технический директор/архитектор
Курсы были просто фантастическими. Пожалуй, это была самая напряженная, но и самая плодотворная неделя моей профессиональной жизни.
Стоил Панков (Stoil Pankov), архитектор
Обучение у Джувела Лёве изменило мою жизнь. Я прошел путь от простого разработчика до настоящего архитектора, применяющего инженерные принципы из других дисциплин к проектированию не только программных продуктов, но и своей карьеры.
Кори Торгерсен (Kory Torgersen), архитектор
Мастер-класс для архитекторов — настоящий урок жизни из области проектирования и квалификации, который я прошел дважды. Знания преподносились на совершенно новом уровне, и я жалею, что не прошел это обучение десятки лет назад, в самом начале своей карьеры. «Перепрошивать мозг» и забывать то, что я знал ранее, неприятно, но я должен был пройти через это. Наконец, каждый последующий день я размышляю над тем, что сказал Джувел на мастер-классе, и пользуюсь новыми знаниями, помогая своей команде даже в мелочах — чтобы со временем все мы могли называть себя Профессиональными Инженерами. (P.S. При втором прохождении я написал более 100 страниц заметок!)
Джейсу Джеячандрат, руководитель разработки ПО, Nielsen
Если вы расстроены, творческой энергии не осталось, если вы чувствуете отчаяние, видя множество неудач в нашей отрасли, этот мастер-класс вдохновит вас. Он поднимет вас на новый уровень профессиональной зрелости, а также придаст надежды и уверенности в том, что вы сможете правильно применить новые знания. Вы выйдете с мастер-класса по планированию проектов с новым образом мышления и достаточным количеством полезных инструментов, с которыми любая неудача программного проекта станет непростительной. Вы тренируетесь, решаете практические задачи, получаете представление о сути вещей и получаете опыт. Да, когда приходит время сообщать ключевым участникам данные о затратах, времени и риске проекта, МОЖНО быть точным. Не ждите, пока ваша компания отправит вас на этот мастер-класс. Если вы серьезно относитесь к своей карьере, поскорее запишитесь на этот или любой другой мастер-класс IDesign. Это лучшее вложение в свою карьеру, которое можно сделать. Спасибо всей команде IDesign за ее непрестанную работу по превращению отрасли разработки в серьезную инженерную дисциплину.
Люциан Мариан (Lucian Marian), архитектор, Mirabel
Как человек, который находится в начале своей карьеры, я могу честно заявить, что этот учебный курс изменил мою жизнь и мое отношение к работе. Я искренне считаю, что он стал поворотной точкой моей жизни.
Алекс Карпович (Alex Karpowich), архитектор
Хочу поблагодарить вас за неделю, изменившую мою (профессиональную) жизнь. Обычно я не могу высидеть на учебных курсах более 50% времени — они занудны и не учат меня ничему, чего бы я не знал или не мог узнать сам. На мастер-классе для архитекторов на протяжении 9 часов каждый день мне не было скучно: я узнал о своих обязанностях как архитектора (я-то думал, что архитектор — всего лишь проектировщик программных продуктов), об инженерном аспекте разработки, о важности реализации не только к заданному времени, но и в рамках бюджета и качества. Я научился не ждать, пока из меня «вырастет» архитектор, а активно управлять своей карьерой, научился оценивать и давать объективную оценку тому, что прежде считал интуитивными инсайтами. После этой недели многое встало на свои места. Не могу дождаться следующего мастер-класса.
Итай Золберг (Itai Zolberg), архитектор
Моему отцу Томасу Чарльзу (Томми) Лёве
Вряд ли хоть кто-то из нас пришел в разработку программного обеспечения не по своей воле. Многие буквально влюбляются в программирование и решают, что хотят зарабатывать им на жизнь. Тем не менее между радужными представлениями о карьере и темной, угнетающей реальностью разработки лежит пропасть. Отрасль программирования в целом переживает глубокий кризис. Этот кризис оказался особенно острым из-за своей многомерности; нарушен буквально каждый аспект разработки:
• Затраты. Бюджет, выделенный для проекта, слабо связан с фактическими затратами на разработку системы. Многие организации даже не пытаются решить проблему с затратами — возможно, потому, что просто не знают, как к ней подойти, или потому, что это заставит их признать, что работа над системой окажется экономически неоправданной. Даже если затраты на первую версию новой системы будут оправданны, часто затраты на протяжении срока жизни системы намного выше положенных из-за некачественного проектирования и неспособности адаптироваться к изменениям. Со временем затраты на сопровождение становятся настолько неприемлемыми, что компании обычно решают начать с чистого листа — только для того, чтобы в ближайшем будущем получить не менее, а то и более дорогостоящую кучу хлама вместо новой системы. Ни в одной другой отрасли не встречается регулярный перезапуск систем просто из-за того, что поддержка старой системы экономически нецелесообразна. Авиакомпании эксплуатируют лайнеры десятилетиями, а жилой дом может быть построен сотню лет назад.
• График. Дедлайны часто оказываются взятыми из головы и являются ничем не обеспеченными конструкциями, так как не имеют никакого отношения ко времени, которое реально потребуется для разработки системы. Для большинства разработчиков дедлайны — всего лишь бесполезные ориентиры, которые проносятся мимо в процессе работы. Если команда разработки выдерживает дедлайн, все удивляются, ибо всегда ждут, что сроки будут сорваны. И это тоже является прямым результатом плохого проектирования системы, что приводит к каскадному распространению в ней новых изменений и новой работы, с которой ранее завершенная работа становится недействительной. Более того, это является результатом чрезвычайно неэффективного процесса разработки, который не учитывает зависимости между активностями и не пытается отыскать самый быстрый и безопасный способ построения системы. Мало того что время реализации всей системы становится неприемлемо большим — и время реализации отдельных функций тоже может увеличиваться. Нарушение сроков в проекте — достаточно плохо; но еще хуже, когда нарушение скрывается от руководства и заказчиков, потому что никто не имеет ни малейшего понятия об истинном состоянии проекта.
• Требования. В конечном итоге часто оказывается, что разработчики решают не те задачи. Между заказчиками или их внутренними посредниками (например, отделом маркетинга) и группой разработки существует постоянное недопонимание. Также многие разработчики не признают свою неспособность сохранить суть требований. Даже идеально переданные требования с большой вероятностью со временем изменятся. Такие изменения делают решение недействительным и ставят под удар все, что пыталась построить команда.
• Персонал. Даже среднестатистические программные системы настолько сложны, что человеческий мозг просто не способен в них полностью разобраться. Внутренняя и внешняя сложность является прямым результатом неудачной архитектуры системы; в свою очередь, она ведет к появлению запутанных систем, создающих массу сложностей в сопровождении, расширении и повторном использовании.
• Сопровождение. Часто сопровождением программных систем занимаются совсем не те люди, которые их разрабатывали. Новые сотрудники не понимают, как работает система, и в результате попытки решения старых проблем постоянно приводят к созданию новых. Все это ведет к стремительному росту затрат на сопровождение и времени вывода продукта на рынок, а также к отмене проектов или их перезапуску с чистого листа.
• Качество. Возможно, ничто не нарушается в программных системах так серьезно, как качество. В программных продуктах есть баги, и можно сказать, что они стали неотъемлемой частью разработки. Говоришь «ПО» — имеешь в виду «баги». Разработчики не могут представить себе программную систему, избавленную от багов. Исправление дефектов (как и добавление новых функций или просто сопровождение) часто увеличивает счетчик ошибок. Плохое качество является прямым результатом малопонятной системной архитектуры, которая создает проблемы с тестированием и сопровождением. Не менее важно то, что многие проекты не учитывают важнейшие активности по контролю качества и не выделяют достаточно времени для того, чтобы каждая активность была завершена идеально.
Несколько десятилетий назад в отрасли начали разрабатываться продукты для решения мировых проблем. В наши дни разработка сама по себе стала проблемой мирового уровня. Проблемы разработки часто проявляются в нетехнических аспектах: рабочих средах с высоким уровнем стресса, высокой текучести кадров, нервном истощении, отсутствии доверия, низкой самооценке и даже в соматических заболеваниях.
Ни одна из проблем разработки не нова1. Некоторые люди за всю свою карьеру в разработке ни разу не видели, чтобы продукт был сделан правильно. Они начинают верить, что это в принципе невозможно, и отвергают любые попытки решения проблем, потому что «уж так сложилось». Они даже могут оказывать сопротивление людям, пытающимся улучшить процесс разработки. Они уже решили для себя, что цель недостижима, поэтому каждый, кто пытается добиться лучших результатов, желает невозможного, а это оскорбляет их самолюбие.
Мой опыт является контрпримером, который доказывает, что успешная разработка программных систем возможна. Каждый проект, за который я отвечал, выпускался в пределах срока, бюджета и без дефектов. История продолжилась и после основания компании IDesign, в которой мы помогаем своим клиентам снова и снова успешно выполнять свои обязательства.
Эта последовательная, систематичная история успеха не была случайна. Я получал образование в области проектирования систем — как физических, так и программных, поэтому было нетрудно узнать сходство этих двух миров. Применение практических принципов — идей, которые считаются проявлением здравого смысла в других инженерных областях, — было оправданно и в области программных систем. Мне никогда не приходило в голову рассматривать разработку программного продукта как техническую задачу или разрабатывать систему без плана. Я не видел смысла идти на компромиссы со своими убеждениями или поступаться принципами, потому что правильный подход работал, а устрашающие последствия отхода от него были очевидны. Мне повезло с учителями; я оказался в нужном месте в нужное время, видел, что работает, а что не работает; мне представилась возможность поучаствовать в больших важных проектах и быть частью культуры высших достижений.
В последние годы я заметил, что проблемы отрасли усугубляются. Все больше программных проектов завершаются неудачей. Эти неудачи обходятся все дороже по времени и деньгам, и даже завершенные проекты обычно отклоняются от своих исходных обязательств. Кризис усугубляется не только тем, что системы становятся все больше, не только жесткими сроками или более высокой частотой изменений. Подозреваю, настоящая причина заключается в том, что знания о том, как правильно проектировать и планировать программные системы, медленно уходят из команд разработчиков. Когда-то в большинстве команд присутствовал ветеран, который обучал молодых и передавал коллективные знания. В наши дни эти учителя перешли на другую работу или вышли на пенсию. В их отсутствие рядовые сотрудники располагают бесконечным количеством информации при нуле знаний.
Я бы очень хотел, чтобы кризис в области программирования мог быть решен каким-то одним способом: применением процесса, методологии разработки, инструмента или технологии. К сожалению, для решения многомерной задачи требуются многомерные решения. В этой книге предлагается комплексное решение: исправление процесса разработки.
По сути, все, что я предлагаю, — проектирование и разработка программных систем с применением инженерных принципов. К счастью, нет необходимости заново изобретать велосипед. Другие инженерные дисциплины были достаточно успешны, поэтому отрасль разработки программного обеспечения может позаимствовать ключевые универсальные принципы проектирования и адаптировать их для программных систем и проектов. Чтобы добиться успеха, необходимо принять инженерную точку зрения. Вы хотите, чтобы программная система была простой в сопровождении и расширении, экономичной, пригодной для повторного использования и реализуемой по времени и риску? Все это инженерные, а не технические аспекты. Они напрямую обусловлены проектированием системы и планированием проекта. Появился специальный термин «архитектор программного продукта» (или просто «архитектор»), которым обозначается участник команды, который отвечает за все аспекты проекта, связанные с проектированием. Соответственно я буду называть читателя «архитектором».
Идеи, представленные в книге, не затрагивают всего, что необходимо знать для достижения успеха. Тем не менее они безусловно станут хорошей отправной точкой, так как направлены на исправление корневой причины всех проблем, упоминавшихся ранее. Корневой причиной является некачественное проектирование самой программной системы или планирование проекта, используемого для построения этой системы. Вы увидите, что программный продукт вполне может быть реализован в пределах графика и бюджета и что планировать системы, удовлетворяющие всем мыслимым требованиям, реально. Такие системы также не создают проблем в сопровождении, расширении и повторном использовании. Надеюсь, что использование этих идей выведет на правильный путь не только систему, над которой вы работаете, но и вашу карьеру, и что оно снова разожжет вашу страсть к программированию.
В книге представлен структурированный инженерный подход к проектированию систем и планированию проектов. Методология состоит из двух частей, нашедших отражение в структуре книги: проектирование системы (создание архитектуры) и планирование проекта. Обе части взаимно дополняют друг друга и являются обязательными. Приложения дополняют основной материал.
Обычно в технической литературе каждая глава посвящена отдельной теме. Такой подход упрощает написание книги, но обычно не соответствует особенностям нашего процесса познания. Напротив, в этой книге процесс обучения напоминает спираль. В обеих частях книги в каждой главе мы возвращаемся к идеям предыдущих глав, погружаясь в материал или развивая идеи на основании дополнительной информации, охватывающей разные аспекты. Такая подача материала повторяет естественный процесс обучения. Каждая глава строится на материале предшествующих глав, поэтому читать главы стоит по порядку. В обе части книги включен подробный практический пример, демонстрирующий основные идеи, а также дополнительные аспекты. В то же время, чтобы итерации были более компактными, обычно я стараюсь избегать самоповторений, так что даже ключевые моменты обсуждаются только один раз.
Ниже приведено саммари глав и приложений.
В главе 1 представлена ключевая идея: чтобы добиться успеха, вы должны спроектировать систему и спланировать проект для ее построения. Обе составляющие крайне важны для успеха. Проект невозможно планировать без архитектуры, а строить систему, которую вы не сможете построить, бессмысленно.
Глава 2 посвящена разложению системы на компоненты, образующие ее архитектуру. Большинство архитекторов выполняет декомпозицию систем худшим из всех возможных способов, поэтому глава начинается с объяснения того, чего делать не следует. Разобравшись с ошибками, вы увидите, как правильно выполнить декомпозицию системы, и освоите простые средства анализа и наблюдения, которые помогут вам в этом процессе.
Глава 3 развивает идеи главы 2 и дополняет их структурой. Вы узнаете, как сохранять требования, как строить иерархию уровней в архитектуре, освоите таксономию компонентов архитектуры, их взаимоотношения, узнаете конкретные рекомендации по классификации, а также некоторые сопутствующие аспекты, такие как проектирование подсистем.
В главе 4 показано, как объединить компоненты системы в композицию, которая удовлетворяет заданным требованиям. В этой короткой главе приводятся некоторые из ключевых принципов книги; она использует материал двух предыдущих глав для создания мощного ментального инструмента, который вы будете применять в любой системе.
Глава 5 содержит обширный практический пример, который демонстрирует основные идеи проектирования систем, рассматривающиеся до этого момента. Последняя итерация спирали проектирования системы представляет реальную систему, выравнивает проектировочное решение с требованиями бизнеса и показывает, как создать архитектуру и проверить ее на жизнеспособность.
Так как большинство людей никогда не слышало о планировании проектов (не говоря уже о том, чтобы применять его), в этой главе описана эта концепция и предоставлены побудительные причины для участия в планировании проекта. Это нулевая итерация спирали планирования проектов.
В главе 7 приведен общий обзор планирования проектов. Глава начинается с определения успеха при разработке, после чего представляются ключевые концепции обоснованных решений, комплектования проекта, сети проекта, критического пути, сроков и затрат. В главе рассматриваются многие идеи и методы, используемые в последующих главах, а завершается она важным обсуждением ролей и обязанностей.
В главе 8 подробно рассматривается сеть проекта и ее использование как инструмента проектирования. Вы увидите, как смоделировать проект в качестве диаграммы сети, узнаете ключевую концепцию временного резерва, научитесь использовать временные резервы при комплектовании и назначении сроков, а также поймете, как временные резервы связаны с риском.
В главе 9 определяются возможные компромиссы между временем и затратами в любых проектах, а также описываются возможности ускорения проектов за счет чистой и правильной организации работы. Кроме этого, вы познакомитесь с ключевыми концепциями уплотнения, кривой «время-затраты» и составляющими затрат.
В главе 10 представлен отсутствующий элемент большинства проектов: риск, выраженный в числовой форме. Вы узнаете, как измерить риск, как связать его с концепциями времени и затрат из предыдущей главы и как вычислить риск на основании сети. Риск часто становится лучшим способом оценки вариантов, это первоклассный инструмент планирования.
В главе 11 все концепции предыдущих глав реализуются на практике посредством систематического применения шагов, задействованных в планировании проекта. Хотя проект обладает всеми качествами примера, он приведен прежде всего для демонстрации процесса мышления, используемого при планировании проектов, а также подготовки представления проекта ответственным за принятие решений со стороны бизнеса.
В соответствии со спиральной моделью обучения в этой главе приводится описание нетривиальных приемов и концепций. Эти методы применяются в проектах разных уровней сложности, от простых до самых сложных. Эти расширенные методы дополняют предыдущие главы и друг друга и часто применяются в сочетании друг с другом.
В главе 13 рассматривается пример планирования проекта, соответствующего примеру проектирования системы из главы 5. Рассматриваемый проект также является практическим примером, демонстрирующим процесс планирования проекта от начала до конца. Пример занимает в этой главе центральное место, а методам отводится вторичная роль.
Последняя глава слегка отступает от технических аспектов планирования. В ней предлагается подборка рекомендаций, советов, точек зрения и идей процесса разработки. Глава начинается с ответа на важный вопрос: когда следует заниматься планированием проекта? А в конце главы рассматривается влияние плана проекта на качество.
Приложение А показывает, как отслеживать прогресс проекта с учетом плана и как принимать меры по исправлению ситуации, когда это потребуется. Отслеживание проекта в большей степени направлено на управление проектом, нежели на планирование проектов, но оно критично для выполнения ваших обязательств после начала работы.
Архитектура сама по себе достаточно широка и неопределенна, и вам придется спроектировать подробности каждого из ее компонентов. Самая важная из этих подробностей — контракты сервисов. В приложении Б обозначен правильный путь проектирования контрактов сервисов. Кроме того, обсуждение модульности, размера и затрат очень хорошо сочетается с большинством глав этой книги.
В приложении В приведен объединенный список ключевых директив, рекомендаций, а также того, что можно или чего нельзя делать в тех или иных ситуациях. Стандарт предельно лаконичен; в нем можно найти ответы на вопрос «что?», а не «почему?». Обоснования стандарта содержатся в основном тексте книги.
Хотя книга написана для архитекторов в области разработки программных систем, она имеет намного более широкую аудиторию. Предполагается, что вы — читатель — являетесь архитектором или старшим специалистом в области разработки, менеджером проекта или совмещаете сразу несколько ролей. Разработчики-энтузиасты, желающие повысить свою квалификацию, найдут в книге много полезного. Впрочем, независимо от вашей текущей должности книга откроет перед вами немало дверей на протяжении всей вашей карьеры. Даже если вы не являетесь опытным архитектором, открывая первую страницу книги, после ее прочтения и освоения методологии вы будете стоять наравне со специалистами высочайшего уровня.
Методы и идеи, представленные в книге, актуальны независимо от языка программирования (будь то C++, Java, C# или Python), платформы (Windows, Linux, мобильные, локальные и облачные) и размера проекта (от самых мелких до самых крупных проектов). Также они актуальны для всех отраслей (от здравоохранения до обороны), любых бизнес-моделей и размеров компаний (от начинающих фирм до крупных корпораций).
Самое важное предположение, которое я делаю относительно читателя, — что вам действительно небезразлично то, чем вы занимаетесь, а текущие неудачи и потери вас беспокоят. Вы хотите добиться большего, но не знаете, что делать, или вас сбивают с толку некачественные практики.
Единственное обязательное условие — непредвзятый ум. Прошлые неудачи и огорчения весьма желательны.
На веб-странице книги представлены файлы примеров, дополнения и исправления ошибок. Страница доступна по адресу
http://www.rightingsoftware.org
Вы найдете файлы и вспомогательный материал для книги по ссылке DownloadSupportFiles.
За дополнительной информацией о книге обращайтесь по адресу
informit.com/title/9780136524038
Также с автором можно связаться по адресу
http://www.idesign.net
Прежде всего, хочу поблагодарить двух друзей, которые убедили меня написать эту книгу, причем каждый по-своему: Гад Меир (Gad Meir) и Яркко Кемппайнен (Jarkko Kemppainen).
Спасибо редактору и по совместительству источнику объективной критики Дэйву Киллиану (Dave Killian): еще немного правки, и мне пришлось бы указывать тебя как соавтора. Спасибо Бет Сайрон (Beth Siron) за рецензирование чернового варианта рукописи. Следующие люди выделили свое время на рецензирование рукописи: Чед Майкл (Chad Michel), Дуг Дархэм (Doug Durham), Джордж Стивенс (George Stevens), Джош Лойд (Josh Loyd), Риккардо Беннет-Ловси (Riccardo Bennett-Lovsey) и Стив Лэнд (Steve Land).
Наконец, я благодарен своей жене Дане, которая вдохновляет меня писать книги и помогает выделить время, освобождая меня от семейных обязанностей. И спасибо моим родителям, привившим мне любовь к техническим дисциплинам.
1 Edsger W. Dijkstra. The Humble Programmer: ACM Turing Lecture. Communications of the ACM 15, no. 10 (October 1972): 859–866. (Дейкстра Эдсгер. Смиренный программист.)
Джувел Лёве, основатель IDesign, — старший архитектор программных систем, специализирующийся на проектировании систем и планировании проектов. Помогал бесчисленным компаниям по всему миру создавать качественные продукты в рамках сроков и бюджета. Признанный компанией Microsoft как один из ведущих экспертов и отраслевых лидеров, он принимал участие во внутреннем стратегическом анализе архитектуры C#, WCF и других сопутствующих технологий и был прозван «легендой от программирования». Опубликовал несколько книг и множество статей практически по всем аспектам современной разработки. Лёве часто выступает на крупных международных конференциях разработчиков и проводит мастер-классы по всему миру. Он обучает тысячи профессионалов навыкам, необходимым для современных архитекторов программных систем, и лидерству, нужному в проектировании, процессах и технологиях.
Цветные версии рисунков вы можете посмотреть, отсканировав QR-код.
Ваши замечания, предложения, вопросы отправляйте по адресу [email protected] (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
Для начинающего архитектора существует множество вариантов решения практически любой задачи.
Для опытного архитектора хороших вариантов совсем немного.
Программная архитектура представляет собой высокоуровневый результат проектирования и структуру программной системы. Хотя проектирование системы занимает немного времени и обходится недорого по сравнению с ее построением, исключительно важно правильно реализовать архитектуру. Если после того, как система будет построена, окажется, что архитектура несовершенна, ошибочна или просто не подходит для ваших целей, сопровождение и расширение системы будет обходиться чрезвычайно дорого.
Сущность архитектуры любой системы заключается в разбиении концепции системы как целого (будь то дом, компьютер или программная система) на составляющие. Хороший архитектор также предписывает, как эти компоненты должны взаимодействовать друг с другом во время выполнения. Акт выявления составляющих компонентов системы называется декомпозицией системы.
Правильная декомпозиция исключительно важна для проектирования. Ошибки в ходе декомпозиции приводят к неправильной архитектуре, которая обернется колоссальными проблемами в будущем, а в некоторых ситуациях систему приходится переписывать с нуля.
В прошлом такими структурными элементами были объекты C++, а позднее — компоненты COM, Java или .NET. В современных системах и в этой книге самой детализированной единицей архитектуры является сервис (в сервисно-ориентированном понимании). Однако технология, применяемая для реализации компонентов, и их подробности (интерфейсы, операции и иерархии классов) относятся к подробностям проектирования, а не к декомпозиции системы. Такие подробности могут изменяться, и эти изменения никак не повлияют на декомпозицию и архитектуру.
К сожалению, многие программные системы (а возможно, абсолютное большинство) проектируются неправильно; можно даже сказать, что они спроектированы худшим из всех возможных способом. Дефекты проектирования являются прямым результатом неправильной декомпозиции систем.
По этой причине данная глава начинается с объяснения того, почему некоторые распространенные способы декомпозиции принципиально неверны. Далее приводятся обоснования процесса декомпозиции, заложенного в основу Метода. Также будут представлены некоторые мощные и полезные приемы, которые могут применяться при проектировании системы.
Функциональная декомпозиция разбивает систему на структурные элементы, определяемые функциональностью системы. Например, если система должна выполнять некий набор операций (например, выставление счетов, оплату и поставку товара), вы определяете сервисы Invoicing, Billing и Shipping.
Функциональная декомпозиция обладает множеством серьезных недостатков. Как минимум функциональная декомпозиция связывает сервисы с требованиями, потому что сервисы отражают требования. Любые изменения в требуемой функциональности приводят к изменению функциональных сервисов. Такие изменения неизбежно возникают с течением времени и приводят к болезненным будущим изменениям в системе, требуя выполнения запоздалой повторной декомпозиции, отражающей новые требования. Помимо дорогостоящих изменений в системе, функциональная декомпозиция препятствует повторному использованию и ведет к появлению слишком сложных систем и клиентов.
Возьмем простую систему с функциональной декомпозицией, использующую три сервиса, A, B и C, которые вызываются в следующем порядке: сначала A, затем B, и затем C. Так как функциональная декомпозиция также совпадает с декомпозицией по времени (сначала A, потом B), она фактически препятствует отдельному повторному использованию сервисов. Предположим, другой системе также необходим сервис B (допустим, Billing). В основу сервиса B встроено представление о том, что он вызывается после A, но до C (например, сначала выставляется счет, затем производится оплата, и только после этого происходит отгрузка). Любая попытка взять сервис B из первой системы и перенести его во вторую систему завершится неудачей, потому что во второй системе никто не выполняет A до, а C после B. При перенесении B к нему привязываются сервисы A и C. B не является независимым сервисом, пригодным для повторного использования, — A, B и C образуют конгломерат из тесно связанных сервисов.
Один из способов выполнения функциональной декомпозиции — создание сервисов для всех разновидностей функциональности. Этот способ декомпозиции приводит к взрывному росту количества сервисов, так как система сколько-нибудь приличного размера может содержать сотни видов функциональности. Вы не только получите слишком много сервисов, но и в этих сервисах часто будет дублироваться большой объем общей функциональности, адаптированной для конкретного случая. Размножение сервисов приводит к непропорционально высоким затратам на интеграцию и тестирование, а также к повышению общей сложности.
Другой способ функциональной декомпозиции основан на объединении всех возможных способов выполнения операций в «мегасервисы». Это приводит к разбуханию сервисов, в результате чего они становятся слишком сложными, а их сопровождение — практически невозможным. Такие монолиты превращаются в уродливые свалки для всех взаимосвязанных разновидностей исходной функциональности вместе с неочевидными отношениями внутри сервисов и между ними.
Таким образом, функциональная декомпозиция часто ведет к тому, что сервисы становятся слишком большими (тогда их слишком мало) или слишком мелкими (тогда их слишком много). Обе напасти нередко встречаются одновременно в одной системе.
ПРИМЕЧАНИЕ В приложении Б, посвященном проектированию контрактов сервисов, более подробно обсуждаются неприятные последствия от слишком малого или слишком большого количества сервисов, а также от их влияния на проект.
Функциональная декомпозиция часто приводит к уплощению системной иерархии. Поскольку каждый сервис или структурный элемент посвящен конкретной функциональности, кто-то должен объединить эти разрозненные функциональности в требуемое поведение. Этим «кем-то» нередко становится клиент. Когда клиент координирует работу сервисов, вы получаете плоскую систему из двух уровней: клиенты и сервисы; все промежуточные прослойки при этом исчезают. Допустим, ваша система должна выполнять три операции (или области функциональности): A, B и C, именно в таком порядке. Как показано на рис. 2.1, клиент должен «сшить» эти сервисы в единое целое.
Набивая клиент логикой координации, вы загрязняете клиентский код бизнес-логикой системы. Роль клиента уже не ограничивается вызовом операций в системе или представлением информации для пользователей. Клиент теперь
Рис. 2.1. Координация функциональности из раздувшегося клиента
должен знать все внутренние сервисы до мелочей: как вызывать их, как обрабатывать ошибки, как компенсировать сбой B после успеха A и т.д. Вызов сервисов почти всегда осуществляется синхронно, потому что клиент идет по ожидаемой последовательности «A, потом B, потом C», и другим способом было бы трудно обеспечить последовательность вызовов и обеспечить быструю реакцию на внешние события. Более того, клиент теперь жестко связывается с необходимой функциональностью. Любые изменения в операциях (скажем, вызов B’ вместо B) должны быть отражены в клиенте. В идеале клиент и сервисы должны быть способны эволюционировать независимо друг от друга. Десятилетия назад программисты обнаружили, что включение бизнес-логики на стороне клиента почти всегда нежелательно. Тем не менее при таком проектировании, как на рис. 2.1, вы вынуждены загрязнять клиента бизнес-логикой соблюдения последовательности, упорядочения, компенсации ошибок и продолжительности вызовов. В конечном счете клиент перестает быть клиентом — он становится системой.
А что, если разнотипные клиенты (например, толстые (rich) клиенты, веб-страницы, мобильные устройства) пытаются вызвать одну и ту же последовательность функциональных сервисов? Вам неизбежно придется дублировать логику между клиентами, что сделает их сопровождение слишком неэффективным и дорогостоящим. С изменением функциональности вам придется синхронизировать изменения между разными клиентами, поскольку они отразятся на всех клиентах. Часто в таких ситуациях разработчики стараются избежать любых изменений в функциональности сервисов из-за каскадного воздействия на клиентов. Если вы создали множество клиентов, каждый из которых имеет собственную версию последовательности вызовов, адаптированную для его потребностей, с изменением или заменой сервисов возникает еще больше проблем, что препятствует повторному использованию поведения между клиентами. По сути, приходится заниматься сопровождением нескольких сложных систем, пытаясь синхронизировать их. В конечном итоге это приводит как к торможению нововведений, так и к затягиванию выпуска новой версии при принудительном введении изменений в разработку и производство.
В качестве иллюстрации проблем функциональной декомпозиции, рассмотренных выше, возьмем рис. 2.2. На нем представлена схема анализа цикломатической сложности одной системы, которой я занимался. В качестве методологии проектирования была избрана функциональная декомпозиция.
Рис. 2.2. Анализ сложности результатов функционального проектирования
Цикломатическая сложность оценивает количество независимых путей в коде класса или сервиса. Чем сложнее устроены и теснее связаны между собой внутренние механизмы, тем выше показатель цикломатической сложности. Программа, использованная для построения рис. 2.2, измерила и построила оценки для различных классов в системе. Чем сложнее класс, тем он больше и темнее на этой схеме. С первого же взгляда мы видим три очень больших и очень сложных класса. Насколько легко будет сопровождать MainForm? Что это — форма, элемент пользовательского интерфейса, канал для взаимодействия пользователя с системой или целая система? Обратите внимание на сложность, необходимую для настройки MainForm, — она отражена в размере и цвете FormSetup. Не отстает от них и класс Resources — изменение ресурсов, используемых в MainForm, оказывается очень сложным делом.
В идеале класс Resources устроен тривиально: он должен представлять собой простые списки изображений и строк. Остальная часть системы строится из десятков меньших простых классов, каждый из которых посвящен конкретной функциональности. Меньшие классы буквально оказались в тени трех огромных классов. Тем не менее, хотя каждый из этих меньших классов может быть тривиальным, огромное количество меньших классов само по себе создает проблему сложности из-за тонкостей интеграции между всеми классами. В результате мы имеем и слишком мелкие компоненты, и слишком крупные компоненты, и разбухший клиент.
Еще один недостаток декомпозиции на рис. 2.1 — необходимость множества точек входа в систему. Клиент (или клиенты) должен входить в систему в трех местах: для A, для B и для C. Это означает, что вам придется в нескольких местах беспокоиться об аутентификации, авторизации, масштабируемости, управлении экземплярами, распространении транзакций, идентификации, размещении и т.д. Если вам понадобится изменить реализацию любого из этих аспектов, придется изменять ее в разных местах по всем сервисам и клиентам. Со временем из-за этих многочисленных изменений добавление новых клиентов будет обходиться очень дорого.
В качестве альтернативы последовательности функциональных сервисов на рис. 2.1 можно рассмотреть то, что на первый взгляд является меньшим злом: функциональные сервисы вызывают друг друга, как показано на рис. 2.3.
У такого подхода есть преимущество: клиенты получаются простыми и даже асинхронными. Клиент выдает вызов сервиса A; затем сервис A вызывает B, а B вызывает C.
Рис. 2.3. Цепочки функциональных сервисов
Но теперь появляется другая проблема: функциональные сервисы тесно связаны друг с другом и с порядком функциональных вызовов. Например, сервис оплаты может вызываться только после сервиса выставления счета, но до сервиса отгрузки. В случае рис. 2.3 в сервис A встраивается информация о том, что он должен вызвать сервис B. Сервис B может вызываться только после сервиса A и до сервиса C. Изменение в требуемом порядке вызовов с большой вероятностью отразится на всех сервисах выше и ниже в цепочке, потому что их реализация должна измениться в соответствии с новыми требованиями к порядку.
Но рис. 2.3 не раскрывает полной картины. Сервис B на рис. 2.3 кардинально отличается от сервиса B на рис. 2.1. Исходный сервис B выполнял только функциональность B. Сервис B на рис. 2.3 должен знать о существовании сервиса C, а в контракт B должны быть включены параметры, которые потребуются сервису C для выполнения его функциональности. На рис. 2.1 за эти подробности отвечал клиент.
Проблема усугубляется сервисом A. Теперь он должен включить в свой контракт параметры, необходимые для вызова сервисов B и C, чтобы те могли выполнить свою соответствующую бизнес-функциональность. Любые изменения в функциональности B и C отражаются в изменениях реализации сервиса A, который теперь тесно связан с ними. Разбухание и привязки такого рода представлены на рис. 2.4.
Рис. 2.4. Объединение функциональности в цепочки приводит к разбуханию сервисов
Как ни печально, даже рис. 2.4 не показывает всей правды. Допустим, сервис A успешно выполнил функциональность A, после чего перешел к вызову сервиса B для выполнения функциональности B. Однако сервис B столкнулся с ошибкой и не смог нормально отработать. Если сервис A вызывал B синхронно, то он должен располагать полной информацией о внутренней логике и состоянии B, чтобы провести восстановление после ошибки. А это означает, что функциональность B также должна присутствовать в сервисе A. Если A вызывает B асинхронно, то сервис B должен каким-то образом вернуть информацию A для отмены функциональности A или же выполнить откат A собственными силами. Другими словами, функциональность A также должна присутствовать в B. Тем самым создается тесная связь между сервисами B и A, а сервис B разбухает из-за необходимости компенсировать успешное выполнение сервиса A. Ситуация изображена на рис. 2.5.
Проблема усугубляется сервисом C. Что, если функциональности A и B отработали успешно и завершились, но сервис C не смог выполнить свою бизнес-функцию? Сервис C должен вернуться к сервисам B и A для отмены их операций. Это приводит к дополнительному разбуханию сервиса C и его связыванию с сервисами A и B. Если взять ситуацию на рис. 2.5, что потребуется для
Рис. 2.5. Дополнительное разбухание и связи из-за необходимости компенсации
замены сервиса B сервисом B’, который выполняет свою функциональность не так, как B? Какие нежелательные последствия это будет иметь для сервисов A и C? И какая степень повторного использования достигается на рис. 2.5, если функциональность сервисов будет востребована в других контекстах — скажем, если сервис B вызывается после D, но до E? Что собой представляют A, B и C — три разнородных сервиса или одну спекшуюся бесформенную массу?
В функциональной декомпозиции кроется почти неудержимый соблазн. Она кажется простым и логичным способом проектирования системы: от вас требуется всего лишь составить список необходимых функциональностей, а затем создать в архитектуре компонент для каждого пункта. Функциональная декомпозиция (и ее близкий родственник — декомпозиция предметных областей, о которой будет рассказано ниже) применяется при проектировании большинства систем. Многие проектировщики считают функциональную декомпозицию естественным выбором, и скорее всего, ваш преподаватель информатики в институте объяснял вам именно этот способ. Распространенность функциональной декомпозиции в плохо спроектированных системах — почти идеальный признак того, что от нее стоит держаться подальше. Всеми силами сопротивляйтесь искушениям функциональной декомпозиции.
Чтобы доказать, что функциональная декомпозиция нежизнеспособна, можно вообще обойтись без аргументов из области программирования. Доказательство кроется в самой природе Вселенной, а конкретно в первом законе термодинамики. Если отбросить математику, первый закон термодинамики просто гласит, что нельзя создать что-то полезное без приложения усилий. В просторечии это называется принципом TANSTAAFL (сокращение от «There ain’t no such thing as a free lunch», то есть «бесплатных завтраков не бывает» — в смысле «даром ничего не дается»).
Проектирование по своей природе является деятельностью с высокой добавочной ценностью. Вы читаете эту книгу вместо еще одной книги по программированию именно потому, что вы понимаете полезность проектирования, или, говоря иначе, вы считаете, что проектирование обладает добавочной ценностью (и немалой!).
Проблема функциональной декомпозиции заключается в том, что она пытается обойти первый закон термодинамики. Результат функциональной декомпозиции (а именно проектирование системы) должен быть деятельностью с высокой добавочной ценностью. Однако функциональная декомпозиция проста и прямолинейна: для заданного набора требований, предусматривающих выполнение функциональностей A, B и C, вы проводите декомпозицию на сервисы A, B и C. «Проще простого! — говорите вы. — Функциональная декомпозиция настолько проста, что с ней справится даже программа». Но именно потому, что этот способ проектирования отличается быстротой, простотой, механистичностью и прямолинейностью, он противоречит первому закону термодинамики. Так как ценность не может добавляться без усилий, сами атрибуты, которые делают функциональную декомпозицию столь привлекательной, не позволяют ей создавать добавочную ценность.
Убедить коллег и руководство использовать что-то другое вместо функциональной декомпозиции — весьма непростое дело. «Мы всегда так делали», — скажут они вам. На этот довод можно привести два возражения. Во-первых, можно ответить: «И сколько раз мы выдерживали сроки или бюджет, которые были утверждены изначально? Что у нас получалось с качеством и сложностью? Насколько просто осуществлялось сопровождение системы?»
Во-вторых, можно провести сеанс антипроектирования. Сообщите команде, что вы проводите конкурс по проектированию системы следующего поколения. Разбейте команду на две половины и рассадите их в разных комнатах. Предложите первой половине разработать лучшее проектировочное решение для системы. Затем предложите второй половине разработать худшее из всех возможных решений: то, которое бы предельно затрудняло сопровождение и расширение системы, которое бы препятствовало повторному использованию и т.д. Дайте им поработать в течение нескольких часов, а затем соберите вместе. При сравнении результатов обычно выясняется, что они выдали практически одинаковые результаты. Подписи на компонентах могут различаться, но суть останется неизменной. Только теперь признайтесь, что они работали над разными задачами, и обсудите последствия. Возможно, пришло время поискать новый подход.
Тот факт, что при проектировании никогда не следует применять функциональную декомпозицию, — универсальное наблюдение, которое не имеет никакого отношения к программным системам. Возьмем построение функциональности дома, как если бы он был программной системой. Все начинается с перечисления необходимой функциональности: приготовление еды, игры, отдых, сон и т.д. Затем для каждого вида функциональности в архитектуре создается отдельный компонент, как показано на рис. 2.6.
Рис. 2.6. Функциональная декомпозиция дома
Хотя рис. 2.6 уже выглядит нелепо, настоящее безумие проявляется только тогда, когда приходит время строить дом. Вы начинаете с пустого участка земли и беретесь за приготовление еды. Только приготовление еды. Вы вынимаете микроволновку из коробки и откладываете ее в сторону. Затем вы заливаете бетоном маленькую площадку, ставите на нее деревянный каркас, накрываете крышкой и ставите на нее микроволновку. Остается построить для микроволновки маленький чулан, сколотить над ней крошечную крышу и подключить к электросети. «Порядок, теперь можно готовить!» — объявляете вы начальству и заказчикам.
Но так ли это? Разве можно готовить еду таким способом? Где вы собираетесь ее подавать, где хранить остатки, куда выкидывать мусор? А как насчет приготовления еды на газовой плите? Что потребуется для того, чтобы повторить эту процедуру для плиты? Какая степень повторного использования может быть достигнута между этими двумя разными способами выражения функциональности приготовления еды? Можно ли легко расширить только одну из них? А если вам потребуется переставить микроволновку в другое место? Все эти проблемы даже не могут считаться первым шагом, потому что все зависит от того, что именно вы готовите. Возможно, вам придется строить разную функциональность приготовления еды, если в процессе приготовления задействованы разные кухонные устройства, а результаты могут отличаться по контексту — например, если вы готовите завтрак, обед, ужин, десерт или легкую закуску. В результате вы получаете либо мириады крошечных сервисов, предназначенных для конкретного сценария, который должен быть известен заранее, либо получается один огромный сервис, в котором есть все. Возможно ли построить дом при подобном подходе? А если нет, то стоит ли так проектировать и строить программные системы?
Когда применяется функциональная декомпозиция
Все это глумление вовсе не означает, что функциональная декомпозиция в принципе плоха. У функциональной декомпозиции есть свое место — это превосходный метод выявления требований. Она помогает архитекторам (или руководителям продукта) выявлять скрытые или неочевидные области функциональности. Даже при нечетких функциональных требованиях можно начать с верхнего уровня и провести функциональную декомпозицию до уровня с высокой детализацией. Вы выявляете требования и отношения между ними, встраиваете требования в древовидную систему и выявляете избыточные аспекты или взаимоисключающую функциональность. Тем не менее попытка превратить функциональную декомпозицию в проектировочное решение ведет к фатальным последствиям. Между требованиями и проектировочным решением никогда не должно быть прямого соответствия.
Проектное решение дома на рис. 2.6 очевидно абсурдно. Скорее всего, в своем доме вы предпочитаете готовить на кухне, поэтому на рис. 2.7 изображена альтернативная декомпозиция дома. Такая форма декомпозиции называется декомпозицией предметной области: система разбивается на структурные элементы в соответствии с предметными бизнес-областями: продажи, техническое обеспечение, бухгалтерия, отгрузка и т.д. К сожалению, декомпозиция предметной области (рис. 2.7) на практике работает еще хуже функциональной декомпозиции на рис. 2.6. Причина заключается в том, что она остается замаскированной функциональной декомпозицией: на кухне (Kitchen) вы готовите, в спальне (Bedroom) спите, в гараже (Garage) ставите машину и т.д.
Собственно, каждая из функциональных областей на рис. 2.6 может быть отображена на предметные области на рис. 2.7, что создает серьезные проблемы. Каждая спальня может быть уникальной, но функциональность сна должна дублироваться во всех спальнях. Дальнейшее дублирование возникает тогда, когда вы спите перед телевизором в гостиной или развлекаете гостей на кухне (практически все вечеринки почему-то заканчиваются на кухне).
Рис. 2.7. Декомпозиция предметной области для дома
Каждая предметная область часто деградирует до уродливой свалки функциональности, что только повышает ее внутреннюю сложность. Повышение внутренней сложности заставляет разработчика держаться подальше от проблем межобластных связей, а взаимодействие между предметными областями обычно сокращается до простых изменений состояния (в стиле CRUD) вместо действий, инициирующих выполнение нужного поведения во всех областях. Компоновка более сложных вариантов поведения между предметными областями становится очень сложной задачей. Некоторые функциональности просто не могут быть представлены в декомпозициях предметных областей. Для примера возьмем дом на рис. 2.7: где бы вы готовили еду, которая не может готовиться на кухне (например, барбекю)?
Как и с чисто функциональным подходом, настоящие проблемы с декомпозицией предметной области становятся очевидными в ходе построения. Представьте, что вы строите дом на основе декомпозиции на рис. 2.7. Строительство начинается с пустого участка земли. Вы роете котлован для фундамента кухни, заливаете его бетоном (только для кухни!) и добавляете арматуру. Затем вы воздвигаете стены кухни (все они должны быть внешними), закрепляете их на фундаменте, прокладываете электропроводку и трубы в стенах; подключаете кухонное оборудование к водопроводу, газопроводу и электросети; устанавливаете системы обогрева и охлаждения; ставите счетчики расхода воды, электричества и газа; строите крышу над кухней; красите стены изнутри; развешиваете шкафы; покрываете штукатуркой наружные стены (то есть все стены) и красите их. Наконец, вы объявляете заказчику, что модуль Kitchen готов, а контрольная точка 1.0 достигнута.