Ситуация усугубляется тем обстоятельством, что проектирование - никак не точная наука. Возьмем проектирование баз данных, одну из технологий, предшествовавших объектно-ориентированному проектированию. Как замечает Хаврис-кевич: "Хотя все выглядит просто и ясно, неизбежно примешивается изрядная доля личного представления о важности различных объектов на предприятии. В результате процесс проектирования не воспроизводим: разные проектировщики могут создать разные модели одного и того же предприятия" [3].
Из этого можно сделать вывод, что при любом самом изощренном и теоретически обоснованном методе проектирования нельзя игнорировать практические соображения. Значит, мы должны принять во внимание управленческий опыт в таких областях, как подбор кадров, управление релизами и контроль качества. Для технолога это в высшей степени скучная материя, но для разработчика это реалии жизни, с которыми надо справляться, чтобы создавать сложные программные системы. Итак, в этой главе мы займемся практическими вопросами объектно-ориентированной разработки и влиянием объектной модели на управление.
7.1. Управление и планирование
Если мы при проектировании опираемся на метод итеративного развития, то важнее всего иметь сильное руководство, способное управлять ходом проекта и направлять его. Слишком много проектов сбились с пути из-за неспособности сосредоточиться на главном, и только сильная команда менеджеров может что-то с этим поделать.
Управление риском
В конечном счете, главная обязанность менеджера программного продукта - управление как техническим, так и нетехническим риском. Технический риск для объектно-ориентированной системы содержится в решении таких проблем, как выбор структуры наследования классов, обеспечивающий наилучший компромисс между удобством и гибкостью программного продукта. Серьезное решение приходится также принимать при выборе механизмов упрощения архитектуры и улучшения эффективности. Нетехнический риск содержит в себе такие вопросы, как контроль своевременности поставки программных продуктов от третьих фирм или регулирование отношений заказчика и разработчиков, что необходимо для выяснения реальных требований к системе на стадии анализа.
Как было описано в предыдущей главе, микропроцесс объектно-ориентированной разработки нестабилен по своей природе и требует активного управления, концентрации усилий. К счастью, существует макропроцесс разработки, который выдвигает ряд конкретных требований и характеристик. Менеджер проекта, изучая соответствие требований и фактических результатов, может оценить состояние разработки и, при необходимости, перенаправить ресурсы команды. Эволюционная суть макропроцесса разработки означает, что можно распознать проблемы в начале жизненного цикла и продуманно учесть связанный с ними риск прежде, чем проект окажется в опасности.
Многие виды деятельности по управлению разработкой программного обеспечения, например, планирование задач и просмотры, предусмотрены не только в объектно-ориентированной технологии. Однако при управлении объектно-ориентированным проектом намечаемые задачи и рассматриваемые результаты не совсем такие, как в других системах.
Планирование задач
Независимо от размера проекта, которым вы заняты, полезно раз в неделю проводить встречу всех разработчиков для обсуждения выполненной работы и действий на следующую неделю. Некоторая минимальная частота встреч необходима, чтобы способствовать общению между членами коллектива. С другой стороны, слишком частые встречи снижают продуктивность и обычно являются признаком потери курса. Объектно-ориентированная разработка требует, чтобы разработчики имели достаточное время для размышлений, введения новшеств и неформального общения с коллегами. Менеджеры команды должны учитывать в плане и это не структурированное время.
Проводимые встречи дают простую, но эффективную возможность гладкой подстройки планов в микропроцессе и распознания показавшихся на горизонте опасных ситуаций. Результатом такой встречи может быть небольшая корректировка в распределении работ, обеспечивающая устойчивость процесса: никакой проект не может позволить хотя бы одному из разработчиков сидеть сложа руки, ожидая, пока другие члены команды приведут в порядок свою часть архитектуры. Это особенно верно для объектно-ориентированных систем, в которых архитектура представляется набором классов и механизмов. Проект может заглохнуть, если разработчикам никак не удается разобраться с одним из ключевых классов.
Планирование задач связано с построением графика представления результатов макропроцесса. В промежутках между очередными релизами менеджеры команды должны оценить трудности, угрожающие проекту [Гилб замечает: "если вы не идете в атаку на трудности, трудности идут в атаку на вас" [5]], сконцентрировать ресурсы, чтобы разрешить возникшие проблемы, и далее заниматься новой итерацией микропроцесса, в результате которой нужно получить стабильную систему, удовлетворяющую сценариям, запланированным для нового релиза. Планирование задач на этом уровне очень часто оказывается неудачным из-за чрезмерно оптимистических графиков [4]. Разработка, которая рассматривалась как "просто вопрос программирования", растягивается на многие дни работы; графики выбрасываются в корзину, когда разработчик, занимаясь частью системы, предполагает определенные протоколы для других частей системы, а потом получает неполно или неправильно изготовленные классы. Смертельную опасность могут представлять внезапно обнаружившиеся ошибки в компиляторе или то, что программа не укладывается в заданное время исполнения. И то и другое часто приходится преодолевать, жертвуя принятыми ранее тактическими решениями.
Ключ к тому, чтобы не поддаваться чрезмерно оптимистическому планированию, - "калибровка" команды и ее инструментов разработки. Типичное планирование задач протекает следующим образом. Вначале менеджер направляет энергию разработчика на специфические части системы, например на проектирование классов для интерфейса с реляционной базой данных. Разработчик анализирует необходимые усилия и оценивает время исполнения, которое менеджер учитывает при планировании других его действий. Проблема в том, что эти оценки не всегда реальны: они обычно делаются в расчете на самый благоприятный случай. Один разработчик может согласиться на решение задачи за неделю, а другой на эту же задачу попросит месяц. Когда работа будет реально выполнена, может оказаться, что она отняла три недели рабочего времени у обоих разработчиков: первый разработчик недооценил усилия (общая проблема многих программистов), а второй разработчик оценил реальные усилия более точно (например потому, что он понимал разницу между действительным рабочим временем и календарным, которое часто заполнено множеством нефункциональных действий). Таким образом, чтобы разработать графики, к которым коллектив может иметь доверие, менеджерам необходимо ввести своего рода "калибровочные коэффициенты" для пересчета оценок времени, заявленных разработчиками. Это не признак того, что менеджеры не доверяют разработчикам, но просто признание того факта, что большинство программистов сосредоточены на технических проблемах, а не на задачах планирования. Менеджер должен помогать разработчикам учиться планировать, - но это тот навык, который может быть приобретен только опытом.
Объектно-ориентированный процесс разработки помогает выявить явные принципы калибровки. Метод итеративного развития позволяет в начале проекта найти множество промежуточных пунктов, которые менеджеры команды использовали бы для накопления данных о достижениях каждого разработчика, определения графиков работы и планирования встреч. При эволюционной разработке руководители коллектива со временем будут лучше понимать реальную продуктивность каждого своего разработчика, а разработчики смогут научиться более точно оценивать объем предстоящей работы. Те же выводы приложимы и к инструментам: архитектурные релизы уже на ранней стадии проекта стимулируют использование инструментов разработки, которые помогают своевременно проверить структурные ограничения.
Просмотр
Просмотр (walkthroughs) - общепринятая практика, которую нужно использовать каждой команде разработчиков. Как и планирование задач, просмотр программного обеспечения был введен независимо от объектно-ориентированной технологии. Однако при просмотре не объектно-ориентированных систем внимание обращается на другое.
Руководитель должен проводить просмотры с разумной частотой. За исключением самых ответственных и уязвимых для ошибок мест, просто неэкономично проверять каждую строчку программы. Следовательно, руководитель должен направить ограниченные ресурсы своей команды на рассмотрение проблем, опасных для стратегии разработки. Для объектно-ориентированных систем это означает большую формальность при проведении просмотров сценариев и архитектуры системы и менее формальную проверку тактических решений.
Как описано в предыдущей главе, сценарии являются первичным результатом объектно-ориентированного анализа. Они должны выражать требуемое поведение системы в терминах ее функциональных точек. Формальные просмотры сценариев проводятся аналитиками команды, вместе с экспертами предметной области или конечными пользователями при возможном участии других разработчиков. Лучше проводить такие просмотры на протяжении всей стадии анализа, чем ожидать выполнения одного глобального просмотра по завершении анализа, когда будет уже слишком поздно сделать что-нибудь полезное, перенаправив усилия аналитиков. Эксперименты показывают, что даже непрограммисты могут понять сценарии, представленные в виде текста или диаграмм объектов [Мы встречались с использованием этой системы обозначении в работе таких непрограммистских групп как астрономы, биологи, метеорологи, физики и банкиры]. В конечном счете просмотр помогает выработать общий словарь для разработчиков и пользователей системы. Привлечение к участию в просмотре других членов команды способствует уяснению ими реальных требований к системе на ранних этапах разработки.
Просмотр архитектуры должен охватывать всю систему, включая ее механизмы и структуру классов. Как и при просмотре сценариев, просмотр архитектуры (архитектором или другими проектировщиками) должен производиться на протяжении всего проекта. Сначала просмотр сосредоточен на общих архитектурных решениях, а позднее, возможно, он акцентируется на некоторых категориях классов или конкретных механизмах. Основная цель просмотра состоит в проверке архитектуры в начале жизненного цикла и выработке общего взгляда на нее. Вторичной целью является поиск повторяющихся шаблонов классов или взаимодействий, которые затем могут быть использованы для упрощения архитектуры.
Неформальный просмотр следует проводить еженедельно. На нем обычно рассматриваются некоторые группы классов или механизмы нижнего уровня. Цель - проверить тактические решения; побочная цель - дать возможность старшим разработчикам научить новичков.
7.2. Кадры
Распределение ресурсов
Один из наиболее замечательных аспектов управления объектно-ориентированными проектами - это тот факт, что в устойчивом состоянии обычно наблюдается сокращение необходимых ресурсов и изменяется график их расходования по сравнению с традиционными методами. Именно "в устойчивом состоянии". Вообще говоря, первый объектно-ориентированный проект, предпринятый организацией, потребует несколько больше ресурсов - главным образом, в соответствии с кривой обучения, описывающей адаптацию ко всякой новой технологии. Выгоды проявятся во втором или третьем проекте, когда разработчики наберутся опыта в проектировании классов, поиске общих абстракций и механизмов, а менеджеры освоятся с методом итеративного развития.
На стадии анализа потребность в ресурсах с переходом на объектно-ориентированные методы обычно мало изменяется. Однако, поскольку объектно-ориентированный процесс уделяет больше внимания архитектуре, мы стремимся привлекать архитекторов и других разработчиков как можно раньше, иногда начиная архитектурные эксперименты еще на последней стадии анализа. Во время эволюции, как правило, потребуется меньше ресурсов, потому что работа облегчится общими абстракциями и механизмами, изобретенными ранее при проектировании архитектуры или выпуске предварительных версий. Тестирование может также потребовать меньше ресурсов, потому что новые функции обычно добавляются к уже корректно ведущей себя структуре класса или механизму. Таким образом, тестирование начинается раньше и является скорее постоянным и постепенным, чем разовым действием. Интеграция обычно требует значительно меньших ресурсов по сравнению с традиционными методами, главным образом потому, что она тоже происходит постепенно, от релиза к релизу, а не одним броском. Таким образом, в устойчивом состоянии трудозатраты оказываются гораздо меньше, чем при традиционных подходах. Более того, если учесть эксплуатационные затраты, то окажется, что весь жизненный цикл объектно-ориентированных программ часто стоит дешевле, так как конечный продукт, скорее всего, будет лучшего качества и окажется более приспособленным к изменениям.
Роли разработчиков
Полезно помнить, что разработка программного продукта в конечном счете производится людьми. Разработчики - не взаимозаменяемые части, и успешное создание любой сложной системы требует уникальных и разнообразных навыков всех членов целеустремленного коллектива.
Эксперименты показывают, что объектно-ориентированная разработка требует несколько иного разделения труда по сравнению с традиционными методами. Мы считаем следующие три роли разработчиков важнейшими в объектно-ориентированном подходе:
Очень плохая практика - нанимать архитектора со стороны, который, образно выражаясь, въезжает на белом коне, провозглашает архитектурные принципы, а потом уматывает куда-то, в то время как другие пытаются справиться с последствиями его решений. Гораздо лучше привлечь архитектора к активной работе уже при проведении анализа и оставить его на как можно более длительный срок, даже на все время эволюции системы. Тогда он освоится с действительными потребностями системы и со временем испытает на себе последствия своих решений. Кроме того, сохраняя в руках одного человека или небольшой команды разработчиков ответственность за архитектурную целостность, мы повышаем шансы получить гибкую и простую архитектуру.
Ответственные за подсистемы - главные творцы абстракций проекта. Они отвечают за проектирование целых категорий классов или подсистем. Каждый ответственный в сотрудничестве с архитектором проекта разрабатывает, обосновывает и согласует с другими разработчиками интерфейс своей категории классов или подсистемы, а потом возглавляет ее реализацию, тестирование и выпуск релизов в течение всей эволюции системы.
Ответственные за подсистемы должны хорошо знать систему обозначений и организацию процесса объектно-ориентированной разработки. Обычно они программируют лучше чем архитекторы проекта, но не располагают обширным опытом последних. Лидеры подсистем составляют от трети до половины численности команды.
Прикладные программисты (инженеры) - младшие по рангу участники проекта. На них возложено выполнение двух обязанностей. Некоторые из них отвечают за реализацию категории или подсистемы под руководством ее ведущего. Эта деятельность может включать в себя проектирование некоторых классов, но в основном связана с реализацией и последующим тестированием классов и механизмов, разработанных проектировщиками команды. Другие отвечают за написание классов, спроектированных архитектором и ответственными за подсистемы, реализуя тем самым функциональные точки системы. В некотором смысле, эти программисты занимаются написанием маленьких программ на языке предметной области, определенном классами и механизмами архитектуры.
Инженеры разбираются в системе обозначений и в организации процесса разработки, но не слишком блестяще; зато они, как правило, являются очень хорошими программистами, знающими основные идиомы и слабые места выбранных языков программирования. Инженеры составляют половину команды или более того.
Разница в квалификации ставит проблему подбора кадров перед всеми организациями, которые обычно имеют несколько сильных проектировщиков и большее количество менее квалифицированного персонала. Социальная польза нашего подхода к кадровой политике состоит в том, что он открывает путь для карьеры начинающим сотрудникам: молодые разработчики работают под руководством более опытных. Когда они наберутся опыта в использовании хорошо определенных классов, они смогут сами проектировать классы. Вывод: не обязательно каждому разработчику быть экспертом по абстракциям, но каждый разработчик может со временем этому научиться.
В больших проектах могут потребоваться и другие роли. Большинство из них (например, роль специалиста в средствах разработки) явно не связаны с объектно-ориентированной технологией, но некоторые непосредственно вытекают из нее (такие, как инженер, отвечающей за повторное использование):
• Менеджер проекта | Отвечает за управление материалами проекта, заданиями, ресурсами и графиком работ. |
• Аналитик | Отвечает за развитие и интерпретацию требований конечных пользователей; должен быть экспертом в проблемной области, однако его не следует изолировать от остальной команды разработчиков. |
• Инженер по повторному использованию | Управляет хранилищем (репозитарием) материалов проекта; участвуя в просмотре и других действиях, активно ищет общее и добивается его использования; находит, разрабатывает или приспосабливает компоненты для общего использования в рамках конкретного проекта или целой организации. |
• Контролер качества | Измеряет результаты процесса разработки; задает общее направление (на системном уровне) тестирования всех прототипов и релизов. |
• Менеджер интеграции | Отвечает за сборку совместимых друг с другом версий категорий и подсистем в релизы; следит за их конфигурированием. |
• Ответственный за документацию | Готовит документацию по выпускаемому продукту и его архитектуре для конечного пользователя. |
• Инструментальщик | Отвечает за создание и адаптацию инструментов программирования, которые облегчают производство программ и (особенно) генерацию кода. |
• Системный администратор | Управляет физическими компьютерными ресурсами в проекте. |
Опыт показывает, что объектно-ориентированная разработка может обойтись меньшим числом занятых в ней людей по сравнению с традиционными методами. На самом деле, чтобы за один год произвести высококачественную программу объемом в несколько сот тысяч строк достаточно 30-40 разработчиков. Однако мы согласны с Боемом, который считает, что "лучшие результаты получаются, когда разработчиков занято меньше, а квалификация их выше" [6]. К сожалению, если при разработке проекта пытаться обойтись меньшим количеством людей, чем считается необходимым, можно встретить известное сопротивление. Как отмечалось в предыдущей главе, такое отношение, возможно, вызвано попытками некоторых менеджеров построить империю. Другие менеджеры любят скрываться за множеством служащих, потому что большее количество людей означает больше власти. Кроме того, в случае провала проекта есть на кого свалить вину.
Применение самого изощренного метода проектирования или новейших инструментов не освобождает менеджера от ответственности за подбор проектировщиков, способных мыслить, и не является основанием для того, чтобы пустить проект на самотек [7].
7.3. Управление релизами
Интеграция
Промышленные программные проекты требуют создания семейств программ. В процессе разработки создаются прототипы и релизы. Очень часто каждый разработчик имеет свое собственное представление о разрабатываемой системе.
В предыдущей главе объяснялось, что в процессе объектно-ориентированной разработки интеграция редко проводится за один раз. Обычно происходит множество мелких интеграции, каждая из которых соответствует созданию нового прототипа или архитектурного релиза. Каждый новый релиз поступательно развивает предыдущие стабильные релизы. "При итеративной разработке сначала строится программный продукт, удовлетворяющий нескольким конечным требованиям, но конструктивно выполненный так, чтобы облегчить затем удовлетворение всех требований и достичь таким образом большей адаптируемости" [8]. С точки зрения конечного пользователя, поток релизов проистекает из макропроцесса. Каждый следующий релиз поддерживает все больше функций, и в конечном счете они развиваются в готовую систему. С точки зрения человека, наблюдающего процесс изнутри, создается много больше релизов, но только некоторые из них будут "заморожены", чтобы стабилизировать важнейшие интерфейсы системы, и взяты за основу для дальнейшей работы. Такая стратегия дает возможность снизить риск разработки, ускоряет выявление проблем в архитектуре и узких мест уже на ранних стадиях.
Для больших проектов организация может готовить внутренние релизы системы каждые несколько недель, а релизы для заказчика - раз в несколько месяцев, в соответствии с потребностями проекта. В стабильном состоянии релиз состоит из множества совместимых подсистем вместе с соответствующей документацией. Приступать к построению релиза можно при условии, что главные подсистемы проекта достаточно стабильны, а их совместное взаимодействие достаточно слажено, чтобы обеспечить новый уровень функциональности.
Управление конфигурацией и версиями
Рассмотрим поток релизов с точки зрения разработчика, занятого созданием некоторой подсистемы. Он имеет текущую версию этой подсистемы. Следовательно, в его распоряжении должны быть как минимум интерфейсы всех импортируемых подсистем. Когда рабочая версия стабилизируется, она передается команде, занимающейся интеграцией, которая отвечает за сборку совместимых подсистем в целую систему. В конце концов набор подсистем замораживается, берется за точку отсчета и входит во внутренний релиз. Внутренний релиз становится текущим оперативным релизом, доступным всем разработчикам, которым нужно провести дальнейшую доработку своих частей реализации. Наш разработчик в это время может трудиться над новой версией своей подсистемы. Таким образом, разработка может вестись параллельно и оставаться устойчивой благодаря четко определенным и защищенным интерфейсам подсистем.
В этой модели неявно подразумевается, что единицей контроля версий является не отдельный класс, а группа классов. Опыт показывает, что управление версиями классов слишком безнадежное дело, так как они слишком зависимы друг от друга. Лучше выпускать версии связанных групп классов, точнее говоря - подсистем, поскольку кластеры классов логической модели системы отображаются в подсистемы физической модели.
В любой момент разработки системы могут существовать несколько версий ее подсистем: версия для текущего разрабатываемого релиза, версия для текущего внутреннего релиза, версия для последующего релиза, предназначенного для заказчика и т.д. Это обостряет потребность в достаточно мощных средствах управления конфигурацией и версиями.
Под понятие "исходный код" подпадает не только текст программ, но и все остальные продукты объектно-ориентированного развития: технические требования, диаграммы классов, объектов, модулей и процессов.
Тестирование
Принцип непрерывной интеграции приложим и к тестированию, которое также производится в течение всего процесса разработки. В контексте объектно-ориентированной архитектуры тестирование должно охватывать как минимум три направления:
Тестирование должно фокусироваться на внешнем поведении системы; его побочная цель - определить границы системы чтобы понять, как она может выходить из строя при определенных условиях.
7.4. Повторное использование
Элементы повторного использования
Любой программный продукт (текст программы, архитектура, сценарий или документация) может быть использован повторно. Как сказано в главе 3, в объектно-ориентированных языках программирования первичным лингвистическим средством повторного использования являются классы: класс может порождать подклассы, специализирующие или дополняющие его. Далее, в главе 4 говорилось о повторном использовании шаблонов классов, объектов и элементов проектирования в форме идиом, механизмов и сред разработки. Повторное использование шаблонов находится на более высоком уровне абстракции по сравнению с использованием индивидуальных классов и дает больший выигрыш (хотя оно труднее достижимо).
Не следует доверять цифрам, характеризующим повторное использование [9]. В удачных проектах, с которыми мы сталкивались, количество повторно использованных элементов доходило до 70% (то есть почти три четверти программного обеспечения системы было взято без изменений из некоторого другого источника), но бывало и нулевым. Не следует думать, что повторное использование должно достичь некоторой обязательной величины; возможность повторного использования сильно зависит от предметной области и нетехнических факторов, таких, например, как степень напряженности рабочего графика, природа отношений с субподрядчиками и соображения безопасности.
Безусловно, любой процент повторного использования лучше, чем нулевой, так как экономит ресурсы, которые иначе пришлось бы потратить еще раз.
Как осуществить повторное использование?
Повторное использование в пределах проекта или даже целой организации не должно протекать по воле случая. Нужно специально выискивать возможности и поощрять успехи. Именно поэтому мы включили поиск повторяющихся шаблонов в макропроцесс.
Лучше всего поручить повторное использование кому-то лично. Как описывалось в предыдущей главе, надо искать возможные общности, обычно выявляемые при просмотре архитектуры, реализовывать их, создавая новые или приспосабливая старые компоненты, а потом отстаивать их перед другими разработчиками. Даже простые формы поощрения, такие, как равное признание автора первоначального кода и первооткрывателя возможности заимствования, оказывают стимулирующее воздействие. Можно придумать что-нибудь посущественнее - обед в ресторане или путешествие на выходные для двоих - и присуждать эти поощрения тем разработчикам, чьи решения были заимствованы чаще всего, или тем, которые заимствовали наибольшую часть кода за заданное время [Близкие к разработчикам люди часто терпят некоторый моральный урон в заключительной горячке разработки, и такая компенсация им будет весьма кстати].
Повторное использование может и не принести краткосрочных выгод, но окупается в долгосрочной перспективе. Этим имеет смысл заниматься в организации, которая имеет обширные, далеко идущие планы разработки программного обеспечения и смотрит дальше интересов текущего проекта.
7.5. Качество и измерения
Качество программного продукта
Шульмейер и МакМанус определяют качество программного продукта как "пригодность к использованию" [10]. Качество программы не должно быть делом случая. Качество должно гарантироваться процессом разработки. На самом деле, объектно-ориентированной технология не порождает качества автоматически: можно написать сколь угодно плохие программы на любом объектно-ориентированном языке программирования.
Вот почему в процессе объектно-ориентированной разработки мы придаем такое значение архитектуре программной системы. Качество закладывается благодаря простой, гибкой архитектуре и осуществляется естественными и последовательными тактическими решениями.
Контроль качества программного продукта - это "систематические действия, подтверждающие пригодность к использованию программного продукта в целом" [11]. Цель контроля качества - дать нам количественные меры качества программной системы. Многие традиционные количественные меры непосредственно приложимы и к объектно-ориентированным системам.
Как описывалось выше, разбор и просмотр не теряют своей значимости в объектно-ориентированных системах, позволяя предсказать качество системы и влиять на него. Возможно, самым главным количественным критерием качества является количество обнаруженных ошибок. Во время эволюции системы мы учитываем программные ошибки в соответствии с их весом и расположением. График обнаружения ошибок отображает зависимость количества обнаруженных ошибок от времени. Как указывает Доббинс, "не так важно действительное число ошибок, как наклон этого графика" [12]. Для управляемого процесса этот график имеет форму горба, с вершиной примерно в середине периода тестирования, а дальше эта кривая падает до нуля. Неуправляемому процессу соответствует неубывающая со временем или медленно убывающая кривая.
Одно из достоинств макропроцесса в объектно-ориентированной разработке состоит в том, что он позволяет вести непрерывный сбор данных о количестве обнаруженных ошибок уже на ранних стадиях разработки. Для каждого нового релиза мы можем провести тестирование системы и нарисовать график зависимости количества ошибок от времени. У "здорового" проекта горбовидная форма этого графика наблюдается для каждого релиза, начиная с самых ранних.
Другая количественная мера - плотность ошибок. Количество обнаруженных ошибок на килостроку программного текста (KSLOC - Kilo Source Lines Of Code) является традиционным показателем, применимым, в частности, к объектно-ориентированным системам. В "здоровых" проектах плотность ошибок "имеет тенденцию достигать стабильного значения при просмотре примерно 10 KSLOC. Просматривая код далее, мы не должны наблюдать увеличения этого показателя" [13].
Мы полагаем, что в объектно-ориентированных системах полезно также измерять число ошибок на категорию классов или на класс. При этом правило 80/20 считается приемлемым: 80% выявленных ошибок в программе сосредоточено в 20% классов системы [14].
В дополнение к этим более формальным подходам к накоплению получаемой при тестировании информации об ошибках, мы считаем полезным устраивать "охоту за ошибками", в которой все желающие могут экспериментировать с релизом в течение ограниченного промежутка времени, после чего награждается призами тот, кто обнаружил наибольшее количество ошибок, и тот, кто отыскал самую незаметную ошибку. Призы не должны быть экстравагантными: для награждения бесстрашных охотников годятся кофейная кружка, талоны на обед, билеты в кино или даже футболка.
Объектно-ориентированные меры
Наверное, самый скверный способ оценить сделанную работу, каким может воспользоваться управляющий, - сосчитать количество написанных строк текста программы. Число строк, попавших во фрагмент кода, абсолютно никак не связано с его завершенностью и сложностью. В дополнение к другим недостаткам этого неандертальского подхода, в нем слишком просто играть с цифрами, что приводит к оценкам производительности, отличающимся друг от друга более, чем на два порядка. Например, что такое строка программы (особенно на Smalltalk)? Считаются ли физические строки или точки с запятой? Как учесть несколько операторов на одной строке и операторы, которые занимают более одной строки? А как измерить количество затраченного труда? Считать код работой всего персонала или, может быть, только программистов, написавших реализацию? Рабочий день должен считаться восьмичасовым или время, проведенное за утренней раскачкой, тоже должно учитываться? Традиционные меры сложности более подходят для первых поколений языков программирования, они не являются показателями завершенности и сложности объектно-ориентированной системы.
Например, цикломатическая метрика МакКэйба не будет сколько-нибудь полезной мерой сложности, если ее применить к объектно-ориентированной системе в целом, потому что она слепа к структуре классов системы и механизмам. Однако, мы находим полезным применять цикломатическую метрику к отдельным классам, - она дает некоторое представление об их сложности и может быть использована для определения наиболее подозрительных классов, которые, вероятно, содержат больше всего ошибок.
Мы склонны измерять прогресс разработки числом готовых и работающих классов (логический аспект), или количеством функционирующих модулей (физический аспект). Как говорилось в предыдущей главе, другой мерой прогресса является стабильность ключевых интерфейсов (то есть насколько часто они подвергаются изменениям). Сначала интерфейсы всех ключевых абстракций изменяются ежедневно, если не ежечасно. Через некоторое время стабилизируются наиболее важные из ключевых интерфейсов, следом - вторые по важности и т.д. К концу жизненного цикла разработки только несколько несущественных интерфейсов нуждаются в доработке, так как основной упор делается на то, чтобы заставить готовые классы и модули работать вместе. Иногда в ключевые интерфейсы требуется внести некоторые изменения, но они обычно остаются совместимыми снизу вверх. Причем, эти изменения производятся только после того, как будет тщательно продумано их влияние. Эти изменения могут быть внесены в разрабатываемую систему при подготовке нового релиза.
Чидамбер и Кемерер предлагают несколько мер, которые непосредственно применимы к объектно-ориентированным системам [15]:
Глубина дерева наследования и число потомков - количественные характеристики формы и размера структуры классов. Как обсуждалось в главе 3, хорошо структурированная объектно-ориентированная система чаще бывает организована как лес классов, чем как одно высоченное дерево. Мы советуем строить сбалансированные по глубине и ширине структуры наследования: обычно - не глубже, чем 7╠2 уровня, и не шире, чем 7╠2 ветви.
Зацепление объектов - мера их взаимозависимости. Так же, как и при традиционном программировании, мы стремимся спроектировать незацепленные (то есть слабо связанные) объекты, поскольку они имеют больше шансов на повторное использование.
Отклик на класс - количество методов, которые могут вызываться экземплярами класса. Связность методов - мера насыщенности абстракции. Класс, который может вызывать существенно больше методов, чем равные ему по уровню классы, является более сложным. У класса с низкой связностью можно подозревать случайную или неподходящую абстракцию: такой класс должен, вообще говоря, быть переабстрагирован в несколько классов или его обязанности должны быть переданы другим существующим классам.
7.6. Документация
Наследие разработки
Разработка программной системы включает в себя гораздо больше, чем просто написание кода. Некоторые аспекты проекта должны быть постоянно доступны менеджерам разработки и внешним пользователям. Мы хотим также сохранить сведения о решениях, принятых при анализе и проектировании для тех, кто будет заниматься сопровождением системы. Как отмечалось в главе 5, результаты объектно-ориентированной разработки обычно содержат диаграммы классов, объектов, модулей и процессов. В совокупности эти диаграммы предоставляют возможность проследить их появление непосредственно из начальных требований к системе. Диаграммы процессов соответствуют программам, которые являются корневыми модулями диаграммы модулей. Каждый модуль представляет реализацию некоторой комбинации классов и объектов, которые, в свою очередь, можно найти на диаграммах классов и объектов соответственно. Наконец, диаграммы объектов представляют сценарии, определяемые требованиями, а диаграммы классов демонстрируют ключевые абстракции, которые формируют словарь предметной области.
Содержание документации
Документация по архитектуре системы важна, но ее составление не должно быть двигателем процесса разработки: документация - существенный, но не самый главный результат. Важно помнить, что документация - живой продукт, и ей надо позволить эволюционировать вместе с релизами проекта. Вместе с текстом программ сопровождающая документация служит основой для проведения многих формальных и неформальных просмотров.
Что должно быть документировано? Очевидно, что документация, представляемая конечному пользователю, должна включать инструкции по установке и использованию каждого релиза. Кроме того, должны быть документированы результаты анализа, чтобы зафиксировать семантику функциональных точек системы в последовательности сценариев. Должна также вестись документация по архитектуре и реализации для согласования в команде разработчиков общего видения системы и деталей архитектуры, а также для того, чтобы сохранить информацию обо всех стратегических решениях - это несомненно облегчает эволюцию и адаптацию системы.
Документация по архитектуре и реализации должна описывать:
7.7. Инструменты
В предыдущих поколениях языков программирования команде разработчиков достаточно было иметь минимальный набор инструментов: редактор, компилятор, компоновщик и загрузчик - вот все, что обычно требовалось (и, чаще всего, все, что имелось). Особо благополучная команда могла обзавестись кодовым отладчиком. Сложные задачи в корне изменили картину: попытка построить большую программную систему с минимальным набором инструментов эквивалентна намерению возвести многоэтажное здание вручную.
Объектно-ориентированный подход также изменил многое. Традиционные инструменты разработки сосредоточены только на тексте программы, но объектно-ориентированные анализ и проектирование выдвигают на первый план ключевые абстракции и механизмы, и нам нужны инструменты, работающие с более богатой семантикой. Кроме того, для ускорения движимого макропроцессом объектно-ориентированного развития, требуются инструменты, позволяющие ускорить циклы разработки, особенно цикл редактирование/компиляция/выполнение/отладка.
Важно выбрать хорошо масштабируемые инструменты. Инструмент, который удобен для программиста-одиночки, занятого написанием небольшого приложения, не всегда подходит для производства сложных систем. На самом деле, для каждого инструмента существует порог, за которым его возможности оказываются превышены: его достоинства перевешиваются неуклюжестью и нерасторопностью.
Виды инструментов
Мы выделяем по крайней мере семь различных видов инструментов для объектно-ориентированной разработки. Первый инструмент - система с графическим интерфейсом, поддерживающая объектно-ориентированную систему обозначений, представленную в главе 5. Такой инструмент может быть использован при анализе, чтобы зафиксировать семантику сценариев, на ранних стадиях разработки, чтобы передать стратегические и тактические решения, принятые при проектировании, а также для координирования действий проектировщиков. Подобный инструмент будет полезен на протяжении всего жизненного цикла и при сопровождении системы. В частности, мы считаем возможным восстанавливать по тексту программы многие важные аспекты объектно-ориентированной системы. Такая способность инструментальной системы очень важна: в традиционных CASE-средствах разработчик может создавать замечательные картинки только для того, чтобы обнаружить, что они устарели, потому что программисты манипулируют реализацией, не отраженной в проекте. Восстановление проекта по коду снижает вероятность того, что документация будет идти не в ногу с реализацией.
Следующий важный для объектно-ориентированной разработки инструмент - броузер, который показывает структуру классов и архитектуру модулей системы. Иерархия классов может сделаться настолько сложной, что трудно даже отыскать все абстракции, которые были введены при проектировании или выявить кандидатов для повторного использования [16]. При изучении фрагмента программы разработчику может понадобиться посмотреть определение класса некоторого объекта. Найдя этот класс, ему вероятно придется заглянуть в описание какого-нибудь из его суперклассов. Рассматривая этот суперкласс, разработчик может захотеть внести изменения в интерфейс класса, но при этом придется изучить поведение всех его клиентов. Этот анализ будет особенно громоздким, если он применяется к файлам, которые представляют физические, а не логические аспекты проекта. По этой причине броузер оказывается очень важным инструментом объектно-ориентированного анализа и проектирования. Броузеры есть, например, в среде Smalltalk. Подобные средства, хотя и разного качества, имеются и для других объектно-ориентированных языков.
Другой вид инструментов, который очень важен, если не абсолютно необходим - инкрементный компилятор. Метод эволюционной разработки, который применяется в объектно-ориентированном программировании, нуждается в компиляторе, который мог бы компилировать отдельные объявления и операторы. Мейровиц замечает, что "UNIX, с ее ориентацией на пакетное компилирование больших программных файлов в библиотеки, которые потом компонуются с другими фрагментами кода, не предоставляет необходимой поддержки для объектно-ориентированного программирования. Совершенно недопустимо тратить десять минут на компиляцию и компоновку, чтобы изменить реализацию метода и тратить целый час на компиляцию и компоновку только для того, чтобы добавить новое поле в суперкласс верхнего уровня! Для быстрой отладки методы и определения полей должны компилироваться инкрементно" [17]. Такие компиляторы существуют для многих языков, описанных в приложении. К сожалению, большинство реализации содержит традиционные пакетно-ориентированные компиляторы.
Мы также полагаем, что нетривиальные проекты нуждаются в отладчиках, которые могут работать с семантикой классов и объектов. При отладке программ часто требуется справка о переменных экземпляра и переменных класса для данного объекта. Традиционные отладчики для необъектных языков ничего не знают о классах и объектах. Так, пытаясь использовать стандартный отладчик языка С для программы на C++, разработчик не сможет получить всею информацию, необходимую для отладки объектно-ориентированной программы. Ситуация особенно критична для объектно-ориентированных языков, поддерживающих несколько потоков управления. При выполнении такой программы в один и тот же момент времени могут быть запущены несколько активных процессов. Естественно требуется отладчик, который позволял бы контролировать каждый отдельный поток и взаимодействие объектов между потоками.
В категорию отладочных средств мы включаем и такие инструменты, как стрессовые тестеры, которые испытывают программы в критических условиях ограниченности ресурсов, и инструменты для анализа памяти, которые распознают нарушения доступа к памяти (запись в неразрешенные участки памяти, чтение из неинициализированных участков, чтение или запись за границами массива).
Для больших проектов требуются инструменты управления конфигурацией и контроля версий. Как упоминалось ранее, для управления конфигурацией лучшими единицами являются категории классов и подсистемы.
Другой инструмент, который мы считаем важным для объектно-ориентированной разработки, - это библиотекарь классов. Многие из языков, о которых упоминается в этой книге, поставляются со стандартными библиотеками или библиотеки для них можно купить отдельно. По мере развития проекта библиотека классов растет, дополняясь новыми предметно-специфическими программными компонентами, годными для повторного использования. Библиотека быстро разрастается до таких размеров, что разработчики не могут отыскать понадобившийся класс. Одна из причин быстрого роста библиотеки состоит в том, что класс может иметь несколько реализации с различными временными и пространственными семантиками. Если ожидаемая стоимость (обычно преувеличенная) отыскания компоненты выше, чем ожидаемая стоимость (обычно преуменьшенная) создания этой компоненты заново, пропадает всякий смысл повторного использования. По этой причине бывает важно иметь хоть какой-нибудь минимальный библиотечный инструмент, который позволял бы разработчику искать классы и модули, удовлетворяющие различным критериям, и заносить в библиотеку полезные классы и модули по мере их разработки.
Еще один тип инструмента, который мы считаем полезным для некоторых систем - генератор графического интерфейса пользователя. Для систем, в которых велик объем взаимодействия с пользователем, лучше иметь специальный инструмент для интерактивного создания диалогов и окон, чем программировать все с нуля. Код, сгенерированный такой системой, может быть потом связан с остальной объектно-ориентированной системой и, если необходимо, вручную подкорректирован.
Организационные выводы
Потребность в мощных инструментах приводит к появлению двух специальных должностей в штате организации, занимающейся разработкой программных систем: инженер по повторному использованию и инженер-инструментальщик. Среди прочего, обязанности первого состоят в сопровождении библиотеки классов проекта. Без активных усилий она может стать необозримым пустырем превратившихся в хлам классов, в который ни один из разработчиков не захочет заглядывать. Часто бывает, что разработчиков надо побуждать к заимствованию существующих компонентов или предотвращать изобретение велосипеда; это тоже входит в задачи инженера по повторному использованию. В обязанности инженера-инструментальщика входят создание новых предметно-зависимых инструментов и переделка существующих инструментов для текущих нужд. Например, может потребоваться целая система тестов для проверки некоторых аспектов пользовательского интерфейса или более специализированный броузер классов. Инструментальщик облегчает свою работу тем, что разрабатывает и развивает инструменты, используя компоненты, уже помещенные в библиотеку классов. Их можно задействовать и в новых разработках.
Менеджер, ограниченный в средствах, может жаловаться, что хорошие инструменты, инженеры по повторному использованию и инструментальщики - непозволительная роскошь. Для некоторых проектов - да. Однако часто все это так или иначе делается, но стихийным, неэффективным образом. Мы стоим за явные затраты на средства и персонал, чтобы сделать эту деятельность более концентрированной и эффективной и увеличить ценность общей организации.
7.8. Специальные вопросы
Узко-специфические проблемы
Мы считаем, что некоторые предметные области заслуживают специального архитектурного рассмотрения.
Проектирование эффективного пользовательского интерфейса - скорее искусство, чем наука. В этой области абсолютно необходимо использование прототипов. Как можно раньше следует установить интенсивную обратную связь с конечными пользователями, чтобы выявить характерные движения рук, реакцию на ошибки и другие парадигмы взаимодействия. При анализе пользовательского интерфейса в высшей степени эффективно составление сценариев.
Некоторые приложения имеют собственную базу данных, для других приложений может требоваться интеграция с существующей базой данных, схема которой не может быть изменена (обычно из-за того, что база уже заполнена большим количеством данных - разновидность проблемы унаследованной сложности). В таких случаях хорошо работает принцип разделения обязанностей: лучше всего инкапсулировать доступ к таким базам данных в небольшом количестве четко определенных интерфейсов классов. Этот принцип особенно важен при совместном использовании объектно-ориентированной декомпозиции и технологии реляционных баз данных (РБД). Объектно-ориентированные базы данных (ООБД) лучше стыкуются с объектными программами, но мы должны помнить, что ООБД больше подходят для обеспечения продолжительности жизни объектов и меньше - для хранения больших объемов данных.
Коснемся также систем реального времени. Понятие реальное время в различном контексте воспринимается по-разному. В диалоговых системах оно может означать отклик в течение менее чем одной секунды, а в системах сбора данных и управления может требоваться отклик быстрее чем за одну микросекунду. Важно ясно понимать, что даже при очень жестких требованиях ко времени не каждая компонента должна (или может) быть оптимизирована. Для сложных систем наибольший риск состоит в том, будет или нет система завершена, а не в том, будет ли она удовлетворять требованиям эффективности. По этой причине мы предостерегаем от преждевременной оптимизации. Сосредоточьтесь на создании простой архитектуры, а узкие места выявятся в процессе эволюции системы сами собой (причем достаточно рано), и вы сможете принять меры.
Термин "унаследованная сложность" относится к приложениям, в которые были сделаны большие капиталовложения и от которых по экономическим причинам или по соображениям безопасности нельзя отказаться. Однако, такие системы могут иметь неподъемную стоимость сопровождения, отчего их и приходится со временем заменять. К счастью, совладать с унаследованной сложностью можно почти также, как с базами данных: инкапсулировать доступ к их услугам в контексте четко определенных интерфейсов классов, а потом постепенно, функцию за функцией, вытеснять их объектно-ориентированной архитектурой. Конечно, необходимо представлять, себе конечный результат, чтобы система в процессе неуправляемых изменений не превратилась в лоскутное одеяло.
Переход на объектные технологии
Как считает Кемпф, "Изучение объектно-ориентированного программирования может оказаться гораздо более трудной задачей, чем просто освоение очередного языка. В данном случае надо научиться другому стилю программирования, а не просто запомнить синтаксис. То есть учить приходится не язык, а способ мышления" [18]. Как развить объектно-ориентированное мировоззрение? Мы рекомендуем:
Согласно нашему опыту, профессиональному разработчику требуется несколько недель, чтобы освоиться с синтаксисом и семантикой нового языка программирования. Потребуется в несколько раз больше времени, чтобы тот же разработчик начал понимать важность и мощь классов и объектов. Наконец, может потребоваться более шести месяцев экспериментов, чтобы разработчик созрел до хорошего проектировщика классов. Это не обязательно плохо - обучение мастерству всегда требует времени.
Мы считаем, что обучение на примерах часто оказывается эффективным и целесообразным подходом. Когда организация накопит критическую массу приложений, выполненных в объектно-ориентированном стиле, станет гораздо легче обучать этому делу новых разработчиков и менеджеров. Разработчики начинают как программисты-исполнители, применяя уже готовые хорошо структурированные абстракции. Через некоторое время разработчики, которые изучали и использовали эти компоненты под наблюдением более опытных сотрудников, сами приобретают достаточный опыт, чтобы проектировать классы.
7.9. Выгоды и опасности объектно-ориентированной разработки
Выгоды
Приверженцы объектно-ориентированной технологии обычно называют два ее главных преимущества. Во-первых, большая конкурентоспособность благодаря предсказуемости, сокращению времени на разработку и большой гибкости продукта. Во-вторых, разрабатываемые задачи могут быть настолько сложными, что не остается альтернативных решений.
В главе 2 говорилось, что использование объектной модели позволяет перенести в программу пять свойств хорошо структурированных сложных систем. Объектная модель формирует концептуальный каркас системы обозначений и процесса объектно-ориентированной разработки; таким образом, и эти выгоды мы получаем непосредственно благодаря методу. Отмечались и преимущества, вытекающие из того, что объектная модель (а значит и процесс разработки):
Опасности
Говоря о теневой стороне объектно-ориентированной технологии, нужно рассмотреть два вопроса: производительность и начальные затраты. По сравнению с процедурными языками, объектно-ориентированные языки определенно вносят дополнительные накладные расходы на пересылку сообщения от одного объекта другому. Как указывалось в главе 3, при вызове методов, которые не найдены и не связаны статически во время компиляции, выполняемая программа должна динамически искать нужный метод по классу объекта-получателя. Исследования показывают, что, в худшем случае, на вызов метода тратится в 1.75-2.5 раза больше времени чем на обычный вызов процедуры [22, 23]. С другой стороны, наблюдения показывают, что при вызове методов динамический поиск действительно необходим примерно в 20% случаев. В строго типизированных языках компилятор часто может определять, какие вызовы могут быть связаны статически и сгенерировать для них вызов процедуры вместо динамического поиска.
Другая причина снижения производительности кроется не столько в природе объектно-ориентированных языков, сколько в способе их использования в процессе объектно-ориентированной разработки. Как говорилось уже много раз, объектно-ориентированная технология порождает многослойные системы абстракций. Одно из следствий этого расслоения в том, что каждый метод оказывается очень маленьким, так как он строится на методах нижнего уровня. Другое следствие расслоения: иногда методы служат лишь для того, чтобы получить доступ к защищенным атрибутам объекта. В результате происходит слишком много вызовов. Вызов метода на высшем уровне абстракции обычно влечет каскад других вызовов; методы верхних уровней вызывают методы нижних уровней и т.д. Для приложений, в которых время - ограниченный ресурс, недопустимо слишком большое количество вызовов методов. Опять же, с позитивной стороны такое слоение способствует пониманию системы; к некоторым сложным системам невозможно даже подступиться, если не начать с проектирования слоев. Мы рекомендуем сначала проектировать систему с желаемыми функциональными свойствами, а потом определять узкие места. Часто их можно "расшить", объявив соответствующие методы как подстановки (выигрывая тем самым время), "подчистив" иерархию классов или нарушив инкапсуляцию атрибутов класса.
Похожий риск для потери производительности происходит из-за большого количества наследуемого кода. Класс, лежащий глубоко в структуре наследования, может иметь много суперклассов; при компоновке программы должно быть подгружено описание их всех. Для маленьких приложений это практически может означать, что нужно избегать глубоких иерархий классов, потому что они требуют чрезмерного количества объектного кода. Проблему можно несколько смягчить, используя высокоразвитые компиляторы и компоновщики, которые могли бы устранять бесполезные участки программы.
Еще один источник проблем для производительности объектно-ориентированных программ - их поведение в системе со страничной организацией памяти. Большинство компиляторов выделяет память сегментами, размещая каждый компилируемый модуль (обычно файл) в одном или более сегментах. Это подразумевает локальность большинства ссылок: процедуры в одном сегменте вызывают в основном процедуры в том же сегменте. В больших объектно-ориентированных системах все не так. Код каждого класса размещается в отдельном файле, а раз его методы интенсивно используют методы других (особенно вышестоящих) классов, при их выполнении происходит интенсивное переключение сегментов. Это поведение противоречит тому, чего большинство компиляторов ожидает от программ, особенно в системах с конвейерным процессором и страничной организацией памяти. Это еще один пример того, почему нужно разделять логические и физические аспекты проекта. Если программа работает слишком медленно из-за чрезмерно частого переключения страниц, можно попробовать изменить физическое расположение классов в модулях. Это проектное решение, касающееся физической модели системы - на логику программы оно не повлияет.
Наконец, еще одна составляющая риска - динамическое размещение и уничтожение объектов. Динамическое выделение памяти из "кучи" сопряжено с дополнительными вычислительными расходами по сравнению с размещением данных в стеке и (конечно) статическим резервированием при компиляции. Во многих системах это не вызывает никаких практических проблем, но иногда дополнительные накладные расходы непозволительны. Существуют простые решения: откажитесь от динамического создания объектов и разместите их все заранее, или замените стандартный алгоритм выделения памяти на специально приспособленный для ваших нужд.
И опять о хорошем: положительные свойства объектно-ориентированных систем часто окупают все перечисленные выше неприятности. Например, Руссо и Каплан сообщают, что производительность программы на C++ часто бывает выше, чем ее функционального эквивалента на С. Выигрыш, как они полагают, связан с использованием виртуальных функций, благодаря которым можно сэкономить на проверке типов и опустить многие управляющие конструкции. Согласно нашему опыту, код объектно-ориентированной программы и в самом деле обычно короче.
Для некоторых проектов начальные затраты на переход к объектно-ориентированной технологии могут оказаться непреодолимыми. Как при всякой смене технологии, придется вкладывать деньги в покупку новых инструментальных средств разработки. Кроме того, если какой-либо объектно-ориентированный язык используется организацией впервые, то нет и предметно-специфического задела для повторного использования. Короче говоря, приходится начинать все сначала или найти какой-то способ использовать существующие программы в объектно-ориентированном окружении. Наконец, первая попытка объектно-ориентированной разработки наверняка провалится, если не было проведено соответствующее обучение. Объектная ориентация - это не "еще один" язык программирования, который можно выучить на трехдневных курсах или по книжке. Как мы многократно указывали, требуется время, чтобы освоить объектно-ориентированное проектирование как новое мировоззрение, которое должно быть усвоено как разработчиками, так и менеджерами.
Выводы
Ван Генучтен (Van Genuchten) [H 1991] и Джоунс (Jones) [H 1992] изучали понятие риска в программном обеспечении. Об образе мышления отдельного разработчика см. работу Вейнберга (Weinberg) [J 1971, H 1988]. Абдель-Хамид и Мэдник (Abdel-Hamid and Madnick) [H 1991] изучали динамику программистских групп.
Глиб (Gilb) [H 1988] и Чаретте (Charette) [H 1989] - это основные ссылки по практике менеджмента разработки программного обеспечения. Работа Арона (Aron) [H 1974] предлагает сравнительный взгляд на управление индивидуальным программистом и командой программистов. Что практически происходит, когда прагматика выталкивает теорию в окно, см. в работах Гласса (Glass) [G 1982], Ламмерса (Lammers) [H 1986] и Хэмфри (Hamphrey) [H 1989].ДeMapко и Листер(DeMarco and Lister) [H 1987], Иордан (Yourdon) [H 1989], Реттиг (Rettig) [H 1990] и Томсет (Thomsett) [H 1990] предложили ряд рекомендаций, интересных для менеджера разработки.
Детали управления просмотром см. в работах Вейнберга и Фридмана (Weinberg and Freedman) [H 1990] и Иордана (Yourdon) [H 1989a].
Шулмейер и МакМанус (Schulmeyer and McManus) [H 1992] - прекрасная традиционная ссылка по вопросу гарантирования качества программного обеспечения. Чидамбер и Keмеpep(ChidamberandKemerer)[H 1991] и Вош(Walsh) [H 1992, 1993] изучают обеспечение качества и количественные характеристики в контексте объектно-ориентированных систем.
Советы по поводу перехода на объектную модель (как индивидуальные, так и для целых организаций) можно найти в работах Голдберга (Goldberg) [G 1978], Голдберга и Кэя (Goldberg and Kay) [G 1977] и Кемпфа (Kempf) [G 1987].