Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (1158635), страница 60
Текст из файла (страница 60)
Мыначинаем с анализа требований к следующему релизу, переходим кпроектированию архитектуры и исследуем классы и объекты, необходимыедля реализации этого проекта. Типичный порядок действий таков:•Определить функциональные точки, которые попадут в новый релиз,и области наивысшего риска, особенно те, которые были выявленыеще при эволюции предыдущего релиза.•Распределить задачи по релизам среди членов команды и начатьновый микропроцесс. Контролировать микропроцесс,просматривая проект, и проверять состояние дел в важныхпромежуточных этапах с интервалами от нескольких дней до двухнедель.•Когда потребуется понять семантику требуемого поведения системы,поручить разработчикам сделать прототип поведения.
Четкоустановить назначение каждого прототипа и определить критерииготовности. После завершения решить, как включить результатыпрототипирования в этот или последующие релизы.•Завершить микропроцесс интеграцией и очередным действующимрелизом.После каждого релиза следует перепроверить сроки и требования восновном плане релизов. Как правило, это незначительные корректировки датили перенос функциональности из одного релиза в другой.Управление изменениями необходимо именно в связи со стратегиейитеративного развития. Всегда соблазнительно вносить неупорядоченныеизменения в иерархию классов, их протоколы или механизмы, но этоподтачивает стратегическую архитектуру и приводит к тому, что разработчикисами начинают путаться в собственном коде.При эволюции системы на практике ожидаются следующие типыизменений:•Добавление нового класса или нового взаимодействия междуклассами.•Изменение реализации класса.•Изменение представления класса.•Реорганизация структуры классов.•Изменение интерфейса класса.Каждый тип изменений имеет свою причину и стоимость.Проектировщик вводит новые классы, если обнаружились новыеабстракции или понадобились новые механизмы.
Цена выполнения такихизменений обычно несущественна для управления разработкой. Еслидобавляется новый класс, нужно рассмотреть, куда он попадет всуществующей структуре классов. Когда вводится новое взаимодействиеклассов, должен быть произведен минимальный анализ предметной области,чтобы убедиться, что оно действительно удовлетворяет одному из шаблоноввзаимодействия.Изменение реализации также обходится недорого. Обычно приобъектно-ориентированной разработке сначала создается интерфейс класса, апотом пишется его реализация (то есть код функций-членов). Если толькоинтерфейс в приемлемой степени стабилен, можно выбрать любое внутреннеепредставление этого класса и выполнить реализацию его методов.
Реализацияотдельного метода может быть изменена (обычно для исправления ошибкиили повышения эффективности) позже. Можно скорректировать реализациюметода, чтобы воспользоваться преимуществами новых методов,определенных в существующем или во вновь введенном суперклассе. Влюбом случае изменение реализации метода обходится сравнительнонедорого, особенно, если она была своевременно инкапсулирована.Подобным образом можно было бы изменить представление класса (вC++ - защищенные и закрытые члены класса).
Обычно это делается, чтобыполучить более эффективные (с точки зрения памяти или скорости)экземпляры класса. Если представление класса инкапсулировано, чтовозможно в Smalltalk, C++, CLOS и Ada, то изменение в представлении небудет разрушать логику взаимодействия объектов-пользователей сэкземплярами класса (если, конечно, новое представление обеспечиваетожидаемое поведение класса). С другой стороны, если представление классане инкапсулировано, что также возможно в любом языке, то изменение впредставлении класса чрезвычайно опасно, так как клиенты могут от негозависеть.
Это особенно верно в случае подклассов: изменение представлениясуперкласса вызовет изменения представления всех его подклассов. Во всякомслучае, изменение представления класса имеет цену: нужно произвестиперекомпиляцию интерфейса и реализации класса, сделать то же для всех егоклиентов, для клиентов тех клиентов и т. д.Реорганизация структуры классов системы встречается довольночасто, хотя и реже, чем другие упомянутые виды изменений. Как отмечаютСтефик и Бобров, "Программисты часто создают новые классы иреорганизуют имеющиеся, когда они видят удобную возможность разбитьсвои программы на части" [26]. Изменение структуры классов обычнопроисходит в форме изменения наследственных связей, добавления новыхабстрактных классов и перемещения обязанностей и реализации общихметодов в классы более верхнего уровня в иерархии классов.
На практикеструктура классов системы особенно часто реорганизуется вначале, а потом,когда разработчики лучше поймут взаимодействие ключевых абстракций,стабилизируется. Реорганизация структуры классов поощряется на раннихстадиях проектирования, потому что в результате может получиться болеелаконичная программа. Однако реорганизация структуры классов необходится даром. Обычно изменение положения верхнего класса в иерархииделает устаревшими определения всех классов под ним и требует ихперекомпиляции (а, значит, и перекомпиляции всех зависимых от них классови т. д.).Еще один важный вид изменений, к которому приходится прибегатьпри эволюции системы, - изменение интерфейса класса. Разработчик обычноизменяет интерфейс класса, чтобы добавить некоторый новый аспект,удовлетворить семантике некоторой новой роли объектов класса или добавитьновую операцию, которая всегда была частью абстракции, но раньше не былаэкспортирована, а теперь понадобилась некоторому объекту-пользователю.
Напрактике использование эвристик для построения классов, которые мыобсуждали в главе 3 (особенно требование примитивного, достаточного иполного интерфейса), сокращает вероятность таких изменений. Однако нашопыт никогда не бывает окончательным. Мы никогда не определимнетривиальный класс так, чтобы интерфейс его сразу оказался правильным.Редко, но встречается удаление существующего метода; это обычноделается только для того, чтобы улучшить инкапсуляцию абстракции.
Чащемы добавляем новый метод или переопределяем метод, уже объявленный внекотором суперклассе. Во всех трех случаях это изменение дорого стоит,потому что оно логически затрагивает всех клиентов, требуя как минимум ихперекомпиляции. К счастью, эти последние виды изменений, добавление ипереопределение методов, совместимы снизу вверх. На самом деле выобнаружите, что большинство изменений интерфейса, произведенного надопределенными классами при эволюции системы, совместимы снизу вверх.Это позволяет для уменьшения воздействия этих изменений применить такиеизощренные технологии, как инкрементная компиляция. Инкрементнаякомпиляция позволяет нам вместо целых модулей перекомпилировать толькоотдельные описания и операторы, то есть перекомпиляции большинстваклиентов можно избежать.Почему перекомпиляция так неприятна? Для маленьких систем здесьнет проблем: перекомпиляция всей системы занимает несколько минут.Однако для больших систем это совсем другое дело.
Перекомпиляцияпрограммы в сотни тысяч строк может занимать до половины сутокмашинного времени. Представьте себе, что вам понадобилось внестиизменение в программное обеспечение компьютерной системы корабля. Каквы сообщите капитану, что он не может выйти в море, потому что вы все ещекомпилируете? В некоторых случаях цена перекомпиляции бывает так высока,что разработчикам приходится отказаться от внесения некоторых,представляющих разумные усовершенствования, изменений. Перекомпиляцияпредставляет особую проблему для объектно-ориентированных языков, таккак наследование вводит дополнительные компиляционные зависимости [27].Для строго типизированных объектно-ориентированных языковпрограммирования цена перекомпиляции может быть даже выше; в этихязыках время компиляции принесено в жертву безопасности.Все изменения, обсуждавшиеся до настоящего времени, сравнительнолегкие:самый большой риск несут существенные изменения в архитектуре,которые могут погубить весь проект.
Часто такие изменения производятчересчур блестящие инженеры, у которых слишком много хороших идей [28].Путевые вехи и характеристики. Мы благополучно завершим фазуреализации, когда релизы перерастут в готовый продукт. Первой меройкачества, следовательно, будет то, в какой степени мы справились среализацией функциональных точек, распределенных по промежуточнымрелизам, и насколько точно соблюдается график, составленный при ихпланировании.Две других основных меры качества - скорость обнаружения ошибок ипоказатель изменчивости ключевых архитектурных интерфейсов итактических принципов.Грубо говоря, скорость обнаружения ошибок - это мера того, какбыстро отыскиваются новые ошибки [29].
Вкладывая средства в контролькачества в начале разработки, мы можем получить количественные оценкикачества для каждого ре-лиза, которые менеджеры команды смогутиспользовать для определения областей риска и обновления командыразработчиков. После каждого релиза должен наблюдаться всплескобнаружения ошибок. Стабильность этого показателя обычно свидетельствуето том, что ошибки не обнаруживаются, а его чрезмерная величина говорит отом, что архитектура еще не стабилизировалась или что новые элементыневерно спроектированы или реализованы.