Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
Другие эвристические принципы В предыдущих разделах были рассмотрены основные эвристические принципы проектирования ПО. Ниже описаны менее полезные, однако заслуживающие упо- минания эвристические принципы. Стремитесь к максимальной связности Понятие связности (cohesion) возникло в области структурного проектирования и обычно обсуждается в том же контексте, что и сопряжение (coupling). Связность характеризует то, насколько хорошо все методы класса или все фрагменты мето- да соответствуют главной цели, — иначе говоря, насколько сфокусирован класс. Классы, состоящие из очень похожих по функциональности блоков, обладают высокой степенью связности, и наша эвристическая цель состоит в том, чтобы целостность была как можно выше. Связность — полезный инструмент управления сложностью, потому что чем лучше код класса соответствует главной цели, тем проще запомнить все, что код выполняет. Стремление к связности на уровне методов давно считается полезным эвристи- ческим принципом. На уровне классов эвристический принцип связности во многом выражен в более общем эвристическом принципе адекватного определения абстракций, что уже обсуждалось в этой главе и будет еще обсуждаться в главе 6. Абстрагирование полезно и на уровне методов, но в этом случае принципы абстрагирования и связности более равноправны. Формируйте иерархии Иерархия — это многоуровневая структура организации информации, при которой наиболее общая или абстрактная репрезентация концепции соответствует вершине, а более детальные специализированные репрезентации — более низким уровням. При разработке ПО иерархии обнаруживаются, например, в наборах классов и в последовательностях вызовов методов (уровень 4 на рис. 5-2). Формирование иерархий уже более 2000 лет является важным средством управле- ния сложными наборами информации. Так, Аристотель использовал иерархию для организации царства животных. Люди часто организуют сложную информацию (такую как эта книга) при помощи иерархических схем. Ученые обнаружили, что люди в целом находят иерархии естественным способом организации сложной информации. Рисуя сложный объект (скажем, дом), люди рисуют его иерархиче- ски. Сначала они рисуют очертания дома, затем окна и двери, а после этого — еще более подробные детали. Они не рисуют дом по отдельным кирпичам, доскам или гвоздям (Simon, 1996). Иерархии помогают в достижении Главного Технического Императива Разработ- ки ПО, позволяя сосредоточиться только на том уровне детальности, который ГЛАВА 5 Проектирование при конструировании 103 заботит вас в конкретный момент. Иерархия не устраняет детали — она просто выталкивает их на другой уровень, чтобы вы могли думать о них, когда захотите, а не все время. Формализуйте контракты классов На более детальном уровне полезную информацию можно получить, рассматривая интерфейс каждого класса как кон- тракт с остальными частями программы. Обычно контракт имеет форму «Если вы обещаете предоставить данные x, y и z и гарантируете, что они будут иметь характеристики a, b и c, я обязуюсь выполнить операции 1, 2 и 3 с ограничениями 8, 9 и 10». Обещания клиентов классу обычно называются предусловиями (preconditions), а обязательства класса перед клиентами — по- стусловиями (postconditions). Контракты помогают управлять сложностью, потому что хотя бы теоретически объект может свободно игнорировать любое поведение, не описанное в контракте. На практике этот вопрос куда сложнее. Грамотно назначайте сферы ответственности Еще один эвристический принцип — обдумывание сфер ответственности, которые следует назначить объектам. Рассмотрение сферы ответственности объекта ана- логично вопросу о том, какую информацию он должен скрывать, но мне кажется, что первый способ может привести к более общим ответам, чем и объясняется уникальность этого эвристического принципа. Проектируйте систему для тестирования На некоторые интересные идеи можно натолкнуться, спросив, как будет выглядеть система, если спроектировать ее для обеспечения максимальной легкости тестиро- вания. Отделять ли пользовательский интерфейс от остальной части программы, чтобы протестировать его независимо? Организовывать ли каждую подсистему так, чтобы минимизировать ее зависимость от других подсистем? Проектирование для тестирования часто приводит к разработке более формализованных интерфейсов классов, что обычно выгодно. Избегайте неудач Профессор гражданского строительства Генри Петроски в интересной книге «Design Paradigms: Case Histories of Error and Judgment in Engineering» (Petroski, 1994), посвященной истории неудач в отрасли проектирования мостов, утверждает, что многие известные мосты рушились из-за чрезмерного внимания к прошлым успехам и неадекватного рассмотрения возможных причин аварий. Он делает вы- вод, что аварий вроде крушения моста Tacoma Narrows можно было бы избежать, если б инженеры тщательно рассматривали возможные причины аварий, а не просто копировали другие успешные проекты. Крупные бреши в защите многих известных систем, обнаруженные в прошедшие годы, заставляют подумать о том, как применить идеи Петроски в области про- ектирования ПО. Перекрестная ссылка О контрак- тах см. подраздел «Используйте утверждения для документиро- вания и проверки предусловий и постусловий» раздела 8.2. 104 ЧАСТЬ II Высококачественный код Тщательно выбирайте время связывания Временем связывания (binding time) называют тот момент, когда переменной присваивается конкретное значение. Раннее связывание обычно упрощает код, но и снижает его гибкость. Иногда к полезным идеям проектирования можно прийти, спросив себя: «Что, если связать эти значения раньше? Что, если связать их позже? Что, если инициализировать эту таблицу в этом месте кода? Что, если получить значение этой переменной от пользователя в период выполнения про- граммы?» Создайте центральные точки управления Ф. Дж. Плоджер говорит, что главным его принципом является «Принцип Одного Верного Места: в программе должно быть Одно Верное Место для поиска нетри- виального фрагмента кода и Одно Верное Место для внесения вероятных изме- нений» (Plauger, 1993). Управление может быть централизовано в классах, мето- дах, макросах препроцессора, файлах, включаемых директивой #include, — даже именованная константа может быть центральной точкой управления. Этот принцип также способствует снижению сложности: если какой-то программ- ный элемент встречается в минимальном числе фрагментов, его изменение ока- жется проще и безопаснее. Подумайте об использовании грубой силы Грубая сила — один из мощнейших эвристических инстру- ментов. Не стоит ее недооценивать. Работоспособное реше- ние проблемы методом грубой силы лучше, чем элегантное, но не работающее решение. Создавать элегантные решения зачастую долго и сложно. Так, описывая историю разработки алгоритмов поиска, Дональд Кнут указал, что, хотя первое описание алгоритма двоичного поиска было опубликовано в 1946 г., алгоритм, правильно обрабаты- вающий списки всех размеров, был разработан только спустя 16 лет (Knuth, 1998). Двоичный поиск элегантнее, но и основанный на грубой силе последовательный поиск часто приемлем. Рисуйте диаграммы Диаграммы — еще один мощный эвристический инструмент. Все знают, что лучше один раз увидеть, чем сто раз услышать. Диаграммы позволяют представить про- блему на более высоком уровне абстракции, и никакие описания их не заменят. Помните: иногда проблему следует рассматривать на детальном уровне, а иногда целесообразно иметь дело с более общими аспектами. Поддерживайте модульность проекта системы Этот принцип подразумевает, что каждый метод или класс должен быть похож на «черный ящик»: вы знаете, что в него поступает и что из него выходит, но не знаете, что происходит внутри. Черный ящик имеет такой простой интерфейс и такую ясную функциональность, что для любых конкретных входных данных можно точно предсказать соответствующие выходные данные. Перекрестная ссылка О времени связывания см. раздел 10.6. Если сомневаетесь, используйте грубую силу. Батлер Лэмпсон (Butler Lampson) ГЛАВА 5 Проектирование при конструировании 105 Концепция модульности связана с сокрытием информации, инкапсуляцией и дру- гими эвристическими принципами проектирования. И все же размышление о том, как собрать систему из набора черных ящиков иногда приводит к догадкам, к ко- торым сокрытие информации и инкапсуляция привести не могут, так что принцип модульности заслужил право занять место в вашем арсенале полезных идей. Резюме эвристических принципов проектирования Ниже приведен список основных эвристических принципов проектирования: 쐽 определите объекты реального мира; 쐽 определите согласованные абстракции; 쐽 инкапсулируйте детали реализации; 쐽 используйте наследование, когда это возможно; 쐽 скрывайте секреты (помните про сокрытие информации); 쐽 определите области вероятных изменений; 쐽 поддерживайте сопряжение слабым; 쐽 старайтесь использовать популярные шаблоны проекти- рования. Следующие эвристические принципы также иногда бывают полезны: 쐽 стремитесь к максимальной связности; 쐽 формируйте иерархии; 쐽 формализуйте контракты классов; 쐽 грамотно назначайте сферы ответственности; 쐽 проектируйте систему для тестирования; 쐽 избегайте неудач; 쐽 тщательно выбирайте время связывания; 쐽 создайте центральные точки управления; 쐽 подумайте об использовании грубой силы; 쐽 рисуйте диаграммы; 쐽 поддерживайте модульность проекта системы. Советы по использованию эвристических принципов Подходы к проектированию ПО могут быть основаны на подходах, применяемых в других областях. Одной из первых книг, посвященных использованию эвристики при решении проблем, является «How to Solve It (Как решать задачу)» Д. Полья (Polya, 1957). Обобщенный подход Полья к решению проблем концентрируется на решении мате- матических задач. Он резюмирован на рис. 5-10 (шрифт оригинала сохранен). Больше беспокоит то, что про- граммист вполне может выпол- нить ту же задачу двумя или тремя способами: иногда нео- сознанно, но довольно часто просто ради изменения или же создания элегантной вариации. А. Р. Браун и У. А. Сэмпсон (A. R. Brown and W. A. Sampson) http://cc2e.com/0592 106 ЧАСТЬ II Высококачественный код Рис. 5-10. Д. Полья разработал подход к решению математических задач, который полезен и при решении проблем, связанных с проектированием ПО (Polya 1957) Одним из самых ценных советов, которые можно дать по поводу проектирова- ния ПО, является использование разных подходов. Если проект, разработанный с помощью UML, неудачен, выразите его на обычном языке. Напишите небольшую тестовую программу. Попробуйте совершенно другой подход. Подумайте об ис- пользовании грубой силы. Продолжайте рисовать эскизы и наброски карандашом, и мозг последует за рукой. Если ничего не выходит, отложите решение проблемы. ГЛАВА 5 Проектирование при конструировании 107 Прежде чем возвращаться к работе над ней, прогуляйтесь, подумайте о чем-то другом. Довольно часто это приводит к более быстрому получению нужного ре- зультата, чем простое упорство. Никто не заставляет вас разработать весь проект за раз. Если натолкнетесь на препятствие, подумайте, обладаете ли вы информацией, достаточной для решения всех специфических проблем? Зачем через силу разрабатывать оставшиеся 20% проекта, если впоследствии они прекрасно станут на свое место? Зачем принимать неудачные решения, основанные на недостаточной информации, если позже можно будет принять более подходящие решения? Некоторые разработчики чувствуют дискомфорт, если не могут создать полный проект программы к окончанию этапа проектирования, но после создания нескольких удачных программ без заблаго- временного решения всех вопросов на этапе проектирования такая ситуация начинает казаться вполне естественной (Zahniser, 1992; Beck, 2000). 5.4. Методики проектирования Предыдущий раздел был посвящен эвристическим принципам, связанным с атри- бутами проектов. Иначе говоря, эти принципы характеризуют то, каким должен быть завершенный проект приложения. В этом разделе мы рассмотрим эвристиче- ские методики проектирования — действия, которые часто приводят к хорошим результатам. Используйте итерацию Возможно, у вас были случаи, когда вы так много узнали во время написания про- граммы, что желали бы написать ее заново, опираясь на полученные знания. Этот же феномен наблюдается и при проектировании, но этап проектирования короче, тогда как влияние, оказываемое им на последующие этапы, выражено сильнее, поэтому вы вполне можете выполнить этап проектирования несколько раз. Проектирование — итеративный процесс. Выйдя из точки А и достигнув точки Б, не останавливайтесь, а вернитесь в точку А. Изучая возможные варианты проектирования и пробуя разные подходы, вы будете рассматривать и высокоуровневые, и низкоуровневые аспекты. Общая картина, которую вы получаете при работе над высокоуровневыми вопросами, поможет вам лучше понять низкоуровневые детали. Детали, которые вы узнаете при работе над низкоуровневыми вопросами, помогут вам создать прочный фун- дамент для принятия высокоуровневых решений. Некоторые конфликты между высокоуровневыми и низкоуровневыми соображениями — вполне здоровое яв- ление; это напряжение способствует созданию структуры, более стабильной, чем структура, полностью созданная «сверху вниз» или «снизу вверх». Многим программистам — и вообще многим людям — трудно переключаться между высокоуровневыми и низкоуровневыми точками зрения, но эта способность — важное условие эффективного проектирования. Занимательные упражнения, по- зволяющие развить гибкость ума, можно найти в книге «Conceptual Blockbusting» (Adams, 2001), описанной в разделе «Дополнительные ресурсы» в конце главы. 108 ЧАСТЬ II Высококачественный код Если первая попытка создания проекта кажется вполне удач- ной, не останавливайтесь! Вторая попытка почти всегда оказывается лучше первой, и при каждой попытке вы буде- те узнавать что-то такое, что поможет вам улучшить общий проект. Говорят, что, когда Томаса Эдисона, который пытался создать нить лампочки и испробовал на тот момент уже тысячу разных материа- лов, спросили, не жалеет ли он о том, что зря потратил время, так ничего и не обнаружив, Эдисон ответил: «Ни в коей мере. Я обнаружил тысячу вариантов, ко- торые не работают». Во многих случаях, решив проблему при помощи одного подхода, вы получите знания, которые позволят решить проблему иным, более эффективным способом. Разделяй и властвуй Как указал Эдсгер Дейкстра, никто не обладает умом, способным вместить все детали сложной программы. То же можно сказать и о проектировании. Разделите программу на разные области и спроектируйте их по отдельности. Если, работая над одной из областей, вы попадете в тупик, вспомните про итерацию! Инкрементное улучшение — мощное средство управления сложностью. Вспомни- те, как Полья советовал решать математические задачи: поймите задачу, составьте план решения, осуществите план и оглянитесь назад, чтобы лучше понять, что и как вы сделали (Polya, 1957). Нисходящий и восходящий подходы к проектированию Слова «нисходящий» и «восходящий» могут казаться устаревшими, но они предо- ставляют много ценной информации об объектно-ориентированных способах проектирования. Нисходящее (top-down) проектирование начинается на высоком уровне абстракции. Например, вы сначала определяете базовые классы или другие неспецифические элементы проекта. По ходу работы вы повышаете уровень де- тальности и определяете производные классы, сотрудничающие классы и другие детали. Восходящее (bottom-up) проектирование начинается со специфики и постепенно переходит ко все большей общности. Как правило, оно начинается с определения конкретных объектов, на основе которых затем разрабатываются более общие объединения объектов и базовые классы. Некоторые разработчики утверждают, что лучше всего начинать с общего и дви- гаться по направлению к частному, а другие — что общие принципы проектиро- вания нельзя определить, не обдумав важных деталей. Аргументы обеих сторон описаны ниже. Аргументы в пользу нисходящего проектирования Нисходящее проектирование основано на том факте, что человеческий мозг в каждый конкретный момент времени может работать только с определенным объемом информации. Если вы начинаете проектирование с общих классов, вы- полняя их декомпозицию на более специализированные классы шаг за шагом, мозгу не приходится иметь дело со слишком большим числом деталей сразу. Перекрестная ссылка Безопас- ный способ попробовать разные варианты кода предоставляет рефакторинг (глава 24). ГЛАВА 5 Проектирование при конструировании 109 Процесс «разделяй и властвуй» итеративен в двух аспектах. Во-первых, потому, что, выполнив один этап декомпозиции, разработчики обычно не останавливаются, а выполняют еще несколько этапов. Во-вторых, потому, что дело обычно не огра- ничивается одной попыткой декомпозиции. Вы выполняете декомпозицию одним способом. На разных этапах декомпозиции у вас будут разные варианты разделения подсистем, формирования дерева наследования и группирования объектов. Вы делаете выбор и смотрите, что происходит. Затем вы возвращаетесь, выполняете декомпозицию иначе и смотрите, что работает лучше. После нескольких попыток вы получите хорошее представление о том, чт у будет работать и почему. До каких пор продолжать декомпозицию программы? До тех, пока вам не покажется, что вместо декомпозиции следующего уровня его было бы проще закодировать. До тех, пока очевидность и простота проекта не станут вас в каком-то смысле раздражать. В этот момент все готово. Если что-то неясно, продолжите декомпо- зицию. Если сейчас решение кажется вам хоть чуть-чуть хитрым, для любого, кто будет работать над ним позднее, оно станет головоломкой. Аргументы в пользу восходящего проектирования Иногда нисходящий подход настолько абстрактен, что его трудно начать. Если вам нужно работать с чем-то более реальным, попробуйте восходящий подход к проектированию. Спросите себя: «Какие функции эта система должна выполнять?» Несомненно, вы сможете ответить на этот вопрос. Вы можете определить несколько низкоуровневых аспектов ответственности, которые можно назначить конкретным классам. Так, вы можете знать, что система должна форматировать конкретный отчет, вычислять данные для отчета, центрировать его заголовки, отображать на экране, печатать на принтере и т. д. Определив несколько низкоуровневых аспектов ответственности, вы скорее всего почувствуете себя достаточно подготовленным, чтобы еще раз взглянуть на вершину. В других случаях основные атрибуты проекта могут быть продиктованы низкоуров- невыми факторами, такими как особенности взаимодействия с оборудованием. Вот некоторые рекомендации, о которых следует помнить при выполнении вос- ходящей композиции: 쐽 спросите себя, какие функции должна выполнять система; 쐽 опираясь на этот вопрос, определите конкретные объекты и их сферы ответ- ственности; 쐽 определите общие объекты и сгруппируйте их, организовав в подсистемы или пакеты, с помощью композиции или наследования (выберите самый подходя- щий вариант); 쐽 поднимитесь на следующий уровень или вернитесь на вершину и попробуйте еще раз начать нисходящее проектирование. Никакого конфликта нет Главное различие между нисходящей и восходящей стратегиями в том, что одна является стратегией декомпозиции, а вторая — композиции. В первом случае вы начинаете работу с общей проблемы, разбивая ее на управляемые фрагменты, во втором вы начинаете с управляемых фрагментов, составляя из них общее реше- 110 ЧАСТЬ II Высококачественный код ние. Оба подхода имеют достоинства и недостатки, которые следует рассмотреть в контексте конкретной проблемы. Сила нисходящего подхода — в простоте. Люди (особенно программисты) пре- красно умеют делить что-то крупное на меньшие компоненты. Еще одно достоинство в том, что нисходящее проектирование позволяет отложить работу над деталями конструирования. Изменения аспектов конструирования (таких как структура файлов или формат отчетов) часто сказываются на всей системе, поэтому лучше заранее знать, что эти детали следует скрыть в классах на нижних уровнях иерархии. Одно достоинство восходящего подхода объясняется тем, что он обычно приводит к раннему определению вспомогательной функциональности, что способствует созданию компактного, хорошо факторизованного проекта системы. Если похожая система уже создавалась, восходящий подход позволяет начать проектирование новой системы с рассмотрения и повторного использования фрагментов старой системы. Восходящая композиция имеет и недостатки: во-первых, ее трудно использовать без применения других подходов. Большинство людей находят разбиение круп- ной концепции на меньшие части более легким, чем объединение небольших концепций в более крупную. Это напоминает старую проблему, связанную с кон- структорами: модель кажется готовой, но почему в коробке остались детали? К счастью, никто не заставляет проектировать программы, применяя исключительно восходящий подход. Во-вторых, при восходящем проектировании иногда оказывается, что из исходных фрагментов создать программу невозможно. Никто не сможет собрать самолет из кирпичей. Чтобы узнать, какие фрагменты понадобятся на нижних уровнях, иногда нужно сначала получить общее представление о системе. Подведем итог: нисходящее проектирование обычно начинается с простого, но иногда низкоуровневые сложности прорываются на вершину, и это может при- водить к усложнению системы, которого можно было избежать. Восходящее про- ектирование начинается со сложных аспектов, но определение этой сложности на ранних этапах позволяет лучше спроектировать высокоуровневые классы.... если к этому моменту сложность не потопит всю систему! В конечном счете это не конкурирующие стратегии — они дополняют друг друга. Проектирование — эвристический процесс, а значит, универсальных решений не существует. Проектирование содержит элементы метода проб и ошибок. Пробуйте разные подходы, пока не найдете тот, что вас устроит. Экспериментальное прототипирование Иногда адекватность конкретного проекта невозможно оценить, не имея дополнительных сведений о деталях реа- лизации. Вы можете не знать, приемлема ли конкретная ор- ганизация базы данных, пока не узнаете, будет ли она удовлетворять конкретным требованиям к производительности. Вы можете не знать, приемлем ли проект http://cc2e.com/0599 ГЛАВА 5 Проектирование при конструировании 111 конкретной подсистемы, пока не будут выбраны конкретные библиотеки GUI. Это примеры существенной «грязи» при проектировании ПО: вы не можете полностью определить проблему проектирования, пока не решите ее хоть частично. Хорошо известен недорогой способ получить ответы на эти вопросы — экспери- ментальное прототипирование. В слово «прототипирование» люди вкладывают разный смысл (McConnell, 1996). В данном контексте оно означает написание абсолютно минимального объема подлежащего выбрасыванию кода, нужного для ответа на отдельный вопрос проектирования. Если разработчики недисциплинированно относятся к написанию абсолютно минимального объема кода, нужного для ответа на вопрос, прототипирование работает плохо. Допустим, вопрос проектирования таков: «Может ли выбранная нами организация базы данных поддерживать нужный объем транзакций?» Для ответа не нужно писать полноценный код, который можно было бы использовать в готовой системе. Вы можете даже не знать специфику базы данных. Вам лишь нужна информация, достаточная для аппроксимации проблемной области: число таблиц, число элементов в таблицах и т. д. Далее вы можете написать простой про- тотипный код, использующий таблицы и столбцы с именами вроде Table1, Table2 и Column1, Column2, заполнить таблицы фиктивными данными и протестировать производительность. Прототипирование также работает плохо, если задача недостаточно конкретна. Вопрос «Будет ли эта организация базы данных работать?» недостаточно хорошо определяет направление прототипирования. В то же время вопрос «Будет ли эта организация базы данных поддерживать 1000 транзакций в секунду при условиях X, Y и Z?» предоставляет более прочную основу для прототипирования. Наконец, еще один фактор риска возникает, если разработчики не рассматривают код как подлежащий выбрасыванию. Я обнаружил, что люди не могут написать аб- солютно минимальный объем кода, нужный для ответа на вопрос, если думают, что код в конечном счете войдет в итоговую версию системы. Из-за этого они вместо прототипирования занимаются реализацией системы. Настроившись на то, что, как только ответ на вопрос будет получен, код будет выброшен, вы сведете этот риск к минимуму. Избежать этой проблемы можно, если создавать прототипы и основную программу, используя разные технологии. Вы можете создать прототип проекта Java на языке Python или смоделировать пользовательский интерфейс в Microsoft PowerPoint. Если вы все-таки создаете прототипы, используя ту же тех- нологию, пусть имена прототипичных классов и пакетов начинаются с префикса prototype. Это хотя бы заставит программиста дважды подумать, прежде чем он решит расширять прототипный код (Stephens, 2003). При дисциплинированном применении прототипирование — эффективный способ борьбы с «грязнотой» проектирования. В противном случае оно делает проектирование еще более грязным. 112 ЧАСТЬ II Высококачественный код Совместное проектирование Обычно при проектировании две головы лучше, чем одна, организованы они формально или неформально. Сотрудни- чество может принимать любую из следующих форм: 쐽 вы подходите к столу коллеги и просите его обсудить с вами некоторые идеи; 쐽 вы с коллегой идете в конференц-зал и рисуете на доске варианты проекта; 쐽 вы с коллегой садитесь перед клавиатурой и выполняете детальное проекти- рование на выбранном языке программирования, т. е. вы можете использовать парное программирование (см. главу 21); 쐽 вы назначаете собрание для обсуждения своих идей с одним или несколькими коллегами; 쐽 вы назначаете формальную инспекцию, включающую все аспекты, описанные в главе 21; 쐽 никто не может провести обзор вашей работы, поэтому вы выполняете неко- торый объем работы, сохраняете ее и возвращаетесь к ней через неделю — вы забудете достаточно, чтобы самостоятельно провести довольно хороший обзор своей же работы; 쐽 вы обращаетесь за помощью к людям, не работающим в вашей компании: от- правляете вопросы в специализированный форум или группу новостей. Если целью является гарантия качества, тогда по причинам, описанным в главе 21, я рекомендую наиболее структурированную методику обзора — формальные инспекции. Но если цель состоит в содействии творчеству и увеличении числа предлагаемых вариантов проекта, а не в простом нахождении ошибок, лучше при- менять менее структурированные подходы. После выбора определенного варианта проекта может оказаться уместным переход к более формальным инспекциям, что определяется конкретной ситуацией. Какую степень проектирования считать достаточной? Иногда перед началом кодирования создается только самый общий набросок архитектуры. В других случаях группы создают проекты с таким уровнем подробностей, что коди- рование становится практически механическим занятием. Насколько детально выполнять проектирование до начала кодирования? Родственный вопрос заключается в том, насколько формаль- ным делать проект. Нужны ли вам формальные, тщательно выполненные диаграммы проекта системы или цифровых снимков нескольких рисунков на доске будет достаточно? Принятие решения о том, какую часть проектирования выполнять до начала пол- номасштабного кодирования и насколько формально документировать проект системы, трудно назвать точной наукой. При этом следует учитывать опыт груп- пы, ожидаемый срок службы системы, желательный уровень надежности, мас- штаб проекта и число членов группы. Влияние этих факторов на подход к про- ектированию отражено в табл. 5-2. Мы пытаемся решить проблему, максимально ускоряя процесс проектирования, чтобы в кон- це работы над системой у нас осталось достаточно времени для нахождения ошибок, до- пущенных из-за слишком бы- строго проектирования. Гленфорд Майерс (Glenford Myers) Перекрестная ссылка О совмест- ной разработке ПО см. главу 21 ГЛАВА 5 Проектирование при конструировании 113 Табл. 5-2. Необходимые уровни формальности и детальности проекта Уровень детальности проекта, нужный перед началом Уровень формальности Фактор конструирования документации Члены группы проектирования/ Низкий Низкий конструирования имеют большой опыт работы в прикладной области. Члены группы проектирования/ Средний Средний конструирования имеют большой опыт, но плохо знакомы с прикладной областью. Члены группы проектирования/ Средний — высокий Низкий — средний конструирования неопытны. Для группы проектирования/конст- Средний — руирования характерен средний — высокий уровень текучести. От приложения будет зависеть Высокий Высокий безопасность людей. Приложение предназначено Средний Средний — высокий для решения ответственных задач. Проект небольшой. Низкий Низкий Проект крупный. Средний Средний Предполагается, что ПО будет Низкий Низкий использоваться недолго (недели или месяцы). Предполагается, что ПО будет Средний Средний использоваться длительное время (месяцы или годы). При создании конкретной системы могут иметь место сразу несколько факторов, подталкивающих к разным решениям: скажем, опытная группа может разраба- тывать ПО, от которого будет зависеть безопасность людей. Тогда вам, вероятно, следует отдать предпочтение более высокой детальности и формальности проекта. Вообще в подобной ситуации нужно оценить роль каждого фактора и решить, какой из них важнее. Если разработчики выполняют какую-то часть проектирования индивидуально, то при снижении проектирования до уровня задачи, которая уже была решена ранее, или до уровня простого изменения или расширения такой задачи, проектирование, вероятно, можно прекратить и начать кодирование. Если я не могу решить, насколько детальным должен быть проект программы перед началом кодирования, я предпочитаю разрабатывать его более детально. Самые крупные ошибки проектирования возникали, когда я думал, что выполнил проектирование в достаточной степени, но позднее оказывалось, что я заблуж- дался и не учитывал дополнительные проблемы. Иначе говоря, самые серьезные проблемы проектирования обычно были связаны не с теми областями, которые я считал сложными и спроектировал неудачно, а с теми, которые я считал легки- ми и не спроектировал вообще. Мне редко встречаются проекты, страдающие от чрезмерного проектирования. 114 ЧАСТЬ II Высококачественный код С другой стороны, иногда я имею дело с проектами, стра- дающими от слишком объемной проектной документа- ции. Здесь вступает в игру своеобразный закон Грешема 1 , и «механические действия начинают вытеснять творчество» (Simon, 1965). Чрезмерное внимание к созданию проектной документации — хорошее подтверждение этого закона. Я бы предпочел, чтобы разработчики тратили 80% усилий на разработку и анализ различных вариантов проектирования и 20% — на создание менее изысканной документации, а не наоборот: 20% — на создание посредственных решений и 80% — на совершенствование документации не совсем удачных проектов. Регистрация процесса проектирования Традиционным способом регистрации проекта является его описание в формальной проектной документации. Однако есть масса других способов, эффективных при использова- нии неформального подхода, при создании небольших систем или когда нужна «облегченная» методика регистрации проекта: Включайте проектную документацию прямо в код Документируйте основные аспекты проектирования в ком- ментариях — как правило, в заголовках файлов или классов. Дополнив этот подход использованием утилиты извлечения документации (такой как JavaDoc), вы позволите програм- мистам, работающим над конкретными фрагментами кода, с легкостью обращаться к соответствующей проектной до- кументации, и повысите вероятность того, что они будут поддерживать ее актуальность. Регистрируйте протоколы обсуждения проекта и принятые решения при помощи Wiki Сохраняйте пись- менные протоколы обсуждения проекта в системе Wiki (на- бор Web-страниц, которые может редактировать каждый член группы при помощи Web-браузера). Это позволяет автоматизировать регистрацию нужных данных, хотя и требует дополнительных затрат на набор текста. Кроме того, в Wiki мож- но хранить полезные цифровые фотографии, ссылки на Web-сайты, содержащие обоснования принятых решений, документы и другие материалы. Этот метод особенно полезен, если члены группы отдалены друг от друга. Пишите резюме дискуссий в форме электронной почты Обсудив проект системы, поручите кому-нибудь записать резюме беседы — особенно принятые решения — и отправить его каждому члену группы. Сохраняйте копии писем в папке, доступной всем участникам проекта. Я никогда не встречал человека, желающего читать 17 000 стра- ниц документации, а если бы встретил, то убил бы его, чтобы он не портил генофонд. Джозеф Костелло (Joseph Costello) http://cc2e.com/0506 Плохие новости заключаются в том, что мы, как нам кажется, никогда не найдем философско- го камня. Мы никогда не най- дем процесса, позволяющий проектировать ПО абсолютно рациональным образом. Но есть и хорошая новость: мы можем его подделать. Дэвид Парнас и Пол Клементс (David Parnas and Paul Clements) 1 Закон Грешема (Gresham’s Law) — монетарный принцип, названный в честь лорда Томаса Грешема, управляющего английским монетным двором при королеве Елизавете. Суть закона Грешема в том, что «худшие» деньги (некачественные или с пониженным содержанием бла- городного металла) вытесняют «лучшие» деньги из обращения, т. е. более «дешевые» деньги вытесняют более «дорогие». — Прим. перев. ГЛАВА 5 Проектирование при конструировании 115 Используйте цифровой фотоаппарат Одним частым барьером, препятствую- щим документированию проекта, является утомительность рисования проектов традиционным способом. Однако способы документирования не ограничиваются вариантами «регистрации проекта с использованием красиво отформатированной формальной нотации» и «полного отсутствия проектной документации». Фотографируйте диаграммы, которые разработчики рисуют на доске, и включайте их в традиционные документы: это гораздо проще, чем рисовать схемы вручную, но не менее эффективно. Храните плакаты со схемами проекта Нет закона, утверждающего, что для документирования проекта нужно использовать стандартные листы бумаги почто- вого размера. Если вы рисуете диаграммы проектов на больших листах, можете просто сохранить их в удобном месте или, что еще лучше, развесить на стенах, чтобы разработчики могли обращаться к ним и обновлять по мере надобности. Используйте карточки CRC (Class, Responsibility, Collaborator — класс, ответственность, сотрудни- чество) Еще один простой вариант документирования проекта — использовать карточки. Напишите на каждой карточке имя класса, аспекты его ответственности и имена классов, с которыми он сотрудничает. Про- должайте работать с карточками, пока не будете удовлетворены результатом. В этот момент вы можете просто сохранить карточки на будущее. Этот способ почти не требует расходов, не пугает своей сложностью и поощряет взаимодействие членов группы (Beck, 1991). Создавайте диаграммы UML с уместным уровнем детальности Одним из популярных способов создания диаграмм проектов является язык UML (Unified Modeling Language; унифицированный язык моделирования), стандартизацией ко- торого занимается организация Object Management Group (Fowler, 2004). Пример UML-диаграммы классов вы уже видели на рис. 5-6. UML предоставляет богатый набор формализованных репрезентаций для проектирования сущностей и их отношений. Вы можете использовать неформальные версии UML для анализа и обсуждения подходов к проектированию. Начните с минимальных набросков и добавляйте детали, только выбрав конкретный вариант проекта. Так как UML стан- дартизирован, он позволяет эффективнее обмениваться идеями и может ускорить процесс рассмотрения вариантов проектов при работе в группе. Описанные способы работают и в различных комбинациях, так что можете сво- бодно смешивать их, приспосабливая к конкретным проектам и даже разным областям одного проекта. 5.5. Комментарии по поводу популярных методологий История проектирования ПО отмечена бурными спорами фанатичных сторонников конфликтующих подходов к проектированию. Когда в начале 1990-х вышло в свет первое издание этой книги, фанатики проектирования утверждали, что перед на- чалом кодирования нужно расставить все точки над «i» на этапе проектирования. Эта рекомендация не имела никакого смысла. http://cc2e.com/0513 116 ЧАСТЬ II Высококачественный код Сейчас, в середине первого десятилетия XXI века, некоторые «проповедники» утверждают, что проектирование вообще не требуется. «Крупномасштабное Предварительное Проекти- рование — это плохо, — говорят они. — Лучше вообще не выполнять проектирование перед началом кодирования!» За десять лет маятник сместился от «проектирования всего» до «проектирования ничего». Но альтернативой Крупномас- штабному Предварительному Проектированию является не отсутствие предварительного проектирования, а Небольшое Предварительное Проектирование или Достаточное Пред- варительное Проектирование. Какой объем проектирования достаточен? Никто не мо- жет точно ответить на этот вопрос. Тем не менее можно с полной уверенностью утверждать, что два варианта обяза- тельно окажутся неудачными: проектирование всех деталей до единой и полное отсутствие проектирования. Крайние точки зрения всегда ошибочны! Как сказал Ф. Дж. Плоджер, «чем догматичнее вы будете в отношении методики проектирования, тем меньше реальных проблем решите» (Plauger, 1993). Рассма- тривайте проектирование как грязный, неряшливый эвристический процесс. Не останавливайтесь на первом проекте, который пришел вам в голову. Сотрудни- чайте. Стремитесь к простоте. Создавайте прототипы, если нужно. Не забывайте про итерацию. Вы будете довольны своими проектами. Дополнительные ресурсы Проектирование ПО описанно во множестве работ. Проблема в том, чтобы определить, какие из них наиболее полезны. Позволю себе дать вам некоторые советы. Общие вопросы проектирования ПО Weisfeld, Matt. The Object-Oriented Thought Process, 2d ed. — SAMS, 2004 — понятное введение в объектно-ориентированное программирование. Если вы уже знакомы с объектно-ориентированным программированием, возможно, вам следует поис- кать более серьезную книгу, но если вы новичок в этой области, эта книга по- знакомит вас с фундаментальными объектно-ориентированными концепциями, такими как объекты, классы, интерфейсы, наследование, полиморфизм, перегруз- ка, абстрактные классы, агрегация и ассоциация, конструкторы/деструкторы, ис- ключения и т. д. Riel, Arthur J. Object-Oriented Design Heuristics. — Reading, MA: Addison-Wesley, 1996. В этой книге основное внимание уделяется проектированию на уровне классов. Еще она легко читается. Plauger, P. J. Programming on Purpose: Essays on Software Design. — Englewood Cliffs, NJ: PTR Prentice Hall, 1993. В этой книге я нашел столько хороших советов по про- ектированию ПО, сколько во всех остальных прочитанных мной книгах вместе Люди, описывающие проекти- рование ПО как дисциплиниро- ванный процесс, тратят много энергии, заставляя всех нас по- чувствовать себя виноватыми. Мы никогда не станем доста- точно структурированными или объектно-ориентированными для достижения нирваны при жизни. Все мы расплачиваемся за первородный грех — изуче- ние Basic в особо впечатлитель- ном возрасте. Но я готов спо- рить, что большинство из нас проектируют программы лучше, чем кажется пуристам. Ф. Дж. Плоджер (P. J. Plauger) http://cc2e.com/0520 ГЛАВА 5 Проектирование при конструировании 117 взятых. Плоджер прекрасно разбирается во многих подходах к проектированию, он прагматичен, и он отличный писатель. Meyer, Bertrand. Object-Oriented Software Construction, 2d ed. — New York, NY: Pren tice Hall PTR, 1997. Мейер приводит убедительные доводы в защиту чистого объек- тно-ориентированного программирования. Raymond, Eric S. The Art of UNIX Programming. — Boston, MA: Addison-Wesley, 2004. Эта книга — хорошо обоснованный взгляд на проектирование ПО сквозь призму UNIX. В разделе 1.6 приведено лаконичное объяснение 17 ключевых принципов проектирования ПО для UNIX. Larman, Craig. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process, 2d ed. — Englewood Cliffs, NJ: Prentice Hall, 2001. Это популярное введение в объектно-ориентированное проектирование в контексте метода Unified Process. Кроме того, здесь обсуждается объектно-ориентированный анализ. Теория проектирования ПО Parnas, David L., and Paul C. Clements. «A Rational Design Process: How and Why to Fake It». — IEEE Transactions on Software Engineering SE-12, no. 2 (February 1986): 251–57. В этой классической статье описывается разрыв между реальным и желательным процессами проектирования программ. Суть статьи в том, что в действительности никто никогда не следует рациональному упорядоченному процессу проектирования, но стремление к этому приводит в итоге к созданию лучших проектов. Работы, в которых было бы приведено исчерпывающее обсуждение сокрытия информации, мне неизвестны. В большинстве учебников по разработке ПО оно обсуждается кратко, часто в контексте объектно-ориентированных подходов. На- верное, до сих пор лучшими материалами по сокрытию информации являются три статьи, принадлежащие перу Парнаса. Parnas, David L. «On the Criteria to Be Used in Decomposing Systems into Modules». — Communications of the ACM 5, no. 12 (December 1972): 1053-58. Parnas, David L. «Designing Software for Ease of Extension and Contraction». — IEEE Transactions on Software Engineering SE-5, no. 2 (March 1979): 128-38. Parnas, David L., Paul C. Clements, and D. M. Weiss. «The Modular Structure of Com plex Systems». — IEEE Transactions on Software Engineering SE-11, no. 3 (March 1985): 259-66. Шаблоны проектирования Gamma, Erich, et al. Design Patterns. — Reading, MA: Addison-Wesley, 1995. Очень по- лезная книга о шаблонах проектирования, написанная «Бандой четырех» 1 Shalloway, Alan, and James R. Trott. Design Patterns Explained. — Boston, MA: Addison- Wesley, 2002. Данная книга представляет собой несложное введение в шаблоны проектирования. 1 «Бандой четырех (Gang of Four)» называют группу авторов, в которую входят Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес. — Прим. перев. 118 ЧАСТЬ II Высококачественный код Проектирование в общем Adams, James L. Conceptual Blockbusting: A Guide to Better Ideas, 4th ed. — Cambridge, MA: Perseus Publishing, 2001. Нельзя сказать, что эта книга посвящена непосред- ственно проектированию ПО, но это не умаляет ее достоинств: она была написана как учебник по проектированию для студентов инженерного факультета Стэн- фордского университета. Даже если вы никогда ничего не проектировали и не проектируете, в ней вы найдете увлекательное обсуждение творческого мышления и много упражнений, позволяющих развить мышление, для эффективного проек- тирования. Кроме того, данная книга включает список литературы, посвященной проектированию и творческому мышлению, с подробными аннотациями. Если вам нравится решать проблемы, вам понравится эта книга. Polya, G. How to Solve It: A New Aspect of Mathematical Method, 2d ed. — Princeton, NJ: Princeton University Press, 1957. Это обсуждение эвристики и решения про- блем концентрируется на математике, но актуально и для разработки ПО. Книга Полья стала первым трудом, посвященным применению эвристики для решения математических проблем. В ней проводится четкое различие между небрежной эвристикой, используемой для обнаружения решений, и более аккуратными ме- тодами, которые применяются для представления найденных решений. Читать ее нелегко, но если вы интересуетесь эвристикой, то в итоге все равно прочитаете ее, хотите вы того или нет. Полья ясно показывает, что решение проблем не является детерминированным процессом и что приверженность единственной методоло- гии аналогично ходьбе в кандалах. Когда-то в Microsoft эту книгу выдавали всем новым программистам. Michalewicz, Zbigniew, and David B. Fogel. How to Solve It: Modern Heuristics. — Berlin: Springer-Verlag, 2000. Это обновленный вариант книги Полья, который содержит некоторые нематематические примеры и менее требователен к читателю. Simon, Herbert. The Sciences of the Artificial, 3d ed. — Cambridge, MA: MIT Press, 1996. В этой интересной книге проводится различие между науками, имеющими дело с естественным миром (биология, геология и т. д.), и науками, изучающими ис- кусственный мир, созданный людьми (бизнес, архитектура и информатика). За- тем в ней обсуждаются характеристики наук об искусственном, при этом особое внимание уделяется проектированию. Книга написана в академическом стиле, и ее следует прочитать всем, кто решил сделать карьеру в области разработки ПО или любой другой «искусственной» области. Glass, Robert L. Software Creativity. — Englewood Cliffs, NJ: Prentice Hall PTR, 1995. Что в большей степени управляет процессом разработки ПО: теория или практика? Является ли он преимущественно творческим или преимущественно детерминиро- ванным? Какие интеллектуальные качества нужны разработчику ПО? В этой книге приведено интересное обсуждение природы разработки ПО со специфическим акцентом на проектировании. Petroski, Henry. Design Paradigms: Case Histories of Error and Judgment in Engineering. — Cambridge: Cambridge University Press, 1994. Главная идея этой книги в том, что анализ прошлых неудач способствует успешному проектированию не в меньшей, а то и в большей степени, чем исследование прошлых успехов. В подтверждение своей позиции автор приводит многие факты из области гражданского строи- тельства (особенно проектирования мостов). ГЛАВА 5 Проектирование при конструировании 119 Стандарты IEEE Std 1016-1998, Recommended Practice for Software Design Descriptions. Данный документ содержит стандарт IEEE-ANSI описания проектов ПО, определяющий, что следует включать в проектную документацию. IEEE Std 1471-2000. Recommended Practice for Architectural Description of Software Intensive Systems. Los Alamitos, CA: IEEE Computer Society Press. Этот документ представляет собой руководство IEEE-ANSI по созданию спецификаций архитектуры ПО. Контрольный список: проектирование при конструировании Методики проектирования Выполнили ли вы несколько итераций проектирования, выбрав самую лучшую попытку, а не просто первую? Попробовали ли вы выполнить декомпозицию системы несколькими спосо- бами с целью нахождения наилучшего варианта? Использовали ли вы для решения проблемы и нисходящий, и восходящий способы проектирования? Выполнили ли вы прототипирование сомнительных или плохо известных частей системы, создав абсолютный минимум подлежащего выбрасыванию кода, нужного для ответа на отдельные вопросы? Был ли выполнен формальный или неформальный обзор вашего проекта другими разработчиками? Довели ли вы проектирование до той точки, в которой реализация проекта кажется очевидной? Выполнили ли вы регистрацию проекта уместными способами, такими как Wiki, электронная почта, плакаты, цифровые фотографии, UML, карточки CRC или комментарии в самом коде? Цели проектирования Адекватно ли проект решает проблемы, которые были определены и от- ложены на этапе разработки архитектуры? Разделен ли проект на уровни? Удовлетворены ли вы тем, как выполнена декомпозиция программы на под- системы, пакеты и классы? Удовлетворены ли вы тем, как выполнена декомпозиция классов на методы? Достигнуто ли минимальное взаимодействие классов между собой? Спроектированы ли классы и подсистемы так, чтобы их можно было ис- пользовать в других системах? Будет ли программа легкой в сопровождении? Является ли проект полным, но минимальным? Все ли его части действи- тельно необходимы? Подразумевает ли проект использование только стандартных методик? Смогли ли вы избежать применения экзотических, трудных для понимания элементов? Помогает ли проект в целом минимизировать и несущественную, и суще- ственную сложность? http://cc2e.com/0527 120 ЧАСТЬ II Высококачественный код Ключевые моменты 쐽 Главным Техническим Императивом Разработки ПО является управление слож- ностью. Управлять сложностью будет гораздо легче, если при проектировании вы будете стремиться к простоте. 쐽 Есть два общих способа достижения простоты: минимизация объема существен- ной сложности, с которой приходится иметь дело в любой конкретный момент времени, и подавление необязательного роста несущественной сложности. 쐽 Проектирование — эвристический процесс. Слепое следование какой-либо единственной методологии подавляет творческое мышление и снижает каче- ство ваших программ. 쐽 Оптимальный процесс проектирования итеративен; чем больше вариантов про- ектирования вы попробуете, тем удачнее будет ваш окончательный проект. 쐽 Одной из самых полезных концепций проектирования является сокрытие информации. Вопрос «Что мне скрыть?» устраняет много сложных проблем проектирования. 쐽 Много полезной и интересной информации о проектировании можно найти в других книгах. Описанные в этой главе идеи — лишь вершина айсберга. |