Предварительные соображения о лексиконе программирования
Академик А.П. Ершов, 1983 г.
В рассуждениях о том, как надо развиваться программированию, нам, к сожалению, приходится начинать с того, что существующая практика программирования совершенно не адекватна тем задачам, которые стоят перед этим новым видом человеческой деятельности. Охарактеризуем вкратце как сегодняшнюю практику программирования, так и задачи в расчете на 15 – 20-летнюю перспективу.
Без больших сомнений можно предположить, что у нас в стране на разных должностях работает порядка 200 тыс. человек, получающих зарплату главным образом за то, что они составляют программы. Назовем их профессиональными программистами. Еще столько же или раза в полтора больше насчитывается тех, кому приходится программировать для себя ради достижения каких-то других целей. Оставим эту группу пользователей ЭВМ пока в стороне и сосредоточим свое внимание на профессиональных программистах.
Эти 200 тыс. профессионалов пишут в год порядка 1 млрд. машинных команд. Скорее всего процентов 90 из них – это разовые программы или, во всяком случае, программы, не выходящие за пределы первичного коллектива, в котором возникла задача на программирование. Порядка 100 млн. команд – это годовой выход программного продукта, который в той или иной форме отчуждается от его изготовителей.
В ходе годичной эксплуатации этого программного продукта примерно в каждой тысяче команд обнаруживается ошибка, что составляет 100 тыс. ошибок в год или в среднем по пять долгов на одного разработчика. Не меньшее количество потребностей в модификации программ возникает в связи с эволюцией оборудования и базового программного обеспечения. Третья составляющая модификаций – это особые условия потребителя, которые не могут быть учтены стандартными процедурами генерации. Суммарно это дает порядка 500 тыс. модификаций в год в уже существующем продукте.
Если считать, что одна модификация затрагивает порядка десяти команд текста программы, а также принять во внимание, что исправление программы обходится примерно на порядок дороже, чем составление новой, получим, что сопровождение и доработка 100 млн.команд годичного программного продукта эквивалентны разработке 50 млн.команд новых программ. Этот объем доработок уже сам по себе является тяжким бременем, однако это лишь малая часть тех потерь, которые возникают от нарушений режимов использования вычислительных средств, вызванных ошибками программного обеспечения. Эти потери очень трудно учесть, но некоторое представление дает следующий расчет. Будем считать, что одна программа применяется в среднем в 1000 экземпляров, ошибки проявляются лишь в десятой части тиража и производственный сбой, вызванный одной ошибкой в программе, обходится предприятию в скромную цифру 100 руб. Уже эта осторожная оценка дает
100 × 0,1 × 1000 × 100 000 = 1 млрд.руб. в год.
Вышесказанное относится к программам как к продукту. Посмотрим теперь, как этот продукт возникает. Фольклорная статистика говорит нам, что программный продукт пишется на следующих языках:
- языке ассемблера 25 %
- Фортране 30 %
- Коболе 15 %
- ПЛ/1 15 %
- Алголе 10 %
- Остальных 5 %
Итого 100 %
Свидетельства правильности программ носят эмпирический и заведомо приближенный характер и основаны на демонстрации поведения машины, управляемой программой, на ограниченной совокупности так называемых «репрезентативных примеров». Составление программы и объявление степени ее достоверности остается личным делом программиста. Сколько-нибудь точная формулировка задачи, решаемой программой, либо отсутствует, либо возникает по ходу дела в уме автора программы, утрачивая тем самым априорный характер.
Существующие правила оформления и приемки программного продукта при всей их кажущейся строгости носят поверхностный характер, не затрагивающий существа продукта, и постоянно выхолащиваются формальными требованиями соблюдения плановых сроков и разнонаправленностью интересов разработчиков и хранителей фондов алгоритмов и программ. Рассмотрим теперь базу знаний профессиональных программистов. Добрых две трети указанных 200 тыс. специалистов были выпущены вузами по специальности «Прикладная математика» и ряду специальностей, ей родственных, в десятилетие с 1968 по 1977 г. Чему их учили, дает представление учебный план Минвуза СССР по указанным специальностям и отечественные наиболее массовые учебные пособия по программированию и практикуму на ЭВМ, выпущенные в 70-х гг. Не подвергая сомнению ни добросовестность авторов, ни необходимость обучения тем начальным сведениям по программированию, которые содержатся в этих книгах, следует все же признать, что то, чему учат сейчас в вузе, с позиций современной науки не выходит за пределы элементарной «компьютерной грамотности». Программированию как научно обоснованному методу составления программ не учат даже сейчас, не говоря уж о содержании образования в указанный период времени.
Конечно, каждый, кто захочет опротестовать нарисованную картину, найдет немало свидетельств противоположному.
Есть проекты, в которых достигается высокая степень надежности программного обеспечения, но дорогой ценой, недоступной массовым разработкам. Есть технология, обеспечивающая жесткую дисциплину программирования, но средствами скорее организационного, нежели интеллектуального контроля, а стало быть, средствами, внешними по отношению к программисту. Наконец, есть поток научной, главным образом переводной литературы, который, казалось бы, должен играть свою формирующую роль. Такой поток действительно есть, но его действие незначительно, по крайней мере по трем причинам. Во-первых, читаются ли эти книги и, тем более, берется ли их содержимое на вооружение, никого не интересует. Во-вторых, тираж и расходимость этих книг показывают, что они становятся достоянием лишь незначительной части профессиональных программистов. В-третьих, знание, заключенное в этих книгах, за редким исключением не может быть использовано в прямой практике и вызывает в результате лишь угнетающее раздвоение между «большой наукой» и неприглядной действительностью. Приходится констатировать, что в среднем личный опыт программиста лишь усиливает эмпирическое начало в программировании.
Стиль программирования – это фундаментальное профессиональное свойство программиста, приобретаемое прежде всего в его учебно-образовательном периоде, и его сохранение или модификация достигается лишь степенью требовательности производственной обстановки, не позволяющей ему действовать как-либо иначе.
Охарактеризуем теперь так же коротко задачи, стоящие перед программированием в расчете на длительную перспективу. Становится ясно, что ускоренное развитие вычислительной техники не имеет никаких пределов, кроме полного насыщения общества средствами хранения, передачи, обработки и воспроизведения информации, которые в расчете на одного активного члена общества составляют порядка десяти машин со встроенными микропроцессорами, одного-двух входов в сеть передачи информации, одного-двух автоматизированных рабочих мест, оборудованных персональной ЭВМ, и одной десятой универсальной ЭВМ, поддерживающей иерархию управления. Это означает, что при полной информатизации такой страны, как наша, общий счет должен идти на сотни миллионов микропроцессоров и контроллеров, на десятки миллионов микроЭВМ и миллионов универсальных ЭВМ типа мега-мини.
Предоставим экономистам и организаторам производства считать, каков должен быть темп роста производства средств связи и вычислительных средств для решения задачи полной информатизации. Однако не дожидаясь уточнения этих оценок, для себя можно сказать достаточно определенно, что мы должны научиться, грубо говоря, удесятерять тираж программного продукта каждые десять лет.
Все наши рассуждения мы ведем для того, чтобы обосновать какие-то перемены в статус-кво. Взяв год на раскачку, будем все относить к периоду с 1986 по 2005 г. Итак, общий тираж программного продукта должен к 2005 г. вырасти в сто раз. Один порядок роста можно отнести за счет увеличения тиражности. Отсюда получаем контрольную цифру минимального объема программного продукта к 2005 г. в 1 млрд.команд. Рассчитывать более чем на двукратное увеличение числа профессиональных программистов, учитывая демографическую ситуацию, не приходится; отсюда получаем задание на пятикратный рост производительности труда. Но этого мало. Стократное увеличение «контактной поверхности» программного продукта при существующем уровне надежности доводит стоимость издержек из-за ошибок в программах до 100 млрд. руб. Это значит, что надежность программного обеспечения должна быть по крайней мере на два порядка выше. Другими словами, число ошибок должно быть в десять раз меньше так же, как и число авостов, случающихся в программах. Наконец, соотношение стоимости разработки к стоимости сопровождения должно быть не порядка 2 : 1 на первый год эксплуатации, а ближе к 10 : 1.
Итак, мы получаем следующую задачу, стоящую перед программированием на ближайшие двадцать лет: при умеренном росте (в 2–3 раза) числа профессиональных программистов не менее чем в пять раз повысить их производительность; повысить надежность программного продукта не менее чем на два порядка и примерно на столько же сократить удельные затраты на сопровождение. Будет полезно сравнить нашу исходную точку отсчета с состоянием двадцатилетней давности (с 1965 г.). Понятие программного продукта еще только складывалось, но ретроспективная переформулировка нашей деятельности в то время может быть выражена следующим образом.
Языки:
- машинный – 30 %
- ассемблера – 30 %
- Алгол – 30 %
- остальные – 10 %
Число программистов – на порядок меньше, производительность – в пять раз меньше, примерно 1000 команд в год, надежность – на порядок ниже, затраты на сопровождение, грубо говоря, равнялись затратам на разработку, тиражность программ была на порядок-полтора ниже. Коротко проанализируем факторы перемен за истекшие двадцать лет. Рост производительности – наполовину за счет исключения восьмеричного программирования, наполовину за счет сервиса операционной системы. Рост надежности – наполовину за счет повышения уровня языка, наполовину за счет появления организационной дисциплины программирования. Динамику затрат на сопровождение можно интерпретировать как стабилизацию двух тенденций: позитивной – организационного выделения функции сопровождения и улучшения документирования программ, и негативной – роста затрат на сопровождение из-за увеличения тиражности при невысокой надежности. По кадрам ситуация тоже двунаправленная. Позитивная – усиление профессионального начала через специализацию «математическое обеспечение ЭВМ» и увеличение числа программистов, и негативная – некоторое понижение среднего иителлектуального уровня программиста. Аналогичная ситуация и в преподавании: усиление педагогически мотивированных программ и методов обучения, с одной стороны, и ослабление творческого, передового начала в учебном материале, с другой стороны.
В целом, если сравнить развитие программирования в первое десятилетие (1955–1964) и второе десятилетие (1965–1984), то оно будет явно не в пользу второго из-за определенной утраты темпа и глубины развития.
Из всего этого вытекает первый тезис: мы не решим проблем, стоящих перед программированием, если предоставим ему развиваться стихийно. Нужно что-то делать для того, чтобы создать какую-то вынуждающую обстановку, которая не позволяет программисту работать по-старому. Причем это должны быть действия одновременно и глобального, и низового характера, затрагивающие всех и каждого, действующие однонаправленно и в учебной, и производственной обстановке.
Рассмотрим существующие регуляторы программирования:
- общий язык;
- интерактивные средства построения программ;
- организационную дисциплину программирования;
- методологию построения программ.
Каждый из этих регуляторов заслуживает детального рассмотрения. Мы вынуждены провести анализ очень схематично.
Мечта об общем идеальном или, лучше сказать, априорном языке до сих пор не оставляет программистов. Главные вехи развития этой мечты: Алгол-60, Кобол, ПЛ/1, Алгол-68 и, наконец, Ада. Каждый из этих языков, а также многие другие становились заметными явлениями в эволюции программирования, но в целом они лишь повысили разнообразие языковой практики программирования. Пожалуй, только Фортран и Бейсик реально сдерживают напор «вавилонского столпотворения», но это ни у кого не вызывает удовлетворения.
Несколько утрируя оценку развития событий вокруг Ады, можно передать реакцию ряда специалистов так: «Все, что связано с Адой, прекрасно, кроме самого языка!» Если же говорить серьезно, то проблема здесь в диалектическом противоречии, возникающем в языках программирования. Одна из ипостасей языка программирования – быть средой, в которой возникает программа. Здесь на первый план выходит образ языка-оболочки, расчлененность семантики, разнообразие изобразительных средств. Другая ипостась языка программирования – быть системой, лежащей в основе транслятора, обеспечивающего коммуникативную функцию передачи программы машине. Здесь предпочтительным является образ языка-ядра, замкнутости семантики, экономности в номенклатуре конструкций. То, что эти ипостаси противоречивы, хорошо известно, и мы не будем повторять с этих позиций анализ мировых языков программирования. Заметим только, что Ада претендует на разрешение этого противоречия, но результат ее испытания транслятором до сих пор еще не ясен.
Из всего этого следует второй важный для нас тезис: мы не можем повлиять на языковое разнообразие и рассчитывать на его заметное сокращение, в связи с чем сколько-нибудь универсальная методология программирования не может ориентироваться на конкретный язык программирования.
Конечно, мы находим в литературе произведения типа «Структурное программирование на Коболе», но приходится понимать, что это всего лишь костыли, гуманно предлагаемые людям, искалеченным смолоду.
Интерактивные средства являются несомненным благом. Экранные редакторы, комплексаторы, редакторы связей, средства наглядной распечатки и документаторы облегчают работу программиста, но сами по себе не решают главной задачи – установления интеллектуального контроля над составлением программы. Конечно, здесь мы сознательно умалчиваем о более глубоких средствах: верификаторах и трансформаторах программ, но эти средства мы побережем для развития главной идеи.
Организационная дисциплина программирования является совершенно необходимой предпосылкой коллективной работы, а также работы по схеме «заказчик-исполнитель». Надо, однако, понимать, что она является схемой разработки программы, претендующей лишь на установление системы личных отношений в коллективе на принципах разделения ответственности. Организационная дисциплина строится в зависимости от доступных технических средств (технологии) и выбранной методологии.
Итак, мы по всем трем рассмотренным регуляторам программирования приходим к методологии. Здесь, несколько опережая линию изложения, мы хотим высказать еще один, весьма позитивный тезис: в программировании на основе накопленного знания сложились методологические принципы, которые в правильном сочетании с другими регуляторами позволяют вывести программирование на требуемый уровень достоверности, надежности и продуктивности, при этом в форме, доступной современному уровню культуры и интеллекта, предоставляемому средней школой.
Мы различаем три формы программирования:
синтезирующее;
сборочное;
конкретизирующее.
Синтезирующее программирование – это программирование в его наиболее натуральной сущности. Оно начинается с задачи и завершается программой ее решения на основе заданных элементарных средств.
Сборочное программирование основано на существующих полуфабрикатах и реализует принцип многократности использования модулей программного обеспечения. Формально оно может рассматриваться как частный случай синтезирующего программирования, но такое сведение неплодотворно, так как схема сборки сильно отличается от схемы пошагового уточнения, лежащей в основе синтеза программ.
Конкретизирующее программирование тоже реализует принцип многократного использования программного продукта, но в отличие от сборки его содержанием является адаптация многопараметрической универсальной программы к особым условиям ее применения. Эта адаптация, если она осуществляется систематически и без потери знания, воплощенного в универсальной программе, является эффективным средством для снятия постоянного противоречия между универсализацией и специализацией программирования в интересах повышения его эффективности.
Сразу укажем тот научный фундамент, на котором покоятся эти три формы программирования. Синтезирующее программирование: метод пошагового уточнения программ на основе спецификации задачи в виде предусловий и постусловий с использованием аксиоматического описания языковых конструкций. Пошаговое уточнение сопровождается сериями преобразований, отражающих внутреннюю природу языковых конструкций или факты о предметной области, известные программисту. Процесс программирования приобретает характер доказательного рассуждения о существовании программы, решающей поставленную задачу. Сама программа является как бы побочным результатом этого рассуждения. Или, наоборот, доказательное рассуждение, собранное как отдельный текст, оказывается сертификатором правильности построенной программы. Прогонка текстов на машине приобретает демонстрационный характер, отражающий особенности машинной арифметики, временные характеристики и другие вторичные моменты.
В последние годы в теории программирования начали созревать очень глубокие аналогии, раскрывающие математический характер доказательного программирования и его связь с другими разделами математики. В частности, спецификацию задачи можно рассматривать как неявное уравнение относительно программы, а пошаговое уточнение программы с применением преобразований – как символический метод решения этого уравнения. При этом возникают как точные, так и приближенные методы.
Роль приближенного решения играет программа, вычисляющая тотальную функцию, принимающую значения в предметной области, дополненной авостом. Все неавостные значения приближающей функции являются значениями функции приближаемой.
Чудес на свете не бывает, и платой за доказательность программирования является необходимость манипулирования с объемами текстовой информации, превышающими на порядок объем собственной программы. Поэтому доказательный синтез программ может стать реальностью только при мощной машинной поддержке.
Сборочное программирование: методы и техника модульного программирования на основе понятия абстрактного типа данных.
Конкретизирующее программирование: методы и техника смешанных вычислений в сочетании с оптимизирующими преобразованиями. К нему же примыкает более старая и эмпирическая техника макрообработки и автоматической генерации программных комплексов (например, генерация операционной системы).
Основная задача, которая возникает в связи с освоением научного багажа рассмотренных трех форм программирования, состоит в том, что в реальной жизни все три формы сочетаются в ходе разработки программного продукта. Синтезированную программу нужно объединять с заготовленным программным модулем, который, в свою очередь, получается конкретизацией более общей программы. Полученная программа должна быть составлена на разные машины и опираться на библиотеки, выраженные на разных языках. Сразу появляется задача о языковой среде, в которой должен выполняться проект, включая и его логическую, доказательную сторону. В качестве альтернативы единому языку программирования мы выдвигаем понятие о языковой среде, которую мы называем лексиконом программирования. Лексикон программирования – это лингвистическая система с фразовой структурой, содержащая в себе формальную нотацию для выражения всех общезначимых конструкций, употребляемых при формулировании условий задачи, при синтезе и преобразовании программ.
В качестве основы лексикон содержит стандартную математическую символику алгебры, теории множеств, математической логики. В дополнение к ней лексикон содержит нотацию для основных конструкций и примитивов программирования на самых разных уровнях. Смысл этих конструкций сам выражается средствами лексикона. Лексикон содержит развитую систему именования и разнообразные правила подстановки.
Чем лексикон отличается от языка программирования? Он выражает не только и не столько программы, сколько их свойства и наши суждения о них. Язык программирования кодирует объекты предметной области задачи, а наше знание об этих объектах остается за пределами программного текста. Лексикон же является средством описания объектов предметных областей и содержит нотацию для построения баз знаний о предметных областях. Программа, выраженная средствами лексикона, в определенном смысле содержит в своем тексте описание своей семантики в виде совокупности нетривиальных фактов о вычисляемой ею функции – в отличие от «чистых» программ, которые не говорят ничего о своих функциональных свойствах.
Лексикон, в отличие от конкретного языка программирования, является открытой системой. Для него в целом не ставится задача трансляции любого его текста в машинную программу, хотя любая машинная программа в случае необходимости может быть выражена в лексиконе. Аналогично естественному языку лексикон обладает способностью описания одной своей части средствами другой своей же части.
Не надо думать, что лексикон – это все и навсегда. Это тщательно отобранная, но развивающаяся система удачных обозначений. Степень его успеха определяется степенью общезначимости и общепонятности его нотации.
Давайте вообразим, как выглядит программирование в лексиконе. Синтезирующее программирование проходит полностью в лексиконе вплоть до получения алгоритма нужной степени детализации. Если это заготовка (модуль или универсальный алгоритм), то она в таком виде и остается. Если программа должна быть передана машине, то она подвергается особой процедуре «лингвизации» (фортранизации, паскализации, алголизации и т.п.). Лингвизация – это перековка программы, которая, сохраняя правильность, придает ей стилистические особенности рабочего алгоритмического языка, после чего прямая транслитерация превращает программу в текст на этом языке, который исполняется или пополняет рабочую библиотеку. Лингвизация может быть творческим процессом, выполняемым специалистом по данному рабочему языку. Творчество, однако, состоит не в сохранении правильности программы (она гарантированно сохраняется), а в придании программе особого шика, наиболее идущего этому рабочему языку.
Программа, составленная сразу на рабочем алгоритмическом языке, получает «права гражданства» только после полного аннотирования на лексиконе. Эта аннотация является свидетельством ее правильности, ее постоянной тенью и позволяет с сохранением функции и структуры транслировать ее в случае необходимости назад в лексикон.
Естественно, что все манипуляции с программами при сборочном и конкретизирующем программировании делаются в лексиконе. Если возникает необходимость строить программный процессор, действующий в рабочем языке, то этот процессор тут же дополняется теневым процессором, преобразующим аннотации программ.
Лексикону учат в ВУЗе раньше, чем какому бы то ни было рабочему языку (игрушечный практикум не в счет). Доказательное программирование является основой курса программирования, подкрепленного каторжным тренингом в духе лучших задачников по математическому анализу. Программиста бьют по рукам, если он посмеет написать оператор цикла, не найдя перед этим его инварианта.
На лексиконе должно быть написано объемистое руководство программиста, содержащее теории наиболее ходовых предметных областей, семантику фундаментальных конструкций программирования, логические и алгебраические законы, базовые трансформации, связывающие основные модели вычислений, перечень стилей основных рабочих алгоритмических языков и правила трансляции в них.
На лексиконе должно быть переписано «Искусство программирования» Д. Кнута в виде свода фундаментальных алгоритмов вместе с полным сертификатом их правильности. Не исключено, что со временем в лексиконе сложится небольшое число конструкций программ, которым в силу экономии мышления будет приписана стандартная семантика. Этой семантикой может владеть каждый человек со средним образованием. Взятые вместе, эти конструкции и образуют общий язык программирования.
В заключение мы приведем ряд свидетельств, показывающих, что идея лексикона не является надуманной, а пробивает себе дорогу.
Недавняя книга Д. Гриса «Наука программировать» (М.: Мир, 1984) является блестящим педагогическим свидетельством возможности учить доказательному программированию.
«Язык широкого спектра», созданный в проекте ЦИП под руководством проф. Ф. Л. Бауэра, является развернутой номенклатурой лексикона. Эта концепция, хотя и не названная явно, пронизывает его новый курс программирования, написанный вместе с Г. Весснером: «Алгоритмический язык и построение программ» (Шпрингер, 1983).
Язык PDL и поддерживаемая им технология построения программ, созданная в Отделении федеральных систем компании ИБМ, является явным шагом в сторону программировании на лексиконе с элементами доказательности.
Концепция языка Кант, разработанная в 1982 году в Институте прикладной математики под руководством М. Р. Шура-Буры, очень близка концепции программирования на лексиконе с последующим вложением в рабочий язык.
Многие авторы де-факто вводят элементы лексикона в попытках «внеязыкового» обучения программированию, т.е. без привязки к конкретному рабочему языку (см., например, курс программирования Мейера и Бодуэна, изданный в переводе в 1982 году издательством «Мир»). Концепция лексикона сейчас настолько носится в воздухе, что просто невозможно указать, кому принадлежат те или иные идеи.
Автор должен, однако, сослаться на неоднократные беседы с В. Турским и исправное чтение его меморандумов, которые сильно повлияли на его оптимизм в отношении лексикона. Контрвлияние работ по языку Ада уже упоминалось.
Разработка лексикона, создание на его основе руководства программиста, свода фундаментальных алгоритмов, написание учебников – прекрасная основа для международного сотрудничества, активизации научной работы на местах, создания мощного «когерентного излучения» новых идей и формирования базы знания современного программирования.
Возвращаясь к задаче развития программирования в СССР на ближайшие двадцать лет, можно разбить эти годы на два десятилетия.
До 1992 г. можно рассчитывать на повсеместное внедрение терминальной интерактивной разработки программ, переход на более современные рабочие языки, укрепление организационной дисциплины программирования. Эти программные обстановки 1-го поколения позволяют, грубо говоря, сделать полдела при условии умеренного роста тиражности программирования. Следующий этап – выйти на доказательное программирование и придать производственный характер сборочному и конкретизирующему программированию.
Это означает, что к началу второго десятилетия основы лексикона должны быть построены и выработана педагогическая доктрина.
Потребовалось примерно полтора века, чтобы, начиная с Эйлера, построить современное здание математического анализа и на его основе создать науку инженерного конструирования, прежде всего машин и сооружений. Нашему и следующему поколениям отпущено не более пяти десятков лет на то, чтобы решить соразмерную задачу по строительству теории программирования и на его основе создать науку инженерного конструирования автоматизированных рабочих мест, роботов и других современных машин.