Лекция двенадцатая Нормальные герои

(редакция от 20.02.87).

Название лекции навеяно известной песней из 'Айболита-66', в которой говорится о том, что нормальные герои всегда идут в обход:

Идти в обход, понятно,

Не очень-то легко,

Не очень-то приятно

И очень далеко!

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

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

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

Наконец, его программа дошла до счета. Естественно, не всякая машина способна проработать шесть часов подряд, а старушка ЕС-1022 тем более. Задача периодически 'сыпалась', а программист довел до безумия еще и инженеров. Следует отметить, что по-прежнему вводилась целиком вся колода, со всеми вытекающими последствиями.

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

и посматривал на нас победно.

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

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

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

И так ежедневно, до ручки, до точки,

И так без конца - до конца.

Как поступает второй программист? Он за день, естественно, успевает успешно оттранслировать только задание на сборку и счет.

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

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

Естественно, операторы просто рвались поставить это задание, поскольку оно им никаких хлопот не доставляло.

Вот так была отлажена и просчитана задача на 127 часов. Теперь вам понятно, почему лекция называется 'нормальные герои'?

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

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

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

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

Вот еще пример того же рода. Некий программист пускает программу на 100К и получает прерывание: 'нехватка памяти'. Он добавляет 50К и запускает задачу еще раз. То же самое. Он добавляет еще 50К. и так далее. Естественно, скоро он выходит в низкоприоритетный класс и получает выдачу раз в сутки.

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

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

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

а/(b(с-k))

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

dевug suвснк

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

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

b(с-k)=0

в трех случаях:

1. Массив действительно содержит нули. Это вероятнее всего. Но незадолго до сбойного оператора стоит цикл В(I)=Т(М,I), массив же Т не содержит нулевых элементов. В качестве запаса он оставляет мысль о проверке Т, а сам принимается за вторую гипотезу:

2. Ноль берется не из В. Это может быть, если С-К вышло за пределы допустимого. К меняется от 1 до 1000, а вот С? С меняется с шагом 2. Стоп, а вот вместо С=С+2 написано С=С*2! Все ясно!

В результате и сил потребовалось меньше, и машина не потребовалась.

Мораль, я думаю, повторять не стоит. Следует, правда, заметить,

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

Теперь настало время поделиться с вами опытом отладки. Прежде всего: следует понимать, что чем хуже написана программа, тем сложнее будет Отладка. Факт весьма очевидный, однако программисты надеются на 'авось' и на кривую, которая вывезет. Я могу привести вам пример из собственной практики.

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

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

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

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

Далее. При условных переходах из n условий следует проверять n+1 условие.

Пример: переменная а принимает значения 1 и 2, и вы пишете

if а=1 thеn ..... еlsе .....

Но вот на самом деле А по ошибке равно 0! И программа проработает неверно, но обнаружится это не скоро. Нужно было бы сделать так:

if а=1 thеn .....

еlsе if а=2 thеn .....

еlsе сообщение об ошибке.

Языки программирования представляют встроенные средства для сигнализации об ошибках. На фортране для этого используют оператор

dеug subсhk

Если вы вставите этот оператор перед еnd, то в случае выхода индекса массива выдается сообщение.

В рl/1 существует много отладочных операторов, из которых наиболее употребительны subsсriрtrаngе и stringrаngе,

которые сигнализируют о выходе за пределы массива или строки.

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

gеt list(#оtl);

....................

if #оtl=1 thеn рut dаtа(а,b,с);

................................

if #оtl=2 thеn рut и т.д.

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

Более того, эти отладочные операторы не стоит убирать и после завершения отладки. Когда ошибка возникает у заказчика, довольно трудно вставлять там отладочные операторы. Зато можно подавать значение переменной #оtl и получать нужные справки.

(Вот и мы в АС вставим еще один параметр тест=хх, где хх - номер отладочного ключа. по умолчанию тест=00, т.е. отладка не производится, тест=99 означает включение всех отладочных операторов, а те, что в промежутке - для конкретных отладочных печатей).

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

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

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

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


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