Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по
Скачать 5.88 Mb.
|
Старайтесь использовать популярные шаблоны проектирования Шаблоны проектирования — это готовые шаблоны, позво- ляющие решать частые проблемы разработки. Конечно, есть проблемы, требующие совершенно новых решений, но боль- шинство уже встречалось разработчикам, поэтому их можно решить, применяя проверенные подходы, или шаблоны. В число популярных шаблонов проектиро- вания входят Адаптер, Мост, Декоратор, Фасад, Фабричный метод, Наблюдатель, http://cc2e.com/0585 CC2_Part2_ch5_2010.indd 99 22.06.2010 12:31:38 100 ЧАСТЬ II Высококачественный код Одиночка, Стратегия и Шаблонный метод. О шаблонах проектирования см. книгу «Design Patterns» Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона и Джона Влис- сидеса (Gamma, Helm, Johnson, and Vlissides, 1995). Шаблоны имеют ряд достоинств, не характерных для полностью самостоятельного проектирования программы. Шаблоны снижают сложность, предоставляя готовые абстракции Если вы скажете: «В этом фрагменте для создания экземпляров производных классов применяется шаблон “Фабричный метод”», — другие программисты поймут, что ваш код включает богатый набор взаимодействий и протоколов программирова- ния, специфических для названного шаблона. Шаблон «Фабричный метод» позволяет создавать экземпляры любого класса, про- изводного от указанного базового класса, причем отдельные производные классы отслеживаются только самим «Фабричным методом». Обсуждение шаблона «Фа- бричный метод» см. в разделе «Replace Constructor with Factory Method» (Замена конструктора на «Фабричный метод») книги «Refactoring» (Fowler, 1999). Если вы будете использовать шаблоны, другие программисты легко поймут вы#бранный вами подход к проектированию без подробного обсуждения кода. Шаблоны снижают число ошибок, стандартизируя детали популярных решений Проблемы проектирования содержат нюансы, которые полностью проявляются только после решения проблемы один или два раза (или три, или четыре, или…). Шаблоны — это стандартизованные способы решения частых проблем, заключающие мудрость, накопленную за годы попыток решения этих проблем, и исправления неудачных попыток. Так что, с концептуальной точки зрения, применение шаблона проектирования похоже на использование библиотеки кода вместо написания собственного кода. Многие программисты рано или поздно решают создать собственный вариант алгоритма быстрой сортировки, но каковы шансы, что его первая версия окажется безошибочной? Так же и в проектировании: многие проблемы довольно похожи на уже решенные задачи, и при столкновении с ними изобретать велосипед ни к чему. Шаблоны имеют эвристическую ценность, указывая на возможные ва- рианты проектирования Проектировщик, знакомый с популярными шабло- нами, может с легкостью перебрать список шаблонов и спросить себя: «Какие из них соответствуют моей проблеме проектирования?» Перебрать набор известных вариантов гораздо проще, чем создавать собственное решение с нуля. Кроме того, код, основанный на популярном шаблоне, будет понятнее, чем код, полностью разработанный самостоятельно. Шаблоны упрощают взаимодействие между разработчиками, позволяя им общаться на более высоком уровне Шаблоны проектирования не только помогают управлять сложностью, но и способны ускорить обсуждение проектов, позволяя разработчикам размышлять и делиться мыслями на более высоком уровне. Если вы скажете: «Не могу решить, какой шаблон следует использовать в данной ситуации: “Создатель” или “Фабричный метод”», — вы в нескольких словах сообщи- те очень подробную информацию — конечно, если и вам, и вашему собеседнику известны эти шаблоны. Представьте, насколько больше времени потребовалось CC2_Part2_ch5_2010.indd 100 22.06.2010 12:31:38 ГЛАВА 5 Проектирование при конструировании 101 бы для обсуждения деталей кода шаблонов «Создатель» и «Фабричный метод» и сравнения этих двух подходов. Если вы еще не сталкивались с шаблонами проектирования, изучите табл. 5#1, где описаны некоторые из самых популярных шаблонов. Табл. 5-1. Популярные шаблоны проектирования Шаблон Описание Абстрактная фабрика Поддерживает создание наборов родственных объектов пу# (Abstract Factory) тем определения вида набора, но не вида каждого отдельно# го объекта. Адаптер (Adapter) Преобразует интерфейс класса в другой интерфейс. Мост (Bridge) Создает интерфейс и реализацию, так что их можно изме# нять независимо друг от друга. Компоновщик Состоит из объекта, содержащего дополнительные объекты (Composite) такого же типа, позволяя клиентскому коду взаимодейство# вать с объектом верхнего уровня и не заботиться о деталь# ных объектах. Декоратор (Decorator) Динамически назначает объекту виды ответственности без создания отдельных подклассов для каждой возможной конфигурации видов ответственности. Фасад (Facade) Предоставляет согласованный интерфейс к коду, который в противном случае не предоставлял бы согласованного интерфейса. Фабричный метод Создает экземпляры классов, производных от конкретного (Factory Method) базового класса, причем отдельные производные классы от# слеживаются только «Фабричным методом». Итератор (Iterator) Этот серверный объект предоставляет доступ к каждому элементу набора в последовательном порядке. Наблюдатель (Observer) Поддерживает синхронизацию нескольких объектов, при которой объект уведомляет набор связанных объектов об изменениях любого члена набора. Одиночка (Singleton) Предоставляет глобальный доступ к классу, который может иметь один и только один экземпляр. Стратегия (Strategy) Определяет набор динамически взаимозаменяемых алгоритмов или видов поведения. Шаблонный метод Определяет структуру алгоритма, оставляя некоторые (Template Method) детали реализации подклассам. Если раньше вы не встречались с шаблонами проектирования, при взгляде на табл. 5#1 у вас может возникнуть мысль: «Почти все эти идеи мне уже знакомы». Этим во многом и объясняется ценность шаблонов проектирования. Они известны большинству опытных программистов, а присвоение шаблонам запоминающихся названий позволяет быстро и эффективно делиться мыслями. С шаблонами связаны две ловушки. Первая — насильственная адаптация кода к какому#нибудь шаблону. Иногда легкое изменение кода в соответствии с извест- ным шаблоном может сделать код более понятным. Но если адаптация кода к стандартному шаблону требует слишком крупного изменения, это может привести к усложнению программы. CC2_Part2_ch5_2010.indd 101 22.06.2010 12:31:38 102 ЧАСТЬ II Высококачественный код Вторая — применение шаблона, продиктованное не целесообразностью, а жела- нием испытать шаблон в деле. Вообще применение шаблонов проектирования — это эффективный инструмент управления сложностью. Некоторые хорошие книги по этой теме указаны в конце главы. Другие эвристические принципы В предыдущих разделах были рассмотрены основные эвристические принципы проектирования ПО. Ниже описаны менее полезные, однако заслуживающие упо- минания эвристические принципы. Стремитесь к максимальной связности Понятие связности (cohesion) возникло в области структурного проектирования и обычно обсуждается в том же контексте, что и сопряжение (coupling). Связность характеризует то, насколько хорошо все методы класса или все фрагменты мето- да соответствуют главной цели, — иначе говоря, насколько сфокусирован класс. Классы, состоящие из очень похожих по функциональности блоков, обладают высокой степенью связности, и наша эвристическая цель состоит в том, чтобы целостность была как можно выше. Связность — полезный инструмент управления сложностью, потому что чем лучше код класса соответствует главной цели, тем проще запомнить все, что код выполняет. Стремление к связности на уровне методов давно считается полезным эвристи- ческим принципом. На уровне классов эвристический принцип связности во многом выражен в более общем эвристическом принципе адекватного определения абстракций, что уже обсуждалось в этой главе и будет еще обсуждаться в главе 6. Абстрагирование полезно и на уровне методов, но в этом случае принципы абстрагирования и связности более равноправны. Формируйте иерархии Иерархия — это многоуровневая структура организации информации, при которой наиболее общая или абстрактная репрезентация концепции соответствует вершине, а более детальные специализированные репрезентации — более низким уровням. При разработке ПО иерархии обнаруживаются, например, в наборах классов и в последовательностях вызовов методов (уровень 4 на рис. 5#2). Формирование иерархий уже более 2000 лет является важным средством управле- ния сложными наборами информации. Так, Аристотель использовал иерархию для организации царства животных. Люди часто организуют сложную информацию (такую как эта книга) при помощи иерархических схем. Ученые обнаружили, что люди в целом находят иерархии естественным способом организации сложной информации. Рисуя сложный объект (скажем, дом), люди рисуют его иерархиче- ски. Сначала они рисуют очертания дома, затем окна и двери, а после этого — еще более подробные детали. Они не рисуют дом по отдельным кирпичам, доскам или гвоздям (Simon, 1996). Иерархии помогают в достижении Главного Технического Императива Разработ- ки ПО, позволяя сосредоточиться только на том уровне детальности, который CC2_Part2_ch5_2010.indd 102 22.06.2010 12:31:38 ГЛАВА 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. CC2_Part2_ch5_2010.indd 103 22.06.2010 12:31:38 104 ЧАСТЬ II Высококачественный код Тщательно выбирайте время связывания Временем связывания (binding time) называют тот момент, когда переменной присваивается конкретное значение. Раннее связывание обычно упрощает код, но и снижает его гибкость. Иногда к полезным идеям проектирования можно прийти, спросив себя: «Что, если связать эти значения раньше? Что, если связать их позже? Что, если инициализировать эту таблицу в этом месте кода? Что, если получить значение этой переменной от пользователя в период выполнения про- граммы?» Создайте центральные точки управления Ф. Дж. Плоджер говорит, что главным его принципом является «Принцип Одного Верного Места: в программе должно быть Одно Верное Место для поиска нетри- виального фрагмента кода и Одно Верное Место для внесения вероятных изме- нений» (Plauger, 1993). Управление может быть централизовано в классах, мето- дах, макросах препроцессора, файлах, включаемых директивой #include, — даже именованная константа может быть центральной точкой управления. Этот принцип также способствует снижению сложности: если какой#то программ- ный элемент встречается в минимальном числе фрагментов, его изменение ока- жется проще и безопаснее. Подумайте об использовании грубой силы Грубая сила — один из мощнейших эвристических инстру- ментов. Не стоит ее недооценивать. Работоспособное реше- ние проблемы методом грубой силы лучше, чем элегантное, но не работающее решение. Создавать элегантные решения зачастую долго и сложно. Так, описывая историю разработки алгоритмов поиска, Дональд Кнут указал, что, хотя первое описание алгоритма двоичного поиска было опубликовано в 1946 г., алгоритм, правильно обрабаты- вающий списки всех размеров, был разработан только спустя 16 лет (Knuth, 1998). Двоичный поиск элегантнее, но и основанный на грубой силе последовательный поиск часто приемлем. Рисуйте диаграммы Диаграммы — еще один мощный эвристический инструмент. Все знают, что лучше один раз увидеть, чем сто раз услышать. Диаграммы позволяют представить про- блему на более высоком уровне абстракции, и никакие описания их не заменят. Помните: иногда проблему следует рассматривать на детальном уровне, а иногда целесообразно иметь дело с более общими аспектами. Поддерживайте модульность проекта системы Этот принцип подразумевает, что каждый метод или класс должен быть похож на «черный ящик»: вы знаете, что в него поступает и что из него выходит, но не знаете, что происходит внутри. Черный ящик имеет такой простой интерфейс и такую ясную функциональность, что для любых конкретных входных данных можно точно предсказать соответствующие выходные данные. Перекрестная ссылка О времени связывания см. раздел 10.6. Если сомневаетесь, используйте грубую силу. Батлер Лэмпсон (Butler Lampson) CC2_Part2_ch5_2010.indd 104 22.06.2010 12:31:38 ГЛАВА 5 Проектирование при конструировании 105 Концепция модульности связана с сокрытием информации, инкапсуляцией и дру- гими эвристическими принципами проектирования. И все же размышление о том, как собрать систему из набора черных ящиков иногда приводит к догадкам, к ко- торым сокрытие информации и инкапсуляция привести не могут, так что принцип модульности заслужил право занять место в вашем арсенале полезных идей. Резюме эвристических принципов проектирования Ниже приведен список основных эвристических принципов проектирования: определите объекты реального мира; определите согласованные абстракции; инкапсулируйте детали реализации; используйте наследование, когда это возможно; скрывайте секреты (помните про сокрытие информации); определите области вероятных изменений; поддерживайте сопряжение слабым; старайтесь использовать популярные шаблоны проекти- рования. Следующие эвристические принципы также иногда бывают полезны: стремитесь к максимальной связности; формируйте иерархии; формализуйте контракты классов; грамотно назначайте сферы ответственности; проектируйте систему для тестирования; избегайте неудач; тщательно выбирайте время связывания; создайте центральные точки управления; подумайте об использовании грубой силы; рисуйте диаграммы; поддерживайте модульность проекта системы. Советы по использованию эвристических принципов Подходы к проектированию ПО могут быть основаны на подходах, применяемых в других областях. Одной из первых книг, посвященных использованию эвристики при решении проблем, является «How to Solve It (Как решать задачу)» Д. Полья (Polya, 1957). Обобщенный подход Полья к решению проблем концентрируется на решении мате- матических задач. Он резюмирован на рис. 5#10 (шрифт оригинала сохранен). Больше беспокоит то, что про- граммист вполне может выпол- нить ту же задачу двумя или тремя способами: иногда нео- сознанно, но довольно часто просто ради изменения или же создания элегантной вариации. А. Р. Браун и У. А. Сэмпсон (A. R. Brown and W. A. Sampson) http://cc2e.com/0592 CC2_Part2_ch5_2010.indd 105 22.06.2010 12:31:38 106 ЧАСТЬ II Высококачественный код Рис. 5'10. Д. Полья разработал подход к решению математических задач, который полезен и при решении проблем, связанных с проектированием ПО (Polya 1957) Одним из самых ценных советов, которые можно дать по поводу проектирова- ния ПО, является использование разных подходов. Если проект, разработанный с помощью UML, неудачен, выразите его на обычном языке. Напишите небольшую тестовую программу. Попробуйте совершенно другой подход. Подумайте об ис- пользовании грубой силы. Продолжайте рисовать эскизы и наброски карандашом, и мозг последует за рукой. Если ничего не выходит, отложите решение проблемы. CC2_Part2_ch5_2010.indd 106 22.06.2010 12:31:38 |