Текст книги "Программист-прагматик. Путь от подмастерья к мастеру"
Автор книги: Эндрю Хант
Соавторы: Дэвид Томас
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 6 (всего у книги 28 страниц)
В то время как многие люди пытаются сохранить свои программы гибкими, вам также стоит подумать о том, чтобы обеспечить гибкость архитектуры, развертывания и интеграции продуктов фирм-субподрядчиков.
Технологии, подобные CORBA, могут помочь в защите компонентов проекта от изменений, происходящих в языке, на котором ведется разработка, или в платформе. Вдруг производительность Java на этой платформе не соответствует ожиданиям? Еще раз напишите программу клиента на языке С++, и больше ничего менять не нужно. Подсистема правил в С++ не отличается достаточной гибкостью? Перейдите к версии на языке Smalltalk. При работе с архитектурой CORBA вы должны обращать внимание только на заменяемый компонент, другие компоненты трогать не нужно.
Вы разрабатываете программы для Unix? Какой версии? Вы рассмотрели все из аспектов переносимости? Вы пишете для конкретной версии Windows? Какой – 3.1, 95, 98, NT, СЕ или же 2000? Насколько сложно будет обеспечить поддержку других версий? Если ваши решения характеризуются мягкостью и пластичностью, то это будет совсем несложно. Но это будет невозможно, если пакет неудачно сформирован, есть высокий уровень связанности, а в тексты программ встроена логика или параметры.
Вы не знаете точно, как отдел маркетинга собирается развертывать систему? Подумайте об этом заранее, и вы сможете обеспечить поддержку автономной модели, модели "клиент – сервер" или n-звенной модели только за счет изменений в файле конфигурации. Мы создавали программы, которые действуют подобным образом.
Обычно вы можете просто скрыть продукт фирмы-субподрядчика за четким, абстрактным интерфейсом. На самом деле мы могли это сделать с любым проектом, над которым мы работали. Но предположим, что вы не смогли изолировать его достаточно четко. Вам пришлось раскидать некоторые инструкции по всей программе? Поместите это требование в метаданные и воспользуйтесь автоматическим механизмом, наподобие Aspect (см. "Инструментарии и библиотеки") или Perl для вставки необходимых инструкций в саму программу. Какой бы механизм вы ни использовали, сделайте его обратимым. Если что-то добавляется автоматически, то оно может и удаляться автоматически.
Никто не знает, что может произойти в будущем, в особенности мы! Дайте вашей программе работать в ритме рок-н-ролла: когда можно – качаться, а когда нужно – энергично крутиться.
Другие разделы, относящиеся к данной теме:
• Несвязанность и закон Деметера
• Метапрограммирование
• Всего лишь представление
Вопросы для обсуждения
• Немного квантовой механики – пример с кошкой Шрёдингера. Предположим, что в закрытом ящике сидит кошка, и в нем же находится радиоактивная частица. Вероятность распада частицы на две равна 50 %. Если распад произойдет, кошка умрет. Если не произойдет, кошка останется жива. Итак, умирает кошка или остается жива? Согласно Шрёдингеру, верно и то, и другое. Всякий раз, когда происходит ядерная реакция, у которой имеются два возможных результата, происходит клонирование мира. В одном из двух миров данное событие произошло, а в другом – нет. Кошка жива в одном из миров и мертва в другом. Лишь открыв ящик, вы осознаете, в каком из миров находитесь вы.
Не удивительно, что программировать на перспективу так трудно.
Но подумайте об эволюции программы по аналогии с ящиком, в котором находится множество кошек Шрёдингера: каждое решение приводит к появлению иной версии будущего. Сколько сценариев будущего поддерживает ваша программа? Какие из них наиболее вероятны? Насколько сложно будет поддерживать их в определенный момент в будущем?
Хватит ли у вас смелости открыть ящик?
10
Стрельба трассирующими
На изготовку, по цели – пли!
Существует два способа стрельбы из пулемета в темное время суток [10]. Вы можете выяснить точно, где находится ваша цель (расстояние, высота и азимут). Вы можете определить погодные условия (температура, влажность, давление, направление ветра и так далее). Вы можете точно определить характеристики используемых вами патронов и пуль и их взаимодействие с реальным пулеметом, из которого вы стреляете. Затем вы можете воспользоваться таблицами или компьютером для вычисления точного азимута и угла возвышения ствола пулемета. Если все работает в точном соответствии с характеристиками, таблицы корректны, а погодные условия не меняются, то пули должны лечь близко к цели. Можно также использовать трассирующие пули.
Трассирующие пули помещаются на пулеметную ленту через равные промежутки наряду с обычными боеприпасами. При стрельбе фосфор, содержащийся в них, загорается и оставляет пиротехнический след, идущий от пулемета до любого места, в которое эти пули попадают. Если в цель попадают трассирующие пули, то, значит, в нее попадут и обычные.
Не удивительно, что стрельбу трассирующими предпочитают математическим расчетам. Обратная связь возникает немедленно, и поскольку трассирующие пули работают в той же среде, что и обычные боеприпасы, то внешние воздействия сведены к минимуму.
Возможно это слишком сильная аналогия, но она применима к новым проектам, особенно когда вы создаете то, чего раньше не было. Подобно стрелкам, вы пытаетесь поразить цель в темноте. Ваши пользователи никогда ранее не видели ничего подобного, поэтому их требования могут быть расплывчатыми. Вы же, в свою очередь, наверняка применяете алгоритмы, методики, языки или библиотеки, с которыми не знакомы, то есть сталкиваетесь с большим количеством неизвестных. И поскольку для выполнения проекта требуется время, вы можете с уверенностью гарантировать, что к моменту окончания работы среда, в которой вы работаете, изменится.
Классический способ решения проблемы – предельно специфицировать систему. Написать горы бумажной документации, регламентирующих каждое требование, связывая каждое неизвестное и ограничивая рабочую среду. Стрелять при помощи жесткого расчета. Один большой предварительный расчет, затем стрельнуть и надеяться.
Однако программисты-прагматики предпочитают стрелять трассирующими.
Программа, которую видно в темнотеСтрельба трассирующими пулями эффективна, поскольку эти пули работают в той же самой среде и подвержены тем же ограничениям, что и реальные пули. Они быстро оказываются у цели, так что стрелок получает немедленную обратную связь. И с практической точки зрения они представляют собой относительно экономичное решение.
Чтобы добиться того же эффекта в программах, мы ищем нечто такое, что позволяет нам быстро, наглядно и многократно проходить путь от требования до некоторой характеристики окончательной версии системы.
Подсказка 15: Пользуйтесь трассирующими пулями, для того чтобы найти цель
Однажды мы работали над сложным маркетинговым проектом с базой данных «клиент-сервер». Частью требований была способность определять и выполнять промежуточные запросы. Серверами являлся ряд реляционных и специализированных баз данных. Клиентский графический интерфейс пользователя, написанный на языке Object Pascal, использовал набор библиотек С для обеспечения интерфейса с серверами. Запрос пользователя хранился на сервере с использованием системы обозначений, подобной Lisp, до момента преобразования в оптимизированный SQL-запрос, предшествующего его выполнению. При этом возникло много неизвестных и много различных сред, и никто не знал наверняка, как же поведет себя графический интерфейс пользователя.
Это был отличный повод для применения программы трассировки. Мы разработали «скелет» внешнего интерфейса, библиотеки для представления запросов и конструкцию для преобразования сохраненного запроса в запрос, определенный базой данных. Затем мы свели все воедино и проверили, работает ли это. Все, что мы могли сделать в первоначальном варианте, был запрос, который выдавал перечень всех строк в таблице, но он доказал, что интерфейс пользователя мог взаимодействовать с библиотеками, библиотеки могли преобразовать запрос в последовательную и параллельную форму, а из результата сервер мог сгенерировать SQL-запрос. На протяжении следующих месяцев мы постепенно разрабатывали основную конструкцию, добавляя новую функциональную возможность путем параллельного наращивания каждого компонента программы трассировки. Когда интерфейс пользователя добавлял новый тип запроса, библиотека увеличивалась, и генерация SQL-запроса становилась более утонченной.
Программа трассировки не является одноразовой: вы пишете ее, чтобы сохранить. Она содержит всю проверку ошибок, структурирование, документацию и самоконтроль, которые имеются в любом фрагменте рабочей программы. Она просто не обладает всеми функциональными возможностями. Однако, как только вы добились сквозного соединения между компонентами вашей системы, вы можете проверить, насколько близко вы находитесь к цели, и в случае необходимости сделать поправку. Как только вы попали в цель, добавление функциональных возможностей облегчается.
Разработка программ трассировки находится в согласии с той идеей, что проект никогда не кончается: всегда будет потребность в изменениях и добавлении функций. Это – инкрементальный подход.
Обычная альтернатива является своего рода тяжеловесным техническим подходом: программа разделяется на модули, которые программируются в вакууме. Модули объединены в подсистемы, которые затем подлежат дальнейшему объединению, пока в один прекрасный день вы не получаете завершенное приложение. И только тогда приложение в целом может быть представлено пользователю и протестировано. Технология программы трассировки имеет много преимуществ:
• Пользователи могут увидеть нечто, работающее еще до выпуска окончательной версии. Если вам удалось передать суть делаемого вами (см. «Большие надежды»), то ваши пользователи будут осознавать, что видят перед собой еще нечто незрелое. Они не будут разочарованы отсутствием функциональных возможностей; будут гореть желанием увидеть некий видимый прогрессе создании их системы. По мере того как проект будет продвигаться, они начнут делать вложения. Эти пользователи и станут теми людьми, которые скажут вам о том, насколько близко к цели находится та или иная итерация.
• Разработчики выстраивают некую структуру, в которой они работают. Наибольший страх вызывает лист бумаги, на котором ничего не написано. Если вы разработали все механизмы взаимодействия между модулями вашего приложения и воплотили их в тексте программы, то вашей команде не придется многое высасывать из пальца. Это делает труд каждого члена команды более производительным и способствует последовательности в их работе.
• У вас есть платформа для интеграции. Как только все компоненты системы связаны друг с другом, появляется некая среда, в которую можно добавлять новые фрагменты программ, прошедшие модульное тестирование. Вы будете заниматься интеграцией каждый день (иногда несколько раз вдень), не пытаясь интегрировать все сразу по методу «большого скачка». Воздействие каждого вновь вносимого изменения становится более очевидным, взаимодействия более ограниченными, поэтому отладка и тестирование будут более быстрыми и точными.
• У вас есть что продемонстрировать. Спонсоры проекта и руководство стремятся увидеть демонстрационные версии в самое неподходящее время. При наличии программы трассировки у вас всегда будет то, что можно им продемонстрировать.
• Вы лучше ощущаете прогресс. При разработке программы трассировки программисты работают над сценариями использования системы по очереди. Они заканчивают один сценарий и переходят к следующему. При этом гораздо проще определить производительность и продемонстрировать пользователю продвижение проекта. Поскольку каждая индивидуальная разработка меньше по объему, вы избежите создания монолитных программных блоков, о которых каждую неделю сообщается, что они готовы на 95 %.
При стрельбе трассирующими вы не всегда попадаете в цельТрассирующие пули показывают, что вы куда-то попали. Это не обязательно должна быть ваша цель. Затем вы корректируете прицел, пока пули не попадают в цель. В этом-то все и дело.
То же самое относится и к программе трассировки. Вы используете методику в ситуациях, когда не уверены на 100 %, куда же вам двигаться. Не стоит удивляться, если две первых попытки сорвались: пользователь говорит: "Это совсем не то, что я имел в виду", нужные данные становятся недоступными в самый неподходящий мо-мент, и явно возникают проблемы с производительностью. Выработайте подход для изменения того, что мешает приблизиться к цели, и будьте благодарны судьбе, что вы используете скудную методологию разработки. Небольшой фрагмент программы отличается малой инерцией – его легко и быстро изменить. Вы сможете собрать отклики на ваше приложение и сгенерировать новую, более точную версию быстрее и дешевле. И поскольку каждый основной компонент приложения представлен в программе трассировки, ваши пользователи могут быть уверены – то, что они видят, основано на реальности, а не на бумажных спецификациях.
Программа трассировки и создание прототиповВы могли бы подумать, что принцип программы трассировки – это то же самое, что и чем создание прототипов, только с более агрессивным названием. Отличие есть. Цель работы с прототипом – исследование определенных характеристик (аспектов) конечной версии системы. Создавая истинный прототип, вы отбросите все то, что критиковали при опробовании принципа, и перепишете его надлежащим образом, используя полученные уроки.
Например, вы создаете приложение, которое помогает транспортным компаниям определять, как упаковывать ящики нестандартного размера в контейнеры. Помимо всего прочего, интерфейс пользователя должен быть интуитивно понятным, а алгоритмы, используемые для определения оптимальной упаковки, очень сложны.
Вы могли бы создать интерфейс для конечных пользователей при помощи соответствующих инструментальных средств. Вашей программы достаточно для того, чтобы сделать интерфейс восприимчивым к действиям пользователя. Как только пользователи согласятся с компоновкой интерфейса, вы можете отбросить его и переписать на этот раз на основе бизнес-логики, используя целевой язык. Аналогично, вы можете захотеть создать прототип ряда алгоритмов, которые осуществляют реальную упаковку. Вы можете запрограммировать функциональные тесты на высокоуровневом, «всепрощающем» языке типа Perl и затем запрограммировать низкоуровневые тесты производительности на языке, который более близок к машинному. В любом случае, как только вы приняли решение, необходимо начать сначала и запрограммировать алгоритмы в окончательной версии среды, которая взаимодействует с внешним миром. Это и есть создание прототипов, и это очень полезно.
Подход типа "стрельба трассирующими" обращается к иной проблеме. Вам необходимо знать, как работает приложение в целом. Вы хотите показать вашим пользователям, как на практике осуществляется взаимодействие, и дать им «скелет» архитектуры, на который наращивается тело программы. В этом случае вы можете сконструировать программу трассировки, состоящую из тривиальной реализации алгоритма упаковки контейнера (возможно, нечто вроде FIFO), и простой, но работающий интерфейс пользователя. Как только вы соедините все компоненты приложения, у вас уже есть каркас, который можно представить вашим пользователям и разработчикам. Спустя некоторое время вы добавляете к этому каркасу новую функциональную возможность, заменяя заглушки программами. Но сам остов остается нетронутым, и вы знаете, что система будет вести себя так же, как и в тот момент, когда завершалась первая программа трассировки.
Различие достаточно важно, чтобы гарантировать повторяемость. Прототипы генерируют одноразовую программу. Программа трассировки является скудной, но завершенной; она образует часть «скелета» конечной версии системы. Рассматривайте создание прототипов как рекогносцировку и сбор данных разведки до начала стрельбы трассирующими.
Другие разделы, относящиеся к данной теме:
• Приемлемые программы
• Прототипы и памятные записки
• Западня спецификации
• Большие надежды
11
Прототипы и памятные записки
Для опробования определенных идей во многих отраслях промышленности используются прототипы; это дешевле, чем организовывать полномасштабное производство. Например, в автомобильной промышленности для новой модели автомобиля может быть построено несколько различных прототипов. Каждый из них конструируется для проверки определенных свойств автомобиля – аэродинамики, дизайна, свойств конструкции и т. д. Для испытания в аэродинамической трубе изготовляется модель из глины, для отдела дизайна создается модель из бальзовой древесины и клейкой ленты и т. д. Некоторые автомобильные фирмы идут дальше и осуществляют значительную часть работы по моделированию при помощи компьютеров, что приводит к еще большему сокращению расходов. В этом случае нет необходимости в реальном изготовлении рискованных элементов конструкции для их опробования.
Мы создаем прототипы программ тем же образом и по тем же причинам – для анализа и выявления риска, предлагая возможности для коррекции при существенно меньших затратах. Подобно тому, как это делается в автомобильной промышленности, мы можем использовать прототип для опробования одного или нескольких характеристик проекта.
Мы склонны полагать, что основой прототипов являются программы, но не это не всегда так. Подобно тому, как это делается в автомобильной промышленности, мы можем строить прототипы из различных материалов. Памятные записки великолепно подходят для создания прототипов таких динамических объектов, как логика документооборота и прикладная логика. Прототип интерфейса может моделироваться на лекционной доске, как модель без функциональных возможностей, изображенная с помощью графической программы или программы-построителя интерфейса.
Прототипы разрабатываются для того, чтобы ответить лишь на несколько вопросов, поэтому их разработка намного дешевле и быстрее, чем приложения, которые идут в производство. Программа может игнорировать незначительные подробности – незначительные в данный момент, но позже могущие оказаться для пользователя очень важными. Если, к примеру, вы создаете прототип графического интерфейса пользователя, то можете смириться с неправильными результатами или данными. С другой стороны, если вы просто исследуете характеристики вычислений или производительности, можете обойтись скудным графическим интерфейсом пользователя или вообще без него.
Но если вы работаете в среде, где нельзя отказаться от подробностей, тогда необходимо спросить себя, а нужно ли вообще создавать прототип. Возможно, в этом случае лучше всего подходит стиль разработки типа "стрельба трассирующими" (см. "Стрельба трассирующими").
Для чего создаются прототипыКакие объекты можно изучать при помощи прототипов? Все, что характеризуются наличием риска. Все, что не подвергались тестированию ранее или являются абсолютно критичными для конечного варианта системы. Вес, что является недоказанным, экспериментальным или сомнительным. Вес то, с чем вы еще не освоились. Вы можете создавать прототипы:
• Архитектуры
• Новой функциональной возможности уже существующей системы
• Структуры или содержания внешних данных
• Инструментальных средств или компонентов, выпущенных фирмами-субподрядчиками
• Рабочих характеристик
• Дизайна интерфейса пользователя
Создание прототипов способствует приобретению опыта. Значение этого опыта заключается не в созданной программе, а в полученных уроках. В этом и состоит смысл создания прототипов.
Подсказка 16: Создавайте прототипы, чтобы учиться на них
Как использовать прототипы
Какими деталями можно пренебречь при построении прототипа?
• Корректность. Там, где это приемлемо, вы сможете использовать фиктивные данные.
• Завершенность. Прототип может функционировать лишь в ограниченном смысле, возможно, лишь с одним наперед заданным фрагментом входных данных и одним пунктом меню.
• Надежность. Процедура проверки ошибок, вероятно, будет неполной или будет отсутствовать полностью. Если вы отклоняетесь от определенного пути, то прототип может выйти из строя и сгореть, как ракета. Это нормально.
• Стиль. Неприятно признавать это, но прототип программы не имеет большого значения для комментариев или документации. При работе с прототипом можно написать горы документации, но сравнительно малая ее часть будет посвящена собственно прототипу системы.
Поскольку в прототипе детали отодвигаются на второй план, а в центре рассмотрения оказываются определенные аспекты системы, вам может показаться реальным создание прототипов с использованием языка очень высокого уровня – выше уровня языка остальной части проекта (язык типа Perl, Python или Tel). Язык сценариев высокого уровня позволяет опускать многие детали (включая указание типов данных) и при этом создавать функциональный (хотя и неполный и медленный) фрагмент программы [11]. Если вам необходимо создать прототип интерфейсов пользователей, изучите инструментальные средства типа Tcl/Tk, Visual Basic, Powerbuilder или Delphi.
Языки сценариев хороши для использования в качестве «клея» при соединении низкоуровневых фрагментов в новые сочетания. При работе в системе Windows язык Visual Basic может «скреплять» средства управления СОМ. В более общем смысле вы можете использовать языки типа Perl и Python для связывания воедино низкоуровневых библиотек языка С – вручную или автоматически при помощи инструментов наподобие бесплатного SWIG [URL 28]. Используя этот подход, вы можете быстро собрать существующие компоненты в новые конфигурации, чтобы посмотреть, как они работают.