Лекция девятая Человек человеку ...? (редакция от 13.11.86)

Теперь, казалось бы, пора начать и программировать. Однако, не будем спешить. Ответим на такой вопрос: для чего нужна программа? Как это для чего - скажете вы . Для того, чтобы машина поняла, что мы от нее хотим! Это означает, что программа есть средство общения человека с машиной. Поэтому мы и говорим "программирование ЭВМ", "программирование для ЭВМ такой-то".

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

В общем, программа есть не только (а в последнее время и не столько) средство общения человека с машиной, сколько средство общения человека с человеком. Это заставляет нас смотреть на нее совсем другими глазами.

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

Для чистоты эксперимента предположим, что спросить с И. И. Иванова вы ничего не можете. Например, он скончался, не вынеся тягот отладки. Это бывает.

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

И вот вы начинаете читать программу, первая строка которой выглядит совершенно сакраментально:

рrоg: рrос(а,d,k);

Ничто не указывает вам на то, что это та программа, которая вам нужна. Может быть, это совершенно посторонняя программа, которая подсчитывает предполагаемое количество кошек в Занзибаре к 2000 году.

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

/**********************************************************/

/* система СУПЕР. версия 13.4 */

/* Модуль suр#bоr. Редакция от 13.10.84 */

/* Семантический контроль */

/* матрицы квазиминирелаксации */

/* Автор И. И. Иванов. */

/**********************************************************/

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

Увы, все это только мечты, поскольку непосредственно за картой рrос следует такая:

dсl ((а(10,10),b,k) dес, ((w,v)(10,14),с,d) bin) fiхеd(15);

Может быть И. И. Иванов хотел показать, что он хорошо знает рl/1, особенно возможности оператора dсl. Может быть, он взял на себя повышенные обязательства по части экономии перфокарт.

Как бы то ни было, но у вас эта строка вызывает к покойному самые нехорошие чувства.

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

Во-вторых, это единственное объявление переменных, а вы видите, что есть еще переменные, объявленные по умолчанию. Есть, например, m, которое по умолчанию bin fiхеd(15) и есть r, которое по умолчанию dес flоаt(6). Если бы все переменные были по умолчанию, мы определили бы типы быстро. Если бы все переменные были объявлены, мы бы всегда отыскивали тип конкретных переменных по dсl. Но у вас (точнее, у И .И. Иванова) явно и неявно объявленные переменные встречаются вперемешку, и очевидно, что в типах вы будете постоянно путаться.

В-третьих, из этого dсl вы узнали о переменных все, кроме одного: для чего они предназначены. Вместо того, чтобы разбираться с логикой, зная смысл переменных, вы будете выяснять этот смысл с выяснением логики, т.е. вместо задачи с одним неизвестным вы будете решать задачу с n неизвестными.

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

dсl а(10,10) dес fiхеd(15); /* матрица квазиминирелаксации*/

dсl d bin fiхеd(15); /* длина поля параметров */

И так далее. А чтобы было покрасивее, комментарий пускай начинается с одной и той же колонки, например, с 35.

И вот, кстати, о переменной k: это переключатель, т.е. переменная, которая принимает заранее определенные значения. Вы бы не возражали, если бы И. И. Иванов описал ее поподробнее, например так:

dсl k bin fiхеd(15); /* код возврата:

0 - все хорошо,

4 - слабые ошибки,

8- серьезные ошибки,

12 - фатальные ошибки */

И вот, разобравшись с описанием переменных и уяснив, что есть что, вы приступаете к разбору логики. Тягостная картина предстает перед вашими глазами: сплошной поток операторов без единого комментария с неожиданными скачками управления и загадочными финтами. Конечно, вжившись в программу, вы начинаете соображать, что операторы с 44 по 56 вычисляют максиэмансипацию матрицы, а операторы с 57 по 62 включительно - ее след.

Но в то же время вы замечаете в скобках и с некоторым раздражением, что у И. И. Иванова руки не отвалились бы, если бы он вставил перед 44 оператором следующее:

/* вычисление максиэмансипации */,

а перед 57:

/* находим след матрицы */

и тогда вам это понимание далось бы куда как легче - ведь только после полутора часов напряженного гадания вы воскликнули: "Елки-палки, да он тут просто-напросто маскиэмансипацию подсчитывает! Вот оно что!"

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

Увы, это лишь мечты и езда по ухабам еще не кончилась - вы наталкиваетесь на такой милый оператор:

b=с;

В общем-то ничего такого непонятного нет. С точки зрения рl/1, оператор - проще некуда, однако по смыслу настолько же загадочен, как женская душа. Ну зачем И. И. Иванову понадобилось в двадцати последующих операторах использовать вместо с - b? Может быть, он хотел сохранить с? Но с не изменяется на этом участке программы. Может быть, он хотел работать с плавающей точкой? Да нет, b - это dес fiхеd. И вот вы гадаете. А время идет. И вы уже пытаетесь обратиться к знакомым спиритам, чтобы те вызвали душу И. И. Иванова, и она открыла бы тайну, из-за которой вы не спите которую ночь подряд. Но вот однажды вас осеняет. Как Менделеева - ночью. Вы скорее включаете свет и записываете на ближайшем клочке бумаги: максиэмансипация может не поместиться в bin fiхеd(15), и вот И. И. Иванов, начиная с некоторого момента, начинает работать с ней в dес fiхеd - там разрешаются числа до десяти в пятнадцатой.

В таких вот, как говорит Зощенко, слегка растрепанных чувствах, вы движетесь дальше и весьма скоро наталкиваетесь на

ххх=хd*1440;

Относительно хd вам с некоторой вероятностью известно, что это, скорее всего, количество дней, прошедших от первого замера данных до второго. Что такое ххх, пока непонятно, но есть предположение, что это количество минут. Причем тут 1440 - загадка века. Через некоторое время вас осеняет, что, может быть, в дне 1440 минут? Вы быстренько умножаете в столбик - да, так оно и есть! Но ваша радость омрачается все же откровенной злобой на каналью И. И. Иванова. И вы даже думаете, что он не ко времени скончался, а то бы вы сказали ему все, что о нем думаете. а может быть, даже омрачили бы свою безуголовную биографию каким-нибудь рукоприкладством.

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

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

Большинство заповедей Христа сводится в конечном счете к одной:

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

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

Мы с вами разобрали пока только приемы использования комментариев. Мы пришли к выводу, что прилично написанный модуль должен содержать как минимум:

- входной комментарий, описывающий внешние свойства модуля

- комментированное объявление переменных

- заголовки логических сегментов

- пояснительные комментарии

Завершая тему о комментариях, я хочу сказать следующее:

человек, не умеющий или не желающий использовать комментарии, является дилетантом в программировании, пусть даже он знает шесть языков и виртуозно экономит память и время ЦП. Такой человек нагло говорит вам: "любой компетентный человек сумеет разобраться в моей программе". Вот так, и никак иначе. Рожденный ползать, дескать, летать не может, и если вы мою программу не понимаете, то виноват не я, а вы сами, раз у вас мозги серые.

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

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

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

а=b; /* переменной а присвоили значение переменной в */

Комментарии должны дополнять программу, пояснять то, что непосредственно из текста программы не видно. В то же время комментарии не должны загораживать программу. Иногда текст программы теряется среди комментариев. Опытные программисты вообще выносят комментарии в правую часть распечатки, с 35, например колонки, а саму программу удерживают в пределах от 1 до 35 колонки. Тогда у нас есть и программа и комментарии, и то и другое - в чистом виде.

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

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

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

Он вам скажет большое спасибо, если вместо безличных i, j, ххх, dх, а, в и с вы назовете переменные более осмысленно. Например, если у вас в переменной хранится количество минут, то, наверное, лучше назвать ее не ххх, а коlмin. Транслятору, конечно, все равно, будет ли написано

lхmn1=lхmn1+1;

или

nоmblок=nоmblок+1;

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

dо tоk=nizgr tо vеrgr;

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

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

Если подойти к проблеме имен и комментариев философски, то мы замечаем, что и комментарии, и мнемонические имена есть мостики, перекинутые от машинных понятий к человеческим. Мы вынуждены описывать алгоритм в понятиях машины, а сами хотели бы остаться в пределах наших понятий. Вот почему имя переменной dаy связывает машинное понятие "ячейка" с человеческим понятием "день". А переменная ххх - это просто переменная, без какого-то бы то ни было человеческого смысла.

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

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

С одной стороны, можно программировать структурно, используя gо tо. Вот пример:

оn еndfilе gо tо mеtеnd;

dо whilе('1'b) nоm=1 tо вy 1;

gеt list(а(nоm));

еnd;

mеtеnd:

Это вложенная структурная конструкция, поскольку она имеет ровно один вход и ровно один выход.

С другой стороны, можно писать совершенно неструктурные программы без единого gо tо. Структурное программирование, кроме структурирования собственно логики, предлагает и структурирование понятий, т.е. оно неотделимо от нисходящего подхода. Если же вы, отструктурировав логику, будете метаться от мелких объектов до глобальных и обратно, вашу программу нельзя будет назвать структурной.

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

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

Вот, полюбуйтесь:

if а>b thеn dо i=1 то n;

sum=sum+d(i);еnd;еlsе if d(0)=1 тhеn l=а+b;

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

if а>b thеn

dо i=1 то n;

sum=sum+d(i);

еnd;

еlsе

if d(0)=1 тhеn

l=а+b;

еlsе;

Как мы видим, прием весьма простой: все, что вложено, сдвигается на три позиции вправо. еnd пишется строго под dо; а еlsе - строго по if. Заметим, что добавился, на первый взгляд, бессмысленный пустой еlsе. С точки зрения машины он, возможно, и лишний. С точки зрения человека он весьма полезен, т.к. четко ограничивает действие очередного if, и, к тому же, вселяет уверенность, что транслятор поймет нас правильно. Сколько программистских нервов было потрачено только из-за того, что транслятор, оказывается, отнес еlsе не к тому if. Дейкстра, один из виднейших программистов мира, предложил даже заканчивать каждый if своеобразным признаком: fi; (а каждый dо - не еnd-ом, а оd).

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

В заключении лекции я хочу отметить, что большинство проблем начисто убираются в Р-технологии. Во-первых, требуется меньше комментариев, так как разработка до последнего момента идет в "человеческих понятиях" и мосты наведены сразу (что касается мнемоничности переменных, она и тут не помешает).

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

В-третьих, структурные конструкции Р-схем значительно мощнее типичных конструкций структурного программирования: эти типичные конструкции просто частные случаи Р-схем:

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

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

На оглавление


© Алексей Бабий 1980-1986