Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по
Скачать 5.88 Mb.
|
Проектирование подразумевает ограничение возможностей Проектирование предполагает не только обеспечение возможностей, но и их ограничение. Если бы люди, создавая физические структуры, обладали бесконеч- ным объемом времени и ресурсов, мы увидели бы на улицах невероятно странные здания с диковинными башенками и сотнями комнат на отдельных этажах. При отсутствии целенаправленно заданных ограничений ПО может оказаться именно таким. Ограниченные объемы ресурсов при конструировании зданий требуют упрощения решения, что в итоге приводит к его улучшению. Проектирование ПО в этом смысле ничем не отличается. Проектирование — недетерминированный процесс Если вы попросите трех человек спроектировать одну и ту же программу, они вполне могут разработать три совершенно разных, но вполне приемлемых про- екта. Как правило, спроектировать компьютерную программу можно десятками разных способов. Дополнительные сведения См. обсуждение этой точки зрения в статье «A Ratio nal Design Process: How and Why to Fake It» (Parnas and Clements, 1986). Перекрестная ссылка Лучший ответ на этот вопрос см. в под- разделе «Какую степень про- ектирования можно считать достаточной?» раздела 5.4. CC2_Part2_ch5_2010.indd 73 22.06.2010 12:31:32 74 ЧАСТЬ II Высококачественный код Проектирование — эвристический процесс Так как проектирование не детерминировано, методы проектирования чаще всего являются эвристическими методами, т. е. «практическими пра# вилами» или «способами, которые могут сработать», а не воспроизводи- мыми процессами, которые всегда приводят к предсказуемым результатам. Про- ектирование — метод проб и ошибок. Инструменты или методы проектирования, оказавшиеся эффективными в одном случае, в другой ситуации могут оказаться куда менее эффективными. Универсальных методик проектирования не существует. Проектирование — постепенный процесс Можно довольно удачно обобщить названные аспекты про- ектирования, сказав, что проектирование — «постепенный» процесс. Проекты приложений не возникают в умах разработ- чиков сразу в готовом виде. Они развиваются и улучшаются в ходе обзоров, неформальных обсуждений, написания кода и выполнения его ревизий. Практически во всех случаях проект несколько меняется во время первоначальной разработки системы и еще боль- ше — при ее модернизации. Степень, в которой изменение выгодно или приемлемо, зависит от особенностей созда- ваемого ПО. 5.2. Основные концепции проектирования Успешное проектирование ПО требует понимания нескольких важных концепций. Здесь мы обсудим роль сложности при проектировании, желательные характери- стики проектов и уровни проектирования. Главный Технический Императив Разработки ПО: управле- ние сложностью Чтобы лучше понять важность управления сложностью, об- ратимся к известной работе Фреда Брукса «No Silver Bullets: Essence and Accidents of Software Engineering» (Brooks, 1987). Существенные и несущественные проблемы Брукс утверждает, что сложность разработки ПО объясняется существенными и несущественными проблемами. Используя два этих термина, Брукс опирается на философскую традицию, уходящую корнями к Аристотелю. В философии существен- ными называют свойства, которыми объект должен обладать, чтобы быть именно этим объектом. Автомобиль должен иметь двигатель, колеса и двери — если объект не обладает каким#нибудь из этих существенных свойств, это не автомобиль. Несущественными (акцидентными) свойствами называют свойства, которыми объект обладает в силу случайности, — свойства, не влияющие на его суть. Так, автомобиль может иметь четырехцилиндровый двигатель с турбонаддувом, вось- мицилиндровый или любой другой и все же являться автомобилем. Тип двигателя http://cc2e.com/0539 Дополнительные сведения ПО — не единственный тип струк- тур, изменяющихся с течением времени. Физические структуры также развиваются; см. об этом книгу «How Buildings Learn» (Brand, 1995). Перекрестная ссылка О влиянии сложности на другие аспекты про- граммирования см. раздел 34.1. CC2_Part2_ch5_2010.indd 74 22.06.2010 12:31:32 ГЛАВА 5 Проектирование при конструировании 75 и колес, число дверей — все это несущественные свойства. Можете также думать о них как о второстепенных, произвольных, необязательных и случайных. Брукс замечает, что главные несущественные проблемы раз- работки ПО уже давно решены. Например, несущественные проблемы, связанные с неудобным синтаксисом языков программирования, постепенно утратили свою значимость по мере эволюции языков. Несущественные проблемы, свя- занные с неинтерактивностью компьютеров, исчезли, когда на смену ОС, работающим в пакетном режиме, пришли системы с разделением времени. Среды интегрированной разработки избавили программистов от про- блем, обусловленных плохим взаимодействием инструментов. В то же время Брукс утверждает, что решение оставшихся существенных проблем разработки ПО будет более медленным. Это объясняется тем, что разработка программ по своей сути требует анализа всех деталей крайне сложного набора взаимосвязанных концепций. Причиной существенных проблем является не- обходимость анализа сложного неорганизованного реального мира, точного и полного определения зависимостей и исключений, проектирования абсолютно, но никак не приблизительно верных решений и т. д. Даже если б мы смогли придумать язык программирования, основанный на той же терминологии, что и требующая решения проблема реального мира, программирование все равно осталось бы сложным из#за необходимости точного определения принципов функционирования мира. По мере того как разработчики ПО берутся за решение все более серьезных проблем реального мира, им приходится анализировать все более сложные взаимодействия между сущностями, что в свою очередь приводит к повышению существенной сложности программных решений. Источник всех этих существенных проблем — сложность как несущественная, так и существенная. Важность управления сложностью Программные проекты редко терпят крах по техническим причинам. Чаще всего провал объясняется неадекватной выработкой требований, неудачным планированием или неэффективным управлением. Если же провал обусловлен все#таки преимущественно технической причиной, очень часто ею оказывается неконтролируемая сложность. Иначе говоря, приложение стало таким сложным, что разработчики перестали по#настоящему понимать, что же оно делает. Если работа над проектом достигает момента, после которого уже никто не может полностью понять, как изменение одного фрагмента программы повлияет на другие фрагменты, прогресс прекращается. Управление сложностью — самый важный технический аспект разработки ПО. По#моему, управление сложностью настолько важно, что оно долж# но быть Главным Техническим Императивом Разработки ПО. Сложность — не новинка в мире разработки ПО. Один из пионеров информати- ки Эдсгер Дейкстра обращал внимание на то, что компьютерные технологии — Перекрестная ссылка В ранних средах несущественные пробле- мы проявляются сильнее, чем в зрелых (см. раздел 4.3). Есть два способа разработки проекта приложения: сделать его настолько простым, чтобы было очевидно, что в нем нет недостатков, или сделать его таким сложным, чтобы в нем не было очевидных недостатков. Ч. Э. Р. Хоар (C. A. R. Hoare) CC2_Part2_ch5_2010.indd 75 22.06.2010 12:31:32 76 ЧАСТЬ II Высококачественный код единственная отрасль, заставляющая человеческий разум охватывать диапазон, простирающийся от отдельных битов до нескольких сотен мегабайт информации, что соответствует отношению 1 к 10 9 , или разнице в девять порядков (Dijkstra, 1989). Такое гигантское отношение просто ошеломляет. Дейкстра выразил это так: «По сравнению с числом семантических уровней средняя математическая теория кажется почти плоской. Создавая потребность в глубоких концептуальных иерар- хиях, компьютерные технологии бросают нам абсолютно новый интеллектуальный вызов, не имеющий прецедентов в истории». Разумеется, за прошедшее с 1989 г. время сложность ПО только выросла, и сегодня отношение Дейкстры вполне может характеризоваться 15 порядками. Дейкстра пишет, что ни один человек не обладает ин- теллектом, способным вместить все детали современной компьютерной программы (Dijkstra, 1972), поэтому нам — разработчикам ПО — не следует пытаться охватить всю программу сразу. Вместо этого мы должны попытаться ор- ганизовать программы так, чтобы можно было безопасно работать с их отдельными фрагментами по очереди. Целью этого является минимизация объема программы, о кото- ром нужно думать в конкретный момент времени. Можете считать это своеобразным умственным жонглированием: чем больше умственных шаров программа заставляет под- держивать в воздухе, тем выше вероятность того, что вы уроните один из них и допустите ошибку при проектиро- вании или кодировании. На уровне архитектуры ПО сложность проблемы можно снизить, разделив систему на подсистемы. Несколько несложных фрагментов информации понять проще, чем один сложный. В разбиении сложной проблемы на простые фрагменты и заключается цель всех методик проектирования ПО. Чем более независимы подсистемы, тем безопаснее сосредоточиться на одном аспек- те сложности в конкретный момент времени. Грамотно определенные объекты разделяют аспекты проблемы так, чтобы вы могли решать их по очереди. Пакеты обеспечивают такое же преимущество на более высоком уровне агрегации. Стремление к краткости методов программы помогает снизить нагрузку на ин- теллект. Этому же способствует написание программы в терминах проблемной области, а не низкоуровневых деталей реализации, а также работа на самом вы- соком уровне абстракции. Суть сказанного в том, что программисты, компенсирующие изначальные ограни- чения человеческого ума, пишут более понятный и содержащий меньшее число ошибок код. Как бороться со сложностью? Чаще всего причинами неэффективности являются: сложное решение простой проблемы; простое, но неверное решение сложной проблемы; неадекватное сложное решение сложной проблемы. Одним из симптомов того, что вы погрязли в чрезмерной слож- ности, является упрямое приме- нение метода, нерелевантность которого очевидна по крайней мере любому внешнему наблю- дателю. При этом вы уподобляе- тесь человеку, который при по- ломке автомобиля в силу своей некомпетентности не находит ничего лучшего, чем заменить воду в радиаторе и выбросить окурки из пепельниц. Ф. Дж. Плоджер (P. J. Plauger) CC2_Part2_ch5_2010.indd 76 22.06.2010 12:31:32 ГЛАВА 5 Проектирование при конструировании 77 Как указал Дейкстра, сложность современного ПО обусловлена самой его приро- дой, поэтому, как бы вы ни старались, вы все равно столкнетесь со сложностью, присущей самой проблеме реального мира. Исходя из этого, можно предложить двойственный подход к управлению сложностью: старайтесь свести к минимуму объем существенной сложности, с ко# торым придется работать в каждый конкретный момент времени; сдерживайте необязательный рост несущественной сложности. Как только вы поймете, что все остальные технические цели разработки ПО вто- ричны по отношению к управлению сложностью, многие принципы проектиро- вания окажутся простыми. Желательные характеристики проекта Высококачественные проекты программ имеют несколько общих характеристик. Если вы сумеете достичь всех этих целей, ваш проект на самом деле будет очень хорош. Не- которые цели противоречат другим, но это и есть одна из задач проектирования — объединение конкурирующих целей в удачном наборе компромиссов. Некоторые аспек- ты качества проекта — надежность, производительность и т. д. — описывают и качество программы, тогда как другие являются внутренними характеристиками проекта. Вот список таких внутренних характеристик проекта. Минимальная сложность В силу только что описанных причин главной целью проектирования должна быть ми- нимизация сложности. Избегайте создания «хитроумных» проектов: как правило, их трудно понять. Вместо этого соз- давайте «простые» и «понятные» проекты. Если при работе над отдельным фраг- ментом программы проект не позволяет безопасно игнорировать большинство остальных фрагментов, он неудачен. Простота сопровождения Проектируя приложение, не забывайте о програм- мистах, которые будут его сопровождать. Постоянно представляйте себе вопросы, которые будут возникать у них при взгляде на создаваемый вами код. Думайте о таких программистах как о своей аудитории и проектируйте систему так, чтобы ее работа была очевидной. Слабое сопряжение Слабое сопряжение (loose coupling) предполагает сведение к минимуму числа соединений между разными частями программы. Для проек- тирования классов с минимальным числом взаимосвязей используйте принципы адекватной абстракции интерфейсов, инкапсуляцию и сокрытие информации. Это позволит максимально облегчить интеграцию, тестирование и сопровожде- ние программы. Расширяемость Расширяемостью системы называют свойство, позволяющее улучшать систему, не нарушая ее основной структуры. Изменение одного фраг- мента системы не должно влиять на ее другие фрагменты. Внесение наиболее вероятных изменений должно требовать наименьших усилий. Работая над проблемой, я ни- когда не думаю о красоте. Я думаю только о решении про- блемы. Но если полученное ре- шение некрасиво, я знаю, что оно неверно. Р. Бакминстер Фуллер (R. Buckminster Fuller) Перекрестная ссылка Эти ха- рактеристики связаны с общими атрибутами качества ПО (см. раздел 20.1). CC2_Part2_ch5_2010.indd 77 22.06.2010 12:31:32 78 ЧАСТЬ II Высококачественный код Возможность повторного использования Проектируйте систему так, чтобы ее фрагменты можно было повторно использовать в других системах. Высокий коэффициент объединения по входу При высоком коэффициенте объединения по входу (fan#in) к конкретному классу обращается большое число других классов. Это значит, что система предусматривает интенсивное использо- вание вспомогательных низкоуровневых классов. Низкий или средний коэффициент разветвления по выходу Это означает, что конкретный класс обращается к малому или среднему числу других классов. Высокий коэффициент разветвления по выходу (fan#out) (более семи) говорит о том, что класс использует большое число других классов и, возможно, слишком сложен. Ученые обнаружили, что низкий коэффициент разветвления по выходу выгоден как в случае вызова методов из метода, так и в случае вызова методов из класса (Card and Glass, 1990; Basili, Briand, and Melo, 1996). Портируемость Проектируйте систему так, чтобы ее можно было легко адап- тировать к другой среде. Минимальная, но полная функциональность Этот аспект подразумевает от- сутствие в системе лишних частей (Wirth, 1995; McConnell, 1997). Вольтер говорил, что книга закончена не тогда, когда в нее больше нечего добавить, а когда из нее ничего нельзя выбросить. При разработке ПО это верно вдвойне, потому что до- полнительный код необходимо разработать, проанализировать, протестировать, а также пересматривать при изменении других фрагментов программы. Кроме того, в будущих версиях приложения придется поддерживать обратную совместимость с дополнительным кодом. Опасайтесь вопроса: «Эту функцию реализовать легко — почему бы этого не сделать?» Стратификация Под стратификацией понимают разделение уровней декомпо- зиции, позволяющее изучить систему на любом отдельном уровне и получить при этом согласованное представление. Проектируйте систему так, чтобы ее можно было изучать на отдельных уровнях, игнорируя другие уровни. Например, если вы создаете современную систему, кото- рая должна использовать большой объем старого, плохо спроектированного кода, напишите уровень, отвечающий за взаи модействие со старым кодом. Спроектируйте этот уровень так, чтобы он скрывал плохое качество старого кода, предоставляя более новым уровням согласованный набор сервисов. Пусть осталь- ные части системы работают с этими классами вместо старого кода. Такой подход сулит два преимущества: 1) он изолирует плохой код и 2) если вы когда#нибудь решите выбросить старый код или выполнить его рефакторинг, вам не придется изменять новый код за исключением промежуточного уровня. Соответствие стандартным методикам Чем экзо- тичнее система, тем сложнее будет другим программистам понять ее. Попытайтесь придать всей системе привычный для разработчиков облик, применяя стандартные популяр- ные подходы. Перекрестная ссылка О работе со старыми системами см. раз- дел 24.5. Перекрестная ссылка Об осо- бенно полезном типе стратифи- кации — применении шаблонов проектирования — см. подраз- дел «Старайтесь использовать популярные шаблоны проекти- рования» раздела 5.3. CC2_Part2_ch5_2010.indd 78 22.06.2010 12:31:33 ГЛАВА 5 Проектирование при конструировании 79 Уровни проектирования Проектирование программной системы требует нескольких уровней детальности. Некоторые методы проектирования используются на всех уровнях, а другие только на одном#двух (рис. 5#2). Рис. 5'2. Уровни проектирования программы. Систему (1) следует разделить на подсистемы (2), подсистемы — на классы (3), а классы — на методы и данные (4); методы также необходимо спроектировать (5) Уровень 1: программная система Первому уровню проектирования соответствует вся система. Некоторые программисты с системного уровня сразу пере- ходят к проектированию классов, но обычно целесообразно обдумать более высокоуровневые комбинации классов, такие как подсистемы или пакеты. Уровень 2: разделение системы на подсистемы или пакеты Главный результат проектирования на этом уровне — определение основных подсистем. Подсистемы могут быть Иными словами — и это неиз- менный принцип, на котором основан всегалактический успех всей корпорации, — фундамен- тальные изъяны конструкции ее товаров камуфлируются их внешними изъянами. Дуглас Адамс (Douglas Adams) CC2_Part2_ch5_2010.indd 79 22.06.2010 12:31:33 |