авторефераты диссертаций БЕСПЛАТНАЯ БИБЛИОТЕКА РОССИИ

КОНФЕРЕНЦИИ, КНИГИ, ПОСОБИЯ, НАУЧНЫЕ ИЗДАНИЯ

<< ГЛАВНАЯ
АГРОИНЖЕНЕРИЯ
АСТРОНОМИЯ
БЕЗОПАСНОСТЬ
БИОЛОГИЯ
ЗЕМЛЯ
ИНФОРМАТИКА
ИСКУССТВОВЕДЕНИЕ
ИСТОРИЯ
КУЛЬТУРОЛОГИЯ
МАШИНОСТРОЕНИЕ
МЕДИЦИНА
МЕТАЛЛУРГИЯ
МЕХАНИКА
ПЕДАГОГИКА
ПОЛИТИКА
ПРИБОРОСТРОЕНИЕ
ПРОДОВОЛЬСТВИЕ
ПСИХОЛОГИЯ
РАДИОТЕХНИКА
СЕЛЬСКОЕ ХОЗЯЙСТВО
СОЦИОЛОГИЯ
СТРОИТЕЛЬСТВО
ТЕХНИЧЕСКИЕ НАУКИ
ТРАНСПОРТ
ФАРМАЦЕВТИКА
ФИЗИКА
ФИЗИОЛОГИЯ
ФИЛОЛОГИЯ
ФИЛОСОФИЯ
ХИМИЯ
ЭКОНОМИКА
ЭЛЕКТРОТЕХНИКА
ЭНЕРГЕТИКА
ЮРИСПРУДЕНЦИЯ
ЯЗЫКОЗНАНИЕ
РАЗНОЕ
КОНТАКТЫ


Pages:     | 1 |   ...   | 5 | 6 || 8 | 9 |

«Б. МЕЙЕР, К. БОДУЭН МЕТОДЫ ПРОГРАММИРОВАНИЯ 2 Перевод с ...»

-- [ Страница 7 ] --

VIII.4.2.2. Доказательства: их роль и границы То, что можно доказать в математическом смысле слова некоторые свойства программы, и в частности правильность выходных утверждений, исходя из правильности входных утверждений, не является открытием для читателя этой книги. В разд. III.4 мы ввели аксиоматические свойства управляющих структур и основных операторов, принадлежащие Хоару. Эти свойства позволяют показать, что некоторые утверждения удовлетворяются в определенных точках программы. Эти свойства часто использовались в предыдущих главах при изложении алгоритмов, и в частности понятия инварианта цикла.

Уточним, что аксиоматика Хоара не является единственной системой, позволяющей обрабатывать программы как формальные объекты со строго определенными математическими свойствами. Среди соперничающих систем можно указать семантику Скота–Стречи [Донэгю 76], [Теннент 76], [Стой 74];

метод «символического выполнения» [Хантлер 76];

метод абстрактных интерпретаторов (Венский язык) [Вегнер 72].

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

Следовательно, нет априорной причины предполагать, что доказательство программы имеет больше шансов быть правильным, чем само программирование.

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

[Минский 67];

некоторые основные идеи даны в упр. III.11 и в разд. VI.1.3 этой книги).

194 Глава VIII Означает ли это тогда, что доказательства не представляют никакого интереса для программиста? Ни в коем случае! Из оговорок, высказанных ранее, следует, что (вообще говоря) напрасно пытаться доказать апостериори корректность уже существующей программы. Напротив, исключительно плодотворный метод, впервые предложенный в книге [Дейкстра 68а], состоит в написании программы, исходя из доказательства, или, в более общем виде, в одновременном построении программы и доказательства. Мы пытались несколько раз применять этот метод, в частности при из ложении Деления (для Быстрой Сортировки) в VII.3.6 и дихотомического поиска в VII.2.3. Формализм Хоара особенно хорошо для этого подходит;

в частности, мы видели тесную связь между понятием цикла – динамического элемента, являющегося частью программы, – и понятием инварианта цикла – статического элемента, принадлежащего доказательству. В самом деле, цикл является реализацией инварианта и условия останова (можно вспомнить перевод свойства Z0, использующего кванторы и в цикле Z1);

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

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

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

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

снабжать эти утверждения убедительными аргументами, дающими наброски возможных доказательств;

относиться с особым вниманием к циклам, возможно чаще указывая соответствующие им инварианты;

строго строить самые «тонкие» элементы программы и те, правильность которых особенно важна, полностью доказывая при этом их правильность;

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

Однако время от времени мы выбираем страницу, описывающую, например, важный момент действия, и пытаемся ее подробно проанализировать и, может быть, перевести, чтобы убедиться, что в принципе мы все можем понять.) Идея, согласно которой программа должна быть написана так, чтобы уметь оправдать ее с помощью доказательства, нам кажется, очень способствует улучшению ее качества. В этой связи очень важно, чтобы каждый программист хотя бы один раз в жизни самостоятельно полностью доказал правильность какой–нибудь нетривиальной программы. Среди опубликованных доказательств с большой пользой можно прочитать доказательство правильности программ ПОИСКА [Хоар 71а] и БЫСТРОЙ СОРТИРОВКИ [Хоар 7Id].

Связь с предыдущим разделом устанавливается, если заметить, что На путях к методологии использование языка Z приводит к такому методу программирования, при котором первый этап, Z0, легко уподобить полной редакции доказательства, а следующие, Z1 – получению программ, все более близких к выполняемой версии, из статического «доказательства».

VIII.4.2.3. Сравнение с традиционными методами Можно было бы спросить: в чем различие между предложенной методикой и обычным программированием? Некоторым образом всякая программа предполагает более или менее сознательное доказательство своей правильности;

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

ISIG = IF(Y – X.GT. EPSILO)ISIG = IF(Y – X.LT. –EPSILO)ISIG = – являются «алгоритмическими» реализациями утверждения, что «целая переменная ISIG должна иметь значение 0, если X и Y отличаются друг от друга самое большее на EPSILO, и в противном случае +1, если X Y. и –1, если X Y», это утверждение может находиться в голове программиста в виде Рис. VIII.5:

Рис. VIII. Это замечание поясняет принцип упомянутого метода программирования: в основном речь идет о том, чтобы явно выразить все предположения, которые обязательно имеются в сознании програм–миста при написании программы и которые вначале могут не быть ясно сформулированными. Например, по внешнему виду элемента программы на ФОРТРАНе IF(I. EQ. LIMITE) H = 0. IF(I.NE. LIMITE) Н = 1./SQRT(FLOAT(I– LIMITE)) ясно, что программист считал очевидным отношение I LIMITE в этом месте программы (если только квадратный корень не является неопределенным). Важно перевести на сознательный уровень предположения подобного рода–и, разумеется, в момент написания программы, а не после. В данном случае первым следствием могла бы быть замена.NE. на.GT., но этого недостаточно, так как H не было бы в этом случае определено в результате несомненной ошибки при I LIMITE. Вторая реакция, и, несомненно, лучшая, состоит в том, чтобы написать утверждение "ЗДЕСЬ I = LIMITE" в качестве комментария в соответствующем месте программы и формально убедиться в его правильности. Рекомендуется на этом не останавливаться и включить динамический текст, проверяющий при выполнении (по меньшей мере на стадии отладки), что I LIMITE. Программисту особенно удобно проделывать проверки такого рода, когда существует специальное языковое средство для этого–как, например, в АЛГОЛе W оператор ASSERT.

VIII.4.2.4. Оператор ASSERT в АЛГОЛе W Как бы ни был программист убежден в правильности программы, выраженной убедительным образом при помощи промежуточных утверждений, он не считает себя 196 Глава VIII непогрешимым и хотел бы, чтобы его утверждения иногда на деле сталкивались с действительностью. Что может быть проще: достаточно соотнести им тесты в окончательной программе.

Это можно осуществить на любом языке:

если утверждение неправильно то печатать соответствующее сообщение об ошибке останов программы Однако на практике мы в большей степени будем склонны выполнять верификацию, если в языке имеется более простой синтаксический оборот. Например, в АЛГОЛе W оператор ASSERT условие выполняет проверку того, что значение условия является истинным (TRUE), и если нет, то останавливает выполнение программы, напечатав сообщение о том, что произошло.

Можно найти (искусственный) пример использования оператора ASSERT на выходе из программы Быстрой Сортировки (VII.3.6) на Рис. VIII.6, другие элементы которого станут понятны после чтения разд. VIII.4.2.5.

0107 | PROCEDURE TRIREC(INTEGER VALUE I, J) 0108 215.--| BEGIN 0109 | INTEGER S;

0110 | IF J – I = SEUL_DE_TRI_SIMPLE THEN 0110 108.--| TRI_SIMPLE(I, J) 0110 || ELSE 0110 107.--| BEGIN S:= PARTITION(I, J);

0112 | IF S – I J – S THEN 0112 26.--| BEGIN TRIREC(I, S – 1);

0114 26.--| TRIREC(S + 1, J) 0114 || END 0114 | ELSE 0114 81.--| BEGIN TRIREC(S + 1, J);

0116 81.--| TRIREC(I, S + 1) 0116 | END 0116 | END 0016 | END 0117 | NIVEAUPILE := 0;

SEUL_DE_TRI_SIMPLE := 14;

TEMPS := TIME(1);

REMPLIR_LE_TABLEAU;

0121 | IMPRIMERTABLEAU;

0122 | BEGIN 0123 | TRIREC(1, N);

0124 1.--| IMPRIMERTABLEAU;

IMPRIMER_LE_TEMPS;

WRITEON(” SEUL :”, SEUL_DE_TRI_SIMPLE, ”)”);

0127 | SEUL_DE_TRI_SIMPLE := SEUL_DE_TRI_SIMPLE + 1;

REMPLIR_LE_TABLEAU;

---- ERROR ------------------------------------------------------------------------------------------ ASSERT 0 ¬= 0;

0129 | ---- ERROR ------------------------------------------------------------------------------------------ 0130 | END;

0131 0.--| END 0131 | END VALUES OF LOCAL VARIABLES TAB(1) = 8414 TAB(2) = 9092 TAB(3) = 1411 TAB(4) = TAB(5) = 9590 TAB(6) = 2795 TAB(7) = 6569... TAB(1000) = NIVEAUPILE = PILE(1) = ? PILE(2) = ? PILE(3) = ? PILE(4) = ?

PILE(5) = ? PILE(6) = ? PILE(7) = ? PILE(30) = ?

BLOCK WAS ACTIVATED FROM (MAIN). NEAR COORDINATE Рис. VIII.6. Использование оператора ASSERT в АЛГОЛе W и фрагмент трассировки.

Когда программируют на языке АЛГОЛ W и начинают использовать такие операторы ASSERT (вначале для очистки совести), вызывает удивление полезность этого оператора и число ошибок, которые он позволяет немедленно «фильтровать», – обычно простые и «глупые» ошибки, но только такие и остаются или должны оста ваться, если пытаться программировать внимательно.

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

На путях к методологии (Упражнение: обсудите, не ожидая разд. VIII.4.7, довод: «это уменьшает эффективность».) VIII.4.2.5. Надо ли тестировать программы?

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

Сам принцип тестов, однако, живо оспаривался [Дейкстра 72], [Дейкстра 72а].

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

даже если множество данных конечно, его размеры настолько велики во всех реальных случаях, что было бы напрасным надеяться выполнить исчерпывающий тест. Классическим примером служит программа, выполняющая умножение двух целых чисел (если предположить, что эта операция действительно требует программу): если целые числа представлены 32 битами, имеется 232 232 = 264 2 1019 возможных случаев, и тогда, если выполнять одно умножение в секунду (!), понадобится около 1014 лет для верификации тривиальной программы. Другими словами, в соответствии с формулой Дейкстры «тесты могут служить для демонстрации наличия ошибок, но не их отсутствия».

Дейкстра из этого делает вывод о необходимости доказательств правильности программ и о «бесполезности» тестов.

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

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

Достаточно ли в соответствии с этими нападками полностью исключить тесты из практики информатики? Это было бы не серьезно. Никто не выпустил бы, например, программу управления движением поездов, не протестировав ее интенсивно с помощью моделирования. Более чем кто–либо другой, программист на опыте знает, что человеческий ум не является непогрешимым и подвержен самовнушению. С другой стороны нет оснований априори предполагать, что даже самое строгое доказательство имеет меньшую вероятность содержать ошибки, чем программа. Действительно, изучение языка Z (VIII.3.5) нам подсказывает, что доказательство можно рассматривать как программу определенного уровня, подверженную тем же ошибкам, что и другие уровни. Таким образом, важно не пренебрегать никаким методом, который помогает гарантировать правильность программы. И если тесты рассматривать с этой точки зрения, то им сразу найдется настоящее место: они могут рассматриваться как вспомогательная техника, помогающая находить ошибки, которые могли бы пройти незамеченными на предыдущих стадиях отладки.

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

Заметим, что построение представительных «отладочных наборов» для данной программы–задача нетривиальная: надо выбрать множество случаев, позволяющее протестировать если не каждую ветвь программы (так как число возможных случаев бесконечно, когда программа содержит неопределенные циклы), то хотя бы представительное множество. По этим вопросам можно обратиться, например, к статьям [IEEE 76]. С практической точки зрения заметим, что в большой группе программистов необходимо поручать тестирование элемента программы не тем программистам, которые его писали, а другим. Ведь порой программисты при написании программы имеют в виду более или менее сознательно не обязательно самый общий случай обработки и тестируют лишь этот частный случай.

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

VIII.4.2.6. Средства диагностики при выполнении Как мы уже сказали, всякому программисту легко себя убедить, что «человеку свойственно ошибаться» и что ошибки происходят часто. Смирившись с таким положением вещей – что может причинить серьезный ущерб его самолюбию, – программист имеет право ожидать от используемой им системы, что она поможет ему найти допущенные ошибки, понять их происхождение и устранить их. Под «системой» здесь следует понимать множество средств, к которым программист имеет прямой или непрямой доступ, – используемую им «виртуальную машину», состоящую из технических средств, операционной системы ЭВМ, транслятора и «системы ис полнения», которая, если она существует, осуществляет контроль за ходом программы пользователей.

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

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

переполнение выделенной в памяти области, являющееся, например, следствием недопустимой индексации в массиве;

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

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

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

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

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

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

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

d) другое необходимое средство, разумеется являющееся дополнительным в системе, но действующее не только когда ошибка уже произошла, это след выполнения, или прокрутка, печатающийся в конце выполнения программы и указывающий, сколько раз каждая группа операторов выполнялась. На Рис. VIII.6 (стр. 196) показан пример такого «следа», выданного системой АЛГОЛ W. Здесь группы операторов, для которых число выполнений вынесено на поля, соответствуют блокам программы, «сверстанной» системой выполнения в соответствии с правилами смещений, близкими тем, которые приняты для представления программы в этой книге.

В примере на Рис. VIII.6, соответствующем случаю ошибки, указывается место этой ошибки.

Такая прокрутка является очень важной информацией для улучшения программ.

Фрагмент программы, выполненный 0 раз, немедленно привлекает внимание;

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

см. [Инглз 71] для систем такого типа, применяемых на ФОРТРАНе.

Перечисленные средства были описаны в предположении, что ЭВМ работает в Мы обсуждаем здесь лишь ошибки, обнаруженные при выполнении. Само собой разумеется, что всякий хороший транслятор должен давать ясную и явную диагностику ошибок, обнаруживаемых на стадии трансляции, а также полезные для программиста сведения: таблицу символов, таблицу перекрестных ссылок, бесполезные операторы или переменные и т.д. На деле большинство трансляторов знают о программе больше, чем они говорят программисту [Хорнинг 75], [Касьянов 78];

это, в частности, случай трансляторов, называемых «оптимизирующими» [Кок 70], которые проводят чрезвычайно глубокий анализ программы, что позволяет им обнаружить нелогичности, такие, как наличие переменной, значение которой может быть использовано прежде, чем будет установлено его начальное значение, объявленные, но не использованные переменные, недоступные части программы и т.д.

200 Глава VIII «пакетном» режиме, когда пользователь не может вмешиваться в ход своей программы.

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

Заметим, что все описанные средства имеются (за исключением измерения процессорного времени) в системе АЛГОЛ W для ИБМ 360/370 и совместимых с ними машин [Саттеруэйт 72].

Показательно, что этот транслятор предназначался в основном для учебных целей, так как «профессионалы» считаются слишком серьезными людьми, чтобы тратить время на такие смешные предосторожности. Немного перефразировав метафору Хоара [Хоар 73], можно сказать, что все происходит так, как если бы на учебных самолетах была бортовая радиоаппаратура, а на рейсовых нет!

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

Можно, однако, предложить два серьезных ответа на вопрос об эффективности:

a) Управляющая система реализуется только в виде вспомогательной версии;

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

Заметьте, что в этом есть отмечавшийся уже парадокс: как только вещи ус ложняются, мы уничтожаем всякий контроль, и... была не была!

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

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

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

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

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

из–за своего рода профессиональной гордости, которая выливается порой в свое образные соревнования («задача о восьми ферзях из 7 операторов и 35 литер»).

Некоторые аспекты языка АПЛ являются иллюстрацией этой позиции.

Поставить крест на этом не так–то легко: эта позиция соответствует реальной психологической и социальной проблеме–потребности программиста утвердить свою техническую квалификацию перед другими и перед самим собой. Добиться признания своей квалификации порой трудно, поскольку программирование на ЭВМ кажется со стороны такой простой задачей–особенно, если после недели освоения БЭЙСИКа вычисляют четырнадцатый десятичный знак числа е!

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

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

Пусть дана подпрограмма на ФОРТРАНе SUBROUTINE ERNEST (А, В, М, N) с объявлениями INTEGER M, N REAL A (M, N), В (М, N) Чтобы переписать в А значения элементов В в теле подпрограммы, запишем (учитывая случай, когда массивы пусты):

IF (M.LE.0.OR.N.LE.0)GOTO DO 200 I = 1, М DO 100 J = 1, N A(I, J) = B(I, J) 100 CONTINUE 200 CONTINUE 1000 продолжение программы На ПЛ/1 или на АПЛ мы бы просто написали (с точностью до синтаксических различий) АВ Можно спросить, какая из двух форм более ясная;

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

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

202 Глава VIII VIII.4.3.2. Стиль и выражение Надо найти, таким образом, как можно более простые и естественные формы выражения, позволяющие читателю установить непосредственное соответствие между текстом программы и тем, что происходит при ее выполнении. Всякий программист должен сосредоточить свое внимание на этом основном свойстве программы.

Одним из ключевых моментов стиля и выражения программы является, очевидно, знаковая система, в которой она представлена, – язык программирования.

Ясно, что выбор языка является решающей проблемой, а читаемость–далеко не единственный критерий;

действительно, приходится выбирать всю «систему програм мирования» целиком, и решение зависит от качеств транслятора, простоты его использования, возможностей его диагностики, его связей с операционной системой управления исполнением (VIII.4.2.6), «переносимости» и т.п. Не обсуждая сейчас важный вопрос сравнительной оценки языков программирования, напомним, однако, что решающим критерием, связанным с избыточностью, является возможность статического контроля. Правила языка должны позволять обнаружить ошибки как можно быстрее и, в частности, при трансляции, а не при выполнении. Этот критерий дает преимущество языкам, где понятие типа играет важную роль. В частности, мы видели, насколько соглашение в ФОРТРАНе или ПЛ/1, по которому идентификатор не обязательно должен быть объявлен, может оказаться опасным. В противоположность тому, что происходит в АЛГОЛе, ПАСКАЛе или СИМУЛЕ, неправильная орфография идентификатора на ФОРТРАНе или ПЛ/1 может остаться незамеченной при компиляции. Однако ошибки тем труднее понять, чем позднее в процессе написания и отладки программы они обнаружены.

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

a) управляющие структуры. Самый трудный элемент при чтении программы это, без сомнения, статическое понимание динамической последовательности операторов. Поэтому исключим анархистские операторы перехода типа GOTO, которые мешают последовательному чтению программы снизу вверх и слева направо;

никто не может одновременно идти двумя путями и тем более четырьмя, восемью, шестнадцатью,... Ограничимся небольшим числом основных структур, таких, как структуры гл. III;

сделаем это, даже если используемый язык программирования более примитивен – в этом случае потребуется «перевод», который, разумеется, выявит (снабдив их комментариями) структуры низкого уровня, такие, как GOТО, но совсем в ином контексте (ср. программы на ФОРТРАНе, разд. VII.3);

b) использование обозначения высокого уровня. Последнее замечание позволяет подчеркнуть, что обычные языки программирования вообще не являются достаточными средствами выражения алгоритмов. Это очевидно в случае ФОРТРАНа и КОБОЛа, например, но верно также и для ПЛ/1, АЛГОЛа и т.д. Все эти языки слишком ориентированы на связь с машиной и слишком подвержены влиянию оправданных или неоправданных исторических решений, чтобы не создавать препятствий свободному и естественному выражению алгоритмов – препятствий, к которым в большинстве языков добавляется еще то, что они основаны на английском языке. Абсурдно и опасно программировать непосредственно на ФОРТРАНе, например;

предпочтительно было бы использовать нотацию высокого уровня, близкую к французскому (русскому. – Перев.) языку, типа той, что использована в этой книге. Такая нотация весьма трудна при отладке;

ее следует принимать для «человеческого» общения, она должна На путях к методологии быть ясной и естественной, но в то же время и достаточно строгой, чтобы ее без особого труда можно было перевести на существующие языки программирования;

c) выбор имен. Программа составлена из заренее определенных символов (операторов, зарезервированных имен и т.д.) и имен, выбранных программистом (имен переменных, подпрограмм, меток и т.д.).

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

Можно оспаривать это правило, заметив, что оно опасно: имя переменной не оказывает никакого влияния на ход программы. Программа, в которой все вхождения идентификатора X заменены на Y (в предположении, что это не вызовет конфликта с именем какого–нибудь другого объекта программы), остается строго неизменной по своей семантике и будет идентичным образом обра ботана компилятором. Программа может быть неправильной, хотя, судя по идентификаторам, можно считать, что она корректна, что может ввести программиста в заблуждение. Например, вследствие ошибки, приведшей к замене знака операции «» на «», переменная МАКСИМУМ– МАССИВА могла бы в действительности обозначать минимум, и было бы трудно обнаружить ошибку из–за особенно выразительных имен. К этому примыкает поставленная Парнасом проблема имен модулей (VIII.3.4), и с такой же проблемой мы сталкиваемся в связи с комментариями. Однако нам не кажется, что эта опасность серьезно противостоит изложенным выше принципам. Чтобы в этом убедиться, достаточно рассмотреть результат одного из следующих опытов:

- взять любую нетривиальную программу (например, в гл. VII) и заменить все идентификаторы выбранными случайным образом именами: u73t, abzef5, zzxy549u,...;

- поменять в программе Реорганизация (из программы Древесной Сортировки, VII.3.7) идентификаторы «правый» и «левый» и соответственно «сын» и «отец».

Конечно, не все языки предоставляют всегда очень широкий выбор для идентификаторов, и 6 литер ФОРТРАНа являются серьезным препятствием (не говоря уже о соглашениях в БЭЙСИКе). Это ограничение не служит серьезным оправданием для распространения в программах таких имен, как ЕРУНДА, ШТУЧКА и т.д., или использование А, АА, AAA, AAAA – подлинно мазохистская практика, поскольку очень возможна опечатка или ошибка при написании. С этой точки зрения заметим, что, если используются похожие имена, психологически более надежно делать их различающимися в начале, а не в кон це, т.е. писать XПРОЕКЦИЯ и YПРОЕКЦИЯ а не ПРОЕКЦИЯХ и ПРОЕКЦИЯY;

надо также вспомнить, что в таких языках, как ФОРТРАН (и в меньшей мере в ПЛ/1), которые не требуют явного объявления переменных, создаваемые чаще всего трансляторами списки переменных или массивы перекрестных ссылок являются важным средством обнаружения ошибок при редактировании программ.

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

А;

WHILE В DO BEGIN С;

D;

IF E THEN BEGIN F;

FOR G : = H UNTIL I DO J;

K;

IF L THEN M END ELSE N;

O;

WHILE P DO BEGIN Q;

IF R THEN S;

Т;

U END;

V END;

Рекомендуется всегда располагать программы с помощью этого метода или эквивалентного соглашения;

это особенно важно в языках, где структура не следует непосредственно из синтаксиса:

С /ПОКА А ПОВТОРЯТЬ/ 100 IF (.NOT.A) GOTO опер опер С /ЕСЛИ В ТО/ IF (.NOT.В) GOTO опер опер GO TO С /ИНАЧЕ/ 150 опер опер опер 170 CONTINUE опер опер GOTO На путях к методологии С 200 CONTINUE опер опер И однако, сколько программ на ФОРТРАНе предстает перед теми, кто их читает, идеально выровненными «по 7–й колонке», полностью маскируя использованные управляющие структуры!

e) объем программных модулей. Все языки программирования позволяют разбивать программы на элементарные единицы, называемые «подпрограммами», «функциями», «процедурами» и т.д. В зависимости от ситуации программист имеет более или менее богатые возможности. В АЛГОЛе W или в ПЛ/1, например, он может объявлять подпрограммы вну три блока или другой подпрограммы, совершая иерархию декомпозиций;

в ФОРТРАНе и КОБОЛе это не так, и все программные модули имеют одинаковый уровень. Каков бы ни был используемый язык, программный модуль должен оставаться тем не менее легко понимаемым объектом.

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

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

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

VIII.4.3.3. Комментарии «Комментируйте ваши программы», – говорят теперь программистам.

Рекомендация похвальна и будет одобрена любым, кому приходилось бы погружаться в программу на ФОРТРАНе из 8000 операторов без единого комментария. К несчастью, здесь тоже мало одних благих намерений, и не все комментарии одинаково полезны. Многие комментарии представляют собой всего лишь «шум» и не несут никакой информации, как в следующем примере (синтаксис ПЛ/1):

/* ПРИБАВИТЬ 1 К ПЕРЕМЕННОЙ I */ I = I + 1;

Такие нередкие, впрочем, комментарии бесполезны и могут служить помехой, так как они затемняют программу в ущерб важным и тонким элементам. Некоторые специальные журналы регулярно публикуют программы, представленные с помощью блок–схем (мы забегаем вперед и обращаемся к вопросу о документации), а затем в виде эквивалентных программ на ПЛ/1;

переход от блок–схем к программам состоит как раз в основном в замене элементов типа 206 Глава VIII I := I + операторами I = I + 1;

Можно усомниться в полезности первой формы (в которой к тому же использовано обозначение, отличающееся от принятого). Точно так же можно спросить себя, необходимо ли рисовать F c V a чтобы растолковать DO WHILE с;

а;

END;

(где с – данное условие, а а – данное действие). Первая форма здесь имеет менее высокий уровень, чем вторая, и скорее затемняет, чем поясняет.

Бывают бесполезные комментарии;

могут существовать и явно вредные, в частности, если они неверны, когда противоречат программе, как в следующем примере, приведенном в книге [Керниган 74]:

/* ПРОВЕРИТЬ ЯВЛЯЕТСЯ ЛИ ЧИСЛО НЕЧЕТНЫМ*/ IF MOD (X, 2) = 0 THEN DO;

SOMME = SOMME + X;

COMPTEPAIRS = COMPTEPAIRS + 1;

END;

Здесь программа обрабатывает четные числа в противоположность тому, что говорит комментарий. Читатель попадает в неудобное положение: либо он верит комментарию, не проверяя его соответствия программе, и будет введен в заблуждение относительно поведения программы;

либо он обнаруживает несогласованность, и в этом случае комментарий бесполезен, так как пришлось подробно Прочитать программу;

неясно, какая из этих возможностей лучше.

Бывают, следовательно, плохие комментарии. А существуют ли хорошие?

Конечно, но и в этом случае не следует давать общих правил. Особенно необходимыми представляются три типа комментариев:

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

b) комментарии также очень полезны для выражения утверждений, На путях к методологии доказываемых в определенных местах программы. Мы видели, однако, что может оказаться полезным превратить эти утверждения в управляющие операторы (ASSERT). Для так называемых «входных» и «выходных»

утверждений подпрограмм комментарии особенно желательны;

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

c) при современной концепции программирования, основанной на поэтапном составлении программы (будь то уровни абстракции в нисходящем программировании, или статические и динамические аспекты в Z, или просто последовательные версии в результате преобразований программы), комментарии являются идеальным средством выражения, позволяющим в окончательной программе отразить эти этапы. Например, при нисходящем подходе элемент программы, полученный на уровне n, является уточнением виртуального оператора уровня n – 1;

поэтому естественно записывать его как параграф, заголовок которого, оформленный комментарием, как раз является этим виртуальным оператором. В качестве примера не полностью развернутая версия Быстрой Сортировки может иметь вид {рекурсивная сортировка массива a[i : j]} если j i то разделить массив индексом s;

Быстрая Сортировка (a[i : s – 1]);

Быстрая Сортировка (a[s + 1 : j]) На следующем уровне, предположив, что мы уточнили виртуальный оператор разделить, получаем {рекурсивная сортировка массива a[i : j]} если j i то {разделить массив индексом s} u i;

v j;

пока u v повторять … {ср. VII.3.6} s. u;

Быстрая Сортировка (a[i : s – 1]);

Быстрая Сортировка (a[s + 1 : j]) Точно так же, если используется язык Z, то «статические» версии, естественно, предлагают свои определения множеств, функций и отношений в качестве комментариев для последующих версий. Если опираться на элементы доказательства, то всякий зачаток довода в пользу правильности, как бы ни был он нестрог, всякий ин вариант цикла даст полезные и эффективные комментарии.

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

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

Эта же идея должна применяться к инструменту, обобщающему понятие комментария, – к документации.

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

Предлагались многочисленные методы документации;

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

Мы не предлагаем теории документации. И все же следующие правила представляются достойными размышления:

a) всякая программа, которая предназначена для использования несколько раз или несколькими лицами, должна иметь документацию (или в этой фразе не является исключающим);

b) документация должна составляться одновременно с программой;

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

c) документация – это трудная задача, требующая особых качеств и времени. В любом проекте программирования, выходящем за рамки обычного, группа программистов должна включать специалистов в этой области, хотя и не должна состоять исключительно из них. Мы вернемся к этому вопросу в разд. VIII.5.1;

d) внешняя документация особенно важна, поскольку она дает возможность пользоваться программой;

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

Это спецификация, и ясно, что упор надо делать на формализмы специфика ций алгоритмов, таких, как Z0, хотя, без сомнения, желательны формы выражения, более близкие к обычному языку;

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

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

VIII.4.4. Надежность;

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


Самый простой пример «анормальных» условий–ситуация, в которой программе дают необычные данные (ср. склоны Гималаев);

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

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

Обычно такие ошибки поспешно приписывают ЭВМ: «Вычислительная машина ошиблась». Публика остается этим удовлетворенной;

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

VIII.4.4.2. Защищающее программирование Принципы, развитые в предыдущих разделах, основаны на одной общей идее, которую можно резюмировать так: ошибка–это реальное явление и лучше попытаться с ней примириться. Перед лицом такой печальной ситуации естественно сделать программирование «защищающим»–идея состоит в том, чтобы предвидеть в программе не идеальное, а такое реальное окружение, в котором все может произойти. В какой–то мере следует возвести в ранг принципа программирования так называемое правило 210 Глава VIII Мэрфи, по которому «если худшее может случиться, то оно произойдет». Ошибки, против которых пытаются вооружиться, могут быть разного рода: ошибки программ, ошибки данных, к которым надо добавить проблему предельных случаев и возможные ошибки аппаратуры. Природа их самая разнообразная.

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

a) составляя программу, внимательно задуматься, как она будет использоваться: предусмотреть естественные и гибкие форматы ввода данных, ясные и понятные результаты и диагностические сообщения об ошибках;

печатать данные по мере того, как они читаются. Сколько программ считывают свои данные в жесткой (колонки перфокарты с 17–й по 22–ю) и не очень логичной форме (на каждой карте целое из 6 литер, действительное – из 7 литер, два целых из 4 и 8 литер соответственно...);

сколько из них, чтобы считать n элементов массива t, где n может изменяться от выполнения к выполнению, используют конструкцию типа читать n;

для i от 1 до n повторять читать t[i] заставляющую пользователя пересчитывать свои данные, открывая тем самым двери для любых возможных ошибок, в то время как совсем просто заставить ЭВМ, читая t[i], считать до установленного предела...

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

Стоит, однако, указать, что не все программы и подпрограммы могут регулярно проверять правильность всех данных, которые им передаются;

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

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

На путях к методологии Ошибки программы Стратегия защиты от ошибок программы была уже изложена в VII.4.2.4–5–6 в связи с рассмотрением средств контроля при выполнении, и в частности оператора ASSERT. Если эти средства,.не существуют в системе, программист всегда более или менее успешно сможет их добавить. Ограничимся здесь тем, что напомним основную идею: речь идет о том, чтобы сделать как можно более явными те мысленные предположения, которые предшествуют написанию программы, и превратить их затем в динамические проверки. Такую ситуацию представляет, в частности, случай свойств, которым удовлетворяют параметры подпрограммы, когда эти свойства можно систематически контролировать. Например, в программе программа uuuu : REEL (аргументы х, у : REELS) {х 0, у 0, у2 – х2 40}... вычисление uuuu {uuuu таково, что...} начальное утверждение может дать повод к проверке ASSERT(X 0) AND (Y0) AND(Y**2– X**2 = 40) с другой формулировкой в языке, отличном от АЛГОЛа W. Некоторые фортрановские трансляторы позволяют считать, что карты, начинающиеся некоторой специальной литерой, содержат оператор или комментарий в зависимости от варианта трансляции;

точно так же в языке АЛГОЛ W оператор ASSERT может обрабатываться как COMMENT, если принят соответствующий вариант трансляции. В обоих случаях можно, следовательно, выполнять регулярную верификацию.только в момент отладки.

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

Самым типичным случаем является подпрограмма, оперирующая с массивом, например, чтобы его отсортировать. Подпрограмма должна уметь корректно обрабатывать случай, когда массив пуст (проверьте с этой точки зрения подпрограммы сортировки гл. VII). Заметим, что в этом случае цикл DO на ФОРТРАНе является опасной конструкцией, поскольку, как мы уже видели, цикл DO метка I = 1, N...

CONTINUE метка всегда будет выполнен по меньшей мере один раз, даже если N отрицательно или равно нулю. Это объясняет, почему мы так часто – ср., например, Древесную Сортировку (VII.3.7) – переводили для с помощью эквивалента цикла пока (ветвление и логическое IF) с явным увеличением индекса (эта практика предпочтительнее, чем включение проверки перед циклом DO). Таким образом, цикл будет эквивалентен пустому оператору, если N равно нулю или даже отрицательно.

Близкая проблема связана с распространенной ошибкой, которая приводит к тому, что «ошибаются на 1» на одной из границ цикла;

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

212 Глава VIII Ошибки аппаратуры К классическим типам ошибок, о которых говорилось выше, следует добавить сравнительно новую категорию, по меньшей мере по сравнению с ситуацией последних двадцати лет – категорию ошибок аппаратуры. С появлением микропроцессоров, малых по размерам и по цене вычислительных машин, у которых возможности обнаружения ошибок в настоящее время весьма ограничены, были построены системы децентрализованных структур, образованных из малых автономных устройств, способных восстановить себя в случае ошибки («поме хоустойчивые системы»);

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

VIII.4.5. Гибкость. Адаптируемость. Универсальность Очень мало программ являются застывшими объектами. Для программиста признаком успеха программы служит то, что пользователи снова просят и добиваются расширения работающей программы, с тем чтобы она могла отвечать все более общим спецификациям. Даже если это не так, хорошо известно, что почти всегда, если эксплуатация программы должна продолжаться некоторое время, спецификации подвергнутся изменениям, которые надо отразить в программе.

Модифицировать программу в таких условиях может оказаться трудным делом.

Поэтому в плане «защищающего программирования», когда стремятся предупредить возможные неприятности, а не исправлять их, законным становится такой вопрос:


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

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

Следует, однако, сказать, что эти идеи не дают полного решения проблемы.

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

Другая трудность состоит в отсутствии определения того, что такое «нормальное» расширение или модификация. [Дейкстра 76] указывает, что обобщение программы, вычисляющей НОД чисел 28 и 77, может быть либо программой, которая находит НОД двух произвольных целых, либо программой, способной выполнять над 28 и 77 какую–нибудь двоичную операцию – сложение, умножение, НОК, т.д. Конечно, довод здесь доведен до парадокса, но он указывает на реальную проблему: трудно заранее угадать, какими будут наиболее вероятные расширения данной спецификации.

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

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

Для расширений второго типа можно дать очевидные советы (например, такой, как: операторы программ никогда не должны содержать константы в явном виде, а должны использовать только символические константы, по крайней мере если язык этого не запрещает, как в командах DIMENSION и FORMAT ФОРТРАНа), но не более:

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

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

вычислительная машина, периферийные устройства, системы доступа к файлам, операционная система, трансляторы и т.д.

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

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

Возможные советы легко перечислить. Если хотят написать переносимую программу–что, очевидно, не всегда имеет место, поскольку некоторые программы предназначаются лишь для единственной среды, – то надо выбрать язык программирования, достигший высокого уровня стандартизации (среди широко распространенных языков можно назвать ФОРТРАН, КОБОЛ, ПАСКАЛЬ), и следует строго соблюдать стандарты языка, запрещая обращения к каким бы то ни было «расширениям» или «улучшениям», реализованным в конкретных трансляторах.

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

Многие трансляторы более или менее тайно позволяют себе свободное обращение со стандартами языка;

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

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

Таким образом, переносимость на 99% является открытой проблемой. С практической точки зрения программист должен знать, что он обязан что–нибудь изменить в своих программах, чтобы приспособить их к любому изменению среды.

Следующее замечание подкрепляет нашу несколько моралистскую точку зрения (которая, хочется надеяться, не очень утомляет читателя);

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

214 Глава VIII VIII.4.7. Эффективность: оптимизация Среди качеств программы, разумеется, надо упомянуть «эффективность», т.е.

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

Мы приступаем к обсуждению этого качества после изучения остальных по той причине, что ему издавна уделялось преувеличенное и плохо ориентированное внимание, в частности при преподавании программирования. Важно напомнить одну истину, которая должна быть очевидной: эффективность имеет смысл лишь для правильной программы и нет смысла «оптимизировать» программу, не убедившись в ее адекватности требуемой задаче. Следующая программа на ФОРТРАНе – самая эффективная в мире по использованию времени и пространства:

STOP END Загвоздка лишь в том, что она отвечает лишь небольшому числу интересных спецификаций.

Вторая из «основных истин оптимизации» состоит в том, что не всегда следует оптимизировать;

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

В действительности можно считать правдоподобным часто приводимое интуитивное правило, по которому около 20% объектного кода ответственны за 80% времени выполнения;

правило рекурсивно применяется и к этим 20% (т.е. 4% множества команд ответственны за 64% времени выполнения и т.д.). Это показывает, что было бы абсурдно оптимизировать за пределами этих 80%. Многие программы «научного» типа свидетельствуют о еще более предельном поведении: «3%–90%».

Значительным моментом для программиста является часто то, что определение этих основных 20% или 3% не очевидно, если исходить из статического текста программы:

лишь тесты позволяют их выделить при выполнении. Любая система программирования должна бы давать соответствующие средства для измерения;

система АЛГОЛ W, например, дает, как мы видели, в качестве дополнительного варианта распечатку программы, указывающую число выполнений каждого блока, каждой ветви альтернативы и т.д. Опыт доказывает, что чтение этих результатов часто вызывает удивление: значащими циклами часто бывают не те, которых мы ожидали. И именно они одни оказываются достойными оптимизации.

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

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

Традиционное преподавание программирования тоже явилось источником многих ошибочных представлений. Например, сколько начинающих программистов усвоило, что никогда не следует писать В**2 –4.*А*С (1) а надо писать В*В–4.*А*С (2) под тем предлогом, что умножение «происходит быстрее», чем возведение в степень!

Сколько раз предписывалось программировать на ФОРТРАНе не так:

Z = ((А – 2 * В) * С + D)/((A –2*B)*E + F) (3) а так:

X=А–В–В Z = (X*C + D)/(X*E + F) ) (4) Оправдывать эти правила соображениями эффективности абсурдно, и, вообще говоря, не правильно. Вычисление (1) может оказаться столь же быстрым, как и вычисление (2), если транслятор умеет распознавать случай, когда показатель равен 2, и представляет это возведение в степень в виде умножения. Вычисление по формуле (3) может оказаться более эффективным, чем по формуле (4), которая требует отведения места для дополнительной переменной X, размещения в памяти и загрузки, в то время как любой хороший «оптимизирующий» траслятор обнаружит в (3) общие под выражения и сможет умножить В на 2 простым сдвигом на один бит влево одной части регистра. Выбор между (1) и (2), между (3) и (4) может обсуждаться, но не по причине эффективности, а по причине удобства чтения. Можно, например, считать (4) более ясным, так как формулы там менее длинные, наоборот, если (3) соответствует обычному виду, в котором, например, физикам известно это уравнение, то (4) будет нежелательным с точки зрения «производительности» программы, поскольку это потребует большего усилия для понимания.

Трансляторы хорошо приспособлены ко всем локальным, «микроскопическим»

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

Как же, каким образом улучшить программу? Ответ был намечен в гл. VII: лишь существенное улучшение алгоритма может внести чувствительный прогресс в эффективность. Это может быть настоящий «концептуальный скачок», который изменит теоретическую сложность алгоритма с O(n2) на O(nlogn). Смехотворно, например, пытаться с помощью «нечестных приемов» улучшить программу сортировки, имеющую сложность O(n2), чтобы получить выигрыш в несколько процентов: лишь переход к алгоритму сложности O(nlogn) может действительно что–то дать. Важные методы, представленные в VII.1 (Разделяй и властвуй, Компромисс пространство–время, Уравновешивание, Компиляция/Интерпретация), являются здесь ключевыми для возможных улучшений, и именно поэтому каждый программист должен их знать. Применение идеи компромисса пространство–время может также дать удивительную выгоду, если заметить, например, что некоторые величины среди 10 возможных постоянно перевычисляются, хотя они редко изменяются, и их можно, следовательно, представить в виде массива из 10 000 элементов и массива из 10 логических величин, указывающих, правильна ли соответствующая величина из перво 216 Глава VIII го массива, или ее надо перевычислять.

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

Наиболее поразительный пример, приведенный Керниганом, это пример программы сортировки, основанной на Пузырьковой сортировке, в котором все возможные улучшения были заботливо учтены, рассмотрены все частные случаи и т.д.: программа длиной в 30 строк на ФОРТРАНе занимала в действительности на 30% больше времени, чем грубое кодирование алгоритма Пузырьковой сортировки (VII.3.6.1) для сортировки 2000 элементов, причем этот процент увеличивался с увеличением числа сортируемых элементов. Сомнительный характер такого улучшения объясняется временем, необходимым для обнаружения «улучшаемых» случаев, и увеличением сложности программы. Заметим, в частности, что в современных операционных системах «пространство» и «время» не являются независимыми: «большая» (длительная. – Перев.) программа обычно «длиннее». Заметим также (что должно быть очевидным), что в реальности «улучшения» надо убедиться с помощью доказательства, либо теста.

Существуют некоторые приемы улучшения эффективности внутренних циклов без подлинных «концептуальных скачков». Они находятся на полпути между «локальными» и «глобальными» оптимизационными задачами, немногие из современных трансляторов используют их для облегчения работы программиста, хотя теоретически это возможно. Можно привести, например, иногда возможное упразднение проверки i n в i l;

пока i n и р(a [i]) повторять q(a[i]);

ii+ где а – массив с границами [1 : n];

при каждом выполнении цикла экономится одна проверка с помощью искусственной подстановки «сторожевого» значения х в а[n + 1], такого, что р(х) имеет значение ложь (ср. упр. III.5). Это, однако, может создать ряд проблем, например, если цикл находится в подпрограмме, аргументом которой является массив а. С риском наскучить читателю напомним, что такой род манипуляций оправдан, лишь если есть уверенность, что мы занимаемся значительной проблемой, т.е. проблемой самых внутренних циклов.

Другой классической оптимизацией является «развертывание циклов».

Имеется элемент программы:

для i от 1 до 100 повторять a(i) При классическом представлении на машине используется проверка и ветвления i 1;

цикл: если i 100 то на продолжение;

a(i);

на цикл;

продолжение:...

На путях к методологии Если действие a(i) занимает сравнительно небольшое время, сравнимое со временем проверки, то можно получить выигрыш в несколько процентов, записав для i от 1 до 99 шаг 2 повторять a(i);

a(i +1) т.е. в терминах представления i 1;

цикл: если i 99 то на продолжение;

(i);

i i + l;

(i);

на цикл;

продолжение:...

Здесь экономится половина проверок i 100. «Коэффициент развертывания»

здесь равен 2;

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

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

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

- рассматриваемый участок программы является действительно критическим в смысле эффективности и весьма не велик по своему объему;

- существует точная функциональная спецификация и эквивалентное описание в языке высокого уровня, непосредственно доступное в случае перемены «среды».

Чтобы завершить эти рассуждения об эффективности, можно было бы сказать:

«не оптимизируйте!»;

а затем смягчить эту рекомендацию следующим замечанием:

- надо сначала получить правильную версию, без этого последующая оптимизация лишена смысла;

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

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

VIII.5. Программист и другие До сих пор мы изучали программирование как вид индивидуальной деятельности программиста, сидящего перед листом бумаги или перед терминалом. Но это, разумеется, только часть вопроса. Если не считать программиста, потихоньку, исподволь подготавливающего чудо, предназначенное для него одного и ни для кого другого (безусловно, реальная ситуация, которая иногда случается в карьере того или 218 Глава VIII иного пользователя ЭВМ, но которая тем не менее остается экономически незначительной), то программирование чаще всего–это работа, выполняемая несколькими лицами для других лиц. Такое состояние вещей ставит особые проблемы.

Этому вопросу были посвящены многочисленные уже книги;

среди наиболее заметных можно привести книгу [Вейнберг 71], описывающую психологические проблемы, связанные с программированием, и книгу [Брукс 75], посвященную, в частности, организации больших проектов программного обеспечения. Для пока еще фрагментарного состояния исследований в этой области типично, что эти две работы, хотя и содержат интересные замечания и сведения, имеют в основном второстепенное значение: первая–как документ об американском образе мышления и о психологии, как она понимается в США, вторая – благодаря изложению этапов разра–ботки некоторых широко распространенных больших программ, несчастные пользователи которых сами оценивают апостериори всю соль этих историй.

VIII.5.1. Бригада программистов Первая из возникающих в проекте проблем, относящаяся одновременно к психологии, социологии и технике информатики, это проблема организации бригады.

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



Pages:     | 1 |   ...   | 5 | 6 || 8 | 9 |
 





 
© 2013 www.libed.ru - «Бесплатная библиотека научно-практических конференций»

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