Философия программирования Win95. Глава 2

Разное


Отсканированная мной книга Лу Гринзоу "Философия программирования Windows 95/NT", глава 2. Обсуждение, исправление опечаток и вопросы - в комментариях.

Философия
разработки программного обеспечения для Windows: макропроблемы

Axioms in philosophy are not axioms until they are proved
upon our pulses: we read fine things but never feel them to
the full until we have gone the same steps as the author.

Джон Китс

Как и в более важных жизненных областях, все, что мы делаем как програм-
мисты, прямо или косвенно определяется нашей философией. Наши стили ко-
дирования, наши предпочтения в выборе типов данных для переменных,
буквально каждое из дюжин решений, которые мы делаем при написании даже
маленьких программ - все это сводится к противопоставлению взглядов, ко-
торые в меньшей степени зависят от установленных фактов, а в большей - от
мастерства. В этой и следующей главах я поведу долгий разговор об основных
философских проблемах программирования для Windows и представлю целый
ряд рекомендаций, которые должны помочь вам избежать многих ловушек. Эта
глава фокусируется на макропроблемах, а глава 3 охватывает микропроблемы.

Стиль

Прежде, чем вдаваться в детали того, что делает программу хорошей, и как соз-
дать хорошую программу, я хотел бы потратить немного времени на близкую
к этим вопросам концепцию стиля.

Стиль - это такая сторона программирования, о которой программисты,
возможно, говорят даже больше, чем о философии (хотя порой они даже не
знают, что говорят именно об этих вещах). Вы часто слышите, как люди раз-
говаривают о стиле форматирования кода у конкретного программиста, об ис-
пользовании или неиспользовании венгерской нотации, о том, насколько агрес-
сивно какой-либо человек использует (или бранит) возможности С++ и т. д. В
некоторых случаях стиль - это не более чем привычка, в плен которой мы од-
нажды попали, и отказаться от которой у нас не было оснований. Делайте что
угодно каким-нибудь способом достаточно долго, и этот способ покажется
правильным; пока ваша практика не сталкивается с проблемами, вы привы-
каете к нему, он становится частью вашего стиля, и влияет на остальную вашу
работу.

Но стиль программирования не является просто результатом вычитания
инертности из случайности. Часто стиль является неожиданно возникшим свой-
ством персональных философии и предположений. Например, Windows-
программисты часто относятся к локальным переменным в функциях как к
практически бесплатной памяти, по крайней мере когда речь идет о небольшом
количестве переменных. Что следует использовать - четырехбайтное целое
или двухбайтное целое - для маленького числа, которое легко уместится в
любой из этих типов данных? Один лагерь говорит, что вам следует выбирать
размер переменных максимально соответствующим их предназначению, чтобы
сделать их «самодокументированными». Другой лагерь предлагает не беспоко-
иться об этой мелочи, а лучше учесть, что наиболее эффективным является тот
тип, размер которого ближе всего к размеру машинного слова. Это один из тех
споров, которые могут длиться вечно, включая в рассмотрение все больше и
больше тонкостей из области характеристик машин, компиляторов, и т. д.
и т. п.

Фактически, большинство из нас даже не задумывается над этой пробле-
мой (за исключением случаев, когда нам необходимо передавать эту
переменную API или другой функции, и когда на самом деле самым удобным
оказывается выбрать тип так, чтобы избежать явного преобразования типа).
Каков бы ни был индивидуальный подход, программист просто напишет дан-
ный фрагмент кода так или эдак и, особо не размышляя об этом, продолжит
работу над проектом. А ведь то, к какому решению мы склонимся в данном ма-
леньком вопросе, является философской проблемой в той же степени, в какой
это является беспокойством о производительности. Интересным знамением ны-
нешнего времени стал тот факт, что практически никто больше не обсуждает
подобных проблем. Когда я начинал свою профессиональную карьеру програм-
миста, во времена администрации Картера, мэйнфреймеры все еще дис-
кутировали на такие темы. Что ж вы хотите, у нас не было ни мышек, ни
графических интерфейсов, с которыми можно было бы поразвлечься, а нам
нужно было хоть чем-то заполнять праздные минуты.

Математики и ювелиры

В течение многих лет я был знаком со многими программистами и обнаружил,
что большинство из них (но определенно не все) распадается на две
категории - математиков и ювелиров. (Несмотря на то, как я сформулировал
это утверждение, я уверен, что получу несколько сердитых электронных писем
от представителей всех этих трех профессий. Я заранее приношу свои извине-
ния на тот случай, если вы почувствуете, что я каким-то образом неуважитель-
но отнесся к вам или вашей профессии.)

Математики это абстрактные ученые. Они делают сильный акцент
(порой в ущерб другим факторам) на вопрос о доказуемой правильности (или,
наоборот, неправильности) того или иного фрагмента кода. Их всецело погло-
щает беспокойство о том, чтобы код работал строго в соответствии с требова-
ниями к нему, при подаче ему правильных входных данных. Если ответ поло-
жителен, то дискуссию, по их мнению, можно дальше не продолжать.
Следующий вопрос, пожалуйста!

С позиций чистой логики, суровой строгости, определяющей роли специ-
фикаций (а это, конечно, самая лучшая отправная точка при оценке кода),
совершенно невозможно спорить с этой группой программистов. Специфика-
ции гласят, что функция должна давать на выходе А, когда на вход поступают
X, Y и Z. Делает она это или нет? Если да, то она очевидно правильна. Тогда
какие проблемы? (Последнее предложение обычно добавляется только
программистами из Нью-Йорка.)

Несмотря на трудность споров с математиками, как профессионал я часто
должен с ними спорить, потому что они склонны игнорировать другие
проблемы, которые могут быть важны не меньше, чем строгая корректность
программы или функции. Предположим, функция Ваrnеу() явно работает
корректно - делает в точности то, что от нее требуется, - но при этом она
представляет собой трудносопровождаемую путаницу, имея 18 параметров
взаимозаменяемых типов, что часто ведет к ошибкам в коде, вызывающем эту
функцию. Очевидно, что такой дизайн плох, и что эта функция может и
должна быть улучшена. В данной ситуации (а она не так уж и надуманна)
экстремистски настроенные математики могут быть весьма упрямы: они не бу-
дут «ковырять» работающий код и рисковать привнесением новых багов в ту
часть системы, которая в текущий момент не имеет известных ошибок. (Я сам
осуществлял сопровождение операционной системы, написанной на ас-
семблере, поэтому я испытываю немалую симпатию к такой точке зрения.)

С другой стороны, ювелиры представляют собой иную проблему. Они из
тех, кто часто говорит об элегантности алгоритмов и о поисках новых методов,
которые будут работать хоть немного, хоть чуть-чуть лучше существующих. В
нормальных условиях это хорошая позиция (говорим ли мы о программирова-
нии, садоводстве или каком-либо другом старательстве). Но лучшее - враг хорошего,
и привычки более фанатичных ювелиров часто превращаются в бес-
конечное вылуживание, вызывая серьезные проблемы по всему большому
проекту в целом. Возвращаясь снова к гипотетическому примеру с функцией
Ваrnеу(), ювелиры обычно с охотой берутся исправить эту функцию (заменяя
18 параметров на один, представляющий собой структуру из 18 элементов, или
еще каким-либо образом улучшая ее интерфейс) с целью уменьшить количество
ошибок в вызывающем коде. Но это приводит к необходимости делать кучу из-
менений во всем проекте, из-за чего возникает риск появления новых ошибок.
И мы опять сталкиваемся с теми проблемами, ради избежания которых все и
затевалось.

На протяжении ряда лет я многократно спорил и с математиками, и с
ювелирами, и «религиозные войны» между этими двумя лагерями почти всегда
сводились к следующему противопоставлению взглядов: что лучше - ужи-
ваться с дефектами и постепенными, капля за каплей, затратами на усовершен-
ствования, или бросить все свои скудные ресурсы по разработке и
тестированию на то, чтобы исправить ситуацию раз и навсегда, даже рискуя
создать большую проблему? Конечно, реальные жизненные ситуации почти ни-
когда не сводятся к буквально такой постановке вопроса, а это означает, что
мы опять возвращаемся прямиком к философии, к тому, как она направляет
наши взгляды, решения и, в конечном итоге, нашу работу.

Вероятно, вы уже заметили, что я склонен быть больше ювелиром, чем ма-
тематиком. Это правда, но известно, что я являюсь ярым ненавистником
программных интерфейсов, которые не ведут себя в точном соответствии с их
описаниями. Например, обратите ваше внимание на дискуссию в главе 14 о не-
которых приключениях, с которыми нам всем придется столкнуться при работе
с Win32 API.

Пускай это будет еще большим обобщением, но весьма интересно по-
наблюдать, как относятся друг к другу эти два лагеря. Ювелиры имеют тен-
денцию смотреть на математиков как на двумерных, слишком суровых проста-
ков, которые не способны понять картину в целом и оценить далекие
последствия, и для которых недоступны хитрость и изящество большинства
проектировочных решений. Математики же теряют терпение при общении с
ювелирами и считают их слишком хлипкими и недостаточно обеспокоенными
жесткими требованиями, предъявленными к программе. Я обрисовываю все это
не для того, чтобы выступить в качестве популярного психолога, а для того,
чтобы подчеркнуть, что оба лагеря правы каждый по-своему. Их любимые за-
боты в самом деле очень важны, и грубое пренебрежение ими может привести
к серьезным несчастьям. Истинная же проблема, как всегда, в нахождении
правильного баланса в вашем конкретном проекте.

У меня есть сильное желание - видеть как программисты используют та-
кой подход, при котором все мы расширяем наши взгляды (так, чтобы объеди-
нить в себе эти два лагеря) и с самого начала продумываем все далеко наперед
(так, чтобы многие из ситуаций, подобных гипотетическому примеру с функ-
цией Ваrnеу(), не возникали вообще). В конце концов, не можем же мы тратить
время на споры о том, как починить то, что никогда не ломается! (Ладно-ладно,
мы можем это делать, программеры есть программеры. Но я надеюсь, вы по-
няли мою основную мысль.) Позже мы вернемся к этому вопросу и поговорим
о нем шире и подробнее. А пока я хотел бы поговорить о том, что же делает
программное обеспечение хорошим.

Три источника и три составные части качества программы

Ваша первейшая цель при написании любого программного обеспечения -
серьезной ли прикладной программы, с которой будет работать множество
пользователей, или функции в библиотеке, которую увидят лишь несколько
других программистов - сделать его по-настоящему полезным. Если вам это
удастся - то есть если выигрыш от использования вашего продукта будет на-
много превышать его стоимость - люди по достоинству оценят ваш труд и бу-
дут им пользоваться, потому что ваше программное обеспечение действительно
расширит их возможности и сэкономит им время. В некоторых случаях (таких,
как одна древняя DOS-программка, которую я до сих пор использую ежеднев-
но), это определение сохраняет силу даже тогда, когда, казалось бы, весь мир
ушел далеко вперед.

Главными целями при написании широко распространяемой функции или
законченной программы должны быть ее корректность, разумность и услужли-
вость. Добейтесь этого, и определение «по-настоящему полезная» приложится
само собой, потому что оно является результатом сложения этих трех качеств.
Если сказанное выше звучит не очень понятно, вы можете применить термины
стратегии и тактики: стратегия заключается в том, чтобы сделать вашу
программу по-настоящему полезной, а тактика - в том, чтобы сделать ее
корректной, разумной и услужливой.

Одна поясняющая деталь: до конца этой главы я буду использовать термин
«программа» в более широком смысле, чем обычно, и буду иметь в виду любое
ПО, с которым будет осуществляться взаимодействие извне, - от законченной
прикладной программы до отдельной функции в совместно используемой биб-
лиотеке. Аналогично, я буду говорить о «пользователях» более широко, не
ограничивая это понятие только людьми, которые запускают прикладные
программы.

Корректность

Корректные программы допускают только два варианта реакции на ввод
пользователя:

В Выполнить ожидаемые функции в точности согласно запросу, учиты-
вая общий контекст программы или системы.

В Элегантно отказаться выполнить запрос из-за неправильно введенных
данных, недостатка ресурсов и т. д. Не нужно объяснять, что термин
«элегантно» не является эвфемизмом для «путем форматирования же-
сткого диска пользователя и затем вызова GPF» или чего-нибудь по-
добного.

Вы должны рассматривать корректность как едва достаточный минималь-
ный уровень приемлемой функциональности. Оценка корректности - это еще
одна из необычайно запутанных проблем. На мой взгляд, любое определение
API, документация, руководство пользователя и т. д. - это контракт между
программой и окружающим ее миром. Если документация и программы не со-
гласуются, провал может потерпеть весь программный пакет. Вопрос же о том,
что конкретно является причиной провала - документация или программа, -
это уже следующая, более глубинная проблема, которая может привести к сво-
ему собственному бесконечному циклу споров. Я знаю немало случаев, когда
вначале функция предназначалась для какого-либо конкретного использования
и была соответствующим образом документирована, а позже выяснялось, что
при каких-то странных условиях это соответствие могло нарушаться, и тогда
код оставался неизмененным, а корректировке подвергалась именно докумен-
тация (так, чтобы оговорить возможную аномалию). Иногда это было просто
запоздалое открытие того факта, что действительно существует патологическая
ситуация, в которой функция никак не может работать согласно исходной до-
кументации (кстати, это означало не что иное, как ошибку в изначальном ди-
зайне, так как он на самом деле обещал недостижимые результаты). А иногда
это было просто проявлением лени со стороны программиста или компании-
производителя, которые не желали чинить свои программы и разрешали проти-
воречие самым дешевым способом - путем корректировки документации и
приведения ее в соответствие с кодом.

Один из великих грехов программирования - написание функциональ-
ных спецификаций с использованием скверного выражения «неопреде-
ленное поведение» (undefined behaviour). Обычно оно встречается
примерно в таком виде: «если параметр lpFlapDoodle равен NULL, то по-
ведение этой функции будет неопределенным». Перевод: «программист
был слишком близорук, чтобы предвидеть, что вы можете случайно
передать NULL через параметр lpFlapDoodle (или слишком ленив для
того, чтобы что-то сделать в связи с такой возможностью), поэтому сия
функция не утруждается проверкой такой ситуации. Передайте NULL
через этот параметр - и вся ваша программа (а возможно, и операцион-
ная система) рухнет на пол, как тонна кирпичей, сброшенных с околозем-
ной орбиты».

Важно отличать вопиющую программерскую лень от условий, которые
действительно не могут быть проверены. Возьмите стандартную С-функ-
цию memset(), которая заполняет указанный блок памяти заданным бай-
товым значением. Когда вы используете ее для обнуления структуры, как
это часто делается в Windows-программировании, подпрограмма mem-
set() должна полагаться на то, что при ее вызове вы передадите ей
корректное значение длины, просто потому что у нее нет никакой возмож-
ности самостоятельно узнать размер вашей структуры. Хотя реализация
этой функции в Visual С++ 2.2 (и, возможно, другие ее реализации)
воспримут NULL в качестве передаваемого указателя.

Еще более крайним примером ситуации, когда возникновение ошибки не-
возможно определить, является тривиальное выключение питания ком-
пьютера без предварительного завершения работы (shutdown) Windows,
что, весьма вероятно, приводит к потере данных. Я не знаю никого, кому
пришло бы в голову обвинять Microsoft за то, что она не предохраняет от
этой обычной проблемы.

Разумность

Несмотря на важность корректности, просто корректная программа еще да-
леко не является «по-настоящему хорошей». Разумные программы не
ограничиваются просто принятием правильного ввода и обеспечением правиль-
ного вывода, они ожидают появления ошибок пользователя и адекватно
реагируют на них. При этом я использую термин «ожидают» не в том узком
смысле, что программа лишь предвидит ошибочное действие пользователя -
программа совершает противодействие еще до того, как это действие
совершено. Противодействие может иметь самые различные формы, такие как
дополнительное усердие программы при проверке входных параметров или
просьба к пользователю подтвердить его намерение выбросить документ без
предварительного сохранения его на диск.

Если принять во внимание все места в Windows-программировании, где в
качестве параметров передаются указатели, неудивительно, что простейшим
способом нарушить работу программы является передача функциям нулевых
указателей. А это неизбежно приведет к началу 1372-го витка философской
войны на тему: чьей работой - вызывающего или вызываемого кода - должно
быть обеспечение того, чтобы NULL-указатели не проникали в сердцевину
программы. Разумеется, ответ на этот вопрос следует искать в спецификациях
на функции, так как именно они являются контрактами между функциями и
окружающим миром.

К сожалению, почти невозможно всегда докопаться до явного описания
того, как обрабатываются те или иные общие патологические случаи.
Например, многие интерфейсы Win32 описаны так: в случае успешного выпол-
нения возвращается значение TRUE, в случае провала - FALSE (или на

оборот), а дополнительную информацию о случившейся ошибке вызывающий
может получить при помощи интерфейса GetLastError(). Но в подавляющем
большинстве случаев это «определение» интерфейса (кавычки означают, что
термин в данном случае применен из снисходительности) не только ничего явно
не говорит о том, что будет делать Windows при неправильном вводе, но даже
не дает и намека на то, какие коды ошибок вы можете ждать от GetLastError()
в данном конкретном случае. (Смотрите главу 14, где вы найдете массу
компрометирующих материалов на функцию GetLastError().)

Конечно же, разумное программное обеспечение должно делать много
больше, чем просто «проверять и катапультироваться» (касательно входных
параметров). В некоторых случаях, функция может не только обнаруживать
условия, не позволяющие выполнить ее работу, но и различать ошибочные си-
туации, которые она может самостоятельно и безопасно исправить. Здесь я
хочу привести мой излюбленный пример такого не слишком опасного дефекта
данных: нежелательные пробелы в началах и концах строковых данных. На
протяжении многих лет я видел огромное число программ, которые теряли
ориентацию (или данные пользователя), сталкиваясь с такой гнусной ошибкой,
как нечаянный ввод пользователем пробела в начале или конце имени. Часто
это является тривиальнейшей задачей для программы - скорректировать по-
добную ситуацию, однако многие программы этого не делают. К этой теме я
более подробно обращусь вновь в главе 3, где буду говорить об оборонительном
программировании.

Пользователи не являются единственным источником «незначительных от-
клонений» (термин позаимствован из области пошива одежды). Программы
часто получают сюрпризы от сторонних библиотек, от самой Windows, или
даже от более пассивного (но далеко не статичного) объекта под названием «со-
стояние системы пользователя» (например, когда стандартная DLL, которая
«просто обязана присутствовать в системе», вдруг отсутствует; или присутст-
вует, но не той версии; или это вообще посторонняя DLL, по случайности но-
сящая то же самое имя).

Я могу еще много чего порассказать о динамических библиотеках (см.
главу 6), но уже здесь я хочу отметить, что при использовании несистемной
DLL разумным подходом является ее явная загрузка (при помощи функции
LoadLibrary()), а в случае ее отсутствия или неподходящей версии - предос-
тавление пользователю осмысленной информации об этой типичной проблеме
(желательно, с возможностью получения еще какой-либо дополнительной по-
мощи, если это уместно). С этой проблемой также перекликается вопрос о том,
что делать программе, когда отсутствует DLL, отнюдь не являющаяся жиз-
ненно важной для работы этой программы. Многие программы в такой ситуа-
ции откажутся работать (тогда как на самом деле они просто неявно загружают
свои DLL и тем самым позволяют Windows самой запрещать запуск этих
программ, вообще не отдавая им управление).

Как и в других спорных ситуациях, упомянутых в данной книге, здесь
рекомендуется искать сбалансированное, компромиссное решение. И если уж
случилось так, что вы пишете одну из тех ужасно редко встречающихся в
природе функций, которые буквально не могут вынести бремя дополнительной
пары строчек типа

if(lpSomeData == NULL)
return ERROR_BAD_DATA;

тогда хотя бы ясно и недвусмысленно напишите в документации, что эта функ-
ция взбесится, если ей передать нулевой указатель (а затем ясно и откровенно
поясните в самом коде, почему такая проверка не была сделана, чтобы потом,
когда вы будете в отпуске в следующем году, какой-нибудь ювелир, в хорошем
смысле этого слова, не оказался в полной беспомощности и вставил в нужное
место вышеприведенный лоскуток кода).

Услужливость

Услужливые программы делают дополнительные шаги для того, чтобы об-
легчить жизнь пользователям (в особенности новичкам). Интересовало ли вас,
когда я закончу ходить вокруг да около человеческого фактора? Если да, то
можете перестать волноваться - это время пришло.

Для примера, каждый диалог в вашей публичной программе должен
содержать кнопку Help. Причем это касается не только «рабочих» диалогов,
которые реально осуществляют ввод/вывод данных пользователя, но и диагно-
стических диалогов, которые «только лишь» показывают кусочек текста (не
важно, является ли он кратким сообщением типа «операция закончена», или
представляет собой что-то более серьезное, - например, весть об отсутствии
нужного файла или ресурса).

Я поражаюсь тому, как часто сама Windows или другие программы
выбрасывают на экран диалоги, которые без сомнения тут же породят кучу
вопросов в голове пользователя (и первым среди них наверняка будет:
«Почему???»), но затем не предоставляют никаких дальнейших пояснений или
помощи. Одним из самых дурацких примеров подобного рода является реакция
Windows 3.51 на попытку запустить программу, у которой ожидаемая версия
Windows указана как 4.0 («ожидаемая версия Windows это набор
значительных и незначительных кодов версии, который хранится в заголовке
исполняемого файла и указывает Windows, какая версия операционной сис-
темы требуется программе). Вместо того, чтобы посоветовать пользователю
приобрести более новую версию Windows (как это делает Windows 95, хотя и
в не слишком оптимальной формулировке), Windows NT открывает диалог, у
которого и заголовок, и текст выглядят одинаково - «Cannot run program»
(«Невозможно запустить программу»). (Этот пример может и не показаться та-
кой уж важной проблемой сейчас, но как только начнут появляться 32-разряд-
ные программы с «ожидаемой версией Windows», равной 4.0, - а мы все
знаем, что они в конце концов появятся - пользователи будут часто наблюдать
это явление и спрашивать авторов незапускаемых программ, что за чертовщина
происходит. Таким образом, загадочное диагностическое сообщение в Windows
NT становится проблемой прикладных программистов.)

Даже когда программы дают возможность обратиться за помощью, иногда
это делается решительно не самым удачным способом. По моему мнению, Page-
Maker 5.0 заслуживает «Премии Секретного Рукопожатия» за его подход в
предоставлении помощи. Вместо того, чтобы сделать такую земную и
сподручную вещь как кнопка Help, он требует от пользователя одновременного
нажатия клавиши Shift и щелчка правой кнопкой мыши по фону диалога.

Эта проблема касается не только пользовательского интерфейса. При
проектировании библиотечных функций следует всегда держать в уме самые
насущные вопросы и цели того, кто потом будет эти функции вызывать.
Например, многие интерфейсы или функции берут набор параметров и
возвращают значение, указывающее, был ли вызов успешно завершен. Если все
было в порядке, то возвращаемые данные (если таковые имеются) помещаются
в буфер, на который указывал один из параметров. Это - простая, ясная и
весьма полезная модель, которая позволяет вызывающему коду делать
примерно такие вещи:

if(SomeApiFunction(/* параметры */) {
  /* Вызов завершился успешно, обрабатываем выходные данные */
}
else {
 /* Вызов потерпел провал, жалуемся пользователю, закрываемся и т.д. */
}

Но встречаются исключения. Одно из них поистине сводит меня с ума:

UINT GetDlgItemInt(  
  HWND   hDlg,             // дескриптор диалога  
  int     nIDDlgItem,       // идентификатор элемента управления  
  BOOL* lpTranslated,    // указатель на переменную, которая должна получить
                                  // значение, индицирующее успех/неуспех  
  BOOL   bSigned          // указывает, является ли значение знаковым/беззнаковым
); 

Этот интерфейс извлекает значение из контроля (элемента управления)
диалога, такого как поле редактирования, и конвертирует его в беззнаковое це-
лое. На мой взгляд, функция GetDlgItemInt() спроектирована «вверх тормаш-
ками», поскольку возвращает она сконвертированное значение, которое
запросто может оказаться нулем и в случае успешного завершения, тем самым
не позволяя вызывающему коду делать проверку в операторе if - в той форме,
которую как правило используют Windows-программисты. Я бы предпочел ви-
деть такой прототип этого API:

BOOL GetDlgItemInt(
  HWND   hDlg,             // дескриптор диалога
  int     nIDDlgItem,      // идентификатор элемента управления
  DINT* lpValue,           // указатель на переменную, которая должна получить
                                 // сконвертированное значение
  BOOL   bSigned         // указывает, является ли значение знаковым/беззнаковым
);

где в случае провала возвращалось бы значение FALSE, в случае успеха -
TRUE, а буфер, указанный параметром lpValue, заполнялся бы целым числом,
полученным от элемента диалога.

Примеры с придирками: что такое «хорошо», и что такое «плохо»

Почти каждый компьютер с установленной Windows напичкан воплоще-
ниями как хорошего, так и плохого дизайна. Я думаю, что нижеприведенные
примеры хорошо показывают, насколько тонка грань между разумным, услуж-
ливым программным обеспечением и программами, которые попросту бьют
мимо цели.

В Windows 95 умеет автоматически загружать программы с CD-ROM.
Когда я впервые увидел это в действии, меня сразу осенила мысль: что
это - одна из тех идей, про которые говорят «почему же никто до сих
пор не догадался сделать это?». Если вы еще не знакомы с этим
трюком и не понимаете, о чем я говорю, то знайте, что работает это
так: вставьте CD-ROM в ваш компьютер, и Windows 95 автоматически
проверит корневой каталог этого диска на предмет наличия там чисто
текстового файла с именем AUTORUN.INF. Если такой файл об-
наружится, Windows 95 прочитает его и выполнит указанные в нем ко-
манды. Например, дистрибутивный CD-ROM с Windows95 содержит
файл AUTORUN.INF, в котором имеются следующие строчки:

[autorun]
0PEN=AUTORUN\AUTORUN.EXE
IC0N=AUTORUN\WIN95CD,ICO

Нетрудно догадаться, что они приводят к запуску программы AUTO-
RUN.EXE с использованием соответствующего значка. Этот трюк мо-
жет показаться совершенно банальным и исполненным на достаточно
примитивном техническом уровне, но именно это я и хочу
подчеркнуть: разумные, услужливые функциональные возможности
вовсе не обязаны быть достижениями передовой науки и требовать де-
сятков тысяч строк сложнейшего кода. Если бы окно Проводника не
дергалось, как эпилептик, когда вы вставляете диск, я бы поставил Mi-
crosoft пять с плюсом за то, как Windows 95 работает с CD-ROM.

В Одна из наиболее примечательных вещей, которые вы обнаруживаете
после перехода на Windows 95, - это две заставки, появляющиеся при
закрытии (кстати, их растровые картинки хранятся в файлах LOG-
OW.SYS и LOGOS.SYS в Windows-каталоге). А одна из самых
распространенных проблем при работе с Windows 95 - выключение
пользователями компьютера без предварительного закрытия Windows
(или до того, как Windows успеет закончить процедуру закрытия). Ко-
нечно, эти две заставки не решают магическим способом данную
проблему, но по крайней мере они элегантно и в то же время доста-
точно навязчиво (в хорошем смысле этого слова) поддерживают у
пользователей мысль о том, что перед щелканием по выключателю пи-
тания следует закрывать систему. Помня, сколько я видел ком-
пьютеров с файловыми системами, искалеченными преждевременными
выключениями питания, я рад видеть хотя бы минимальную помощь в
этой области.

В Попробуйте взять файл из America Online (или поместить файл туда),
и понаблюдайте за тем, какую интересную вещь делает программа
передачи файлов. Она показывает вам традиционную (читайте: обяза-
тельную) графическую «бензиновую шкалу», поясняя, сколько време-
ни осталось до конца перекачки. В этом ничего удивительного пока
нет. Но текст, который при этом используется, не представляет собой
типичные супер-точные минуты и секунды, к которым мы привыкли в
других коммуникационных пакетах. Вместо них программа округляет
время. Например, она сообщает «осталось примерно 5 минут», затем
надпись меняется на «осталось почти 5 минут», «осталось примерно 4
минуты» и т. д. Я предпочитаю именно такой подход, потому что,
искренне говоря, мне не нужно точно знать, сколько минут и секунд
будет длиться перекачка. Если она будет занимать больше одной-двух
минут, я, вероятно, отвлекусь и займусь чем-нибудь другим, отличным
от наблюдения за дурацкой «бензиновой шкалой». (Блиц-вопрос на за-
сыпку: а меняются ли реально числа на шкале, если никто на нее не
смотрит?) А если даже я и взгляну на нее по какой-то причине, то ин-
формации об округленном значении будет вполне достаточно. Путем
показа приблизительных значений America Online придает интерфейсу
чуть-чуть менее искусственный, менее навязчивый оттенок, если
угодно - создает ощущение человечности.

В Недавно я видел по крайней мере две инсталляционные программы,
которые копируют файлы с дискет на винчестер, а потом готовятся со-
вершить неизбежный перезапуск системы для того, чтобы изменения
конфигурации вошли в силу. В этот момент программы проявляли сла-
бый проблеск разума: они проверяли, не осталась ли до сих пор дис-
кета в дисководе, и если она там присутствовала, программы выдавали
сообщение, предлагавшее вынуть дискету. Это еще один пример того,
как «совсем не передовая наука» оказывается превосходным дополне-
нием к программе: она деликатно предупреждает типичную ошибку
пользователя, причем обходится это ей совсем не дорого.

В Панель задач (Taskbar) Windows 95 явилась новаторским решением
проблемы, с которой, согласно предположениям Microsoft, сталкива-
лись многие новые пользователи Windows - с потерей открытых
окон. (А я знаю наверняка, что некоторые пользователи-новички дей-
ствительно имеют проблемы в этой области.) Это была еще одна воз-
можность Windows 95, которая поначалу казалась мне диковинной, но
вскоре начала нравиться. Следует заметить, что одно из преимуществ
Windows 95 - ее способность намного лучше манипулировать систем-
ными ресурсами - могло бы еще более усугубить проблему «потери
окон»: если пользователям становится легче держать запущенными не-
сколько программ одновременно, то даже новички будут делать это,
что только увеличивает вероятность заблудиться в многочисленных
открытых окнах.

В то же время, на мой взгляд, Microsoft очень близко подошла к тому,
чтобы панель задач оказалась неправильным изобретением: если бы
они не предоставили опцию «Автоматически убирать с экрана» (Auto-
hide), которая прячет панель задач, как только другая программа по-
лучает фокус, то присутствие панели задач на экране было бы слиш-
ком навязчивым. (Я не знаю, на каком этапе проектирования Windows
95 возникла идея опции «Автоматически убирать с экрана», но эта
идея служит ярким примером того, как можно найти хорошее, но еще
недостаточно правильное решение, и как совсем небольшое изменение
может-таки привести точно к цели.) Если бы они дали нам еще и такую
опцию, при которой панель задач умела бы автоматически менять свои
размеры (чтобы не менялись размеры кнопочек на ней)... Что ж, мо-
жет быть, нас порадуют этим в следующей версии?

В Заметили ли вы, что происходит в Windows 95, если вы пытаетесь что-
нибудь напечатать, а ваш принтер (типа моего доисторического HP La-
serJet Series II) еще не прогрелся? Windows 95 показывает диалог, со-
общающий о том, что принтер не откликается. Изящная же деталь со-
стоит в том, что если вы просто игнорируете этот диалог, или если вас
нет в комнате (надеюсь, эти две ситуации неразличимы для ком-
пьютера, но никогда нельзя быть уверенным), Windows 95 продолжает
пытаться послать данные принтеру. Когда принтер, наконец, отклика-
ется, Windows закрывает диалог и печатает без дальнейших хлопот.

В Этот пример я обнаружил совершенно случайно: загрузите файл в Vis-
ual SlickEdit for Windows, затем загрузите этот же файл в другой
редактор, измените файл и сохраните его на диск. А теперь
переключите фокус на Visual SlickEdit - он сообщит вам, что файл
изменился, и предложит перезагрузить его. Не знаю, как вы, но я
имею склонность держать открытыми множество окон при работе в
Windows, и вышеописанная возможность Visual SlickEdit исключает
по крайней мере один из путей, который мог бы привести к путанице
и потере данных.

В этом примере есть один нюанс, связанный с тем, что пользователь
может забыть об изменениях, сделанных им в самом Visual SlickEdit.
Когда файл перезагрузится с диска, эти изменения будут потеряны
(если конечно они не были повторены в другом редакторе). У Visual
SlickEdit (как, впрочем, и у любой другой программы) нет хорошего
способа обойти эту проблему. Вероятно, единственным жизнеспособ-
ным решением было бы проверять, изменял ли пользователь файл ко-
гда-нибудь в течение текущего сеанса редактирования (а не только со
времени последней операции сохранения), и напоминать, что эта рабо-
та может быть потеряна при перезагрузке файла, если перед этим не
сохранить этот файл на диске под другим именем.

В Проверьте правописание в документе при помощи Word for Windows
6.0, и он запомнит исправления, сделанные вами, на весь сеанс
проверки (даже если вы не добавляете их во вспомогательный сло-
варь). Например, если ваш документ содержит слово <<WinWord», оно
будет помечено как ошибочное. Замените его на «Word for Windows»,
и это значение будет предлагаться вам в качестве правильного при всех
последующих появлениях «WinWord» на протяжении текущего сеанса
проверки. (К сожалению, Word 6.0 уже не помнит эти исправления во
время других сеансов проверки в текущем или других сеансах редак-
тирования.)

В Проводник использует тот же самый подход для разных данных, что
и в упомянутом выше примере с перекачкой файлов в America Online.
Но, в отличие от первого примера, этот подход в исполнении Провод-
ника только лишь расстраивает меня до невозможности. В данном
случае округляются размеры файлов. Я не могу представить, что за-
ставило Microsoft округлять размеры файлов до ближайшего килобай-
та, и при этом не давать пользователю даже опциональной возможно-
сти видеть точные размеры файлов. Каким бы ни было обоснование, я
уверен, что категорически не согласен с таким решением. Когда я был
бета-тестером Windows 95 и увидел это впервые, я подумал, что опция
просто погребена где-то в дебрях меню, и не смог ее найти. Потом я
думал, что она появится позже. Но она так и не появилась, и это одна
из причин, по которой я использую Проводник только для тестирова-
ния. (Я пытался еще исследовать эту проблему, но пока так и не нашел
решения. Если вы знаете о суперсекретном ключе в реестре
Windows 95 или о другом механизме, управляющем этим поведением
Проводника, пожалуйста, сообщите мне.)

В В версии 4.0 справочной системы Windows появилось несколько воз-
можностей и функций, которые я всячески приветствую. Но без одной
из них (по крайней мере, в ее нынешнем виде) я определенно мог бы
прожить - без этого диалога с. надписью «Загрузка перечня слов...»
(«Creating word list...») и авторучкой, заполняющей страницу за
страницей в маленькой книжке. Обратите внимание на то, что эта
функция нарушает сразу несколько правил, связанных с человеческим
фактором: иногда это (очень) долгая операция; не предоставлена воз-
можность ее отменить (если она есть, то я ее точно не смог найти; она
должна была бы быть реализована в виде кнопки Cancel прямо под
книжкой); и, наконец, не показывается ни индикатор прогресса, ни ка-
кая-либо другая информация, позволяющая оценить, сколь долго бу-
дет продолжаться этот процесс. Каким же раздражительным это пока-
залось, когда я создавал перечень слов для 39 7.50 389-байтного файла
API32.HLP, пришедшего с пакетом Visual С++ 2.2, и наблюдал бестол-
ковые каракули этой авторучки в течение 15 минут. (Когда я повторно
проводил этот тест для книги, я велел программе WinHelp сделать
полнофункциональный вариант перечня слов для API32.HLP на ком-
пьютере с Pentium/75 МГц и 24 Мб оперативной памяти. Процесс
продолжался ровно 12 минут, после чего появился диалог с таким со-
общением: «Please free memory by closing applications or by removing
unnecessary files.» («Пожалуйста, высвободите память путем закрытия
приложений или удаления ненужных файлов.»). В моей системе были
24 Мб ОЗУ и два винчестера, на которых было по 50 и 700 Мб сво-
бодного пространства, соответственно. И это двусмысленное сообще-
ние заставило меня гадать, какого же именно ресурса мне не хватило
(оперативной памяти или места на жестком диске), а также подумать,
что кто-то в Microsoft обязательно должен потратить время и сделать
этот диалог более ясным.)

В Моя самая «любимая» причуда Windows 95 (точнее, программы
«Проводник») это та глупость, с которой обрабатываются перетас-
кивания файлов левой кнопкой мыши. Перетащите левой кнопкой
непрограммный файл, скажем FRED.TXT, из одного каталога в
другой, и он будет перемещен, как и ожидалось. Но попробуйте
перетащить левой кнопкой исполняемый файл FRED.EXE, и Windows
95 возьмет на себя ответственность оставить исходный файл на месте,
- а в целевом каталоге создать ярлык (shortcut) для него.

Совершенно ясно, что Microsoft сделала это для того, чтобы
предохранить нас - бедных, глупых пользователей - от случайных перемещений
туда-сюда исполняемых файлов и от нарушения файло-
вых ассоциаций. Я нахожу это поразительным, потому что был намно-
го лучший способ добиться той же цели и не раздражать при этом всех
нас, старых работников Windows. Все, что им нужно было сделать, это
перенести текущую функцию перетаскивания правой кнопкой (всплы-
вающее меню с опциями Переместить, Копировать, Создать ярлык(и)
(Move Here, Copy Here и Create Shortcut(s) Here) на левую кнопку,
на ту, с которой мы привыкли работать. А перетаскивание правой
кнопкой вообще убрать из продукта. Затем им следовало бы дать нам
«ключ гуру» для левокнопочной операции, чтобы можно было заста-
вить ее работать так, как она работала в File Manager и в тысяче-и-
одном его замещениях. И тогда Крутые Пользователи конфигурирова-
ли бы все и вся под себя, новички и нерегулярные пользователи были
бы под защитой новой схемы работы, и никто из нас не замусоривал
бы свой диск нежелательными ссылками.

В Мы продолжаем слышать о том, как объектно-ориентированные техно-
логии и даже скромные динамические библиотеки в скором времени
перенесут нас в волшебные сферы, где мы сможем настраивать для
себя наши системы, использовать только те компоненты, которые нам
действительно нужны, и т. д. и т. п. Не знаю как вы, а я не вижу дос-
таточных оснований для этой утопии. Во многих случаях решение
напрашивается само собой: хотите сохранить некоторое количество
места на винчестере вашего ноутбука и в то же время никогда не ис-
пользуете в вашем текстовом процессоре компоненты для рисования и
составления уравнений? Просто удалите внешние программы, которые
реализуют эти возможности. Такие неуклюжие оптимизации почти ни-
когда не документируются, даже когда они безопасны и, главное, дают
желаемый результат. Именно поэтому я давал пользователям Stickies!
возможность отключать некоторые вспомогательные функции («под-
сказку дня», историческую справку про сегодняшнее число, «цитату
дня»), так чтобы базы данных, необходимые для этих функций, мож-
но было удалить. И разумеется, все это было ясно отражено в
справочном файле. (Некоторые более солидные прикладные програм-
мы при установке дают возможность при желании не инсталлировать
какие-то свои компоненты или даже деинсталлировать их потом. К со-
жалению, немногие сегодняшние программы способны так же гибко
помогать пользователю в распоряжении ресурсами компьютера.)

Пожалуйста, постарайтесь запомнить, что в программном обеспечении
волшебство кроется за кулисами, а совсем не в пользовательском
интерфейсе. В этом не так-то просто убедить, особенно в то время, когда
разработка для Windows буквально утопает в интерфейсных наворотах от
Microsoft и бесчисленных поставщиков VBX (а скоро и OCX). Не пой-
мите меня превратно, я приветствую все эти усовершенствования пользо-
вательского интерфейса с раскрытыми объятиями и наслаждаюсь видом
этого цветущего конкурентного рынка производителей, борющихся не на
жизнь, а на смерть за наши доллары. Не имеет значения, насколько
улучшают практическую ценность конкретной программы все эти древо-
видные списки, форматированные поля редактирования, мультипликаци-
онные заставки и украшения (хотя в некоторых случаях они и в самом
деле весьма помогают). Все-таки именно стержень функционирования
программы (или, если угодно, «реальное программирование») решает,
станет ли она тем, с чем мы хотим жить и работать изо дня в день. Один
из моих любимых авторов, Джойс Кэрол Оутс, однажды сказал: «Тех-
ника изложения приковывает внимание читателя и ведет его от одного
предложения к другому, но только содержание останется в его мыслях».
Я не смог бы выразиться лучше.
{mospagebreak}
Десять макроуровневых рекомендаций

Давайте перейдем к конкретике. В этом разделе я представлю первые десять
рекомендаций по написанию хороших Windows-программ. Мне могут доста-
точно резонно возразить, что все они на самом деле являются общими советами
из области разработки программного обеспечения, и что они не имеют ничего
специфичного для работы с Windows. В каком-то смысле это правда, но так же
верно и то, что зыбучие пески мира Windows делают многие из этих рекомен-
даций чрезвычайно важными именно для Windows-разработчиков, порой по не
таким уж очевидным причинам.

Все рекомендации этой главы касаются того, что я называю макроуровнем;
в главе 3 я представлю микроуровневые рекомендации. Это раздвоение не
означает, что я считаю макропроблемы более важными. Скорее эта разбивка
призвана отразить то, как эти рекомендации затрагивают вашу работу.
Макроуровневые вопросы больше касаются дизайна, проектирования и
ориентированы на перспективу, а микроуровневые обычно вступают в игру, ко-
гда вы сидите в окопах, то есть когда кончики ваших пальцев касаются клавиш,
и вы плывете вперед в час ночи с музыкой Томаса Долби в наушниках (и не
всегда думаете о чем-то ином, кроме как о достижении следующей успешной
компиляции).

0. Имейте широкий взгляд на мир и планируйте на будущее

Не [the poet] must write as the interpreter of nature, and
the legislator of mankind, and consider himself as presiding
over the thoughts and manners of future generations; as a
being superior to time and place.

Сэмюэль Джонсон

Я искренне желаю, чтобы все программисты подобно поэту, персонажу
строк Джонсона, как можно глубже осознавали свое место во времени и свои
обязательства перед будущим. На самом деле, если бы у меня была волшебная
палочка и возможность исполнить одно и только одно желание, я без колебаний
переделал бы программистские головы так, чтобы исполнилось именно это. Се-
годняшние программисты слишком интересуются тем, «как заставить это рабо-
тать», и намного меньше, чем следовало бы, заботятся о создании того, что не
будет кодом одноразового пользования, и что не придется выбрасывать с появ-
лением новой версии операционной системы, инструментария разработки или
самой программы. 

Я не упрекаю программистов в такой ситуации. Сегодня практически все
профессиональные программисты находятся под сильнейшим давлением
временных графиков, а это означает, что значительная часть кода в публичных
программах спроектирована и продумана не так хорошо, как следовало бы,
просто потому что не хватает времени. Тот, кто изобрел поговорку «никогда нет
времени сделать правильно, но всегда есть время переделать», наверняка был
программистом сопровождения (maintenance programmer), в очередной раз
исправлявшим чужие ошибки. Для Windows-программистов дела обстоят на-
много хуже. Мы тратим уйму времени лишь на попытки понять, как работают
последние версии наших сред разработки, компиляторов, компоновщиков,
ресурсных редакторов, динамических библиотек, сторонних библиотек и, нако-
нец, самого Windows API. Поэтому чудом является уже то, что мы хоть что-
нибудь
написали, так что оставьте в покое заботы о будущей адаптивности на-
шего кода.

И все же существует несколько приемов и правил, при помощи которых
мы можем делать наш код более «дружественным к будущему», причем боль-
шинство из них требует очень малых временных затрат:

В Всегда, всегда, всегда пытайтесь продумать все возможные ответвле-
ния ваших проектировочных решений и удостоверьтесь, что вы пони-
маете (настолько полно, насколько позволяют ваши способности) все
потери и выигрыши этих вариантов. Наилучший пример (который я
могу найти в Windows 95) такого подхода - решение реализовать
компоненты GDI и USER 16-разрядным кодом, а затем обрезать
(читайте: кромсать) значения графических координат и индексов в ок-
нах-списках. (См. главу 14, в которой этот вопрос освещен более-
подробно. )

Очевидно, что вы не можете заглядывать далеко в будущее, а даже
если бы могли, то программное обеспечение, спроектированное с
учетом этого знания, зачастую оказывалось бы очень плохо согласо-
ванным с современными ему аппаратным обеспечением и другими
программами. (См. врезки «Патологический случай: размер кластера
в файловой системе FAТ» на стр. 69 и «Конец (компьютерного) света
близок» на стр. 75, в которых вы найдете размышления на тему
прогнозирования.) Но существует целый ряд легко предсказуемых со-
бытии, которые вы должны (должны были) соответствующим образом
оценивать и учитывать в своих планах: переход Windows на 32-разряд-
ную платформу, появление новых стандартных возможностей С++
(которые так широко обсуждались в разнообразных журналах еще за-
долго до того, как была завершена работа над стандартом ANSI/ISO)
и т. п.

В На как можно более ранней стадии цикла разработки вашей публичной
Win32-программы решите, является ли она самостоятельной независи-
мой версией, результатом переноса из Win16 или просто незначитель-
но пересмотренной версией уже существующей 32-разрядной програм-
мы, какие из трех 32-разрядных платформ (Win32s, Windows 95 или
Windows NT) вы будете официально поддерживать. Эту проблему ни
в коем случае не следует решать с налету простым огульным заявлени-
ем типа «давайте будем работать везде», потому что существует боль-
шое число мелких (а также несколько крупных) различий, которые
легко могут вынудить вас остерегаться одной или двух из этих плат-
форм. Например, я настоятельно рекомендую не позволять вашей
программе запускаться под Win32s. (См. главу 14, в которой вы най-
дете больше информации о проблемах с Win32s и о том, как с ними
справляться.)

В Побеспокойтесь о вопросах локализации сразу, а не перед самым вы-
пуском программы. (Под «локализацией» я понимаю создание версий
вашей программы, использующих при общении с пользователем раз-
личные языки.) Конечно, не все программы нуждаются в локализа-
ции, и даже не все публичные программы. Но некоторые нуждаются.
Поэтому вы окажетесь в намного лучшем положении, если направите
серьезные усилия на то, чтобы как можно раньше обнаружить, что ваш
проект потребует локализации. Это одна из тех проблем, которые мо-
гут превращаться в бесконечные споры между управляющими и тех-
нарями. Поэтому технари часто пугаются ее и избегают лишний раз
обращать на нее внимание управляющих. Управляющие же решают
эту проблему просто, без особых раздумий - планировать на будущее,
заложить данную возможность сейчас, несмотря на то, что ближайшие
планы не предусматривают ничего, кроме одного языка, и т. п. Тех-
нари рассматривают такой вариант как потенциальную растрату уси-
лий впустую, поэтому пытаются игнорировать вопрос. Иногда это
правильно, а иногда нет. Все зависит от нескольких факторов - глав-
ным образом, от вероятности того, что нужно будет поддерживать
другие языки, и от наличия достаточных ресурсов для выполнения не-
обходимой работы.

В Я часто вижу публичные программы, написанные вообще без каких-
либо заготовок для очевидных будущих усовершенствований. Это оз-
начает, что, когда клиент, администрация или заказчики просят сде-
лать неминуемое изменение (либо когда вы сами вдруг поймете, что
это является хорошей идеей), вы обнаруживаете, что реализация этого
изменения потребует огромной работы, включая порой выбрасывание
и изменение значительной части старого кода. Л это ведет к большему
количеству ошибок и значительному увеличению накладных расходов
на тестирование (ведь вы уже однажды тестировали старый код, чтобы
убедиться в его правильности, а теперь вам приходится делать это сно-
ва после его изменения пли замены).

Иногда вы можете серьезно сэкономить ваши усилия по тестированию
и кодированию путем применения одного из моих излюбленных
трюков, который я бесцеремонно называю «подвешивание функции на
булевскую переменную». А теперь перевод: это означает, что вначале
вы идентифицируете какую-либо новую функциональную возмож-
ность, которую вы добавите (или можете захотеть добавить) в будущей
версии вашей программы. Быть может, вы хотите предоставить поль-
зователю выбор между «длинными» и «короткими» меню, как делают
некоторые Windows-программы; или вы захотите сделать опциональ-
ным создание страховочных файлов для сохраненных документов. Как
только вы определили для себя вероятное будущее усовершенствова-
ние такого типа, сделайте следующее:

1.     Заведите глобальную переменную, которая будет отражать
параметры этой запланированной функции. В моей практике, эта
переменная обычно оказывалась булевской, - вот откуда взялось
мое название данного приема. Присвойте этой переменной то
значение, которое потом (когда следующая версия позволит его
менять) будет ее значением по умолчанию.

2.    Убедитесь, что вы понятно задокументировали при помощи
комментариев то, что сделали! Это один из тех редких случаев,
когда вы пишете код, который явно неявляется само-
документированным, поэтому вы должны потратить минутку на
пояснение ситуации. Вам не нужно писать «Войну и мир», просто
вставьте строчку, гласящую что-то типа «Флажок UseShortMenus
контролирует, как программа показывает свои меню. Это значение
не меняется в текущей версии, но в будущем может стать новой
настраиваемой опцией».

3.      «Пишите ваш код так, чтобы он вел себя по-разному в
соответствии со значением этой переменной, пусть даже оно и не
меняется в текущей версии программы.

4.      Удостоверьтесь, что ваша программа правильно сохраняет и
постанавливает значение этой переменной из того постоянного
хранилища (например, INI-файла или файла специального
формата), которое ваша программа использует для хранения
пользовательских настроек между запусками программы.

5.        Тестируйте вашу программу, поочередно задавая этой переменной
все возможные допустимые значения, которые будут реально
присваиваться ей потом. Как только вы убедитесь, что программа
работает корректно, можно считать, что заготовка для будущей
функции сделана. (Очевидно, при очень большом числе
допустимых значений этой переменной не обязательно тестировать
каждое из них. Вместо этого протестируйте несколько «краевых»
значений, которые наиболее вероятно могли бы привести к
возникновению проблем.)

6.        Когда подойдет время новой версии, и вы решите сделать данную
функцию настраиваемой, вся тяжелая работа окажется уже
сделанной и проверенной. Вам останется лишь добавить
подходящий элемент управления в диалог с настройками и связать
его с глобальной переменной. Старый код остается без изменения,
значит ваши накладные расходы на тестирование относительно
малы и состоят из проверки таких вещей, как правильная работа
диалога и корректная реакция программы на изменен не значения
переменной во время сеанса. Вот вы и получили новую
функциональную возможность ценой небольших усилий и очень
незначительного риска ввести новые ошибки.

Я использовал этот подход в программах, которые написал для за-
казчиков, а также несколько раз применял его в ранних версиях Stick-
les!. В одних случаях я сам хотел, чтобы программа делала что-то раз-
личными способами, но просто не был уверен, следует ли давать поль-
зователю возможность настраивать это поведение. В других я
предвидел будущие запросы клиента и обеспечивал для себя отсутст-
вие проблем в тот момент, когда клиент позвонит мне и потребует сде-
лать изменение как можно скорее. Во всех этих ситуациях я «подве-
шивал функцию на булевскую переменную», что давало мне (и моему
клиенту) замечательную гибкость в решении вопроса о том, будет ли
эта переменная поднята на поверхность в виде настраиваемой опции.
В большинстве случаев я в конце концов делал такое изменение, и при
этом необходимая работа была минимальной - в среднем такая «до-
полнительная разработка» занимала буквально не более 10 минут, по-
скольку касалась она фактически только диалога настроек.

Этот прием может также спасти вас в случае опасности, когда вы вдруг
обнаружите, что свежеиспеченная новая функция как-то неуловимо
взаимодействует с системой или порождает какой-нибудь таинствен-
ный сбой, который вы не заметили при тестировании. Вы можете
просто вставить соответствующий переключатель в исходный текст и
перекомпилировать программу, заблокировав новую опцию. Или мож-
но даже позволить пользователю заблокировать ее путем непосредст-
венной правки файла, хранящего эту настройку. Никто из нас не
любит делать исправления наспех, но в кризисной ситуации это может
сэкономить вам денек-другой.

В самом ли деле я призываю вас догадываться о том, какие
функции вам придется реализовывать в будущем, и уже сейчас
тратить на них дефицитные рабочие ресурсы? Не сошел ли я с
ума? Да, я призываю вас делать это, и нет, я не сошел с ума. Но
я вовсе не предлагаю вам сидеть над листингом вашей
программы и искать все умопостижимые будущие изменения,
которые ваш босс или вы сами могли бы захотеть сделать, и тут
же разукрашивать вашу программу поддержкой каждого из них
до того, как оно понадобится. Это было бы абсурдным, безответ-
ственным растранжириванием вашего времени. По я в самом
деле утверждаю, что случаются такие моменты, когда, работая
над программой, разговаривая со своим клиентом, боссом или
пользователем, вы вдруг безошибочно, буквально собственным
нутром чувствуете, какой следующий запрос от них поступит.
Такое случается даже тогда, когда вы не ищете этих деталей спе-
циально. Происходит это нечасто, но, по моим наблюдениям, ко-
гда такое чувство появляется, оно чаще всего оказывается
правильным.

В Никогда не забывайте, что очень легко увлечься повышением гибкости
ваших программ, а в результате можно лишь обременить пользовате-
лей. Например, у функции RegOueryValueEx() из Win32 API третий
параметр определен так:

lpdwReserved  // Зарезервирован; должен быть равен NULL

Зарезервированный, обязанный быть равным NULL указатель на
DWORD? Это - абсурд, а людям, ответственным за такое, следовало
бы надавать по рукам. Пожалуйста, не смешивайте этот пример с мно-
гочисленными местами в Windows API, где при описании сообщений
говорится о ненужности параметра wParam (или lParam) и необходи-
мости задавать его равным нулю. В этих случаях у Microsoft нет воз-
можности убрать эти необязательные параметры, так как они вмурова-
ны в базовую архитектуру Windows. И кроме того, как раз в этих
случаях совершенно разумно требовать задавать их равными нулю: тем
самым Microsoft получает максимальную свободу для будущего усиле-
ния API без риска нарушить работу существующих приложений. В
этом примере мы видим, как очень незначительное требование ин-
терфейса дает выигрыш обеим сторонам.

Патологический случай: размер кластера
в файловой системе
FAT

Все файловые системы (или по крайней мере все те, о которых
я знаю) предоставляют место на диске в виде «кусочков»
(chunks), а это означает, что количество свободного пространства
на диске, расходуемого на добавление нового файла, всегда кратно
размеру такого кусочка. Создайте однобайтовый файл, и система
предоставит намного больше одного байта (и даже еще больше,
если учесть дополнительный административный расход на под-
держку еще одного элемента в каталоге).

В файловой системе, использующей FAT (таблицу размеще-
ния файлов), «размер кусочка» также называют «размером кла-
стера»

Размер раздела

Тип FAT

Количество
секторов
в кластере


Размер
кластера


0 Мб - 5 Мб

12-битовая

8

4

16 Мб - 127 Мб

16-битовая

4

2

128 Мб - 255 Мб

16-битовая

8

4

256 Мб - 511 Мб

16-битовая

16

8

512 Мб - 1023 Мб

16-битовая

32

16

1024 Мб - 2048 Мб

16-битовая

64

32

Иными словами, в файловой системе FAT на каждом файле
теряется в среднем 16 Кб (половина от 32 Кб) свободного
пространства, если общий размер раздела превышает 1 Гб, что со-
ответствует самому обычному на сегодня размеру жесткого диска.
Очень маленькие файлы, такие как заголовочные файлы (*.Н,
*.НРР) или объектные модули (*.OBJ), с которыми постоянно
имеют дело разработчики, приводят к потере почти всех 32 Кб.

Давным-давно, на заре каменного века, когда эти ограничения
были только что изваяны из... хмм... камня, сама мысль о наличии
в персональном компьютере жесткого диска размером свыше одно-
го гигабайта должна была казаться устремленной в такое далекое
и туманное будущее, что о ней не стоило и задумываться. Ну а те-
перь уже поздновато начинать об этом думать.

Первая серьезная проблема, с которой я столкнулся и которая
была следствием этой ситуации с размером кластера, затронула
моего бухгалтера, Чарли. Я только что помог ему улучшить парк
его рабочих персональных компьютеров до более мощных, бы-
стрых систем с жесткими дисками большей емкости. Мы детально
проанализировали его потребности и совместно решили, какие
скорости и емкости нужно обеспечить сразу, чтобы потом в
течение года не нужно было делать дополнительных апгрейдов.
Через несколько месяцев после того, как мы инсталлировали сто
сверкающее новенькое аппаратное обеспечение, он позвонил мне и
пожаловался, что ему перестало хватать свободного места на дис-
ках. Я был уверен, что причина должна быть очень простой
(например, заполнение временных каталогов всяким «мусором»,
который можно стереть и высвободить сотню-другую мегабайт).

Когда я исследовал систему, я не обнаружил никакого подоб-
ного тайника с ненужными файлами. Зато я обнаружил каталог, в
котором находилось 3434 файла, - все они содержали какие-то
данные по клиентам Чарли и были созданы программным пакетом,
который он использовал. Средний размер этих файлов был равен
почти ровно 500 байт. У Чарли был 540-мегабайтный жесткий
диск, следовательно, на каждом из этих маленьких файлов
терялось по 16 Кб - 500 байт = 15 884 байт. В сумме же потери
составляли 54 545 656 байт, или 96.9% от всего пространства, за-
нимаемого ими. Это был бы неплохой бензин, как заметил кто-то
из свидетелей данной потрясающей картины.

Пока я писал эту книгу, я добавил новый компьютер к своим
владениям. Он прибыл с 850-мегабайтным винчестером и с
обычным изобилием предварительно установленного программно-
го обеспечения. В частности, на диске был каталог C:\MSIN-
PUT\MOUSE\EFFECT, содержавший 457 файлов с курсорами,
каждый размером ровно 326 байт. Это значило, что я использовал
7 487 488 байт (457 х 16 Кб) для хранения 148 982 байт (457 х
326 байт) реальных данных, имея взамен великолепный 98-
процентный бензин. (Я дам вам 457 попыток угадать, какие имен-
но файлы я первыми упаковал в ZIP-архив на этой системе.)

Суть здесь не в том, чтобы побранить первоначальный дизайн
файловой системы FAT за отсутствие в нем перспективного пла-
нирования (хотя эта ситуация служит интересной поучительной
повестью о планировании). Но обратите внимание, что Windows
95 не дает нам радикального решения этой проблемы. Я ожидал,
что она все-таки обеспечит (хотя бы в виде дополнительно оп-
лачиваемой опции) какой-нибудь инсталлируемый компонент, ко-
торый позволил бы использовать файловую систему Windows NT
(NTFS), гораздо лучше управляющую свободным пространством
на диске. К сожалению, такой опции нет, хотя я подозреваю, что
тот или иной предприимчивый производитель в конце концов возь-
мется сделать это. В любом случае, Windows 95 выходит на рынок
во времена удивительно дешевых жестких дисков (о чем Micro-
soft, конечно же, была осведомлена), и свободное место на них все
еще является системным ресурсом, с которым Windows 95 не мо-
жет управляться сколько-нибудь разумным способом.

Кстати, менее чем за две недели до официального выхода
Windows 95, я приобрел жесткий диск размером 1.2 Гб в моем ме-
стном компьютерном магазине за какие-то 298 долларов (что
было - невероятно! - дешевле 25 центов за мегабайт, и что без
сомнения будет казаться немыслимо дорогим вскоре после выхода
этой книги). Вышеупомянутые мной файлы с курсорами заняли
бы на этом диске ужасающе много - 14 974 976 байт (или почти
в сто раз больше своего реального содержания).

Если пример с маленькими курсорными файлами кажется вам
слишком искусственным, позвольте мне рассказать, что
случилось, когда я установил на этот новый диск реальный
продукт. Я инсталлировал компилятор С++, но не весь пакет це-
ликом, а только инструменты, заголовочные файлы и библиотеки.
Я не инсталлировал примеры (которые, как известно, очень вели-
ки по объему и включают сотни очень маленьких файлов) и исход-
ные тексты библиотек. Результатом были 1767 файлов с сум-
марным объемом данных 89 759 861 байт, занимающих 146 538 496
байт дискового пространства, из которых терялись 56 778 63.5
байт, или около 39 процентов. Это на самом деле весьма мило-
сердный пример, потому что компиляторы С++, инсталлируемые
таким образом, включают некоторые файлы очень большого раз-
мера, которые уменьшают средний эффект потерь свободного мес-
та на диске из-за размеров кластера. Тем не менее, если экстрапо-
лировать эту оптимистичную процентовку на весь диск, я потерял
бы 468 Мб дискового пространства (или 116 долларов).

Как же нам обойти эту проблему? Одно из решений - ис-
пользовать компрессию диска, которая размещает дисковое
пространство по своей собственной схеме и тем самым позволяет
избежать катастрофы с кластерами. Но это решение совершенно
не годится для людей типа меня, которые вынуждены делить дис-
ковое пространство одной машины между Windows 3.1, Windows
95, Windows NT и OS/2. He существует общего способа компрес-
сии диска для 16- и 32-разрядных Windows; не говоря уж о
чужеродной диковине под названием OS/2.

Историческая справка: у меня сохранилась копня журнала PC
(не путать с PC Magazine) - выпуск 1 Номер 1, Февраль-Март
1982 года, - на странице 22 которого есть реклама от компании
под названием VR Data. Реклама предлагает несколько жестких
дисков и в том числе экземпляр емкостью 6.3 Мб, который постав-
ляется покупателю в корпусе размером с системный блок
современного настольного компьютера. Цена указана
пустяковая - какие-то символические 2895 долларов (459.52 за
мегабайт, кхе... кхе...), не считая еще 249 долларов за адаптер и
программу-драйвер. Представьте, что сказали бы бравые парни из
VR Data, если бы они в 1982 году увидели мой винч емкостью 1.2
Гб и габаритами 3.5 дюйма на 1 дюйм.

1. Стремитесь к балансу во всех решениях

Then at the balance let's be mute.
We never can adjust it;
What's done we partly may compute,
But know not what's resisted.

Роберт Бернс

В этой книге я неоднократно буду возвращаться к одной и той же мысли,
которую все мы забываем время от времени: в программировании почти не бы-
вает абсолютов. Практически каждое решение, которое вы принимаете - будь
то выбор языка программирования и среды разработки для нового проекта или
типа локальной переменной в функции - имеет свои компромиссы. Иногда эти
компромиссы могут быть очень незначительными, и почти всегда у них длин-
ные руки - в том смысле, что они могут начать преследовать вас не сразу, а
лишь спустя удивительно долгий промежуток времени (см. врезку «Конец
(компьютерного) света близок» на стр. 75).

Один способ избежать проблем - это просто, помнить, что компромиссы
встречаются повсюду, и что почти все ваши решения имеют две стороны. Ни-
когда не следует ставить себе задачу-максимум - заставить что-то работать на-
столько хорошо, насколько это возможно; фанатичная упертость в этом приво-
дит к бедствию так же верно, как пренебрежение качеством. Вероятно, вы
слышали старый афоризм «лучшее - враг хорошего». Он чрезвычайно актуа-
лен в нашей области, потому что программисты очень часто стремятся к
совершенству, не останавливаются на достаточном, и дело заканчивается
печально - выпуском никому не нужного кода. Но этот афоризм - палка о
двух концах, и программисты так же часто будут использовать его в качестве
оправдания и заявлять, что, раз уж совершенство недостижимо, то нет смысла
делать те или иные предложенные изменения для улучшения качества
программы.

Вы должны быть экономистом. (Бьюсь об заклад, вы никогда не
собирались им стать.) Чтобы не попасть в ловушку экстремизма, вы должны
всегда думать о соотношении «затраты/выгода». Например, программисты
часто говорят, что нет смысла добавлять в программу минимальные защитные
функции, поскольку они могут быть легко взломаны тем, кто понимает, как та-
кая защита работает. Но эти же самые люди наверняка закрывают па замок
двери своих автомобилей, паркуясь около магазина, даже если дело происхо-
дит днем, и отходят они лишь на пару минут. И делают они это (как и я), по-
тому что видят пользу даже в таком примитивном способе защиты: она
предохраняет по крайней мере от части воров, хотя для опытного угонщика
взлом такой защиты - дело нескольких секунд. Иными словами, запирание
автомобиля в таких случаях осмысленно, так как это обеспечивает хотя бы
какую-то безопасность при практически нулевых затратах. Соотношение здесь
достаточно хорошее, оттого мы это и делаем.

Когда я говорю об оценке «затрат» на то или иное проектировочное реше-
ние, я имею в виду не только время, необходимое для его выполнения, или
влияние его на размер и скорость работы программы. Я использую этот термин
в максимально широком смысле и включаю в рассмотрение такие вещи, как
перспективное сопровождение кода, практичность результирующей
программы, общая «дружественность программы по отношению к системе»
(например, насколько просто инсталлировать или деинсталлировать ее, требует
ли это изменений в пользовательском файле SYSTEM.INI и тому подобное).
Конечно, вопрос о сбалансированности затрат пользователя и ваших собствен-
ных затрат тоже немаловажная проблема, обсуждение которой может
быстро превратиться в самостоятельную бесконечную цепочку философских
споров. Выступая как пользователь многих коммерческих программ, я твердо
уверен, что программисты, делавшие эти программы, уделяют своим пользова-
телям гораздо меньше внимания, чем следовало бы.

В своей классической работе The Mythical Man-Month, Фредерик Брукс
говорит: «Поскольку целью является легкость использования, окончательной
оценкой системного дизайна является отношение функциональности к концеп-
туальной сложности. Ни функциональность, ни простота сами по себе не
обеспечивают хорошего дизайна». Он не называет это «соотношением затраты/
выгода», но, уверен, имеет в виду ту же самую концепцию. И, я думаю, он аб-
солютно прав. Однако также я полагаю, что программистам следует иметь на-
много более широкий и содержательный взгляд на обе части уравнения, и в
особенности - на «затратную» сторону.

В частности, когда вы оцениваете стоимость рассматриваемого замысла или
проекта, не забудьте включить в эту оценку следующее:

В Полная стоимость разработки, включая документацию. В нашем
деле никто не любит задумываться о документации (по меньшей мере,
таково мое безошибочное впечатление от того, насколько отвратитель-
ной и до ужаса неточной является большая часть виденной мною доку-
ментации). Но она является важной частью целого, даже если вы соз-
даете «всего лишь» библиотеку функций для домашнего пользования
парой-тройкой других людей, не говоря уж о публичной программе,
которая будет продана миллионам пользователей.

В Стоимость сопровождения и поддержки. Опять же, мыслите широко
и помните, что исходный код, который вы пишете для публичной
программы, может иметь очень долгое время жизни. Если вы создаете
вашу программу при помощи какого-либо необычного языка или экзо-
тической технологии, в то время как другие люди (включая вас самого
по прошествии нескольких месяцев) должны эту программу сопрово-
ждать, вы, вероятно, нарветесь на серьезнейшие перспективные
затратит, которые сильно перевесят весь выигрыш.

Будьте осторожны с применением возможностей операционной систе-
мы. Не перегружайте вашу программу такими сложными деталями,
как поддержка OLE, если они на самом деле не нужны. (В наши дни
существует тенденция, которую я называю «инфляцией галочки», и
которой подвержено множество коммерческих программ. С целью
зарабатывания как можно большего чиста «галочек» в итоговых об-
зорных таблицах журнальных статей, производители программного
обеспечения загромождают свои творения такими функциональными
возможностями, которые пользователям в действительности не нужны.
В результате получаются огромные программы, наполненные почти
никем не используемыми функциями, да еще потребляющие массу
ресурсов при разработке и поддержке. Если вам кажется, что я шучу
или преувеличиваю, поговорите с пользователями многочисленных
текстовых процессоров И узнайте, как много мудреных опций они от-
ключают
или игнорируют.)

В Стоимость потенциальных усовершенствований. Если вы собирае-
тесь сэкономить время разработки путем использования какого-либо
третьестороннего продукта, такого как библиотека функций или клас-
сов, задумайтесь над тем, каковы шансы на получение хорошей под-
держки от этой третьей стороны, и с какой вероятностью эта третья
сторона все еще будет заниматься этим продуктом через пару-тройку
лет. Что, если вы решите в следующей версии перенести вашу програм-
му на платформу OS/2, а у этого третьестороннего продукта нет соот-
ветствующей версии? Третьесторонний пакет может значительно по-
мочь вам с выпуском в срок текущей версии, но создать вам огромные
дорогостоящие проблемы в будущем. Конечно, во многих проектах та-
кая проблема не стоит, но вам следует как можно раньше выяснить,
является ли ваша конкретная задача одним из них.

В Стоимость для пользователей. Она включает затраты на изучение и
освоение новых свойств программы, скорость ее работы, объем потреб-
ляемых ею ресурсов и, наконец, реальную потребность в ней.

В сущности, проблема сбалансированности сводится к вопросу: «Выгодна
ли сделка X для всех участников?» К сожалению, многие публичные програм-
мы изначально проектируются абсолютно без учета этого вопроса. Например,
менеджеры часто проталкивают свои любимые проекты или технологии скорее
в угоду своим «имперским интересам» (увеличению размера своего штата и
усилению своего влияния в компании), чем в стремлении выпустить наилучший
конечный продукт. У нас, программистов, в этом смысле тоже рыльце в пушку,
поскольку мы часто позволяем себе использовать инструменты и приемы, ко-
торые применять вовсе не следовало бы, и в результате наносим серьезный
ущерб конечному продукту. И на этом фронте я могу дать вам лишь один совет:
защищайтесь от этих вредных тенденций внутри себя самого, внутри вашей ко-
манды и будьте готовы к тому, что борьба не будет легкой.

Конец (компьютерного) света близок

Я уверен, все вы уже слышали, что огромное число деловых
прикладных программ в мире сойдут с ума в 2000 году. Проблема,
как известно, в том, что эти программы хранят информацию о ка-
лендарном годе в виде только лишь двух цифр. И когда они
начнут делать бухгалтерские подсчеты или что-нибудь еще для. ян-
варя 2000 года, нулевое значение календарного года вызовет жут-
кий хаос.

Насколько я могу судить, это как раз тот единственный
случай, когда многочисленные пророки Страшного Суда могут
оказаться правы. Общеизвестно, что сегодня многие компании ис-
пользуют довольно старое программное обеспечение, причем во
многих случаях они даже не имеют его исходных кодов, а следо-
вательно они не смогли бы устранить проблему, даже если были
бы осведомлены о ней. Кроме того, во многих случаях базы дан-
ных и другие программы настолько пропитаны этими двух-
цифровыми значениями года, что починка окажется страшно
дорогой и наверняка приведет к появлению новых ошибок. Так
что в течение первых месяцев нового тысячелетня вы смело можете
ожидать целую волн}' репортажей CNN о том, как кто-то оплачива-
ет (а кто-то получает) чеки на миллионы долларов. Помимо пред-
стоящего развлечения для CNN, я предвижу как минимум два-три
серьезнейших скандала, порожденных этой проблемой (например,
чересчур агрессивно настроенное программное обеспечение по ве-
дению счетов в каком-либо банке начнет самостоятельно
закрывать счета, или еще хуже, вплоть до появления человеческих
жертв). Я искренне надеюсь ошибиться в своих предсказаниях, но
пока оптимизма мало.

Как профессионал, я нахожу эту ситуацию весьма позорной,
она очень ярко демонстрирует насколько близорукими могут быть
программисты. Справедливости ради следует отметить, что у мно-
гих авторов таких программ просто не было выбора - например,
они могли писать новую программу, которая должна работать с су-
ществующей уже базой данных заказчика, и им просто пришлось
работать с имеющимся форматом независимо от того, насколько
очевидным был для них тот факт, что они лишь усугубляют этим
грядущую катастрофу.

Эта история является настолько поучительной для программи-
стов, настолько ярко доказывает важность предусмотрительности,
что я не хочу больше смаковать этот пример. Только, пожалуйста,
будьте осторожны сами.

2. Сделайте принцип KISS вашей религией

Give mе a look, give mе a face,
That makes simplicity a grace;
Robes loosely flowing, hair as free:
Such sweet neglect more taketh me,
Than all the adulteries of art;
They strike mine eyes, but not my heart.

Бен Джонсон

Our life is frittered away by detail... Simplify, simplify.

Генри Дэвид Topo

Ax этот принцип KISS («Кеер It, Simple, Stupid»)! Если и есть в мире что-то такое,
что почти все программисты постоянно повторяют наизусть, как мантры, но при этом
откровенно игнорируют, то это - принцип KISS. Иногда я даже думаю, что
следовало бы к каждому программистскому компьютеру прицепить бирку с ло-
зунгом: «KISS это не просто хорошая идея, это закон». (Правда,
практический эффект от этого вероятно был бы нулевым. Ведь программисты,
эти законченные комики, обязательно стали бы использовать эти бирки в
качестве скребков для льда, дверных стопоров или подкладок для выравнива-
ния стола).

Позвольте пояснить: в данном контексте под словом «проще» следует по-
нимать то, насколько понятным и годным для сопровождения является код. По
какой-то странной причине (которую я хотел бы в конце-концов найти), многие
программисты неправильно интерпретируют этот принцип как «используй как
можно меньше строк при написании кода». А потом растрачивают драгоценные
программистские силы на дурацкие конкурсы (типа «Эй, я могу реализовать
эту функцию 47-ю ударами по клавишам! Слабо побить этот рекорд?»). Я не
люблю различных обобщений по половому признак)', но мой опыт показывает,
что данное явление почти без исключений свойственно мужчинам. Еще более
поразителен тот факт, что большинство мужчин отрицает свое пристрастие к
этому, а большинство женщин немало удивляется, зачем же это отрицать.

Нигде эта проблема не стоит так остро, как в С-программах. Некоторые
фрагменты рабочего С-кода, по которым я пробегал (или они пробегали по
мне?), выглядят так, как будто бы их писали под влиянием недавно прочитан-
ной книжки по развлекательной химии. Я не спорю со всей идиоматикой С:
характерная минималистская грация хорошо написанного С-кода может не
только быть эстетически приятной, но и способствовать его пониманию. Однако
я категорически протестую против таких бессмысленных конструкций, как ус-
ловный оператор. Загляните в ассемблер, сгенерированный следующим
оператором:

х = а > 100 ? y; z;

и скорее всего вы обнаружите, что точно такой же ассемблер был бы сге-
нерирован, если бы вы написали

if(а > 100)
 х = у;
else
 х = z;

На самом деле, некоторое время назад я проводил эксперименты с несколь-
кими компиляторами и обнаружил, что условный оператор действительно
порождает тот же самый код, что и более земная конструкция if-then-else, а в
некоторых случаях - даже немного больший по размеру код. Я честно пытался
найти пример, в котором условный оператор оказался бы эффективнее, но най-
ти его так и не смог. Другими словами, мы имеем явные издержки при исполь-
зовании этого оператора - более низкую читабельность и большую среднюю
длину строки, - и не имеем ровным счетом никакого выигрыша. (Я даже ви-
дел код, который был вручную раздерган - вставками условного оператора где
только можно - в интересах производительности, результатом чего явилось
еще меньшее быстродействие программы. Это замечательный пример так назы-
ваемой «ручной слоптимизации» [программерский жаргонизм «sloptimization»
(«slop» помои, слякоть), означающего процесс, обратный оптимизации.] ) Кстати, многие программисты попадают в
плен иллюзии того, что меньшее количество знаков или строк кода в итоге
превращается в меньшее количество процессорных команд. Это опасное заблу-
ждение, наоборот, очень часто приводит к проблемам с производительностью.

Даже когда вы не используете «сишные навороты» и пишете по-настоя-
щему хороший код, вы все равно можете непроизвольно нарушить принцип
KISS. Например, в такой ситуации, когда задача лучше всего решается каким-
нибудь действительно сложным, замысловатым алгоритмом. Что ж, выход
здесь безумно прост - используйте этот алгоритм, по обязательно добавьте не-
сколько комментариев о том, что данный код делает. Я знаю, как банален и
очевиден этот совет, но я все-равно упоминаю его, потому что я до сих пор
продолжаю встречать код, где автор либо не думал над этим вообще, либо был
стишком занят, «заставляя этот код работать» (ох, опять эта фраза!).

Конечно же, стремление к упрощению может конкурировать с другими це-
лями. Например, вспомните мой трюк с «подвешиванием функции на булев-
скую переменную», описанный ранее. С точки зрения ближайшей задачи, этот
прием явно нарушает принцип KISS, поскольку реализация текущей версии
оказывается более сложной, а ее тестирование - более долгим, чем это необ-
ходимо. Но с точки зрения перспективы его использование весьма осмысленно,
поскольку получаемый в конечном итоге выигрыш значительно перевешивает
небольшие дополнительные затраты.

3. You(Now) != You(Later)

Time present and time past
Are both perhaps present in time future,
And time future contained in time past.

Т. С. Элиот

Заголовок данной рекомендации - это в действительности только квинтес-
сенция (записанная в стиле языка С) наблюдения о том, что всем профессио-
нальным программистам приходится работать над множеством проектов, и что
во время этой работы мы меняемся. Наша программистская философия и стиль
меняются, наши взгляды на многие вещи меняются, и мы медленно превраща-
емся буквально в других людей. Этот очевидный факт имеет некоторые
нетривиальные последствия для программистов. Наиболее важное из них
заключается в том, что даже когда вы являетесь единственным человеком,
который когда-либо увидит исходный код данной программы (а зачастую даже
это суждение ошибочно, если говорить о перспективе), через достаточный
промежуток времени данный проект станет коллективным. Обычно програми-
стский коллектив (команда) составляет от нескольких до нескольких сотен
людей, параллельно работающих над проектом, то есть команда прежде всего
разбросана в пространстве. Команда также может быть разбросана и во
времени - например, вы сегодня (you(now)) плюс кто-то другой (или
you(later)), работающий над тем же проектом, спустя месяцы или годы.
Обратите внимание, что распределение во времени может быть более серьезной
проблемой, чем распределение в пространстве - просто потому, что не может
быть преодолено таким простым способом: посетить офис коллеги, швырнуть
на столь листинг и спросить, что, черт побери, этот код делает. (Кстати, это
был бы неплохой сюжет для фантастического ужастика - путешествия
сопровождающего программиста во времени, но я бы не хотел особо развивать
эту тему, если вы не возражаете.)

И даже на мгновение не смейте подумать, что речь тут идет о годах. Даже
пара месяцев работы над другим проектом могут сделать вас аутсайдером ва-
шего собственного кода. Программисты особенно подвержены этой проблеме,
поскольку мы склонны хорошо выполнять лишь следующий простой алгоритм:
загрузить весь замысел в кратковременную память, замариноваться в нем, реа-
лизовать его, а затем выгрузить его и приступить к следующей задаче. В
течение девяти лет моей работы в IBM., я принимал участие в разнообразных и
многочисленных проектах (в качестве программиста, дизайнера, тестера) и не-
сколько раз попадал в такую ситуацию. Когда кому-нибудь из моих коллег
приходилось искать ошибку или просто делать изменения в коде, написанном
мной за несколько месяцев или лет до этого, и когда этот человек приходил ко
мне с вопросами, я точно помню, как я порой едва мог понять свой собственный
код и мучительно вспоминал, зачем я делал ту или иную вещь таким способом.

В действительности, мы сейчас говорим об одном из самых опасных мифов
программирования: «Я наверняка пойму, что делает этот код, когда увижу его
в следующий раз». Если «следующий раз» случится лишь несколько недель
спустя, то да, программист вероятно поймет свой код без особых трудностей.
Но если пройдет шесть месяцев или больше, в течение которых голова програм-
миста будет занята другим проектом со всеми его деталями и проблемами, то я
уже не так уверен. Тем не менее, этот миф часто используется программистами
в качестве оправдания своего нежелания тщательно документировать свой код.

Явление «you(now) != you(later)» - очень опасная, скользкая тропинка,
прямиком ведущая к проблемам с качеством и эффективностью. Когда кому-
нибудь (нe исключая вас самого) придется заглядывать в ваш «старый» код,
часто это будет делаться для исправления какой-либо ошибки или для внесения
каких-либо авральных изменений. И происходить это будет, скорее всего, под
сильнейшим давлением плановых сроков. Вы прекрасно знаете, во что это
обычно выливается: беглый просмотр листинга, мучительная попытка в
течении пяти минут расшифровать, что же этот код «делает на самом деле»,
несколько строк нового кода, написанные впопыхах и без учета недоку-
ментированных особенностей проекта, и, если программа недостаточно
протестирована, ошибка благополучно становится головной болью ваших поль-
зователей. И даже если финальный тест обнаружит ошибку раньше, чем
программа попадет к пользователям, все начнется сначала - код снова ока-
жется у вас в руках, и вам придется исправлять ошибку, вероятно, под еще бо-
лее жестоким прессингом временных рамок.

Решением всех этих проблем, разумеется, является документирование
кода. Без сомнения, вы воочию знакомы с такой практикой, как ведение
подробных «мемуаров» обо всех изменениях, сделанных в модуле (в виде спе-
циально оформленных комментариев, помещаемых в начале исходных тек-
стов). Необходимо ко всем программистам предъявлять требование следовать
этой полезной практике, поскольку она является косвенным признанием
простого и важного факта: всякий код имеет свое «время жизни» и в течение
этого времени может кардинально меняться. Это особенно справедливо в тех
случаях, когда над одним и тем же кодом работают несколько программистов,
или когда значительные изменения касаются интерфейса, обеспечиваемого
этим кодом.

Очень важно документировать не только то, что в коде есть, но и то, чего
в нем нет. Почти никто не удаляет из «истории изменений» те записи, которые
со временем становятся неактуальными (и удалять их, кстати, категорически
не следует). Но очень немногие добавляют туда информацию о тех изменениях,
которые были сделаны и почти сразу же были отменены по каким-либо суще-
ственным причинам. А ведь именно эта сторона использования комментариев
имеет особенно важное значение в Windows-программировании - нам очень
часто приходится перебирать массу вариантов и приемов до тех пор, пока не
отыщется то заветное волшебное заклинание (читайте: комбинация API-вызо-
вов и их параметров), которое сделает то, что мы хотели. Еще большую пользу
такие комментарии могут принести в тот момент, когда вы спустя какое-то
время вновь обратитесь к коду с целью «вычистить» его. Когда вы будете ис-
кать лазейки для хотя бы минимальной оптимизации «старого кода», вы
запросто можете сделать такую вещь, как преждевременное освобождение па-
мяти или ресурса, если рядом не окажется предусмотрительного комментария,
предохраняющего от этого поступка. Оптимизация такого сорта
чрезвычайно коварная вещь: бегло посмотрев на код опытным взглядом, заме-
тив места, потенциально улучшающие производительность или алгоритм, но не
заметив никаких предостерегающих комментариев, программист смело присту-
пает к изменениям (читайте: к неизбежному внесению новых ошибок).

Например, в своем коде я постоянно расставляю краткие комментарии та-
кого вида: «Следующая строка должна находиться именно здесь, а не в
конструкторе, так как нам нужно иметь правильное значение параметра hWnd
для вызова данной функции» или: «Нельзя использовать функцию FredEx(),
поскольку в данной ситуации она не работает правильно. Поэтому мы идем в
обход и добиваемся той же цели при помощи функции Fred()».

При непосредственном написании процедур, вы должны взять на себя труд
по документированию всех неявных и непараметрических зависимостей и свя-
зей (например, «главное окно программы не свернуто» или «передаваемая
строка имеет формат, в котором функция GetOpenFileName() возвращает спи-
сок имен файлов, выбранных пользователем»). Еще более важным является до-
кументирование проверки ошибок функцией (а также - должное внимание к
уже имеющейся документации такого типа). Бросив взгляд на функцию, легко
сказать: «Ерунда! Зачем напрягаться и проверять указатель на равенство
NULL? Этого не должно произойти». Но если документация гласит, что функ-
ция отказывается работать при вызове с нулевым указателем, то крайне риско-
ванно менять такое ее поведение (особенно в большой программе или в том
случае, когда функция входит в состав библиотеки, используемой большим
количеством людей то есть когда с большой вероятностью работа другого
кода зависит от ее реакции на переданный нулевой указатель).

Например, в главе 3 на стр. 130 я упоминаю функцию FileExists(), которая
возвращает булевское значение, указывающее, существует ли файл с заданным
именем. Эта функция проверяет переданный ей указатель имени файла на
равенство NULL и, кроме того, она проверяет, не передано ли ей имя нулевой
длины; в обоих случаях она возвращает FALSE. Далее, эта функция использу-
ется в моей «оберточной библиотеке» Win32u - вызывается функциями,
названными GetShortPathName() и uGetShortPathName(), которые, в свою
очередь, уже не заботятся о правильности указателя, передаваемого функции
FileExists(), полагаясь на то, что все необходимые проверки она сделает сама.
Удалите проверку параметра из FileExists(), и это изменение тут же пагубно
отразится на работе uGetShortPathName(). Именно поэтому я считаю доку-
ментирование макро-, а не микровопросом: правильное или неправильное ис-
пользование комментариев может решать или создавать проблемы, далеко вы-
ходящие за рамки отдельных модулей или подпрограмм.

Позвольте мне преподнести вам еще один пример из реальной практики.
Некоторое время назад мы с компаньоном сделали заказной программный
продукт для одного клиента. По прошествии одного года с того момента, как
мы трогали тот код в последний раз, клиент позвонил нам и попросил сделать
небольшое изменение в программе. Это изменение было несложным, но после
такой долгой «разлуки» с проектом мы могли бы потратить не день и не два на
восстановление в памяти особенностей проекта, реализацию и тестирование но-
вого кода. Но в главном файлe с исходными кодами я в свое время оставил
примерно 100 строчек с подробными комментариями об истории изменений
кода, наших пробах и ошибках, конечной версии, а также о паре «функций,
подвешенных на булевскую переменную». В результате нам потребовалось
всего полчаса на приведение проекта в рабочее состояние и восстановление его
в памяти, а затем еще пара часов на реализацию и проверку затребованной за-
казчиком новой функциональной возможности. Нам удалось решить задачу без
введения новых ошибок в программу, а сэкономленное при этом время, опреде-
ленно, значительно превышало то время, которое я год назад потратил на до-
кументирование.

Еще один из интересных программистских мифов, который я постоянно
слышу, это утверждение о том, что тот или иной фрагмент кода является «са-
модокументированным». Существует ли в природе самодокументированный
код? Конечно, да! Я был бы последним из тех, кто посоветовал бы вам ком-
ментировать каждую строчку кода. Проблема не в этом. (На самом деле, в на-
писанном мной коде полно подпрограмм, в которых нет ни строчки ком-
ментариев, за исключением простого общего описания назначения функции и
ее параметров. В этих случаях я не вставлял комментарии потому, что они не
были нужны. Разумеется, в большинстве своем это были несложные
функции типа функций поиска по таблице, например содержащие
дюжину-другую строк кода.) Проблема заключается в том, что этот миф (как
и многие другие программистские мифы, независимо от степени их правдопо-
добности) слишком уж часто используется программистами во вред - оправ-
дывает увиливание от выполнения необходимой работы. (Я знаю, знаю -
наверное, я уже начинаю звучать так же, как Уилфорд Бримли в своей рекламе
овсяных хлопьев! [американский актер, прославившийся на всю страну благодаря
многочисленным рекламным роликам, в которых он с добродушным видом, но достаточно
назойливо, увещевал и убеждал зрителей, как полезно есть овсяные хлопья на завтрак. Но, в
отличие, скажем, от Лени Голубкова, Бримли рекламировал (и довольно успешно) действи-
тельно полезную вещь.] Но я говорю истинную правду! Зачастую дело вообще до-
ходит до абсурда: «Почему я должен комментировать это? Разве вы не можете
понять, что этот код делает? Лично я прекрасно понимаю». И даже когда
формулировка не так вульгарна (а я слышал много вариаций высказывания
этой позиции - от прямых и резких до сильно завуалированных), програм-
мист тем самым дает понять, что отсутствие достаточного числа комментариев
в своем коде он считает чуть ли не величайшим своим достоинством, показате-
лем своих умственных способностей и энергичности.

Другая проблема, являющаяся но сути перевертышем предыдущей,
заключается в том, что некоторые программисты находятся во власти такой
причудливой идеи: если по какой-либо причине (читайте: по капризу програм-
миста) приходится использовать ужасно плохой стиль и приемы кодирования,
то такой код будет приемлемым, если снабдить его подробнейшими ком-
ментариями. Конечно же, такая идея неправильна и вредна.

Голова идет кругом от того количества бессмыслиц, которые я наблюдал в
рабочем коде многих программ. И я могу дать вам лишь один совет - не под-
давайтесь соблазну следовать всем этим дурным примерам, и старайтесь
предостерегать от этого ваших коллег и сотрудников. К сожалению, пока еще
не изобрели ни инструментов, ни технологий, которые могли бы помочь вам в
этом вопросе. Рассчитывайте только на свои собственные силы - на свою
старательность и свои убеждения. Самое важное, всегда помните о том, что тем
человеком, будущей работе которого вы на самом деле помогаете, может ока-
заться ваш друг или даже вы сами.

Великий Том и таинственный цикл

Однажды, во время моей работы с операционной системой
VM в IBM, мне пришлось адаптировать и встраивать в систему ас-
семблерный код, ранее написанный кем-то из исследовательской
лаборатории. Код был очень хорошо написан, но весьма скупо за-
документирован. В целом, его задачей было создание совместно
используемого сегмента памяти (грубо говоря, аналога комбина-
ции DLL и проецируемого в память файла в Windows).

В какой-то момент я наткнулся на небольшой, буквально в не-
сколько строчек, фрагмент кода, который показался подозритель-
но бессмысленным. Он циклически сканировал область памяти,
предназначенную для сегмента, и загружал в регистр первые
четыре байта из каждой 4-килобайтной страницы памяти. По-
скольку с прочитанными значениями ничего не делалось, все вы-
глядело так, как будто этот код просто «трогал» все страницы,
причем явно по какой-то осмысленной причине. Поскольку эту
причину я сам понять не мог, я решил воздержаться от удаления
этого фрагмента кода. Я обратился к нескольким местным знато-
кам системы VM с просьбой помочь мне разгадать, чем же все-таки
занимается этот таинственный код, но никто не смог мне ничего
подсказать. Наконец, я встретился с великим гуру этой части
операционной системы, Томом:, который взглянул на код и мгно-
венно прояснил ситуацию. Этот магический цикл просто-напросто
обеспечивал синхронизацию страниц в сегменте и в пуле. Как ока-
залось, в операционной системе имелся патологический недоста-
ток, приводивший к десинхронизации страниц памяти, что в свою
очередь не позволяло корректно создавать требуемый сегмент; а
этот презренный ничтожный цикл как раз и служил затычкой этой
дыры. (На самом деле, технических деталей было много больше,
но, я надеюсь, вы и так уже представили себе суть ситуации.)

Как только я понял, что происходит, я написал внушительных
размеров комментарий, объяснявший как саму природу данной па-
тологии, так и предназначение этого загадочного фрагмента кода.
(Кстати, именно тогда я, должно быть, поставил свой личный
рекорд отношения объема комментария к объему комментируемого
кода - что-то вроде 10:1, если не больше.)

Несколько моих коллег, увидевших это, пытались пошутить
над моим излишним, по их мнению, писательским усилием. Но я
категорически не согласился с ними, поскольку я как никто знал,
сколько работы совершили мои ноги для того, чтобы разобраться
в предназначении этого кода. И я с гордостью получил доказатель-
ство своей правоты спустя несколько месяцев. Другой програм-
мист, работая с этим кодом и не зная о том:, что этот комментарий
сделал я, случайно обмолвился при мне, что он чрезвычайно бла-
годарен автору этого фрагмента за время, потраченное на такой
полезный и ясный комментарий.
{mospagebreak}
4. Уважайте ресурсы вашего пользователя

Is there no respect of place, persons, nor lime, in you?

Уильям Шекспир, «Двенадцатая ночь»

Как насчет такой очевидной мысли: вы пишете программы, которые будут
запускаться на компьютерах, но пользователями которых будут люди. Звучит
банально? Но тогда почему так много современных программ игнорируют это
и относятся к компьютеру как: к дешевому мотелю, населенному студенческими
общинами на весенних каникулах?

Пишете ли вы коммерческий продукт, который собираетесь продавать мил-
лионами копий, или небольшую программу для домашнего использования или
для ваших коллег по работе вы всегда обязаны с уважением относиться к
вашим пользователям и ко всем ресурсам, которыми они располагают.

В Не калечьте систему пользователя при инсталляции вашей програм-
мы.
Эта заповедь чрезвычайно важна. Лично я отказался от использо-
вания бессчетного числа программ только потому, что они нарушали
это простое правило: заменяли системные DLL и VBX без моего
разрешения (нет, простого сообщения об этом в README.TXT недос-
таточно!), изменяли файлы AUTOEXEC.BAT, CONFIG.SYS,
WIN.INI и SYSTEM.INI, сообщая об этом только после того, как дело
уже сделано, портили настроенные мной файловые ассоциации и, на-
конец, просто требовали установки каких-то своих файлов в строго
определенные каталоги. И этот краткий перечень - лишь малая часть
тех выкрутасов, из-за которых ненависть к инсталлятору в два счета
переносится на саму программу. Кстати, проблемам, связанным с ме-
стоположением DLL-файлов, подробно посвящена часть 6.

Тесную связь с этой проблемой имеет вопрос о том, насколько легко и
удобно будет пользователю запускать вашу программу под управлени-
ем разных инсталляций Windows на одной и той же машине. Если вни-
мательно присмотреться к большинству современного программного
обеспечения, то нетрудно заметить, что забота об этом является кон-
цепцией, просто-таки чуждой разработчикам. Я не могу припомнить,
как часто мне приходилось инсталлировать одни и те же программы по
нескольку раз для последующего запуска их под Windows 3.1, Win-
dows 95 и Windows NT. Конечно же, трудность таких операций объек-
тивно возрастает: программы все больше и больше зарывают свои на-
стройки в систему, и запуск их под разными инсталляциями Windows
становится намного более сложной задачей, чем просто запуск главно-
го исполняемого файла. Появление Windows 95 еще больше усугубля-
ет ситуацию: с одной стороны, все больше стимулируется использова-
ние программами специфичного для этой версии Windows реестра, а с
другой стороны, большинство пользователей (особенно те, для кого
работа с компьютером является критичной в их бизнесе) наверняка не
будут спешить с окончательным переходом на новую платформу, а
значит еще как минимум в течение многих месяцев Windows 95 будет
сосуществовать с Windows 3.1 или Windows 3.51 на их машинах.

В Позволяйте пользователям работать так, как предпочитают они.
Важно . всегда помнить, что ресурсы пользователя отнюдь не
ограничиваются самим компьютером и программным обеспечением.
Они включают в себя такие вещи, как знания и квалификация пользо-
вателя, а также его привычки и персональный опыт. Например, я
считаю весьма приличными мои навыки работы с клавиатурой и
мышью - я могу довольно быстро манипулировать как Windows-
программами, с которыми работал раньше, так и с самой Windows. Но
когда я гляжу на свою приятельницу Марию, которая около восьми
лет проработала графическим дизайнером, и вижу, как виртуозно она
работает с PageMaker на своем Маке, у меня возникает навязчивое же-
лание срочно поступить на Лечебно-Исправительные Курсы По Управ-
лению Мышью. А ее стремительный стиль выполнения верстки и
других DTP-работ служит для меня ярким напоминанием о том, на-
сколько отработанными и закрепленными в привычку могут быть те
или иные процедуры, выполняемые пользователями, и насколько важ-
но помнить об этом нам, программистам при выпуске очередных
версий наших программ. Другим, пусть менее драматичным, напоми-
нанием об этом были мои личные проблемы при первом знакомстве с
программой «Проводник» в Windows 95: например, привыкнув ко-
пировать файлы в File Manager при помощи горячей клавиши F8, я
долго и безуспешно пытался пользоваться ею в Проводнике.

В Не заставляйте пользователя тратить свое время понапрасну. Я
имею в виду не только программы, работающие слишком медленно (об
этом грехе будет отдельный разговор), но и те программы, которые де-
лают такие глупые вещи, как принуждение пользователя любоваться
красочной заставкой при запуске (без документированного способа от-
ключения).

Вообще говоря, всякий раз, когда ваша программа собирается
«устроить шоу» продемонстрировать мультипликацию, сыграть му-
зыку и т. д. и т. п. - следует всегда делать такое развлечение настраи-
ваемым. При этом недостаточно просто давать возможность отменить
начавшееся шоу, необходимо сделать это шоу отключаемым раз и на-
всегда. Довольно традиционной является рекомендация делать все зву-
ковые эффекты в программе отключаемыми (поскольку в рабочей об-
становке они могут серьезно раздражать не только непосредственно
пользователя, но и окружающих). Это превосходный совет, но я обя-
зательно распространил бы его и на всяческие не относящиеся к делу
видеодемонстрации. Пользователи часто дорожат каждой секундой
своего времени, и заставляя их просматривать глупый (с их точки
зрения) мультфильм в 7325-й раз, вы серьезно рискуете навсегда отва-
дить пользователей от своей программы.

Говоря о «развлекательных шоу» в программах, нельзя не вспомнить
о так называемых «Пасхальных яйцах», которые программисты так
любят прятать в своих продуктах. Я имею в виду небольшие красочно
оформленные анимационные представления (чаще всего - подробные
списки разработчиков программы), которые пользователь может на-
блюдать после выполнения каких-нибудь «секретных» нажатий кла-
виш или манипуляций мышью. Довольно экстравагантный пример та-
кого представления описан во врезке «Пасхальное яйцо Windows 95»
на стр. 89. Признаюсь, у меня двойственное отношение к этим штукам.
Да, они могут быть по-настоящему забавны, но, как правило, только
однажды или дважды. Потом они становятся абсолютно неинтересны.
Или даже начинают раздражать. Например, наличие Пасхального
яйца в программе, которая имеет ряд действительно ужасных ошибок
или не обеспечивает каких-нибудь очевидно необходимых функций,
вызывает у меня чувства, весьма далекие от радостного трепета. (Воз-
можно, я становлюсь скрягой и занудой в свои тридцать с хвостиком,
но когда я смотрю на Пасхальное яйцо в Windows 95, меня мучает
один вопрос: почему они не использовали драгоценные программист-
ские часы, потраченные на это шоу, для более полезных дел -
например, для обеспечения настраиваемого показа файловых атрибу-
тов в окне Проводника или для поддержки горячей клавиши F8 при
копировании файлов, как в Диспетчере файлов?) Кстати, пресловутое
Пасхальное яйцо в Windows 95 вызывает у меня дополнительное бес-
покойство но другой, более серьезной причине - меня настораживает
тот факт, что такая техническая реализация этого шоу очевидно
потребовала изменений в самой глубине операционной системы - в
процедурах переименования и открытия объектов (задумайтесь: при
каждом переименовании или открытии папки системе приходится
сравнивать введенные пользователем символы с «секретными
ключами» Пасхального яйца!). Неужели стоило трогать ядро оболочки
операционной системы, пусть даже незначительно, для таких целей,
как одноразовое развлечение?! Я уж не говорю о том, что ребята явно
перестарались с «засекречиванием» этого шоу, - почему бы не посту-
пить проще и не привязать его куда-нибудь поближе к диалогу
«About». Одним словом, если бы я имел право голоса при обсуждении
реализации этого Пасхального яйца, я несомненно проголосовал бы
категорически против нынешнего технического решения.

В Не заставляйте пользователя зря тратить пространство на жест-
ком диске, оперативную память или процессорное время.
Отчасти,
это просто рекомендация писать такие инсталляционные программы,
которые позволяют пользователю выбрать необходимые ему функцио-
нальные возможности (и которые, разумеется, потом подчиняются
требованиям пользователя и действительно копируют на его жесткий
диск только те компоненты, которые необходимы). Увы, я много раз
видел ситуацию, когда на винчестере оказывались как раз те компо-
ненты, которые пользователь специально старался исключить.

В более широком смысле, это мольба об эффективности и экономично-
сти ваших программ. Признаюсь, такой призыв наверняка звучит для
вас комично (или патетично - все зависит от степени вашего цинизма)
в эпоху 30-мегабайтных текстовых процессоров и 100-мегабайтных
сред разработки на С++, с легкостью генерирующих 200-килобайтные
показательные программы весовой категории «Hello, World!».

В Будьте элементарно вежливы. Я уверен, многие удивленно поднимут
брови, но я слишком часто поражался тому, насколько грубыми могут
быть сообщения в программах. Например, попробуйте запустить
программу с «ожидаемой версией Windows», большей 4.0, и Windows
95 скажет вам следующее: «The file expects a newer version of Windows.
Upgrade your Windows version.>> («Программа рассчитана на более но-
вую версию Windows. Обновите вашу версию Windows».) Когда я уви-
дел это в первый раз, мне тут же захотелось обратиться в Microsoft с
просьбой заменить мою версию Windows 95 на более вежливую. Я даже
готов был заплатить за это еще несколько долларов. В самом деле, неу-
жели им было так трудно (или дорого?) сказать то же самое, но с иной
интонацией: «The file expects a newer version of Windows than your cur-
rent version. Sorry, but you'll have to upgrade to a newer version of Win-
dows to run this program.» («Программа рассчитана на версию Win-
dows, более новую, чем ваша нынешняя. Извините, пожалуйста, но вам
придется обновить вашу текущую версию Windows для того, чтобы за-
пустить эту программу».) Я не намереваюсь бранить именно Microsoft
(как раз она работает в этом плане немного лучше других); просто вы-
шеприведенный конкретный пример оказался под рукой и является весь-
ма показательным в качестве иллюстрации для этой книги.

Поделюсь с вами маленьким секретом, который я открыл при тесном
знакомстве с рынком условно-бесплатных программ: если ваша
программа окажется полезной людям и понравится им, то у нее гораз-
до больше шансов на успех, чем если она всего лишь окажется полез-
ной. А если ваша программа будет вежливой и доброжелательной (без
перебарщивания, разумеется), то людям она понравится больше. Де-
лайте язык ваших программ более вежливым, потому что именно так мы,
программисты, должны разговаривать с пользователями (с теми,
без кого мы были бы просто толпой безработных). В конце концов, де-
лайте ваши программы учтивыми хотя бы из жадности (это один из тех
немногих случаев, когда хороший поступок, сделанный из плохих по-
буждений, в конечном итоге окажется праведным делом - вряд ли
пользователей будет волновать ваша мотивация, но программа им
понравится и они отблагодарят вас).

Пасхальное яйцо Windows 95

Как только в свет выходит новая версия известной Windows-
программы (или самой Windows), немедленно начинается массовый
бум - поиски знаменитого Пасхального яйца, «скрытого» развлека-
тельного представления, помещенного программистами в продукт.
Ниже приведены подробные инструкции для просмотра Пасхального
яйца Windows 95 (я благодарен нескольким моим респондентам, под-
сказавшим мне эти «заклинания»). Набирайте текст внимательно -
названия папок должны в точности совпадать с тем, что описано ниже1.

1.        Щелкните правой кнопкой мыши по поверхности стола и за-
тем выберите в контекстном меню команду New | Folder.

2.    Назовите только что созданную папку «and now, the moment
you've all been waiting for» (без кавычек).

3.        Тут же переименуйте эту папку так: «we proudly present for
your viewing pleasure» (снова без кавычек).

4.        Снова переименуйте папку, на этот раз так: «The Microsoft
Windows 95 Product Team!».

5.        Откройте папку и посмотрите (и послушайте) представление.
(Кстати, на одном из моих компьютеров закрытие этой папки
посредине представления иногда оставляет мою карту Sound
Blaster в возбужденном состоянии - она монотонно издает
низкий, противный гул до тех пор, пока я не завершу работу
Windows. Интересно, принимает ли Microsoft сообщения об
ошибках и сбоях в Пасхальном яйце?)

5. Используйте DLL осторожно

Динамические библиотеки - лучший пример «тепличного создания» в
Windows 95. В контексте данной книги, под «тепличными созданиями» я по-
нимаю такие функции, возможности и свойства, которые прекрасно работают
в жестко контролируемых, «тепличных» условиях (в руках экспертов или даже
в руках своих создателей), но надежность которых резко падает, когда они
брошены на произвол судьбы во враждебных, непредсказуемых условиях
реального мира.

Более подробный и интересный разговор о DLL я отложу для главы 6, а
пока я хотел бы подчеркнуть одну деталь: в Windows-программировании ди-
намические библиотеки зачастую чрезмерно широко и интенсивно ис-
пользуются (читайте: неправильно используются), а также неверно распо-
лагаются на диске. Единственным результатом этого является масса проблем,
возникающих у всех участников (в особенности, у пользователей).

6. Придерживайтесь широкого взгляда на инструментарий

Man is a tool-using animal... Without tools he is nothing, with tools he is all.

Томас Карлайл

Почти все программисты ужасно любят забавляться со своим
инструментарием разработки; но при этом порой проявляют выдающуюся не-
способность правильно сделать его выбор. Это приводит к значительным и не-
гативным последствиям для их программ.

Этой проблеме (а также другим проблемам, связанным с инструментарием
разработки программного обеспечения) подробно посвящена глава 4. Но один
из аспектов данного вопроса кажется мне настолько важным, что я уже здесь
обращаюсь к нему, как к одной из макропроблем программирования: програм-
мисты слишком узко определяют понятие «инструментарий разработки», сводя
его к набору компиляторов, компоновщиков, отладчиков, текстовых
редакторов, третьесторонних библиотек и т. п. Подобный подход недальнови-
ден, потому что его результатом чаще всего является такое поведение: програм-
мист готов тратить кучу денег на то, чтобы установить на свой жесткий диск
самую последнюю и самую крутую версию излюбленного компилятора или
редактора, но при этом пренебрегает инструментом, который в действительно-
сти так же важен, как и все инструменты-программы вместе взятые - своим
собственным образованием и обучением.

В наши дни Windows-программирование представляет собой такое высокое
и многослойное нагромождение инструментов, интерфейсов и архитектур, что
сегодня, как никогда, нам необходимо изо всех сил пытаться быть студентами,
постоянно изучающими программирование, и оставаться открытыми для всех
возможных источников информации.

Вспомните старые добрые времена программирования для DOS: вы поку-
пали компилятор (например, Turbo С или Turbo Pascal), пару книг
впридачу - и все, вы уже были достаточно вооружены для продолжительной
результативной работы. Многочисленные SDK? Подписка на Development Net-
work? Третьесторонние библиотеки? Огромные архивы исходных кодов на CD-
ROM? В то время о таких вещах либо вообще не слышали (не слышали в бу-
квальном смысле слова, потому что многих из них еще не существовало в
природе), либо они были реально необходимы лишь горстке программистов,
работавших на передовом крае индустрии.

Другой побочный эффект любви программистов к своим инструментам вы-
глядит довольно парадоксально (и с течением времени только усиливается):
многие из нас так и не находят времени для действительно детального изучения
наших инструментов. На первый взгляд, достаточно приобрести ту или иную
программу - и ничто не мешает изучить ее возможности досконально. Но
сильнейшее давление на нас со стороны рабочих проектов, планов и сроков
зачастую ведет к следующему сценарию: мы инсталлируем новую версию ком-
пилятора, игнорируем 95 процентов новых возможностей и свойств и доволь-
ствуемся лишь теми улучшениями и исправлениями, которые достаются нам ав-
томатически. (Последите за дискуссиями в телеконференциях, и вы будете
часто встречать реплики настоящих асов программирования, которые до сих
нор ведут все свои разработки, управляя своими инструментами из командной
строки. Эти люди вовсе не являются нео-луддитами, которые страшатся выхода
в новый прекрасный мир визуального программирования. Они просто четко
знают, что для них годится, а что нет, и поэтому продолжают работать с тем,
что годится.) Проблема заключается в том, что, недостаточно изучив свои
инструменты (а я грешу этим так же, как и большинство других программи-
стов), мы порой просто не знаем, что они могут делать, а что нет. Насколько
привычными для слуха стали обсуждения, подобные такому диалогу двух
программистов: «Как жаль, что Visual С++ 2.2 до сих пор не позволяет создать
или отредактировать ресурс с описанием версии. - Вы неправы, 32-разрядная
версия VC++ давно позволяет делать это!».

Как я упоминал выше, эта проблема все больше и больше обостряется по
мере развития Windows-программирования. Недавно мой близкий друг очень
своеобразным способом проиллюстрировал этот факт. Имея серьезнейший
опыт в программировании (но не для Windows), он спросил меня, что (и в ка-
кой последовательности) я посоветовал бы изучить и освоить среднему DOS/
С-программисту для того, чтобы он смог начать разрабатывать Windows-
программы профессионального качества. Я догадался, что он намекал на себя
самого и ждал от меня честного ответа. И когда я начал перечислять все те об-
ласти, которые я рекомендовал бы для исследования этому гипотетическому
новичку основы архитектуры Windows, событийно-управляемое
программирование, С++, какую-нибудь каркасную [framework] библиотеку (скорее всего
MFC) и прочее - мы оба удивились и одновременно ужаснулись количеству
основных пунктов в этом списке и грандиозности соответствующей им
суммарной кривой обучения.

Примерно то же настроение вызывает наблюдение другого моего друга -
однажды он справедливо заметил, что сегодня стало практически невозможно
написать более-менее сложную прикладную Windows-программу в одиночку. И
это - исключительно благодаря размеру и сложности тех инструментов и
архитектур, которыми необходимо овладеть. Я думаю, что наша профессия уже
давно и неуклонно двигалась в этом направлении. По в то же время последние
достижения, такие как пакеты RAD (программы для быстрой разработки
приложений), в какой-то степени противодействуют этой тенденции.
Подчеркну: я вовсе не утверждаю, что Delphi (или Visual Basic, или другие по-
добные пакеты программ) могут заменить знания программиста о системе. Эти
инструменты очень хорошо помогают повторно использовать старый код,
быстро писать новый, и даже защищают от многих ловушек Windows API . Но
они не могут защитить вас от всего. И как только вы продвинетесь достаточно
далеко вперед от рубежа «hello world», вы снова окажетесь в холодном
открытом океане, и снова вашей единственной надеждой и опорой будет самый
важный и самый основной инструмент - ваши собственные знания.

  7. Не забывайте о производительности

You can't be too rich, too thin or have a too fast computer.

Старый программистский афоризм

Dost thou love life? Then do not squander time, for that's the stuff life is made of.

Бенджамин Франклин

Настало время правды в рекламе: я - человек, издавна привыкший мыс-
лить на уровне битов. Я всегда был одним из тех людей, которых обвиняют в
желании иметь самый большой, самый быстрый, самый-самый компьютер на
свете, независимо от того, нужен он или нет. В каком-то смысле это правда. Но
и я, в свою очередь, убежден, что некоторые люди недооценивают важность
производительности. Например, мне нравится слушать старую байку о том, что
для работы с текстами и документами не нужно больших компьютерных мощ-
ностей. Если под «работой с текстами» подразумевать просто ввод текста в
компьютер, то да, я согласен. Но если вы собираетесь делать такие вещи, как
генерация индексов и оглавлений, проверка грамматики и правописания, за-
пуск макрокоманд и т. п., вас охватит страстное желание немедленно поставить
на свой стол самый последний BelchFire 5000 Wonderbox [выдуманная автором марка воображаемого супермощного компьютера, которое можно перевести примерно так: «Чудесный Ящик, Извергающий Огонь»]. (Я говорю все это с
целью прояснить одну деталь: когда я рекомендую не изменять код для улучше-
ния его производительности, я всегда делаю это после тщательного обдумыва-
ния и колебаний, потому что на самом деле я с самого рождения хочу, чтобы
все работало максимально быстро и в доли секунды откликалось на действия
пользователя.)

Смысл этой моей макрорекомендации совсем не в том, чтобы лишь в
очередной раз отметить важность такой характеристики программного обес-
печения, как производительность. Большинство программистов (но не все)
прекрасно это понимают, и им не нужно напоминать, что чем оно быстрее, тем
лучше. Я гораздо больше обеспокоен тем, как некоторые превращают заботу о
производительности в свою религию, и к каким последствиям это приводит.
Вот несколько примеров разнообразных проявлений этой проблемы:

В Меньше строк кода == меньше инструкций процессора == лучше
производительность. Этот миф очень коварен, он может приводить к
неслыханно бессмысленным спорам. Даже опытнейшие программисты
порой забывают, что, когда они пишут на ЯВУ (языке высокого уров-
ня), они по определению используют некоторый уровень абстракции,
виртуальную машину, и тем самым заведомо отказываются от не-
посредственного управления генерацией машинного кода. Всякий, кто
хотя бы раз заглядывал в высокооптимизированный выходной код
компилятора, может удостоверить, что нет никакой гарантии соответ-
ствия между этим кодом и структурой исходного кода на таких язы-
ках, как Pascal или С. Оптимизаторы только тем и занимаются, что
перемещают куски кода с места на место, изменяют их до неузнавае-
мости или даже вообще удаляют их. Все зависит только от степени
агрессивности оптимизатора и от того, насколько хорошо был написан
исходный текст.

Как я уже упоминал выше, этому мифу наиболее часто поддаются
именно программисты, использующие C/C++, часто стремящиеся втис-
нуть ту или иную функцию в как можно меньшее количество строк.
Результат таких действий, как правило, совершенно противоположен
желаемому и приводит к «слоптимизированному» коду.

В Еще более страшной ошибкой, очень часто наносящей вред качеству
публичного программного обеспечения, является то, что я называю «синдромом невзвешенного усреднения». Попробую пояснить это на
простом примере: если вы принесли из магазина шесть покупок стои-
мостью 2, 2, 2, 2, 2 и 10 долларов соответственно, то какова средняя
стоимость покупки? Равна ли она 3.33 доллара (20/6) или 6 долларов
(12/2)? Разумеется, ответ зависит от того, как вы усредняете - со
взвешиванием или без. (Ну, и еще от того, какой результат вы хотите
получить - больший или меньший. Ладно, ладно, статистики дейст-
вительно не врут, но статистики - это совсем другое дело.) В нашей
же профессии данный синдром заключается в том, что многие програм-
мисты (особенно новички), сильно озабоченные вопросом производи-
тельности, имеют тенденцию смотреть на все детали без взвешивания
и поэтому тратят массу времени на оптимизацию того, что нет смысла
оптимизировать. Да, быстрее - всегда лучше, чем медленнее. Но это
не значит, что имеет смысл проходить буквально по каждой тропке,
позволяющей сэкономить хотя бы пару-тройку шагов процессора.
Баланс - вот в чем ключ к решению этого вопроса. Если вы об-
наруживаете, что можно на 2 процента ускорить печать документа ва-
шей программой, но при этом придется выскрести и переписать 200 ООО
строк кода - стоит ли это делать? Я подозреваю, что правильный
ответ нет (если, конечно, вы не выполняете ваш проект в каких-ни-
будь уж очень необычных условиях).

Концентрируясь на подобных изначально неверно понимаемых
проблемах, команды разработчиков часто проматывают самый драго-
ценный свой ресурс рабочие часы программистов. И при этом
пренебрегают гораздо более важными (но менее сексапильными)
вещами - такими, как тщательное комментирование исходного кода и
более скрупулезное тестирование - а ведь именно они, несмотря на
свою кажущуюся невзрачность, являются реальным залогом качества
продукта (особенно, если речь идет о перспективе - о будущих новых
версиях).

В У «синдрома невзвешенного усреднения» есть еще один побочный
эффект, еще худший, чем растрата программистского времени - он
может приводить к таким компромиссам, результатом которых стано-
вится пустой и слабый код. Я имею в виду непосредственное уменьше-
ние надежности кода в погоне за производительностью. Дело в том,
что любой фрагмент вашего кода должен очень скептически относиться
ко всем данным, которые в нем используются и которые не были соз-
даны в нем самом (это касается не только таких потенциально опасных
данных, как ввод пользователя, но и данных, приходящих из других
частей кода). Лучшим способом борьбы в такой ситуации является соз-
дание простых заградительных барьеров, которые будут обнаруживать
ошибочные данные  и отбрасывать  или  (если возможно)  коррек-
тировать их. (Такой подход я называю «оборонительным програм-
мированием», и более подробный разговор о нем состоится в сле-
дующей главе.) Как раз эти барьеры - небольшие участки кода,
проверяющие ошибки, - и оказываются за бортом первыми, когда
программист «вычищает» код, стараясь выгадать пару лишних
микросекунд.

Даже такие маленькие вещи, как проверка указателя на равенство NULL
или проверка попадания целого значения в допустимый интервал, часто
выбрасываются, когда программисты начинают срезать углы. Это так
соблазнительно - вмиг сократить размер кода с 23 строк до 17. Программист
так доволен и горд этой экономией - целых 25 процентов! А тем временем
реальный выигрыш в производительности даже близко не стоит к этой цифре.
Действительно, если удаляются строки вида

if(lpStuff == NULL)
  return ERROR_BAD_PARAMETER;

которые требуют лишь нескольких инструкций для выполнения, и если срав-
нить их с остальными внутренностями функции (например, с циклом, который
и выполняет основную работу), то станет ясно, что реальный выигрыш в произ-
водительности будет много меньше одного процента. Мне не хотелось бы, что-
бы мои слова прозвучали как обвинение программистов в идиотизме. Я сам был
там и знаю, как это происходит: вам необходимо срочно ускорить работу како-
го-то фрагмента кода, и вы начинаете срезать углы. Идея убрать «лишние» 25
процентов кода с экрана кажется такой заманчивой правой половине вашего
мозга. Левая половина настойчиво твердит, что реальной экономии в этом -
всего 1 процент; по вы торопитесь, вам нужно ускорить работу кода немедлен-
но, и
вы удаляете эти строчки, несмотря ни па что, получаете короткую
иллюзию удовлетворения и приступаете к оптимизации следующей функции.

А что мы имеем, если рассматриваем эту тенденцию в терминах соотноше-
ния «затраты/выгода»? В большинстве случаев мы получаем незначительный
выигрыш в производительности и порядочного размера, но не такие очевидные,
затраты - вполне вероятно, что удаленные строки кода незаметно исправляли
опасную проблему с данными, возникающую в неизвестной стрессовой ситуа-
ции, и, благодаря этому, никто в вашей команде даже не подозревал, что это
происходило. Возможно, эта обнажившаяся проблема не будет обнаружена при
регрессивном тестировании, и тогда дело кончится отгрузкой ошибки пользова-
телю. То есть, пользуясь сугубо технической терминологией, вы просто-
напросто отстреливаете себе (и своему продукту) палец, добившись такого не-
значительного улучшения производительности, что его никто и не заметит'.

Подобно другим общим проблемам программирования, обсуждаемый здесь
вопрос серьезно усугубляется реалиями Windows. Ваш код просто обязан быть
откровенно параноидальным, чтобы правильно и надежно работать в условиях
бесконечных вывертов со стороны драйверов, и постоянной толкотни в толпе
одновременно запущенных программ, многие из которых недостаточно образо-
ванны и учтивы, чтобы оставить в покое своих соседей (в главе 12 на стр. 421
вы найдете замечательный пример такого поведения - рассказ о том, как Nor-
ton Navigator от Symantec крушит другие программы).

Что же я могу порекомендовать в этом случае, помимо тщательной оценки
того выигрыша, который вы ожидаете получить при «сжатии» кода? Прежде
всего, сконцентрируйте ваше внимание на алгоритмических вопросах: какой
механизм сортировки лучше использовать в этом месте? Так ли нужно исполь-
зовать здесь связанный список, или нам следует разместить большие блоки па-
мяти фиксированного размера и избежать лишних перипетий с указателями?
Трудность заключается в том, что наличие «проблем с производительностью»
зачастую долго «не обнаруживается» и становится «сюрпризом» лишь на позд-
них стадиях цикла разработки. (Кавычки в данном случае расставлены для
того, чтобы намекнуть на обычное реальное положение дел: как правило, ко-
манда программистов на протяжении всего проекта прекрасно знает, что
продукт куча... неэффективного кода, но лишь по прошествии достаточно
долгого времени эта информация просачивается наверх и кто-то наконец издает
приказ срочно что-нибудь предпринять в связи с этим.) Обычно к тому мо-
менту, когда улучшение производительности становится чуть ли не самым на-
сущным вопросом, уже поздно заниматься алгоритмическим анализом такого
рода и делать соответствующие изменения в коде. Когда к виску программиста
приставляют двустволку план + производительность - он приходит в бе-
шенство и начинает вспарывать все, что можно. И тогда проверки ошибок,
которые «на самом деле не нужны, поскольку ничего не делают», становятся
первыми жертвами.

Что касается выявления г. программе мест, действительно критичных для
производительности, то надо признаться честно: во многих случаях (если не в
большинстве их) такие места можно увидеть еще до того, как начнет писаться
код. Мой коллега (программист и писатель) Пит Дэвис однажды делал
компрессор растровых картинок для своего клиента. Главной задачей этой
программы было масштабное уменьшение графического изображения, но не
простое, а очень интеллектуальное такое, чтобы уменьшенная растровая
картинка нормально смотрелась. Известно, что такая задача весьма требова-
тельна к такому ресурсу, как рабочее время процессора. Поэтому с самого
начала Пит спроектировал (а потом и реализовал) программу должным
образом: пользовательский интерфейс был выполнен на С++ и MFC, а ядро,
занимающееся собственно масштабированием графики на ассемблере
«ручной выделки».

Среди многочисленных примеров проявления обсуждаемых здесь проблем
встречаются и исключения (которые, правда, все равно в конечном счете сво-
дятся к оплошностям со стороны программистов). В моей практике был один
проект, который можно упомянуть в качестве хорошей иллюстрации - это
была очень большая программа, имевшая свою собственную внутреннюю
службу размещения и управления памятью. Уже в ранней альфа-версии стала
заметна ужасающе слабая производительность программы. Поначалу это пока-
залось неожиданным для всех, но оперативное исследование быстро выявило
причину - в самой глубине кода системы размещения памяти был обнаружен
цикл, написанный на высокоуровневом языке, который при каждом запросе на
выделение памяти совершал побайтную чистку размещаемого блока памяти.
Этот фрагмент кода был немедленно заменен на ассемблерную вставку, выпол-
нявшую эквивалентную работу при помощи подходящей инструкции
процессора 80x86. В результате мы получили впечатляющее увеличение произ-
водительности всей системы в делом.

В заключение, еще одна идея, которую следует постоянно держать в го-
лове: у вас есть хороший союзник в борьбе за повышение производительности
ваших программ - повторное использование однажды написанного кода. Ино-
гда случается так: вы прекрасно знаете, что некоторая подзадача в вашем
проекте имеет более эффективное решение, вы даже четко видите, как: его реа-
лизовать, по у вас катастрофически не хватает времени на это. Например, вме-
сто того, чтобы хранить частичные описания объектов в отдельных связанных
списках и использовать бинарный поиск, вы просто сваливаете все в один ли-
нейный список, а затем порождаете кучу простых циклов, гоняющих указатели
туда-сюда по всей цепочке. Часто в подобной ситуации есть смысл взглянуть
на задачу немного шире и заметить, что она является достаточно общей и может
встречаться не только в других частях этого же проекта, но и во многих других
проектах. А тогда вполне резонным будет потратить немного больше времени
па ее эффективное решение прямо сейчас - написать правильный производи-
тельный код и превратить его в отдельный, достаточно самостоятельный ком-
понент, который потом легко можно будет использовать в других местах.
Переведя это на язык вашего руководства - указав на возможность разбросать
эти небольшие дополнительные затраты по нескольким проектам, вы,
наверное, без особого труда сможете добиться согласия на то, чтобы пойти
правильным путем. В результате от этого выиграют все.

8. Не забывайте в тестировании

Поразительно, как сильна иллюзия того, что красота есть добродетель.

Лев Толстой

Computers don't make mistakes. They do, however, execute yours very carefully.

Джек Хупер

Вы терпеть не можете тестировать? Лично я, определенно, ненавижу это
делать. Для меня в цикле разработки программного обеспечения нет задачи, ме-
нее интересной и менее приятной, чем тестирование. Даже собрания програм-
мистов и инспекции кода, как правило, приносят больше удовольствия,
поскольку есть возможность пообщаться на темы проекта с другими людьми,
кто-то из них обязательно принесет с собой пончики или еще что-нибудь вкус-
ненькое... Но, прошу прощения, я отвлекся.

Парадокс заключается в том, что никто не любит заниматься тестирова-
нием (и поэтому разработчики часто смотрят па это занятие свысока, как на
грязную черновую работу), в то время как оно является невероятно важной
задачей. Моя жена Лиза шестнадцать лет проработала в качестве программиста
и системного аналитика, и мы вместе с ней очень часто замечали, как легко нам
удается находить ошибки в коммерческих Windows-программах. Обычно для
этого не требовалось и десяти минут небрежного использования. При этом я не
говорю о настоящих «шоу-стопперах», таких как разрушение операционной
системы или потеря данных (хотя и их я встречал больше, чем положено на
мою долю по статистике). Прежде всего, я имею в виду мелкие, не очень суще-
ственные, по тем не менее неприятные проблемы типа полей редактирования,
вылезающих за края диалогового окна, горячих клавиш, конфликтующих друг
с другом, или (мой излюбленный тип) опечаток, забавных и не очень, в диало-
гах, сообщениях и справочных файлах.

Я хотел бы выделить два интересных аспекта этой проблемы, показы-
вающие, как происходит (и не происходит) тестирование при разработке
программного обеспечения для Windows:

В Тестирование - это защитная сеть вокруг вашей программы, послед-
няя и порой единственная надежда па серьезное повышение ее качества
до того, как она выйдет в свет. Это та самая красная черта, разде-
ляющая приватный («что хотим, то творим») и публичный («Мы
затрагиваем интересы реальных людей!») периоды жизни вашей
программы. А еще это как раз та часть цикла разработки, чью долю
в плановом расписании проекта очень часто «съедают» проектирова-
ние и собственно реализация программы.

Я нахожу такое положение дел одновременно ужасающим и удиви-
тельным, потому что тестирование действительно одна из важней-
ших сторон разработки (если рассматривать готовую программу как
результат сделки между разработчиком и пользователем, то я не побо-
ялся бы определить тестирование как одно из неотъемлемых условий
этой сделки). Многие программисты видели в той или иной вариации
известную диаграмму зависимость стоимости исправления ошибки
от момента времени, когда эта ошибка была обнаружена. Во время
проектирования и дизайна стоимость ошибки минимальна; в период
реализации она возрастает, но все еще остается небольшой; во время
тестирования ошибки становятся уже чувствительно дорогими; и, на-
конец, после выпуска продукта в свет мы наблюдаем жуткий изгиб -
из почти горизонтальной линии кривая превращается в почти верти-
кальную, а починка уже требует не только проектирования и тес-
тирования, но и доставки исправленной версии всем клиентам. (Ко-
нечно, широко распространенная ныне практика сопровождения каж-
дой новой версии потоком «заплат» к ней (patches, bugfixes) позволяет
современным поставщикам программного обеспечения радикально
сократить свои расходы на исправление ошибок. Более того, даже най-
ден оригинальнейший способ превращать свои собственные ошибки в
дополнительный источник доходов: хотите получать самые последние
версии и заплаты к ним? - тогда покупайте годовую подписку на
нашу службу обновления продукта! Хмм. И куда же, черт возьми, де-
вается этот докучливый Департамент юстиции США, когда возникает
реальная необходимость в нем?)

Разумеется, тщательное тестирование Windows-программ может быть
ужасно трудным делом, и я готов побиться об заклад, что почти каж-
дое коммерческое Windows-приложение можно положить на обе ло-
патки путем создания достаточного дефицита какого-либо ком-
пьютерного ресурса. (При этом я хотел бы сразу провести различие
между теми программами, которые вовремя обнаруживают такой де-
фицит и элегантно завершают свою работу, и теми, которые продол-
жают пахать, не обращая внимание на проблему, и в результате рабо-
тают неправильно.)

В Некоторые разработчики часто озабочены лишь тем, чтобы порадовать
начальство вовремя написанным и выпущенным кодом, и тогда они ис-
пользуют небрежное тестирование в качестве костылей. Они выпол-
няю!' минимальное тестирование какой-либо функции и говорят: «Ви-
дите? Работает!» Это основной грех программирования по-ковбой-
ски: использовать мимолетную демонстрацию в качестве
доказательства (неважно, себе или кому-то другому) того, что
проверяемый код закончен, работает правильно и с ним буквально
ничего не нужно больше делать. И это не вопрос из области противо-
поставления математиков и ювелиров, это вопрос выполнения тес-
тирования в осмысленном объеме. Такие короткие демонстрации почти
никогда этого не обеспечивают - программист обычно подает на вход
функции лишь относительно банальные, «ручные» наборы данных,
которые, как правило, не включают в себя общие патологические
варианты. Как же можно после этого заявлять, что функция
окончательно готова?

Решение первой проблемы очень простое с концептуальной точки
зрения и неимоверно трудное с точки зрения реализации, поскольку это, на са-
мом  деле,  вопрос  культуры  управления  проектом  (особенно  большим).
Простые административные указания и проскрипции - например, требование
полного тестирования продукта на всех поддерживаемых платформах (огром-
ное спасибо всем этим странным различиям между тремя вариациями Win32!)
или настаивание на тщательном повторном тестировании последних
изменений - делу помогают слабо, поскольку они чаще всего вообще иг-
норируются из-за постоянной нехватки времени или других ресурсов. Нахо-
дясь перед сложнейшим выбором - либо выпустить продукт на рынок сейчас,
несмотря на неопределенность риска в связи с его качеством, либо выпустить
его в заведомо лучшем состоянии, но с таким опозданием, что он все равно
проиграет своим конкурентам - большинство компаний просто бросают кости
и выбирают первую альтернативу. Когда разговор касается денег, давление
мгновенно превращается в ясно различимый пинок: «Отгружайте продукт не-
медленно!» И нам ничего не остается, кроме как выдерживать в этих условиях
тяжелейшую битву за то, чтобы отгружаемое имело как можно более высокое
качество.

9. Думайте о повторном использовании кода

If men could learn from history, what lessons it might teach
us! But passion and party blind our eyes, and the light which
experience gives is a lantern on the stern, which shines only
on the waves behind us!

Сэмюэль Тэйлор Кольридж

Независимо от того, какие при этом будут использоваться названия - ком-
поненты, программные интегральные схемы, объекты, шаблоны или еще что-
нибудь я уверен, что программирование в конце концов вышло на уровень
серьезного и масштабного отношения к повторному использованию кода. Мы
уже сегодня получаем в руки такие инструменты, которые убедительно под-
тверждают, что это - основное направление развития индустрии. И скоро мы
сможем опираться в своей работе уже не на стопы других программистов, а на
их плечи. Это будет замечательное время.

Суть, разумеется, вовсе не в вышеупомянутых инструментах (таких, как
Delphi, к примеру). Сводить проблему повторного использования кода к
применяемым для этого инструментам было бы глупо. Особенно если вспом-
нить, что практически все DOS- и Windows-программисты уже давно, целые
десятилетия применяли это самое повторное использование кода в той или иной
форме. Вставка директивы «#include» в вашу С-программу и последующая
компоновка с соответствующим LIB-файлом не так проста и сексапильна, как,
скажем, Перетаскивание Компонента в Ваш Программный Объект при помощи
Визуальной Интегрированной Среды Разработки. Но оба эти действия прино-
сят практически эквивалентный выигрыш - вместо того, чтобы изобретать ко-
леса, вы используете хорошо проверенный и (как правило) хорошо понятый
код, созданный ранее. Вот в этом и заключается суть вопроса.

Во время основной работы где-нибудь в уголке вашего мозга должен посто-
янно идти еще один процесс - процесс, отслеживающий, какие куски кода,
создаваемого вами, могут и должны быть трансформированы в повторно-ис-
пользуемые компоненты. В подавляющем большинстве случаев код, который
вы пишете, настолько специален для конкретного проекта или программы, что
было бы глупо тратить ваше время на превращение его в такой компонент. (В
конце концов, какой смысл в повторно используемом компоненте, который
применяется только в одной программе?) Но в этом потоке кода обязательно
встретятся фрагменты (функции, модули или даже целые библиотеки), ко-
торые являются хорошими кандидатами на повторное использование. Вот их
обычные характерные признаки:

В Они либо имеют общее назначение, либо могут быть обобщены дос-
таточно легко.
Такие вещи, как контейнерные классы, и даже целые
каркасные библиотеки для Windows-программирования (например,
MFC или OWL) имеют достаточно обобщенный дизайн и обес-
печивают достаточно входов, выходов, лазеек для тонких настроек и
адаптации - с их помощью вы можете сделать почти все, что захотите.

В Они имеют очень специальное назначение. У меня есть целый чемодан
кода, который я таскаю за собой из проекта в проект, и в котором мож-
но найти множество узкоспециализированных подпрограмм, выпол-
няющих такие мелкие поденные работы как сброс атрибута read-only
 у файла, добавление обратной косой черты (backslash) в конец
строки, если этого символа там пет, подсчет количества файлов, ука-
занных в строке, возвращенной функцией GetOpenFileName() и т. д.

в Они делают некоторую противную работу, которую вы не хотели бы
кодировать и тестировать больше одного раза. (А если вы найдете ка-
кой-нибудь хороший третьесторонний компонент, делающий это, то
считайте, что вам очень повезло - и одного раза не понадобится.) От-
личным примером в данном случае может служить каркасная Win-
dows-библиотека. Конечно, и в наши дни можно найти причины не ис-
пользовать в Windows-проекте ни MFC, ни OWL, ни какую-либо
другую подобную библиотеку, но тогда эти причины должны быть
весьма и весьма основательными.

B Они сами напрашиваются на надежную реализацию - то есть вы чув-
ствуете, что можете сделать их почти стопроцентно пуленепробиваемы-
ми. Это очень важная характеристика для будущего повторно-исполь-
зуемого компонента, потому что как только вы опубликуете его, другие
люди будут смотреть на него так же, как на функции стандартной биб-
лиотеки С-компилятора - не утруждая себя доскональным изучением
сопроводительной документации, они будут предполагать, что ваш код
просто не может работать неправильно. (Всегда помните, что вашей
главной целью является повышение вашей производительности и
качества ваших программ, а вовсе не выигрывание в кафетерии споров
о том, кто чей код неправильно использует.)

В главе 10 я расскажу значительно больше об абстрагировании и создании
ваших собственных библиотек, которые смогут повысить эффективность ваших
разработок для Windows. К сожалению, Win32 API имеет вполне достаточное
количество вывертов и различий между тремя своими платформами доста-
точное для того, чтобы вы захотели (или даже были вынуждены) написать
свою собственную «обертку» для этого API. Подробнее об этом я также расска-
жу в главе 10.

Довольно чувствительной может оказаться проблема тестирования по-
вторно-используемого кода, разработанного вашей командой. Одним из пред-
полагаемых выигрышей от применения ранее изготовленных компонентов яв-
ляется уменьшение продолжительности циклов тестирования всего проекта в
целом. В самом деле, если вы используете компонент, проверенный ранее, то
зачем нужно тратить время и бить по нему молотком тестовых примеров снова
и снова? Я знаю как минимум пару серьезных оснований для того, чтобы это
делать.

Во-первых, границы между новым и повторно-используемым кодом очень
редко совпадают с границами тестовых задач. Например, вы. пишете класс для
работы со списком заказчиков, старательно тестируете его специальными
программами-примерами и в какой-то момент решаете, что этот класс дос-
таточно проверен и достоин помещения его в ваш арсенал повторно используе-
мых компонентов.   Позже,  когда вы приступаете к разработке реальной
программы - например, программы для работы со счетами и накладными -
вы используете в пей этот класс (совместно с другими классами). И при
тестировании этой программы вы вряд ли получите заметную экономию
времени, потому что вам придется писать множество новых тестовых примеров,
проверяющих именно совокупную работу всех частей и компонентов. Пожалуй,
единственным моментом, когда вы сможете реально сэкономить время за счет
повторного использования кода, является поиск причин обнаруженных ошибок
и сбоев - в этом случае вы можете смело концентрироваться прежде всего на
новом коде.

Во-вторых, после того, как компонент изготовлен и протестирован в изо-
ляции, часто обнаруживается, что последующее его использование в рабочем
проекте несколько отличается от того, что предполагалось изначально. Чаще
всего это отличие бывает чисто количественным, а не качественным. Например,
написанный ранее класс для работы со списком заказчиков планировался для
манипулирования всего лишь парой сотен элементов (и, следовательно, был
строго протестирован только для соответствующих объемов данных), а реаль-
ная программа должна иметь дело с десятками тысяч заказчиков. Это значит,
что вы должны снова потратить время на тестирование вашего компонента -
на этот раз в новых, изначально не предусмотренных условиях. В противном
случае вы сильно рискуете неожиданно попасть в засаду -- какая-нибудь
ошибка в дизайне или реализации вашего класса может «всплыть» именно то-
гда, когда вы попытаетесь занести в список больше, чем 32К элементов.

В условиях этой дилеммы с тестированием есть, пожалуй, только один
правильный путь: делать главный акцент все-таки на тщательном тестировании
компонентов в изоляции и добиваться от них максимально возможной надеж-
ности до того, как они будут применяться в реальном проекте. К сожалению,
как раз этот тип тестирования наиболее часто оказывается выброшенным за
борт, когда планы прижимают программистов к стенке: пытаясь рациона-
лизировать ситуацию, разработчики заявляют, что, если они потом достаточно
хорошо протестируют законченный продукт, то все ошибки будут найдены и
исправлены, а ведь только это и имеет значение в конечном итоге. На мой
взгляд, этот аргумент очаровательно противоречив, поскольку в действительно-
сти он эквивалентен такой формулировке: мы можем позволить себе реши-
тельно ослабить часть тестирования (и даже вообще исключить ее) за счет того,
что оставшаяся часть тестирования будет проведена супер-экстра-тщательно.

Повторное использование кода является важным приемом в
программировании любого типа, но в разработке для Windows его ценность ве-
лика, как никогда. Как я уже упоминал ранее, сегодня Windows-программи-
стам приходится ценой тяжелейших усилий поддерживать свои знания на том
уровне, который необходим. Поэтому очень серьезной подлюгой для вас может
стать именно разбивка работы на части и последующее использование получен-
ных компонентов, независимо от того, состоит ли ваша команда из нескольких
человек (разброс в пространстве) или только из вас одного, работающего над
несколькими частями проекта по очереди (разброс во времени). Кстати, в по-
следнем случае особенно велик соблазн отказаться от дополнительных усилий
по превращению того или иного фрагмента кода в настоящий компонент или
хотя бы в тривиальный LIB-файл. Но вы должны преодолевать этот соблазн и
но возможности находить время для создания и документирования компонен-
тов так, как если бы они заведомо предназначались для использования другим
человеком, которого вы никогда не встретите и которому не сможете потом
помочь; если вам это удастся, то позже - когда вы посмотрите на этот компо-
нент уже как пользователь, а не как автор, и когда настанет ваша очередь рас-
шифровывать его документацию и интерфейс - вы скажете себе огромное спа-
сибо.