Кинга Эндрю Ханта и Дэвида Томаса известна многим, кто занимается программированием. Этот сборник практических советов для разработчиков и скоро он отметит двадцатилетний юбилей, но его до сих пор приводят как источник ценной информации. Авторы книги говорили по большей части о фундаментальных принципах построения рабочего процесса. Базовые подходы к разработке, тестированию, взаимодействию внутри команды и с аудиторией остаются актуальными и сейчас.
Подсказка 1: Позаботьтесь о вашем ремесле
Нет смысла разрабатывать программы, если вы не заботитесь о качестве работы. Делать это нужно не только в краткосрочной перспективе, применительно к конкретным проектам, но и в долгосрочной — формируя правильный подход и принципы работы.
Что отличает программиста-прагматика?
- Опережающее восприятие и быстрая адаптация. У прагматиков инстинкт на полезные технологии и методы, которые они с удовольствием проверяют на практике. Они способны быстро схватывать новую информацию и объединять ее с уже имеющимися знаниями.
- Любознательность. Прагматики задают вопросы, собирают мелкие факты, интересуются чужим опытом.
- Критическое осмысление. Прагматики не принимают ничего на веру, не ознакомившись предварительно с фактами.
- Реализм. Прагматики пытаются нащупать, где находятся подводные камни в каждой проблеме, с которой приходится сталкиваться.
- Универсальность. Прагматики стремятся познакомиться с большим числом технологий и операционных систем и работают над тем, чтобы идти в ногу со временем.
Подсказка 2: Думайте о работе
Во время написания кода следует полностью концентрироваться на том, что делаете. Никогда не переходите в режим автопилота. Думайте постоянно, критически осмысляя свою работу в реальном времени. Это называется осознанным программированием. Его освоение займет некоторое время и потребует усилий, но наградой станет привычка беспрестанно вносить мелкие усовершенствования, повышая качество кода в целом и сокращая сроки разработки.
Глава 1: Прагматическая философия
Прагматическое программирование ведет свое начало от философии прагматического мышления. В данной главе приводятся ее основные положения.
Подсказка 3: Представляйте варианты решения проблемы, а не отговорки
Одним из краеугольных камней прагматической философии является идея принятия ответственности за себя и за свои действия. Программист-прагматик исходит из того, что его карьера и результаты работы зависят прежде всего от него, и не боится признаться в неведении или ошибке.
Принятие ответственности за результат предполагает готовность отчитываться. Если вы допустили ошибку (а мы все их допускаем), признайте ее честно и попытайтесь предложить варианты исправления. Не стоит перекладывать вину на коллег, партнеров, инструменты или выдумывать отговорки — это непродуктивная трата времени. То же относится и к ситуациям, когда вы сталкиваетесь в требованиями, которые не сможете удовлетворить: не говорите просто: «Это невозможно», а объясните, что нужно для спасения ситуации (дополнительные ресурсы, реорганизация).
Подсказка 4: Не оставляйте разбитых окон
Энтропия – это термин из физики, обозначающий уровень «беспорядка» в системе. Энтропия во вселенной стремится к максимуму, и в разработке наблюдается та же закономерность. Увеличение степени беспорядка в программах на профессиональном жаргоне называется порчей программ. Существует много факторов, вносящих свой вклад в порчу программ, но наиболее важный из них — культура работы над проектом.
Согласно теории разбитых окон, неряшливые решения и проблемные места имеют свойство размножаться. Не оставляйте «разбитые окна» (неудачные конструкции, ошибки, некачественный код) без внимания. Если нет времени на надлежащий ремонт, хотя бы закомментируйте ошибочный фрагмент или выведите на экран сообщение «В стадии разработки», или используйте фиктивные данные. Необходимо предпринять хотя бы малейшее действие, чтобы предотвратить дальнейшее разрушение, и показать, что вы контролируете ситуацию. Небрежность ускоряет порчу быстрее, чем любой другой фактор.
Подсказка 5: Будьте катализатором изменений
Если вы видите, что необходимо сделать, не ждите инициативы от окружающих. Составьте план, проработайте подробности — люди будут охотнее вас поддерживать, если увидят, что работа уже начата.
Подсказка 6: Следите за изменениями
Не сводите глаз с общей картины происходящего. Постоянно наблюдайте за тем, что происходит вокруг вас, а не только за тем, что делаете вы лично. Большинство катастроф в коде начинаются с малозаметных вещей, которые копятся, пока в один прекрасный день проект не пойдет вразнос. Шаг за шагом система отклоняется от требований, код обрастает «заплатами», пока от оригинала не остается ничего. Зачастую именно скопившиеся мелочи приводят к разрушению морали и команд. Но если захватить этот процесс на ранних стадиях и немедленно принять меры (см. предыдущий пункт), можно отделаться малой кровью.
Подсказка 7: Сделайте качество одним из пунктов требований
Качество должно быть договорным пунктом в контракте, который вы заключаете с пользователями. Конечно, в идеале оно должно быть максимальным, но часто вы будете оказываться в ситуациях, когда приходится идти на компромисс из-за жестких сроков или нехватки ресурсов. И здесь полезно приучить себя к созданию приемлемых программ. «Приемлемая» не значит «сделанная тяп-ляп»: вы просто даете пользователям право голоса при определении того порога качества, который можно считать допустимым. Удивительно, но многие предпочтут использовать программы с некоторыми недоработками сегодня, вместо того чтобы год ожидать выпуска мультимедийной версии.
Кроме того, иногда программы становятся лучше благодаря сокращению инкубационного периода. В разработке существует проблема «перешлифовки» — внешние ограничения помогают вовремя остановиться в погоне за совершенством.
Подсказка 8: Регулярно инвестируйте в свой портфель знаний
Под портфелем знаний понимаются все, что программист знает о разработке в своей области, а также накопленный им опыт. Управление портфелем знаний очень похоже на управление финансовым портфелем:
Общие принципы таковы:
- Инвестируйте на регулярной основе. Даже если объем инвестиций невелик, эта привычка полезна сама по себе.
- Инвестируйте в различные сферы. Чем больше областей вы захватываете, тем большую ценность представляете. Как минимум, вы обязаны знать конкретные технологии, с которыми работаете в данный момент, от и до. Но не останавливайтесь на этом. Спрос на технологии и их применимость постоянно меняются. Чем больше инструментов у вас в арсенале вы освоите, тем легче вам будет приспособиться.
- Взвешивайте риски. Технологии существуют в некоем диапазоне – от рисковых и потенциально высокодоходных до низкорисковых и низкодоходных. Вкладывать все в рискованные варианты, курс которых может внезапно обвалиться, не лучшая идея, но и излишняя осторожность, не позволяющая воспользоваться выгодными возможностями – тоже. Лучше держаться средней линии.
- Покупайте подешевле, продавайте подороже. Освоить передовую технологию до того, как она станет популярной, — сложная задача, но оно того стоит: ранние последователи часто делают головокружительную карьеру.
- Регулярно проводите пересмотр и повторную балансировку. Программирование – очень динамичная отрасль. Будьте готовы периодически критически пересматривать свои активы: отказываться от устаревших вариантов, восстанавливать поднявшиеся в цене и восполнять пустующие ниши.
Процесс обучения расширит ваше мышление, открывая для вас новые возможности и новые пути в творчестве. Если вы узнали что-то новое, постарайтесь применить это знание к проекту, над которым вы работаете в настоящее время, насколько позволяют используемые технологии.
Подсказка 9: Критически анализируйте прочитанное и услышанное
Необходимо убедиться, что знание в вашем портфеле является точным, что оно не искажено теми, кому это выгодно, и что его ценность не раздута хайпом. Опасайтесь фанатиков, настаивающих на том, что их догма обеспечивает единственно правильный ответ — вполне возможно, что именно в вашем проекте он неприменим.
Подсказка 10: Важно, что говорить и как говорить
Большая часть дня программиста проходит в общении — с командой, руководством, пользователями, будущими поколениями разработчиков через документацию и комментарии в коде. Поэтому необходимо овладеть его искусством. Чем эффективнее это общение, тем выше ваша способность претворять идеи в жизнь.
Принципы эффективного общения:
- Знайте, что вы хотите сказать: Спланируйте, что будете говорить заранее, набросайте план и пару стратегий, как лучше донести мысль. Это работает и для составления документов, и для важных переговоров.
- Знайте вашу аудиторию: Вы общаетесь только в том случае, если передаете информацию. Для этого вам необходимо осознавать потребности, интересы и способности аудитории. Представляйте информацию так, чтобы она была понятна и интересна слушателю.
- Выбирайте подходящий момент: Важный момент для понимания аудитории — понимание ее сиюминутных приоритетов. То, что вы говорите, должно быть не только уместным по содержанию, но и своевременным. Если нужно, спросите прямо: «Удобно ли сейчас поговорить о…?»
- Выбирайте нужный стиль: Определите стиль подачи материала в соответствии с требованиями аудитории: кто-то предпочитает голые факты, кто-то — подробности, примеры и пространные введения. Опять же, если сомневаетесь, уточните.
- Встречают по одежке: Умейте должным образом «сервировать» свои идеи. В конечном документе должно быть выверено все: правописание, макет, стили текста, печатное оформление.
- Привлекайте свою аудиторию: По возможности привлекайте будущих читателей к процессу создания документов. Используйте их идеи. Так вы получите лучший результат и укрепите рабочие взаимоотношения.
- Умейте слушать: Вступайте с людьми в беседу, задавая вопросы или заставляя их подытожить сказанное вами. Превратите собрание в диалог, и вы лучше донесете то, что хотели сказать, а возможно, заодно почерпнете что-то для себя.
- Не обрывайте разговор: Всегда отвечайте на запросы и сообщения хотя бы обещанием вернуться к этому вопросу позже. Если вы держите людей в курсе, они чувствуют, что о них не забыли, и намного легче прощают случайные промахи.
Глава 2: Прагматический подход
Существует ряд подсказок и уловок, применимых на всех уровнях разработки программ — идеи, которые можно считать аксиомами, процессы, которые практически универсальны. В этой главе проводится обзор этих идей и процессов.
Подсказка 11: Не повторяйтесь
Каждый фрагмент знания должен иметь единственное, однозначное, надежное представление в системе. Альтернативой является представление одного и того же предмета в нескольких местах. Это доставляет неудобства: если в одном месте что-то редактируется, нужно немедленно внести изменения во всех остальных, иначе программа рухнет под грузом противоречий. Рано или поздно вы что-нибудь забудете, это вопрос времени.
Большинство случаев дублирования подпадает под одну из следующих категорий:
- Навязанное дублирование. Разработчики чувствуют, что у них нет выбора – дублирование необходимо по каким-то внешним причинам: стандартам документации, сочетанию нескольких платформ с разными средами, языкам и и библиотеками, спецификой самого языка. В некоторых случаях остается только смириться, но в других можно все-таки найти обходные пути при помощи фильтров, активных генераторов кода, метаданных и верного подхода к комментированию.
- Неумышленное дублирование. Разработчики не осознают, что дублируют информацию. Обычно это происходит как следствие ошибок или неувязок на глубинном уровне (например, один и тот же атрибут прописывается в нескольких объектах), и устранение требует реорганизации. В некоторых подобных случаях принцип позволительно нарушать ради производительности, но только в пределах класса.
- Нетерпеливое дублирование. Разработчики производят дублирование, потому что им кажется, что так проще. Обычно это происходит путем копирования кусков кода. Здесь все сводится к самодисциплине — не полениться потратить несколько лишних секунд, чтобы избежать головной боли в будущем.
- Коллективное дублирование. Фрагмент информации дублируется несколькими членами одной команды разработчиков в ходе работы. Самый сложный случай с точки зрения как обнаружения, так и разрешения. На высоком уровне проблема решается за счет ясного проектного решения, сильного технического руководителя четкого и разделения обязанностей. На модулярном — за счет активной коммуникации между разработчиками: создавайте группы для общения, заведите в каталоге общедоступное место для хранения сервисных подпрограмм и скриптов, поощряйте изучение и обсуждение чужого кода.
Подсказка 12: Сделайте так, чтобы программу можно было легко использовать повторно
Постарайтесь создать среду, где проще находить и многократно использовать существующий материал, чем создавать его самому с нуля. Это помогает снизить риск дублирования. Только имейте в виду: если повторное использование сопряжено с какими-то сложностями, люди не станут этого делать.
Подсказка 13: Исключайте взаимодействие между объектами, не относящимися друг к другу
Это правило называют также принципом ортогональности. Два или более объекта ортогональны, если изменения, вносимые в один из них, никак не влияют на остальные. Подобная схема дает два больших преимущества: увеличение производительности и снижение риска.
Когда изменения в системе локализуются, сроки разработки и тестирования сокращаются. После того как небольшой самодостаточный компонент спроектирован, реализован и протестирован о нем можно попросту забыть, вместо того чтобы постоянно вносить изменения по мере добавления в код новых фрагментов.
Ортогональный подход также способствует многократному использованию компонентов. Чем меньше связанность в системах, тем легче провести реконфигурацию или реинжиниринг.
Снижение риска вызвано тем, что ошибочные фрагменты оказываются изолированы и не влияют на всю систему; соответственно, исправить или заменить их тоже проще. Вследствие этого система становится более устойчивой — проблемные участки остаются участками. Помогает и то, что тестирование на уровне модулей обычно проводится более тщательно.
Принцип ортогональности должен соблюдаться не на уровне отдельных технологий, а охватывать все процессы: от проектирования до подбора инструментов, от тестирования до управления продуктом. Он сводит к минимуму дублирование и делает систему гибче и прозрачнее.
Подсказка 14: Не существует окончательных решений
Требования, пользователи и аппаратные средства изменяются быстрее, чем мы разрабатываем программное обеспечение. Поэтому нужно всегда быть готовыми к тому, что любое решение, которое вы принимаете (не только в пределах кода, но и, допустим, при выборе инструмента от третьей стороны, архитектурного шаблона, модели развертывания), придется пересмотреть в будущем под влиянием внешних факторов. Принцип «минимум дублирования», принцип несвязанности и использование метаданных позволяют сделать систему более обратимой.
Подсказка 15: Пользуйтесь трассирующими пулями, для того чтобы найти цель
Раскрывая метафору: при создании нового продукта команда разработчиков зачастую действует вслепую, работая с малознакомыми методиками, языками и библиотеками. Предугадать конечный результат можно либо путем жесткого прогнозирования, основанного на очень развернутом анализе технологий, либо при помощи «трассировки» — создания серии упрощенных, пробных, постепенно совершенствующихся рабочих версий, чтобы собрать воедино компоненты системы и проверить, как они работают в связке.
Альтернатива такому подходу — изолированная разработка отдельных модулей, которые на завершающем этапе собираются воедино и тогда уже тестируются на уровне системы — более тяжеловесна и менее удобна. Среди прочего, метод трассировки дает вам черновую версию продукта (ее можно представить пользователям, чтобы показать им суть проекта, заинтересовать и получить фидбэк); более плавную и фокусную интеграцию готовых новых модулей в среду и возможность немедленно выявлять и легко устранять ошибки во взаимодействии.
Подсказка 16: Создавайте прототипы, чтобы учиться на них
В отличие от описанных выше тестовых версий прототипы имеют более узкую направленность: они создаются, чтобы отработать несколько конкретных характеристик и требуют значительно меньше ресурса. Все подробности, не имеющие отношения к рассматриваемой проблеме, опускаются, даже если они крайне важны для работы системы в целом. При работе над прототипом можно пренебречь корректностью, завершенностью, надежностью и стилем.
Для прототипирования необязательно создавать полноценное рабочее приложение, иногда достаточно просто схемы на бумаге или доске. Если оно все-таки необходимо, то имеет смысл выбрать для этой цели язык очень высокого уровня – выше уровня языка, который используется остальной части проекта (язык типа Perl, Python или Tel). Язык сценариев высокого уровня позволяет опускать многие детали (включая указание типов данных) и при этом создавать функциональный, хотя неполный и медленный, фрагмент программы.
Подсказка 17: Пишите код с оглядкой на область применения
Языки программирования влияют на то, как вы думаете о проблеме и о взаимодействии с пользователем. В каждом языке имеются свои особенности, которые наталкивают на определенные решения или, наоборот, препятствуют им. Решение, создаваемое в стиле Lisp, отличается от решения, основанного на мышлении приверженца языка С, и наоборот. Верно и обратное – язык, отражающий специфику проблематики, с которой вы работаете, может, со своей стороны, предложить решение в области программирования.
Прислушиваясь к требованиям пользователей, вы можете понять, на какой язык будет проще всего их перевести на высшем, абстрактном уровне. Разные типы пользователей (конечные — аудитория, для которой вы делаете проект, и вторичные — менеджеры, будущие поколения разработчиков) могут потребовать генерации собственных мини-сред и языков.
Подсказка 18: Проводите оценки во избежание сюрпризов
Давать примерную оценку — это навык, и существенная часть этого навыка — умение определять приемлемую точность с опорой на контекст. Единица измерения, которую вы выбираете, также должна отражать степень точности (сравните: задача займет две недели и задача займет 75 рабочих часов).
Оценка проводится в несколько стадий. Сначала мы вникаем в суть заданного вопроса и оцениваем масштаб предметной области; при этом нередко сама формулировка вопроса наталкивает на ответ. Затем выстраивается модель проблемы — примерная последовательность стадий, которые нужно будет пройти при ее решении. Модель подвергается декомпозиции на компоненты, для каждого из которого задается параметр значимости. На базе этих параметров и примерных значений производятся расчеты. Последний шаг осуществляется постфактум — прогноз сопоставляется с действительным положением вещей, при серьезных отклонениях проводится работа над ошибками.
Подсказка 19: Уточняйте график ведения проекта, если того требует код
Это может не понравиться руководству, которому обычно нужно, чтобы цифры были озвучены еще до начала проекта, и не подлежали изменению. Вам придется донести до них, что график выполнения задач будет определяться продуктивностью команды и обстоятельствами. Формализуя эту процедуру и уточняя график в ходе каждой итерации, вы сможете дать руководству максимально точные оценки сроков для каждого этапа.
Глава 3: Походный набор инструментов
Инструменты – средство усиления вашего таланта. Чем они лучше и чем лучше вы ими владеете, тем больше сможете сделать. Начните с универсального, «походного» набора инструментов, который будете использовать для всех базовых операций. Это набор будет пополняться по мере того, как вы копите опыт и сталкиваетесь со специфическими требованиями.
Подсказка 20: Сохраняйте информацию в формате простого текста
Лучшим форматом для постоянного хранения знания является простой текст, позволяющий обрабатывать информацию как вручную, так и с помощью любых доступных инструментов. Проблема большинства двоичных форматов состоит в том, что контекст, необходимый для понимания данных, отделен от самих данных. А с помощью простого текста, доступного для чтения без дешифровки, вы можете создать самодокументированный поток данных, не зависящий от программы, которая его породила.
Простой текст обладает двумя основными недостатками: (1) при хранении он может занимать больше места, чем сжатый двоичный формат, и (2) с точки зрения вычислений интерпретация и обработка файла с простым текстом может проводиться медленнее. В зависимости от приложения неприемлемыми могут оказаться одна или обе вышеописанные ситуации. Но и в этих случаях допустимо сохранять метаданные, которые будут описывать исходные данные в формате простого текста.
Простой текст — это:
- Гарантия того, что данные не устареют
- Более короткий путь к цели
- Более простое тестирование
Подсказка 21: Используйте сильные стороны командных оболочек
Если вы работаете только с графическим интерфейсом, то используете далеко не все возможности, предоставляемые операционной системой — не автоматизируете типовые задачи, не используете доступные инструменты в полную силу, не комбинируете разные решения для создания специализированных макроинструментов. Преимуществом графического интерфейса пользователя является то, что они работают по принципу «что видишь, то и получаешь». Главный недостаток графического интерфейса можно сформулировать так: «получаешь только то, что видишь». Сфера применения таких инструментов обычно ограничена задачами, для решения которых он изначально задумывался. Если вы хотите выйти за пределы этого шаблона (а рано или поздно придется захотеть), вам с ними не по пути.
Приложите немного усилий для ознакомления с оболочкой, и вы удивитесь, насколько продуктивнее станет ваша работа. Строчные команды могут быть непонятными, но они отличаются мощностью и краткостью. Сводя их в файлы сценариев, вы можете создавать последовательности команд для автоматизации часто выполняемых процедур.
Подсказка 22: Используйте один текстовый редактор, но по максимуму
Обработка текста должна отнимать минимум усилий, так что лучше овладеть одним-единственным редактором в совершенстве и использовать его для решения всех задач, связанных с редактированием: работа с текстом программ, документацией, записками, системное администрирование и т. д. Трудно быть экспертом сразу в нескольких программных средах, довести работу с каждой до рефлекса, учитывая что в каждой из них имеется свой набор команд и стандартов. Пытаясь комбинировать несколько редакторов, вы рискуете повторить ситуацию с вавилонским столпотворением.
Выбор редактора — это почти что религия, поэтому конкретных рекомендаций тут дать нельзя. Однако при принятии решения стоит учитывать следующие параметры:
- Настраиваемость. Все свойства редактора должны настраиваться по вашему пожеланию, включая шрифты, цвета, размеры окон и горячие клавиши.
- Расширяемость. Редактор не должен устареть, как только появится новый язык программирования. Он должен обладать способностью интегрироваться в любую компиляторную среду, которую вы используете в данный момент. У вас должна быть опция «обучить» его нюансам любого нового языка программирования или текстового формата.
- Программируемость. Вы должны располагать возможностью запрограммировать редактор для осуществления сложных многоступенчатых операций.
Подсказка 23: Всегда используйте управление исходным кодом
Системы управления исходным кодом отслеживают любые изменения, которые вносятся в текст и документацию. Лучшие из них также могут отслеживать изменения в версиях компилятора и операционной системы. С помощью системы управления исходным текстом, сконфигурированной надлежащим образом, всегда можно вернуться к предыдущей версии программы.
Система управления исходным текстом дает много больше, чем просто отмену ошибочных действий. Хорошая система позволяет отслеживать изменения и дает ответы на характерные вопросы: «Кто внес изменения в данную строку текста? В чем состоит разница между версией, существующей на данный момент, и версией, существовавшей на прошлой неделе? Сколько строк текста программы были изменены в данной версии? Какие файлы изменяются чаще всего?». Подобная информация бесценна при отслеживании ошибок, аудите, оценке производительности и качества.
Подсказка 24: Занимайтесь устранением проблемы, а не обвинениями
Мы переходим к теме устранения багов — очень щекотливой и крайне актуальной для командной работы. Здесь, как нигде больше, важен правильный настрой. Проникнитесь тем фактом, что отладка представляет собой такую же задачу, как и все остальные, и подходите к ней именно с этой позиции. На самом деле, не важно, кто виноват в ошибке – вы или кто-то другой. Это все равно остается вашей проблемой.
Подсказка 25: Не паникуйте
Очень важно сделать шаг назад, подавить первую эмоциональную реакцию, и подумать над тем, что же на самом деле является первопричиной симптомов и как с этим разобраться. Не поддавайтесь искушению просто устранить симптомы и тем самым решить проблему на поверхностном уровне — работайте с глубинной причиной.
Перед тем как взглянуть на ошибку, убедитесь, что вы работаете над программой, которая прошла стадию компиляции чисто – без предупреждений. Тратить время на ошибки, которые видит даже компилятор, не имеет смысла. Соберите максимум доступной информации; если о баге сообщила третья сторона — подробно расспросите тех, кто с ним столкнулся.
Подсказка 26: Ищите ошибки вне пределов операционной системы
Исходите из предположения, что с операционной системой, базой данных и прочим ПО все в порядке. Если вы «внесли всего одно изменение», и система перестала работать, то, скорее всего, именно оно является причиной случившегося, каким бы абсурдным ни казалось это утверждение. Если не знаете, с чего начать, то всегда можете положиться на старый добрый двоичный поиск.
Исключение: если какой-то из ваших инструментов недавно обновлялся, возможно, проблема вызвана конфликтами с новой версией. Отслеживайте график грядущих изменений, чтобы минимизировать последствия таких конфликтов.
Подсказка 27: Не предполагайте – доказывайте
То удивление, которое вы испытываете, когда что-то идет не так как надо, прямо пропорционально уровню веры в корректность программы. Поэтому, столкнувшись с неожиданным сбоем в работе программы, вы должны смириться с тем, что одно или более ваших предположений неверны. Не доверяйте слепо фрагменту кода, вызвавшему ошибку, только потому, что «знаете», что он работает нормально. Вначале докажите это — в реальном контексте, с реальными данными и с реальными граничными условиями.
Столкнувшись с неожиданной ошибкой, постарайтесь принять меры, что исключить возможность ее распространения, влияния на другие фрагменты кода и повторного возникновения. Если она является результатом чьих-то ошибочных представлений, обсудите проблему со всей командой.
Подсказка 28: Изучите язык обработки текстов
Время от времени нам приходится выполнять некоторые преобразования, которые не могут быть осуществлены с помощью походного инструментария. В таких случаях необходим универсальный инструмент для обработки текста. Используя языки обработки текстов, вы можете быстро решить все проблемы с утилитами и создать прототипы идей – при работе с обычными языками на это потребовалось бы раз в пять-десять больше времени.
Они также облегчают создание генераторов кода, которые будут рассмотрены далее.
Подсказка 29: Пишите код, который будет писать за вас код
От программистов часто требуется выполнять однотипные задачи: обеспечить ту же функциональность, но в другом контексте, воспроизвести информацию или просто перепечатывать один и тот же текст до бесконечности. Тут на помощь приходят шаблоны. Для их создания программист может построить генератор кода, который можно использовать всю оставшуюся жизнь проекта практически бесплатно.
Генераторы кода бывают активными и пассивными. Пассивные генераторы запускаются один раз для достижения результата, который затем становится независимым. Фактически они представляют собой настроенные шаблоны, которые экономят время, необходимое на набор текста, и используются для таких операций, как создание новых исходных файлов, осуществление двоичных преобразований или создание таблиц поиска и других ресурсов, вычисление которых слишком накладно.
Активные генераторы кода используются всякий раз, когда возникает необходимость в результатах их работы. Они могут быть крайне полезны для следования принципу «минимум дублирования». С помощью активного генератора кода вы можете использовать представление некоторого фрагмента знания и преобразовать его во все формы, необходимые вашему приложению. Это не является дублированием, поскольку эти формы являются расходным материалом и создаются генератором по мере необходимости. Когда нужно организовывать совместную работу двух совершенно разных сред, стоит подумать об использовании активных генераторов кода.
Глава 4: Прагматическая паранойя
Подсказка 30: Невозможно написать совершенную программу
За всю историю программирования никому не удалось написать ни одного совершенного фрагмента кода. Маловероятно, что вы станете первым. И когда вы примете это как факт, то перестанете тратить время и энергию впустую в погоне за призрачной мечтой.
Подсказка 31: Проектируйте в соответствии с контрактами
Методика проектирования по контракту предлагает выстраивать взаимодействие программных модулей на базе их задокументированных прав и обязанностей, чтобы обеспечить корректную работу программы. Под корректностью при этом понимается способность делать именно то, что заявлено.
Контракт между подпрограммой и любой потенциально вызывающей ее программой может быть сформулирован так: «Если вызывающая программа выполняет все предусловия подпрограммы, то подпрограмма гарантирует, что по завершении ее работы все постусловия и инварианты будут истинными». Если одна из сторон нарушает условия контракта, то применяется предварительно согласованная мера, например, добавляется исключение или происходит завершение работы программы. В разработке придерживайтесь классических принципов заключения контракта: прописывая предусловия, будьте крайне въедливы, а в том, что касается постусловий, наоборот, не давайте лишних обещаний.
Самая большая польза от использования этого принципа состоит в том, что он ставит вопросы требований и гарантий во главу угла. В период работы над проектом простое перечисление факторов – каков диапазон входных значений, каковы граничные условия, что можно ожидать от работы подпрограммы (или, что важнее, чего от нее ожидать нельзя), – является громадным шагом вперед. Не обозначив эти позиции, вы скатываетесь к программированию в расчете на совпадение, на чем многие проекты и терпят крах.
Подсказка 32: Пусть аварийное завершение работы программы произойдет как можно раньше
Во многих случаях такое завершение программы – это лучший выход из положения, так как альтернативы приведут к серьезным, иногда необратимым последствиям. Прагматики смотрят на ситуацию так: если ошибка имеет место, значит произошло что-то очень скверное и лучше перестраховаться.
Ясно, что в ряде случаев экстренный выход из выполняющейся программы неуместен (возможно, вам необходимо сначала занести что-то в логи, завершить открытые транзакции или провести взаимодействие с другими процессами). Однако основной принцип остается тем же – если программа обнаруживает, что произошло событие, которое считалось невозможным, она теряет жизнеспособность. Начиная с этого момента, все действия, совершаемые программой, попадают под подозрение, и ее выполнение должно быть прервано как можно быстрее. В большинстве случаев мертвая программа приносит намного меньше вреда, чем испорченная.
Подсказка 33: Если что-либо не может произойти, воспользуйтесь утверждениями, которые гарантируют, что это не произойдет
Всякий раз, когда вы начинаете думать в ключе: «Ну конечно, такого просто не может произойти», удостоверьтесь в этом с помощью кода. Самый простой способ осуществить это – использовать утверждения. В большинстве реализаций языков С и С++ имеется некоторая разновидность макроса assert или _assert, который осуществляет проверку логического условия. Эти макрокоманды могут представлять огромную ценность. Допустим, если указатель, передаваемый к вашей процедуре, ни в коем случае не должен принимать значение NULL, пропишите обязательное выполнение этого условия.
Утверждения не должны использоваться вместо реальной обработки ошибок. Они лишь осуществляют проверку на то, что никогда не должно произойти. Существует точка зрения, согласно которой утверждения нужны только в период отладки, а когда проект сдан, превращаются в мертвый груз. Это чересчур оптимистичный взгляд: тестирование скорее всего не выявит всего, что может произойти в реальных условиях. Даже при наличии проблем с производительностью, отключайте только те утверждения, которые действительно оказывают на нее серьезное воздействие.
Подсказка 34: Пользуйтесь исключениями только в исключительных случаях
На практике, однако, проверка на все вообразимые ошибки может привести к тому, что программа станет уродливой; нормальная логика может сойти на нет из-за перенасыщенности процедурами обработки ошибок. Реализовать все изящнее помогут исключения.
Основная проблема с исключениями, заключается в том, что необходимо знать, когда их использовать. Не стоит злоупотреблять исключениями для нормального хода выполнения программы; они должны быть зарезервированы для внештатных ситуаций.
Исключение представляет собой мгновенную нелокальную передачу управления – своего рода многоуровневый оператор goto. Программы, использующие исключения в своей обычной работе, испытывают те же проблемы с удобочитаемостью и сопровождением, которые свойственны классическим неструктурированным программам. В них нарушается принцип инкапсуляции: подпрограммы и программы, их вызывающие, оказываются сильнее связаны между собой за счет обработки исключений.
Подсказка 35: Доводите до конца то, что начинаете
При написании программ нам приходится управлять ресурсами: памятью, транзакциями, потоками, файлами, таймерами – словом, разными типами объектов, которые доступны в ограниченном объеме. Большую часть времени использование ресурса следует предсказуемой схеме: ресурс назначается, используется, а затем освобождается. Однако многие разработчики не имеют четкого плана по распределению и освобождению ресурсов, что может привести к их нехватке. Выход здесь простой: подпрограмма или объект, которые запрашивают ресурс, должны нести ответственность за освобождение этого ресурса.
Если нескольким подпрограммам одновременно необходимо более одного ресурса, добавляется еще два правила:
- Освобождайте ресурсы в последовательности, обратной той, в которой происходило их распределение. При этом можно избежать появления «осиротевших» ресурсов, если один из них содержит ссылки на другой.
- При распределении одного и того же набора ресурсов в различных местах программы необходимо осуществлять эту операцию в одном и том же порядке. Это уменьшает вероятность взаимоблокировки.
В программах, которые используют динамические структуры данных, возникают моменты, когда основная схема распределения ресурсов не годится. В этом случае хитрость состоит в установлении семантического инварианта для выделения памяти.
Глава 5: Гибкость против хрупкости
Чтобы не отставать от сегодняшнего головокружительного темпа развития отрасли, необходимо писать гибкие программы с минимумом связей. В противном случае, программа быстро устареет или станет настолько неустойчивой, что устранять ошибки будет невозможно, и в конечном итоге окажется в хвосте сумасшедшей гонки в будущее.
Подсказка 36: Минимизируйте связывание между модулями
Разбейте вашу программу на ячейки (модули) и ограничьте взаимодействие между ними. Если один модуль находится под угрозой и должен быть заменен, то другие модули должны быть способны продолжить работу.
Необходимо отслеживать, с каким количеством модулей вы взаимодействуете при каждой операции и, главное, каким образом и по какой причине. Системы, в которых имеется большое число ненужных зависимостей, в большинстве случае весьма нестабильны и ресурсозатратны в плане сопровождения. Для того чтобы поддерживать число зависимостей на минимальном уровне, соблюдайте закон Деметера при проектировании методов и функций.
Использование закона Деметера делает вашу программу более адаптируемой и устойчивой, но за это приходится платить. Вам придется создавать большое количество методов-врапперов, которые просто направляют запрос далее к делегату. Эти влечет за собой расходы рантайма и дискового пространства, которые могут оказаться весьма значительными, а для некоторых приложений даже запредельными. Как и при использовании любой методики, вы должны взвешивать все «за» и «против» для конкретного приложения и выстраивать компромиссы.
Подсказка 37: Осуществляйте настройку, а не интеграцию
Используйте метаданные для конфигурации приложения: подгонки параметров, глобальных параметров пользователя и т. д. Метаданные – это любые данные, которые описывают приложение – как оно выполняется, какие ресурсы обязано использовать и т. д. Обычно доступ к данным и их использование осуществляется на этапе выполнения, а не компиляции.
Подсказка 38: Помещайте абстракции в текст программы, а подробности – в область метаданных
Метаданные можно использовать не только для предпочтений, но и более широко. Наша цель – думать описательно (обозначая, что должно быть сделано, а не как это должно быть сделано) и создавать высокодинамичные и адаптируемые программы. Это можно сделать, придерживаясь общего правила: программировать для общего случая и помещать всю специфику в другое место – за пределы компилируемого ядра программы.
Такой подход характеризуется несколькими преимуществами:
- Он вынуждает вас минимизировать связи, что приводит к созданию более гибкой и адаптируемой программы.
- Он заставляет вас создавать более устойчивую, абстрактную конструкцию за счет выведения всех подробностей за пределы программы.
- Вы можете настроить приложение без повторной компиляции. Вы также можете использовать этот уровень настройки для обеспечения обходных путей при возникновении серьезных ошибок.
- Метаданные могут быть выражены языком, который лучше приспособлен к предметной области, чем универсальные языки программирования.
- Наконец, вы можете реализовывать несколько различных проектов, используя одно и то же ядро приложения, но с различными метаданными.
Как было упомянуто в разделе «Преимущество простого текста», рекомендуется представлять метаданные о настройке в формате простого текста – это делает жизнь проще.
Подсказка 39: Анализируйте последовательность операций для увеличения параллелизма
Время – категория, которая часто игнорируется в архитектуре программного обеспечения. Существует два временных аспекта, представляющих для нас важность: параллелизм (события, происходящие в одно и то же время) и упорядочивание (относительное положение событий во времени). При построении последовательности операций необходимо добиться максимального параллелизма, определив те процессы, которые могут осуществляться одновременно.
Подсказка 40: Проектируйте, используя сервисы
Для управления временными аспектами вместо компонентов мы рекомендуем создавать сервисы – независимые, параллельные объекты, скрытые за четко определенными, непротиворечивыми интерфейсами.
Подсказка 41: При проектировании всегда есть место параллелизму
При работе с линейной программой легко делать предположения, которые в конечном итоге приведут к небрежно написанным программам. Но параллелизм заставляет задумываться о происходящем несколько глубже. Поскольку многие события могут теперь происходить одновременно, вы можете внезапно столкнуться с зависимостями, связанными со временным фактором. Прежде всего, необходимо защитить любые глобальные или статические переменные от параллельного доступа, а заодно задать себе вопрос, зачем вообще нужна та или иная глобальная переменная. Кроме того, необходимо убедиться в том, что вы предоставляете непротиворечивую информацию о состоянии независимо от порядка вызовов, не полагаясь на стечение обстоятельств.
Подсказка 42: Отделяйте визуальные представления от моделей
Ослабляя связанность между моделью и ее визуальным представлением/контроллером, вы приобретаете большую гибкость практически за бесценок. Эта методика является одним из важнейших способов обеспечения обратимости.
Модель. Абстрактная модель данных, представляющая целевой объект. Модель не располагает непосредственной информацией о любых визуальных представлениях или контроллерах.
Визуальное представление. Способ интерпретации модели. Оно подписывается на изменения в модели и логические события, приходящие от контроллера.
Контроллер. Способ контроля визуального представления и снабжения модели новыми данными. Он осуществляет публикацию событий для модели и визуального представления.
Очевидно, мы не хотим иметь три отдельных копии одних и тех же данных. Поэтому мы создаем модель – сами данные и обычные операции для их обработки. Затем мы можем создать отдельные визуальные представления, которые отображают данные различными способами: в виде электронной таблицы, графика или поля суммы с накоплением. Каждое из этих визуальных представлений может располагать собственными контроллерами. Ни одно из этих средств не оказывает влияния на данные, только на представление.
Это и является ключевым принципом, на котором основана парадигма «модель-визуальное представление-контроллер»: отделение модели от графического интерфейса, ее представляющего, и средств управления визуальным представлением.
Подсказка 43: Используйте доски объявлений для координации потоков работ
Изначально доски объявлений разрабатывались в системах искусственного интеллекта для решения масштабных и сложных задач – распознавания речи, принятии решений на основе баз знаний и т. д. Доска объявлений — это пространство, на котором потребители и производители информации могут обмениваться данными анонимно и в асинхронном режиме. В программировании при помощи таких систем можно сохранять активные объекты (а не только данные) на доске объявлений и извлекать их при частичном соответствии полей (через шаблоны и трафаретные символы) или с использованием подтипов. Поскольку мы можем хранить объекты, то можно использовать доску объявлений для проектирования алгоритмов, основанных на потоке объектов, а не только на данных.
Большое преимущество этого стиля программирования заключается в том, что он снимает потребность в сведении разнородных интерфейсов, позволяя создавать более элегантную и последовательную систему.
Глава 6: Пока вы пишете программу
Написание программ – не механическая процедура. Нам ежеминутно приходится принимать решения, которые требуют тщательного обдумывания и многое решают в жизни программы. Дисциплинированные разработчики постоянно контролируют ситуацию, отыскивают потенциальные проблемы и оказываются в нужном положении, если происходит непредвиденное.
Подсказка 44: Не пишите программы в расчете на стечение обстоятельств
Зачастую мы пишем код, полагаясь на удачу, и принимаем случайные успехи за гарантию корректной работы системы. Обычно это происходит по следующим схемам:
Случайная реализация: все «вроде бы работает», но на самом деле не должно — в реальности подпрограмма не рассчитана на то, что вы с ней делаете. Успех объясняется недокументированной ошибкой или граничными условиями.
Случайный контекст: программа будет работать, но только в определенном контексте, который вы рассматриваете как незыблемый, хотя он таковым не является (например: у программы имеется графический интерфейс; пользователь говорит на том же языке, что и вы, или хорошо разбирается в программировании).
Неявные предположения: вы привлекаете недостаточную фактическую базу, принимаете совпадения за паттерны и устанавливаете неверные логические связи.
Чтобы этого избежать, достаточно принять несколько правил:
- Всегда отдавайте себе отчет в том, что делаете.
- Не пишите программ вслепую — работайте только с тем, что хорошо понимаете.
- Действуйте исходя из плана
- Полагайтесь только на проверенные факты
- Проверяйте и документируйте все предположения, в истинности которых не уверены
- Правильно расставляйте приоритеты, двигайтесь от сложного к простому
- Не давайте прошлому опыту затмить текущую ситуацию
Подсказка 45: Оцените порядок ваших алгоритмов
Особый вид оценок, который прагматики применяют практически ежедневно — это оценка ресурсов, используемых алгоритмами (времени, работы процессора, объема памяти). Зачастую этот вид оценки является решающим.
Большинство нетривиальных алгоритмов обрабатывает какие-то переменные входные массивы. Обычно объем входных данных оказывает влияние на алгоритм: чем он больше, тем больше время выполнения алгоритма и объем используемой памяти. При этом наиболее важные алгоритмы не являются линейными: время их выполнения или требования к объему памяти возрастают намного быстрее, чем число строк в коде.
При написании любых программ, содержащих циклы или рекурсивные вызовы, вы должны взять за привычку оценивать требования ко времени выполнения и объему памяти. Это необязательно превращать в формальную процедуру, достаточно быстрой проверки — рационально ли мы поступаем в заданных обстоятельствах. Однако иногда случаются ситуации, когда приходится проводить более детальный анализ. В этом случае весьма полезной оказывается система обозначений «O()» («O-большое») — математический способ обозначения приближений.
Подсказка 46: Проверяйте ваши оценки
Говоря о теории, не стоит забывать и о практических соображениях. При работе с небольшими массивами входных данных может показаться, что время выполнения возрастает линейно. Но если программа обрабатывает миллионы записей, то внезапно время выполнения резко увеличивается, по мере того как система начинает «пробуксовывать». Прагматики стараются не упирать на голую теорию, но также тестировать систему в условиях, приближенных к стандартным. Примерные оценки — это хорошо, но на деле имеет значение скорость выполнения вашей программы в реальных условиях эксплуатации и с реальными данными.
При выборе подходящего алгоритма также необходимо придерживаться прагматического подхода – самые быстрые алгоритмы не обязательно являются наилучшими для конкретного случая. Кроме того, необходимо опасаться преждевременной оптимизации. Перед тем как потратить ваше драгоценное время на улучшение алгоритма, всегда есть смысл убедиться, что он действительно является «узким местом».
Подсказка 47: Реорганизация должна проводиться часто и как можно раньше
По мере развития программы возникает необходимость в переосмыслении ранее принятых решений и переработке отдельных фрагментов текста программы. Этот процесс абсолютно естественен. Переписывание, переработка и перепланирование текста программы описывается общим термином «реорганизация».
Программу можно считать нуждающейся в реорганизации в следующих случаях:
- Дублирование. Вы обнаружили нарушение принципа «минимум дублирования»
- Неортогональность конструкции. Вы обнаружили некий фрагмент программы или конструкцию, которой можно придать большую ортогональность.
- Устаревшие знания. Технологии, требования или ваш уровень мастерства изменились, и программа больше им не соответствует.
- Рабочие характеристики. Для лучшей производительности вам необходимо перенести функциональную возможность из одной части системы в другую.
Часто реорганизацию откладывают, ссылаясь на жесткие временные рамки. Но это оправдание не должно становиться нормой: если вы не сможете провести реорганизацию сейчас, то позже, когда придется принимать во внимание большее число зависимостей, на устранение возникшей проблемы потребуется намного больше времени.
Мартин Фаулер предлагает пару простых советов, как провести реорганизацию, чтобы это не принесло больше вреда, чем пользы:
- Не пытайтесь одновременно производить реорганизацию и добавлять функциональные возможности.
- Перед тем как начинать реорганизацию, убедитесь, что тестирование прошло успешно. Проводите тестирование как можно чаще. В этом случае вы сразу увидите нарушение, которое было вызвано внесенными изменениями.
Подсказка 48: Проектируйте с учетом тестирования
Подобно нашим коллегам, работающим с «железом», нам необходимо с самого начала встраивать средства тестирования в программы и тщательно тестировать каждый фрагмент, перед тем как предпринять попытку свести все воедино.
Когда вы проектируете модуль или целую программу, вы обязаны заодно спроектировать контракт и программу для проверки его выполнения. При этом если проектируем мы согласно контракту, то тестируем «против контракта» — то есть проверяем границы, чтобы убедиться, что программа не выходит за их пределы. Это позволяет учесть граничные условия и другие аспекты, которые в ином случае остались бы без внимания. Лучше всего не устранять ошибки, а избегать их с самого начала.
Модульные тесты не должны оказываться где-то на периферии исходного дерева. Они должны располагаться так, чтобы с ними было удобно работать. Если проект небольшой, можно внедрить модульный тест в сам модуль. Для более крупных проектов можно поместить каждую из процедур тестирования в отдельный подкаталог. В любом случае необходимо помнить, что если тест сложно отыскать, то никто не будет им пользоваться.
Делая тестовую процедуру доступной, вы наделяете разработчиков, которые могут воспользоваться вашей программой, двумя бесценными ресурсами:
- Примерами того, как использовать все функциональные возможности вашего модуля
- Средствами построения процедур регрессионного тестирования для проверки правильности любых изменений, которые будут вноситься в программу впоследствии
Подсказка 49: Тестируйте ваши программы, в противном случае это сделают ваши пользователи
Все создаваемые вами программы будут протестированы – если не вами и вашей командой, то конечными пользователями, так что можно с тем же успехом заняться этим самому. Небольшая предусмотрительность окажет серьезную помощь в минимизации затрат на сопровождение и снизит количество обращений в техподдержку.
Подсказка 50: Не пользуйтесь программой функции-мастера, если не все в ней понимаете
Никто не может отрицать – создавать приложения становится все сложнее и сложнее. Разработчики стараются быть в форме, но не всегда находят время овладеть новыми инструментами в совершенстве. На этот случай придумана палочка-выручалочка – функция-мастер.
Проблема с ней в том, что применение функции-мастера, спроектированной неким компьютерным гуру, не делает автоматически из разработчика компьютерного эксперта. Этот принцип, конечно работает, для любой технологии, но здесь ситуация еще более серьезна: функции-мастера генерируют код, который становится неотъемлемой частью приложения и тем самым подталкивает вас к программированию в расчете на стечение обстоятельств. Если вы используете функцию-мастера и не понимаете всего, что она делает, то не сможете поддерживать свое собственное приложение в рабочем состоянии и будете тратить много сил на отладку.
Глава 7: Перед тем, как начать проект
У вас никогда не возникало ощущения, что ваш проект обречен еще до его начала? Возможно, так и будет, если предварительно не принять некоторые основополагающие правила.
Подсказка 51: Не собирайте требования – выискивайте их
Слово «сбор» наводит на мысль, что все требования уже имеются в наличии, только хватай. Это не совсем так. Требования редко лежат на поверхности. Обычно они находятся глубоко под толщей предположений, неверных представлений и политик.
В само общем виде, требование формулирует необходимость осуществления каких-либо операций. В идеале это должна быть четкое, однозначное утверждение, базирующееся на реальной, конкретной потребности. В реальности пользователи не всегда ясно понимают и четко выражают, что им нужно. Прежде чем рассматривать какой-то запрос как требование, необходимо установить, действительно ли существует необходимость и верно ли она сформулирована. В конечном итоге разрабатываемой программе придется решать проблемы, а не просто отвечать заявленным требованиям.
Подсказка 52: Работайте с пользователем, чтобы мыслить категориями пользователя
Существует простая методика: чтобы взглянуть изнутри на требования ваших пользователей, нужно самому стать пользователем. Попробуйте сами повыполнять реальные типовые задачи, на которые рассчитана программа, или понаблюдайте за тем, как это делает кто-то другой. Так вы получите представление, какое впечатление система производит в деле, и вдобавок выстроите более доверительные отношения со своей аудиторией.
Подсказка 53: Абстракции живут дольше, чем подробности
При составлении документов, содержащих требования, возникает серьезная опасность чрезмерной спецификации. Хорошие документы остаются абстрактными. В том, что касается требований, простейшая формулировка, точно отражающая суть потребности, является наилучшей. Однако это не означает, что вы можете допустить неопределенность: нужно зафиксировать основополагающие семантические инварианты в качестве требований и задокументировать конкретную или же существующую на данный момент практику в качестве политики.
Подсказка 54: Используйте глоссарий проекта
Очень сложно создать успешный проект, в котором пользователи и разработчики обозначают одно и то же разными словами или, что еще хуже, говорят о разных вещах, используя один термин. Создайте и поддерживайте в актуальном состоянии «глоссарий проекта», где будут определены все специфические термины. Все участники проекта, от конечных пользователей до специалистов службы поддержки, обязаны использовать глоссарий, чтобы избежать недопонимания.
Подсказка 55: Не мыслите «за пределами» — лучше найдите эти пределы
Абсолютные ограничения должны соблюдаться, какими бы неприятными и нелепыми они ни казались. С другой стороны, некоторые якобы очевидные ограничения в реальности могут не иметь под собой никакой основы — тогда на них можно не обращать внимания. Во многих сложных ситуациях разгадка — определить, какие ограничения действительно существуют, а какие нам только мерещатся.
Столкнувшись с серьезной проблемой, представьте все возможные направления, в которых вы можете двигаться. Не отвергайте никакие варианты, какими бы бесполезными или глупыми они ни казались. Теперь просмотрите весь список и объясните, почему нельзя идти по тому или иному пути, предоставляя доказательства для каждого пункта.
Иногда вам приходится работать над проблемой, которая оказывается намного сложнее, чем вы думали. В этот момент необходимо сделать шаг назад и задать себе несколько вопросов:
- Существует ли более простой способ?
- Вы пытаетесь решить главную проблему или отвлекаетесь на второстепенные технические детали?
- Почему это является проблемой?
- Что делает эту проблему столь сложной для решения?
- Стоит ли делать это именно таким образом?
- Стоит ли это делать вообще?
Во многих случаях выход найдется, как только вы попробуете ответить на один из этих вопросов. Зачастую новая интерпретация требований может унести с собой целый ворох проблем.
Подсказка 56: Прислушайтесь к сомнениям – начинайте тогда, когда полностью готовы
С одной стороны, необходимо прислушиваться ко внутреннему голосу, который говорит вам, что время еще не пришло. С другой, так можно попасть в ловушку страха перед чистым листом и бесконечной прокрастинации. Хороший способ выйти из положения — взяться за создание прототипа продукта. Выберите аспект, с которым, как вам кажется, возникнут проблемы и попытайтесь реализовать его в черновом варианте. Далее возможно два сценария развития событий: либо вы заскучаете, поймете, что ваше опасения были несостоятельны, и переключитесь на работу, либо действительно нащупаете проблемное место и продумаете, как действовать, заранее, тем самым сэкономив время всей команде.
Подсказка 57: Некоторые вещи лучше сделать, чем описывать
Составление спецификации – это большая ответственность. Проблема состоит в том, что многим проектировщикам трудно остановиться. Они полагают, что, пока каждая второстепенная деталь не будет расписана до мельчайших подробностей, они даром едят свой хлеб. Это неверно по ряду причин. Во-первых, наивно полагать, что спецификация вообще способна зафиксировать каждую подробность некой системы или предъявляемых к ней требований. Во-вторых, сам естественный язык, который мы используем для общения, не приспособлен для такого градуса точности. В конечном счете, даже двухсотстраничный трактат, подписанный заказчиком, не спасет от вас от просьб и критики, когда проект будет сдан. Наконец, существует «эффект смирительной рубашки» — излишне подробное описание сужает вам пространство для маневра и не оставляет места творчеству.
Прагматик рассматривает сбор требований, проектирование и реализацию как различные стадии одного процесса – поставки заказчику качественной системы. Каждый из этих аспектов должен плавно переходить в другой без искусственных границ. Поэтому задумывайтесь, как те или иные формулировки будут влиять на вашу работу в дальнейшем.
Подсказка 58: Не будьте рабом формальных методов
Формальные методы имеют ряд серьезных недостатков. Большинство из них фиксирует требования, используя сочетание диаграмм и пояснительных фраз, которые широкая аудитория понимает только с опорой на пояснения разработчика. Следовательно, проверка требований со стороны фактического пользователя при такой схеме, по сути, отсутствует.
Также формальные методы поощряют чрезмерную специализацию без должного взаимодействия групп. Это часто приводит к плохой коммуникации, трате усилий впустую и глухой войне между проектировщиками и программистами. Мы предпочитаем воспринимать систему, над которой работаем, как нечто целостное. Глубоко проникнуть в суть каждого аспекта системы вряд ли получится, но вы обязаны знать, как взаимодействуют между собой компоненты, куда помещены данные и каковы требования.
Наконец, большинство современных формальных методов сочетают модель статического объекта или данных с некоторой разновидностью механизма построения диаграммы событий или процесса. Мы пока не встречали механизма, который был отображал нужную степень динамизма. Это подталкивает разработчиков к заданию статических отношений между объектами, которые на самом деле должны быть связаны между собой динамически.
Сказанное не означает, что формальными методами нельзя пользоваться — просто рассматривайте их как один инструмент из вашего арсенала. Прагматики смотрят на методологии критическим взглядом, затем берут лучшее из каждой и внедряют их в набор практических технологий, который постоянно совершенствуют.
Подсказка 59: Дорогие инструменты не всегда создают лучшие решения
Не подавайтесь ложному авторитету метода. Старайтесь не думать о том, сколько стоит тот или иной инструмент, глядя на результаты его работы.
Глава 8: Прагматические проекты
Подсказка 60: Организуйте команду на основе функциональности, а не должностных обязанностей
Традиционная организация команды основана на устаревшем методе создания программного обеспечения, известного под названием «метода водопада». Отдельным членам команды назначаются роли, основанные на их должностных обязанностях. В некоторых командах диктуется строгое разграничение ответственности: тем, кто делает программы, не разрешено общаться с теми, кто их тестирует, а им, в свою очередь, не разрешено общаться с главным архитектором и т. д. Некоторые организации еще более усложняют задачу, заставляя различные подгруппы отчитываться через отдельные цепочки управления. Однако мнение о том, что различные действия при работе над неким проектом – анализ, проектирование, написание программы и тестирование – могут происходить изолированно друг от друга, ошибочно.
Разделите ваших сотрудников на небольшие группы, каждая из которых будет нести ответственность за конкретный функциональный аспект конечной версии системы. Каждая группа обладает совместно установленными обязательствами перед другими группами, участвующими в проекте. Этот набор обязательств изменяется с каждым новым проектом, так же как и распределение людей по группам.
Таим образом вы способствуете доброкачественной изоляции: она заключается не в том, что команды работают «вслепую», не учитывая действия друг друга, а в том, что крупные изменения могут быть замкнуты внутри одной группы, не затрагивая остальные (см. принцип ортогональности). При надлежащем исполнении подобный подход может существенно снизить число пересечений в работе, снизить затраты времени, повысить качество и уменьшить число багов, а также сделать команды более сплоченными.
Однако этот подход работает только при наличии ответственных разработчиков и сильного руководства. Создать пул автономных групп и позволить им разбалтываться в отсутствие руководства – это кратчайший путь к катастрофе. Проекту необходимы как минимум два руководителя – один технический, другой административный.
Подсказка 61: Не используйте процедуры, выполняемые вручную
Любая повторяющаяся задача, возникающая в ходе проекта должна выполняться автоматически. Только так можно гарантировать полноту и единообразие выполнения, особенно если аспекты конкретной процедуры открыты для интерпретации разными людьми. Сценарий оболочки или пакетный файл будут раз за разом выполнять одинаковые инструкции в одинаковом порядке и без отклонений, вызванных человеческим фактором.
Подсказка 62: Тестируйте как можно раньше. Тестируйте часто. Тестируйте автоматически
Лучше обнаружить ошибку сейчас и самому, чем ждать, пока кто-нибудь на нее вам укажет. Поиск багов можно уподобить ловле рыбы с помощью сети. Мы используем мелкие, небольшие сети (модульные тесты) для ловли пескарей и большие, крупные сети (комплексные тесты) для ловли акул-убийц. Иногда рыбе удается выскользнуть, поэтому мы заделываем все найденные дыры в надежде поймать как можно больше ошибок. Чем раньше обнаружен дефект, тем дешевле обходится его устранение.
Команды, использующие автоматизированные процедуры тестирования, имеют больше шансов на успех. Тесты, запускающиеся в ходе каждого процесса сборки, являются более эффективными. Сложные планы ручного тестирования редко реализуются на практике.
Подсказка 63: Программа не считается написанной, пока не пройдет тестирование
Тот факт, что вы закончили работу с фрагментом программы, вовсе не означает, что можно идти к шефу или заказчику, чтобы сообщить ему о «готовности». Пока код не пройдет все имеющиеся тесты, вы не можете утверждать, что он может использоваться кем бы то ни было.
Существует несколько видов процедур тестирования программного обеспечения, которые нужно выполнять при работе над программой:
- Модульное тестирование (тестирование работы отдельных компонентов, основа для всех других видов)
- Комплексное тестирование (тестирование основных подсистем, из которых состоит проект, на нормальное взаимодействие; как правило, главный источников багов)
- Подтверждение правильности и верификация (проверка прототипа на соответствие функциональным требованиям системы и реальным ожиданиям пользователей)
- Тестирование в условиях нехватки ресурсов, ошибки и их исправление (тестирование в условиях, приближенных к реальности, в частности с ограничениями по объему памяти, дискового пространства, мощности процессора, пропускной способности сети, разрешению экрана…)
- Тестирование производительности (проверка, выдержит ли система имитацию реальной нагрузки, которую будет испытывать)
- Тестирование удобства использования (диалог с конечными пользователями, у которых был опыт работы с программой)
Этот перечень ни в коей мере не является полным: в некоторых специализированных проектах потребуются другие виды процедур тестирования. Но он служит хорошей отправной точкой.
Подсказка 64: Используйте диверсантов для тестирования самих тестов
Мы установили, что не писать совершенные программы невозможно, значит и в программах для тестирования будут недочеты. Соответственно, необходимо тестировать и сами тесты. Если вы серьезно относитесь к тестированию, стоит назначить диверсанта проекта, чья роль состоит в том, чтобы на отдельной копии исходного дерева преднамеренно внести дефекты и убедиться, что тестирование их выявляет.
Подсказка 65: Тестируйте степень покрытия состояний, а не строк текста программы
Как узнать, достаточно ли тщательно проведено тестирование? Честно говоря, никак, однако на рынке есть продукты, которые могут дать примерный ответ. Они анализируют степень покрытия: мониторят программу при тестировании и фиксируют, какие строки были выполнены, а какие нет. Они дают общее представление о том, насколько исчерпывающей является процедура тестирования. Но даже если выполненными окажутся все строки программы, это еще не все. Важно то число состояний, в которых может находиться программа. Даже при высокой степени покрытия огромное значение имеет также то, какие данные вы используете в процедуре тестирования, и, что еще более важно, порядок, в котором вы выполняете программу.
Подсказка 66: Дефект должен обнаруживаться единожды
И наконец, мы хотели бы раскрыть единственный и самый важный принцип тестирования. Он очевиден, и практически в каждом учебнике говорится о том, что это нужно делать именно так. Но в силу некоторых причин в большинстве проектов этого все еще не делается.
Если тестировщик обнаруживает баг, это должен быть первый и последний раз, когда эта ошибка вылавливалась человеком. Как только баг зафиксирован, автоматизированные тесты модифицируются так, чтобы осуществлять проверку на его наличие. Это правило должно соблюдаться всегда и без исключений, какой бы тривиальной ни была ошибка и как бы разработчик ни стонал и ни уверял, что такого больше не повторится.
Подсказка 67: Считайте естественный язык одним из языков программирования
Идея не нова, но прагматики воспринимают документацию как неотъемлемую часть общего процесса разработки. Любое противоречие между программой и документацией должно быть устранено — считайте их двумя визуальными представлениями одной и той же модели. На самом деле, стоит даже пойти немножко дальше и применить все прагматические принципы не только к созданию программ, но и к составлению документации.
Подсказка 68: Встраивайте документацию в проект, а не накручивайте ее сверху
Внутренняя документация включает комментарии исходных текстов, документы, касающиеся проектирования и тестирования, и т. д. Программа должна иметь комментарии, но слишком большое их количество может быть так же плохо, как и недостаточное. В общем и целом, комментарии должны говорить о том, почему выполняется та или иная операция, каково ее назначение. Как именно это делается, видно из кода, поэтому комментирование на этот счет – избыточная информация.
Комментарии в тексте кода дают отличную возможность документировать неуловимые фрагменты проекта, которые не могут фиксироваться где- либо еще: технические компромиссы, почему было принято то или иное решение, какие альтернативные варианты были отвергнуты.
Предпочтительнее всего оставлять простой комментарий в заголовке (на уровне модуля), комментарии к существенным данным и объявлениям типов и краткие заголовки для каждого из классов и методов, описывающие, как используется именно эта функция и все ее неочевидные действия. Имена переменных должны выбираться четко и со смыслом. Вы можете также документировать параметры, но задайте себе вопрос, нужно ли это делать в каждом конкретном случае.
Вот список того, чего не должно быть в комментариях к коду:
- Перечень функций, экспортируемых программой в файл. Существуют программы, которые анализируют код. Воспользуйтесь ими, и этот перечень никогда не устареет.
- Хронология изменений. Для этого предназначены системы управления исходным кодом. Однако будет полезно включить информацию о дате последнего изменения и сотруднике, который внес это изменение.
- Список файлов, используемых данным файлом. Это можно более точно определить при помощи автоматических средств.
- Имя файла. Если оно должно указываться в файле, не прописывайте его вручную.
Подсказка 69: Слегка превосходите надежды пользователей
Пользователи обычно приходят к вам с некоторым видением того, что они хотят. Это видение необходимо принимать во внимание, отслеживать на предмет изменений и при необходимости немного корректировать в ходе диалога (допустим, если их требования технически невыполнимы или слишком консервативны). Работайте со своими пользователями, чтобы они точно понимали, что получат в итоге. Этим необходимо заниматься на протяжении всего процесса разработки.
Однако если вы работаете в тесном взаимодействии с пользователями и держите их в курсе событий, то при завершении проекта особых сюрпризов на их долю не выпадет. И это плохо. Постарайтесь приятно удивить ваших пользователей, дайте им немного больше, чем они ожидают. Усилия, которые потребуются на реализацию какой-то бонусной возможности, показывающей, что вы думаете об удобстве аудитории, окупится с лихвой. Таким бонусом могут стать: горячие клавиши, всплывающие подсказки, расцвечивание, экран-заставка, настроенный для фирмы-заказчика.
Подсказка 70: Ставьте свою подпись под работой
Как вы помните, программисты-прагматики не уклоняются от ответственности. Анонимность, особенно при работе с масштабными проектами, может оказаться благодатной почвой для небрежности, ошибок, лени и неудачных решений. Слишком легко рассматривать себя лишь в качестве винтика в большой машине и оправдываться в бесконечных отчетах, вместо того чтобы просто создавать хорошие программы. Подписываясь под кодом и признавая, что это — ваша собственность, вы получаете мотивацию обеспечить максимально возможное качество.
Этот принцип не стоит доводить до абсурда, иначе команда может впасть в состоянии феодальной раздробленности, где каждый стоит за своим куском кода, не допускает к нему никого и не желает объединять усилия для совместной работы над какими-то фундаментальными вещами. Гордость за свою работу не должна вызывать неуважение к чужой или предубеждения против нее.
Отправить ответ