Гай Стил — настоящий программист-полиглот. На вопрос, какие языки он серьезно использовал, Гай выдал список: Кобол, Фортран, ассемблер IBM 1130, машинный язык PDP-10, APL, Си, C++, BLISS, GNAL, Common Lisp, Scheme, Maclisp, S-l Lisp, *Lisp, C*, Java, JavaScript, Tel, Haskell, Фокал, Бейсик, ТЕСО и ТеХ. «Это основные», — прибавил он.

Он приложил руку к созданию двух основных существующих сейчас диалектов общего назначения языка Лисп: Common Lisp и Scheme. Он участвовал в работе комитетов по стандартам, определивших облик языков Common Lisp, Фортран, Си, ECMAScript и Scheme. Билл Джой нанял его, чтобы он помог в написании официальной спецификации языка Java. Сейчас он разрабатывает Fortress — новый язык для высокопроизводительного научного программирования.

Стил получил степень бакалавра в Гарварде, затем степень магистра и PhD в Массачусетском технологическом институте (MIT). В последнем он вместе с Джеральдом Сассменом выпустил серию основополагающих работ, ныне известных как «The Lambda Papers», где был впервые описан язык Scheme. Он также был летописцем хакерской культуры, одним из создателей Jargon File и редактором книжной версии «The Hacker's Dictionary» — словаря хакера (впоследствии дополненный Эриком Рэймондом, он стал «The New Hacker's Dictionary»). Стил также играл заметную роль в создании Emacs и одним из первых занялся портированием программы ТеХ Дональда Кнута.

Стил — член Ассоциации вычислительной техники (АСМ), Американской академии искусств и наук, Национальной академии инженерных наук. Он лауреат премии имени Грейс Мюррей Хоппер (1988) от АСМ и премии Dr. Dobb's Excellence in Programming Award (2005).

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

Сейбел: Как вы начали программировать?

Стил: В начальной школе я страшно интересовался разными науками, математикой, много читал, например, одна из моих любимых книг -«Magic House of Numbers» (Волшебный дом чисел) Ирвинга Адлера. Еще мне очень нравилась всякая детская фантастика, вроде серии книг про Дэнни Данна. Так что меня вообще влекло в научную, математическую сторону. Я читал все подряд про науку и математику, прочел кое-что и об этих новомодных компьютерах — тогда они только появились.

Сейбел: Когда это было?

Стил: В начальной школе я учился с 1960 по 1966 год. Но, думаю, поворотный момент случился, когда я уже был в Бостонской латинской школе, — примерно в девятом классе. Приятель спросил меня: «Слыхал о новом компьютере, который установили в подвале?» Я подумал, что это очередная его выдумка: до этого он говорил о бассейне на четвертом этаже, а в школе их было всего три. Но он сказал: «Нет, он правда есть».

Оказалось, Винсент Лирсон организовал для Бостонской латинской школы мини-компьютер IBM 1130 и установил его в подвале. Он сам был выпускником школы и очень ей помогал. Приятель показал мне программу из пяти строк на Фортране, и она сразу же покорила меня.

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

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

Кроме того, рядом был книжный магазин с книгами про разные экзотические языки вроде ПЛ/1, там мы тоже кое-что покупали. Вот так в школе мы и познакомились с техникой IBM. В самой школе была только IBM 1130, а мы мечтали о машине System 360. Мы читали о ней, но поработать на ней было негде.

А весной 1969-го я принял участие в программе MIT для старшеклассников. Здорово было — по утрам в субботу студенты объясняли нам все эти крутые штуки. Я слушал лекции по теории групп, программированию и чему-то там еще. И очень сильно прикипел тогда к MIT. Благодаря этой программе я смог поработать и на IBM 1130, и на PDP-10 фирмы DEC. Так мы и познакомились с компьютерами DEC.

Мы, старшеклассники, узнали, что на Сентрал-сквер есть офис DEC. Хотя он был рассчитан на студентов MIT, нам не глядя выдавали разные руководства. Просто здорово! В предпоследнем или последнем классе мы с приятелем предложили им реализовать APL на PDP-8, и они отнеслись к этому серьезно. А примерно через неделю пришел ответ: мол, мы считаем это нецелесообразным, но спасибо за предложение.

Сейбел: Вы помните свою первую интересную программу?

Стил: Так как моим первым языком был Фортран, интересное началось для меня с языка ассемблера для IBM 1130. Первым интересным произведением была программа для генерации контекстных указателей по ключевым словам. IBM делала «быстрые» алфавитные указатели к своим руководствам: ключевое слово можно было найти в алфавитном указателе, а по обе стороны от него располагались и другие слова из того отрывка, где оно встречалось.

Сейбел: Отрывка текста, где было слово?

Стил: Отрывка из собственно руководства. Итак, в средней колонке шли слова по алфавиту, а по бокам от них — слова из этих отрывков. Я подумал, что при помощи IBM 1130 справлюсь с этим. А так как в ее оперативной памяти помещалось только 4000 слов, было ясно, что придется работать с данными на диске. Так что мне пришлось изучить методы сортировки данных вне оперативной памяти. Интересной в этой программе была не столько возможность генерации ключевых слов в контексте, сколько именно эта возможность сортировки слиянием вне оперативной памяти. Программа оказалась более-менее эффективной. К сожалению, в оперативной памяти я использовал пузырьковую сортировку данных. А надо было использовать также сортировку слиянием, но тогда я до этого не додумался.

Сейбел: А сколько времени прошло с того момента, как вы узнали о компьютере в подвале, до написания этой программы? Месяцы? Недели?

Стил: Это случилось в течение первых двух лет. Не уверен, был ли это первый год. Я выучил Фортран осенью 1968-го. И помню, что APL был моим третьим языком, значит, язык ассемблера я освоил на Рождество или чуть позже. Помню, что APL я выучил весной 1969-го, потому что тогда в Бостоне проходила Весенняя объединенная вычислительная конференция.

У IBM там была выставка, посвященная всем их продуктам, прежде всего APL-360, и я бродил вокруг их стенда. По окончании выставки они собирались выбросить демонстрационные распечатки от терминала Selectric, и тут я подошел и спросил: «Вы хотите это выкинуть?» Женщина, которая этим заведовала, удивленно поглядела на меня и протянула мне эту бумагу — так, словно это был рождественский подарок. Это и был рождественский подарок.

Сейбел: Что за бумага?

Стил: Фальцованная бумага от терминала Selectric, на котором они последние пару дней демонстрировали возможности APL. Небольшие примеры программ и все то, что они там набирали. Вот при помощи этих примеров да еще буклета IBM с этой выставки я и выучил APL.

Сейбел: Значит, в MIT вам было хорошо, но вы все же пошли учиться в Гарвард, и при этом работали в MIT. Почему?

Стил: Когда пришло время подавать документы в колледж, я подал бумаги в MIT, Гарвард и Принстон, но учиться хотел в MIT. Меня приняли во все три. Директором Бостонской латинской школы был Уилфрид О'Лири, классицист старой школы, истинный джентльмен. И он сказал моим родителям: «Вы знаете, что вашего сына приняли в Гарвард, а он собирается пойти в MIT?» То есть он надавил на них, они надавили на меня, и в конце концов я решил пойти в Гарвард.

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

Где-то в начале июля я узнал, что Билл Мартин из MIT ищет Лисп-программистов. Я подумал: «Ага, я же знаю Лисп». Я так часто бывал в MIT, что раздобыл документацию Лиспа в лаборатории искусственного интеллекта MIT. Я пробирался в лаборатории и возился с компьютерами. Везде было открыто — это было еще до протестов в связи с вьетнамской войной, из-за которых им пришлось врезать дверные замки. Так что в выпускном классе я писал свою собственную реализацию Лиспа для IBM 1130.

И вот я пришел в офис к Биллу Мартину, такой тощий парень из ниоткуда, сунул голову в дверь и спросил: «Я слышал, вы ищете Лисп-программистов?» И он не рассмеялся мне в лицо, а посмотрел на меня и сказал: «Вам придется пройти мой тест по Лиспу». «ОК, прямо сейчас?» Я уселся и два часа разбирался со всеми вопросами и задачами. Закончив, я протянул ему бумаги, он минут десять смотрел то, что я понаписал, и наконец сказал: «Я вас беру».

Сейбел: Лисп вы тоже освоили на курсах MIT для школьников?

Стил: Отчасти, но там больше преподавали Фортран и другое.

Сейбел: У вас в самом начале были сильные учителя?

Стил: В Латинской школе у меня были хорошие учителя по математике, они меня поощряли как раз в нужном направлении. Когда я был в девятом классе, Ральф Уэллингс — тот, кто дал мне книги накануне Дня благодарения, — предложил сделку. Он сказал: «Я заметил, что ты сдаешь все математические тесты со стопроцентным результатом. И я разрешу тебе четыре урока в неделю проводить в компьютерном классе, если на пятом ты будешь получать стопроцентный результат. Если когда-нибудь получишь меньше, договор отменяется». Это был отличный стимул. И до конца года я продолжал блестяще сдавать тесты. Чтобы иметь доступ к компьютеру, мне пришлось очень серьезно заниматься математикой. А на следующий год учитель уже ничего не предложил — и правильно, потому что с математикой у меня стало хуже. Так что у меня были хорошие учителя, они дали мне то, что нужно, чтобы все выучить.

Сейбел: А позже, когда вы уже увлеклись компьютерами, кто именно помогал вам в этом?

Стил: Билл Мартин, конечно, который нанял меня. И Джоэль Мозес, который руководил проектом Macsyma, — для работы над ним меня и взяли в MIT.

Сейбел: И в итоге вы проработали над этим проектом в течение всей учебы в колледже?

Стил: Да, я работал в MIT все то время, пока учился в Гарварде. Летом это была работа на полную ставку, остальное время — на полставки.

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

Сейбел: И вы работали только над проектом Macsyma на Лиспе?

Стил: Да. В частности, я отвечал за интерпретатор Maclisp. Йонл Уайт, ответственный за интерпретатор и компилятор, к тому времени стал уже чем-то вроде гуру. Я взял интерпретатор, и это разделение труда было почти идеальным. Моим наставником был Йонл Уайт. Но фактически все, кто работал над проектом, взяли меня под крыло. Еще я познакомился кое с кем из лаборатории искусственного интеллекта. Поэтому я без проблем поступил в магистратуру MIT — там уже знали, кто я и чем занимаюсь.

Сейбел: Вы стали бакалавром компьютерных наук?

Стил: Да. Вообще-то я хотел получить диплом по математике, слушал соответствующие лекции, но потом понял, что у меня нет способностей к изучению бесконечномерных банаховых пространств. Я просто изнывал над ними. Но, к счастью, я слушал достаточно лекций по компьютерам, просто из интереса, чтобы сменить специальность. На самом деле диплом был по прикладной математике — компьютерные науки были ее разделом, а прикладная математика относилась к инженерному факультету Гарварда.

Сейбел: С какими машинами вы имели дело в Гарварде?

Стил: С DEC PDP-10. В кампусе была PDP-10, но, кажется, к ней допускали только магистров. А нам давали доступ к телетайпным терминалам коммерческой системы, которую Гарвард арендовал или что-то в этом роде.

Сейбел: Вы бы хотели что-то изменить в том, каким путем пришли к программированию? Может быть, вы что-то упустили?

Стил: У меня никогда не было четко поставленной цели. И я не жалею о своем пути. Думаю, в моей жизни было много интересных совпадений и полезных напутствий.

Сейчас я понимаю, что посещать одновременно MIT и Гарвард было довольно необычно. Я курсировал туда-сюда и говорил, например: «Профессор с того берега утверждает то-то и то-то». А мне отвечали: «Чепуха, на самом деле вот как все обстоит». Так что я за короткий срок получил обширные знания.

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

Сейбел: В чем больше всего изменились ваши взгляды на программирование по сравнению с теми годами? Кроме того, что теперь вы понимаете, что пузырьковая сортировка — не лучший способ?

Стил: Пожалуй, вот что: сейчас невозможно знать абсолютно все, что происходит внутри компьютера. Есть вещи, вам неподвластные, — сегодня невозможно знать все о всем программном обеспечении. В 1970-е память компьютера вмещала только 4000 слов. Можно было сделать дамп ядра и проверить каждое слово. По распечаткам исходного кода операционной системы можно было понять, как она работает. Я изучал утилиты для работы с диском и с устройством для чтения перфокарт, создавал свои варианты. И мне казалось, что я понимаю, как работает вся IBM 1130. Ну, или понимал в этом достаточно для себя. Сейчас все не так.

Сейбел: Вам помогали книги?

Стил: В 1970-е — конечно, да. Например, «Искусство программирования» Кнута.

Сейбел: Вы прочли ее от корки до корки?

Стил: Почти. Я делал столько упражнений, сколько мог. Некоторые требовали знаний, которых у меня было, — скажем, высшей математики. Такие я пропускал или делал кое-как. Но первые два тома и солидный кусок третьего я прочел очень внимательно. Сортировке я учился по книге алгоритмов Ахо, Хопкрофта и Ульмана. Что до остальных, то надо поглядеть в моей библиотеке. Я ведь настоящий старьевщик и храню все свои книги. Но те, которые я назвал, сразу всплывают в памяти. И еще книги по Лиспу. Например, та, что издали Беркли и Бо-броу: это скорее собрание отдельных статей, но я многое вынес из него. Потом я начал читать журнал «SIGPLAN» и ежемесячный журнал «Communications of the ACM» («CACM»). Тогда в «САСМ» было полно технических подробностей, их интересно было читать.

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

А в Гарварде, если у меня было утром окно, я шел в библиотеку и делал одно из двух: читал «Scientific American», узнавая что-нибудь из истории науки, или «САСМ», заглядывая в будущее. Я не пропускал ни одной колонки Мартина Гарднера про математические игры. А в «САСМ» я читал то, что меня интересовало. В1972 году журналу исполнилось только 15 лет, и я прочитал все выпуски довольно быстро.

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

Стил: Именно так: целую отрасль. Было полно одностраничных статей. Например: «Вот новый эффективный метод хеширования». Я много читал такого.

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

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

Сейбел: Это ведь то, что делает Кнут?

Стил: Верно. Кнут и такие, как он.

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

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

Во-вторых, как найти хороший материал, тем более что само это понятие меняется со временем? То, что хорошо сегодня, через десять лет устареет. Надо спросить у того, кто в этом разбирается. Что было полезным мне? Кнут, Ахо, Хопкрофт и Ульман. Затем «The Psychology of Computer Programming» Джеральда Вайнберга — она все еще не утратила ценности. Кое-что мне дал «The Mythical Man-Month»1 Фреда Брукса.

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

Ф. Брукс «Мифический человеко-месяц или Как создаются программные системы». — СПб.: Символ-Плюс, 2000.

В основном учебники по Си и Java. Но если хорошо порыться, найдется немного книг по теории программирования, алгоритмам и так далее.

Сейбел: Есть другое чтение, и я знаю, что вы считаете его важным, — это чтение кода. Как вы ориентируетесь в чужом коде большого объема?

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

Сейбел: Путь исполнения?

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

Сейбел: А что значит «отслеживаете»? Смотрите на код и пытаетесь выполнить его в уме? Или берете отладчик и проходите код шаг за шагом?

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

Сейбел: И после этого вы зададите точку останова и начнете пошаговое исполнение от нее или просто исполните программу в уме?

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

Сейбел: А что такое правильный порядок содержимого файла?

Стил: Отличный вопрос. Удивительно, что одна из проблем такого языка, как Паскаль, связана с тем, что он задумывался для однопроходного компилятора: подпрограммы в файле располагаются «снизу вверх», так как перед использованием подпрограммы она должна быть определена. В итоге читать программы на Паскале необходимо задом наперед — только так получится взглянуть на них «сверху вниз». Сейчас все свободнее, и будет ли программа выстроена в удобном для вас порядке, зависит исключительно от вкуса программиста, который ее писал. Но вообще, сейчас есть удобные интегрированные среды разработки с перекрестными ссылками, и линейный порядок программ уже не столь важен.

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

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

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

В MIT прекрасно было то, что человек мог знакомиться с кодом, написанным довольно умными хакерами, — код не держали под замком. Так я изучил код операционной системы ITS, реализации ТЕСО и Лиспа. И программу структурной печати Билла Госпера для Лиспа. Я освоил их еще в старших классах и попытался воспроизвести кое-что на своей IBM 1130.

Но я бы не смог реализовать Лисп на IBM 1130, не имея доступа к реализациям этого языка для других компьютеров. Я просто не знал бы, что делать. И это было важной частью моего образования. Отчасти проблема сегодня в том, что программы приобрели ценность, что большинство серьезных программ — коммерческие, и у нас не так много хороших примеров кода для изучения. Открытый исходный код отчасти решает эту проблему. Можно, к примеру, посмотреть код Linux. Для меня очень полезным оказалось чтение исходного кода ТеХ, потому что это был хорошо продуманный, хорошо отлаженный код.

Сейбел: Я обычно читаю код, если мне надо узнать, как работает программа. А что подвигло вас читать исходный код ТеХ?

Стил: Иногда у меня есть четко определенная цель, поскольку мне требуется решить проблему. Дважды я не мог найти ошибку в макросе ТеХ, читая книгу «Все про ТеХ», и тогда пришлось читать «ТеХ: The Program», чтобы точно понять, как что работает. И оба раза я справлялся с трудностями за четверть часа, так как исходный код ТеХ очень хорошо документирован, снабжен перекрестными ссылками. Это само по себе откровение — то, что программа может быть так тщательно построена, документирована и индексирована, что все находится быстро.

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

Сейбел: Добравшись до конца, что вы для себя выносите?

Стил: Я понимаю, как устроен код, и у меня могут возникнуть идеи, как рациональнее построить свой собственный. Вряд ли я смогу подражать Кнуту, как не смогу писать в стиле Фолкнера или Хемингуэя. Но чтение этих авторов воспитывает чувство стиля. Может быть, по той или иной причине я приму сознательное решение не писать, как Хемингуэй. Это ценный опыт. Ну и потом, читать хорошо написанный роман или код — само по себе удовольствие.

Сейбел: Вы занимались литературным программированием?

Стил: Последовательно, в духе Кнута — нет. Он повлиял на меня, заставив задумываться о таких вещах, и теперь, прежде чем написать подпрограмму, я обычно пишу абзац текста. Но последовательно литературным программированием я не занимался. Иногда мне интересно — пишет ли он литературно, занимаясь исследовательским программированием, до того как готовит свои программы для публикации? Я не знаю, как выглядит весь этот процесс.

Сейбел: Значит, вы пробовали, но не сочли этот способ делающим процесс программирования более эффективным или приятным?

Стил: Отчасти мне не хотелось самому делать утилиты для литературного программирования. У Кнута все утилиты были организованы сначала вокруг Паскаля, а потом Си. Паскаль еще ладно, но недостатки Си я видел ясно, и литературное программирование, по-моему, не позволяло их преодолеть. Вот если бы Кнут сделал утилиты литературного программирования для Common Lisp, возможно, я смог бы на них быстро переключиться.

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

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

Сейбел: Какой код вы читали в последний раз для собственного удовольствия?

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

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

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

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

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

Сейбел: Ну, это в случае когда вам надо изменить код. Но приносит ли какую-нибудь пользу или удовольствие просто чтение кода? Распечатать, почитать, сделать какие-то пометки — и отложить в сторону?

Стил: Да. Если бы я этим и ограничился, просто чтение кода все равно было бы полезным упражнением. Я многое узнал о VRML, а в JavaScript, на мой вкус, маловато абстракций. Динамическая типизация в объектно-ориентированном языке, по-моему, все же лишена нужной строгости.

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

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

Иногда я рисовал блок-схемы — у меня был шаблон для блок-схем IBM и специальная бумага. Я учился программировать до эпохи структурного программирования, поэтому среди моих программ были и структурированные, и нет. Потом я понял пользу структурного программирования, и в 1970-е мои программы на языке ассемблера стали более структурированными: я делал циклы, if-then-else, больше заботился о структуре своего кода.

Я составлял списки того, что хотел задать на входе и получить на выходе, иногда приводил краткие примеры. Недавно я нашел одну из своих ранних программ на APL. Мне тогда было лет 15-16. То был кусок кода на APL — я набросал его на бумаге, прежде чем попробовать запустить. Рядом лежал другой клочок бумаги — примеры того, что у меня должно было быть на входе и на выходе. Там были ошибки, примеры были несовместимы с кодом, но, по крайней мере, я пытался дать примеры использования этой программы. Примеры того, что как я думал, увижу на терминале.

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

Сейбел: Вы говорили, что Йонл передал вам работу над интерпретатором, — до того он занимался и интерпретатором, и компилятором.

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

Сейбел: Значит, в интерпретаторе и компиляторе надо было отразить новые языковые свойства, которые приобрел Maclisp?

Стил: Да, языковые свойства. Многие из них системно-ориентированного характера — управление ресурсами, выделение страниц. Я ввел новый тип данных, который назвал «hunk», и это оказалось настоящей катастрофой. Он представлял собой cons-ячейку более чем с двумя указателями. Это был акт отчаяния, потому что мы вышли за пределы адресного пространства PDP-10, которое, напомню, было 18-битным. Половина указателей в списке уходила на то, чтобы поддерживать структуру списка, в то время как в цепочке данных типа hunk на это уходила всего лишь восьмая часть указателей. В результате память использовалась лучше.

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

Стил: Мы пару раз серьезно все переписывали. А однажды полностью пересмотрели структуру программы и реализацию всех операций ввода/вывода в языке — кажется, в 1975 или 1976 году. Старая система позволяла иметь один поток данных на входе и один на выходе и могла взаимодействовать с терминалом. Мы поняли, что получим гораздо более гибкую систему, если Лисп-объекты станут каналами ввода/вывода. И таких одновременно открытых каналов могло быть до 15.

Другим поводом было то, что Maclisp начали портировать на другие операционные системы. В разных местах использовался свой вариант операционной системы PDP-10, и, посмотрев на список клиентов, мы поняли, что придется поддерживать полдюжины различных операционных систем: TENEX, TWENEX, ITS, TOPS-10, WAITS и вариант CMU.

И вот тем летом мы с Джоном создали новый набор API. Тогда этот термин еще не употреблялся — были описания функций, пригодных для создания файловых объектов, их открытия и закрытия, систематизации процессов удаления и переименования и для получения перечня файлов в каталоге.

В какой-то момент я получил свежую распечатку всего Maclisp и на неделю засел в летнем доме родителей с шестью руководствами по ОС. Каждый день я шесть часов переделывал код.

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

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

Сейбел: Почему именно так?

Стил: Потому что каждой функции предшествовала громадная исследовательская работа. Как я уже говорил, надо было прочесть спецификации на шесть ОС. Один час в день я занимался этим, потом писал 30-90 строк кода. Не было особого смысла сидеть за компьютером — я ведь не мог ничего погуглить или найти онлайновую документацию. И лучше было заняться бумажной работой.

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

Стил: Так я и поступаю время от времени. Иногда просто приходится его выключать — вентилятор словно нашептывает: «Проверь почту, проверь почту». Я выключаю его или перевожу в режим сна, сажусь за стол на другом конце комнаты, раскладываю бумаги и начинаю думать, или пишу что-нибудь на доске, и так далее.

Сейбел: Как-то раз вы переиначили фразу Фреда Брукса насчет блок-схем и таблиц, сказав примерно так: «Покажите мне ваш интерфейс — и мне не нужен будет ваш код: это будет уже лишним и не будет относиться к делу». Имея дело с языками вроде Java, вы начинаете проектирование с интерфейса?

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

Сейбел: Если не рассматривать возможность реализации, как вы оцениваете интерфейс?

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

Я спроектировал много чего и теперь могу сказать, насколько хорошо это вышло. Сейчас я иногда проектирую вещи, родственные тем, что делал раньше. Например, создавая спецификации для числовых функций в Java, я вспоминаю, что уже делал это для Common Lisp. А еще я документировал числовые функции для Си. Я знаю, в чем подводные камни, если говорить о реализациях и спецификациях для таких вещей. Я потратил много времени, исследуя пограничные случаи. Об этом я вычитал у Тренчарда Мора, который разработал теорию массивов данных для APL. Он считал, что если прояснить все с пограничными случаями, промежуточные прояснятся сами собой. Правда, Мор не говорил об этом прямо, этот вывод сделал я сам.

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

Сейбел: Работая в MIT, вы были причастны к рождению Emacs. Но ранняя история Emacs несколько туманна. Можете ли вы что-нибудь прояснить?

Стил: Я занимался стандартизацией. Был избран такой способ отображения, что ТЕСО становился чем-то вроде WYSIWYG-редактора. На наших экранах размером 24x80 21 строка предназначалась для отображения тех строк, что были в буфере, а нижние 3 строки относились к командной строке ТЕСО. Там надо было вводить команды ТЕСО, и они исполнялись лишь при двойном нажатии клавиши Alt. Был и режим редактирования в реальном времени — там этого нажатия не требовалось. ТЕСО реагировал немедленно на каждый введенный символ как на команду. Вводишь один символ — исполняется команда, вводишь другой — исполняется другая. А большинство печатных символов вставлялось автоматически. Для перемещения вперед, назад, вверх и вниз служили управляющие символы. Это выглядело как очень примитивная версия Emacs.

Затем произошел прорыв. Идея была вот в чем: сейчас мы берем символ, ищем его в таблице, потом выполняем команду ТЕСО. Почему не применить это к редактированию в реальном времени? Каждый символ, который может быть введен, ищется в таблице. Таблица по умолчанию говорит, что печатные символы вставляются в текст, а управляющие символы делают то-то и то-то. Давайте сделаем это программируемым и посмотрим, что получится. Что же получилось? Несколько ярких личностей, связанных с MIT, одновременно придумали, что с этим можно сделать. И через несколько месяцев мы получили пять взаимно несовместимых GUI-интерфейсов для ТЕСО.

Сейбел: То есть они в основном настраивали клавиши?

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

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

Сейбел: Одним из этих парней был Ричард Стеллмен?

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

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

Я бегал с блокнотом по всему зданию, говорил со всеми этим парнями по несколько раз, пытаясь найти общий знаменатель. Я пытался как-то организовать те сочетания клавиш, которые они выбрали, пытался сделать так, чтобы они легко запоминались и складывались хоть в какую-то систему. Я не заботился об удобстве печатания вслепую, меня такие вещи вообще мало интересуют: я заботился о легкости запоминания. Вот почему Meta-C, Meta-L, Meta-U означают «с заглавной буквы», «строчные буквы», «заглавные буквы».

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

Стил: С этим сталкивалась моя жена. Это как-то прошло мимо меня — я не слишком ловко управляюсь с клавиатурой. Жена 20 лет не пользовалась Emacs, пока я не поставил эту программу на ее Макинтош. Она что-то ввела и спросила: «А как сохранить? Я забыла, как сохраняют файлы». Ее пальцы делали это автоматически, и она не помнила само сочетание. Тогда она проделала это, следя за своими пальцами, и сказала: «Ах да, Ctrl-X, Ctrl-S». To есть она печатала неосознанно.

Сейбел: Вы установили стандартные сочетания клавиш. Как это прошло? Люди были довольны?

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

И я поначалу решил разработать уплотнитель макросов на основе идеи Муна. То есть идея была не моя. Я стал думать, как организовать первые несколько функций, взял в качестве примера уже имевшиеся пакеты макросов, сделал своего рода синтез. Тут появился Стеллмен и сказал: «Чем ты занят? Выглядит интересно». Он подключился к этому делу, и все пошло в десять раз быстрее — Ричард знал ТЕСО досконально.

Выходит, я всерьез работал над реализацией Emacs 4-6 недель, не больше. Потом стало ясно, что Стеллмен все понял в этой программе, и я могу возвращаться к моим магистерским делам. Стеллмен сделал 99,999% всей работы. Но начал ее я.

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

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

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

Заметим, что у операции сложения есть нейтральный элемент. Вы должны начать с нуля. Тривиальное замечание, но все же сделаем его.

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

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

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

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

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

Сейбел: Да, это хороший пример, потому что его поймет каждый, кто знает арифметику. Но не считаете ли вы, что таким же образом в программирование входят и понятия более высокого уровня?

Стил: Предположим, я генерирую отчет. Типичная ситуация: я делаю сколько-то выводов на печать, они должны быть выведены в определенном порядке. В многоядерном мире я, возможно, захочу делать это не в линейном порядке, а разложить на разные процессоры. Как мне связать все воедино? Можно ли использовать те же методы, что при сложении чисел? Выясняется, что здесь есть ассоциативность, но нет коммутативности, — тогда я знаю, какие приемы будут работать для строк, а какие не будут. Как разработчик языков программирования, имеющий дело с созданием параллельных языков, я нахожу эти понятия и прилагающийся к ним словарь очень полезными.

Сейбел: Кстати о создании языков: как менялся ваш подход с течением времени?

Стил: Главную перемену я описал в своем докладе «Growing a Language» (Выращивание языка) на конференции по объектно-ориентированному программированию OOPSLA 1998 года. В 1970-е годы создатель языка готовил для него проект, реализовывал его и на этом всё. Или не всё; но вы оставались с мыслью, что язык завершен.

Возьмем Паскаль. Что-то по каким-то причинам туда вошло, что-то не вошло, но проектирование языка был завершено. Если бы выяснилось, что чего-то не хватает, что работа со строками никуда не годится, что ж, не повезло. Вирт создал такой язык. ПЛ/1 и Ада были спроектированы таким же образом. Возможно, Ада и C++ стали едва ли не последними языками такого рода. В меньшей степени C++ — он все-таки развивался с течением времени.

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

Сейбел: Значит, по-вашему, Java проектировался уже по-другому?

Стил: Наверное, нет. Хотя должен был бы. Java менялся благодаря Java Community Process. Это касалось больше API, чем ядра. Последние 12-13 лет к языку добавлялись все новые свойства, но, наверное, его создатели в начале 1990-х думали, что создают совершенный язык для конкретных ограниченных целей. Вы знаете, их целью были ресиверы для телевизоров.

Сейбел: Верно.

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

Особенно требовалось нечто вроде итерации с помощью цикла for путем перечисления. И это добавили в язык. Ученые, которые занимались высокопроизводительными научными вычислениями, требовали большей поддержки операций с плавающей запятой и тому подобного. Но участники Java Community Process это в целом отвергли — по причинам не столько техническим, сколько социальным, мне кажется.

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

Сейбел: A Common Lisp, в создании которого вы участвовали, может служить в этом смысле примером?

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

Сейбел: Недавно три языка, которые вы в той или иной степени проектировали, прошли или еще проходят болезненный редизайн. Scheme прошел R6RS; JavaScript — ECMAScript — проходит через споры «ES4 против ES3». И с Java спорят по поводу того, надо ли, а если надо, то как добавлять туда замыкания.

Стил: Кстати, да.

Сейбел: Это примеры языков, которым не хватило встроенных технических или социальных средств для того, чтобы развиваться нормально, и поэтому им пришлось пройти через этот болезненный процесс роста? Или это неизбежно?

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

Сейбел: А есть примеры языков, которые развивались легко?

Стил: Ну, думаю, Лисп может служить примером языка, который легко вырос — благодаря гибкости системы макросов. А отчасти тут сыграла роль атмосфера, царившая в группе разработчиков.

Scheme, напротив, развивался с большим трудом — отчасти потому, что внутри сообщества разработчиков установилось правило: любое изменение должно быть одобрено всеми. Или почти всеми. Один черный шар — и все. А разработчики Common Lisp решили, что достаточно согласия большинства. Там народ не сходил с ума из-за каждой мелочи — люди знали, что взамен получат что-то полезное.

Сейбел: Выбор языка действительно важен? Есть ли серьезные доводы в пользу одного языка или другого? Или все это дело вкуса?

Стил: А что, вкус — плохой довод?

Сейбел: Допустим, я люблю ванильное мороженое, вы — шоколадное, но мы не станем из-за этого спорить. А люди спорят из-за языков программирования.

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

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

Например, проектируя алгоритмы, я смешиваю языки. Обдумывая что-то, я пишу на доске код на Java и тут же на Фортране, на APL. Я сам смогу разобрать, что у меня вышло, и это главное. Такая форма записи позволяет мне сделать каждый кусок алгоритма как можно более ясным и полезным.

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

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

Сейбел: Одно из решений как раз предлагает Лисп, где все записи средней длины. И пользователи могут делать свои синтаксические расширения на базовом уровне языка — в том же духе, с записями средней длины. Тем не менее многие не любят S-выражения. Многие лисперы самоуверенно считают, что недовольные этим языком просто не видят всей прелести этого решения. А вы достаточно самоуверенны, чтобы полагать, что если человек действительно понимает Лисп, то скобки не будут его раздражать?

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

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

Сейбел: Какими языками вы пользовались всерьез? Список, наверное, будет длинным...

Стил: Я заработал свои первые деньги, программируя на Коболе, еще в старшей школе. Я подрядился сделать систему по генерации табелей успеваемости для какой-то другой школы. Потом были Фортран, язык ассемблера IBM 1130, машинный язык PDP-10, APL. Снобол всерьез я не использовал. Ну и, конечно, Си, C++, Bliss, язык реализации для DECsystems, разработанный в университете Карнеги-Меллона, GNAL, основанный на Red, — его я использовал довольно серьезно.

Кроме них всяческие разновидности Лиспа, включая Common Lisp, Scheme, Maclisp. И версию Лиспа, которую мы с Диком Гэбриелом создали для S-1 — S-1 Lisp; потом он стал одной из четырех или пяти частей, которые вместе образовали Common Lisp. Я разработал Connection Machine Lisp, но вроде ничего серьезного на нем не писал. Он, кажется, был реализован на *Lisp. He надо путать *Lisp и Connection Machine Lisp — это два разных языка.

Довольно много я писал на С* — его мы тоже разработали для Connection Machine. Java, конечно же. Писал на некоторых скриптовых языках — JavaScript, Tel.

Всерьез я имел дело также с Haskell — работал с ним больше месяца, написал длинный фрагмент кода. Фокал, ранний интерактивный язык для компьютеров DEC, похожий... немного на Бейсик, немного на JOSS. Помнится, на Бейсике я тоже писал. ТЕСО (Text Editor and Corrector) — я пользовался им для создания ранней версии Emacs, значит, его тоже можно отнести к языкам программирования. На ТЕСО пришлось писать очень много. И еще ТеХ, если и его рассматривать как язык программирования. Думаю, это основные.

Сейбел: Из сказанного вами я делаю вывод, что на вопрос «Какой ваш любимый язык программирования?» ответом будет дзэнское «му».

Стил: У меня трое детей: кого я люблю больше всех? Они все хорошие — это разные личности с разными способностями.

Сейбел: А есть языки, которыми вам просто не нравится пользоваться?

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

На Perl я писал слишком мало, чтобы говорить уверенно, но он меня не привлек. И C++ тоже. Я писал код на C++. Думаю, все, что делается на C++, можно так же хорошо сделать на Java, но с меньшими усилиями. Если во главу угла не ставится эффективность.

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

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

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

Сейбел: Из-за чего именно труднее?

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

Сейбел: Интересно, что вы сказали «все более совершенные языки». Есть такое течение — его можно назвать «поклонники стиля Scheme». Его представители считают, что единственный способ победить сложность — делать все, включая языки программирования, очень простым.

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

Сейбел: Ведь в Javadoc мы видим главным образом вполне читаемую прозу, которая развилась из кода?

Стил: Отчасти да, отчасти нет. В коде на Java плохо улавливается связь между параметрами. Скажем, у нас есть массив данных и есть целое число, и это целое число должно быть корректным индексом этого массива. В Java выразить это не так просто. А это важно. В Fortress это можно сделать.

Сейбел: Это скомпилировано в утверждения (asserts) времени выполнения или проверяется статически?

Стил: Зависит от того, как удобнее. И так, и так. Мы стараемся, чтобы в Fortress такие связи улавливались. Ранее мы говорили об алгебраических связях, о том, что некоторые операции ассоциативны. Мы хотим, чтобы в Fortress это можно было указывать явно. Вряд ли каждый прикладной программист будет останавливаться и думать: «Ага, подпрограмма, которую я написал, ассоциативна».

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

Сейбел: А как насчет того, чтобы язык не позволял совершать ошибки? Одни думают так: «Если сделать язык достаточно закрытым, то невозможно будет писать плохой код». Другие же, наоборот, говорят: «Забудьте, такой подход обречен, мы можем с тем же успехом оставить все широко открытым, а программистам надо просто быть умнее». Как здесь найти баланс?

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

Сейбел: Есть ли в современных языках какие-нибудь интересные сюрпризы?

Стил: Python очень неплох — в том смысле, что хорошо структурирован. Но Гвидо изначально не добавил сборку мусора, и мне это не нравилось. Потом он вроде бы пересмотрел свое решение, как я и предвидел. Там есть интересные синтаксические находки: отступы, например, и двоеточия в конце некоторых конструкций; это очень остроумно. Реализация объектов и замыканий тоже довольно любопытна.

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

Стил: Это правда. Однако Гвидо приходилось идти на компромисс между ясностью, возможностью реализации и так далее. И все равно получился интересный набор свойств. Я сделал бы по-другому, но он работал для определенного пользовательского сообщества. Я понимаю, почему он в каждом случае поступил так, а не иначе, и уважаю его выбор. Haskell — красивый язык, я люблю его, хотя использую мало.

Сейбел: Итак, любя Haskell, вы сейчас проектируете язык, но Fortress — это не чисто функциональный язык?

Стил: Сейчас в Haskell используются монады: монада ввода/вывода, монада транзакционной памяти. Есть теория, что это очень функционально; может, это и правда помогает в работе. Но с другой стороны, кажется, что язык становится все более императивным. Как там говорил Белый Рыцарь из «Зазеркалья»? «Но я обдумывал свой план, как щеки мазать мелом, а у лица носить экран, чтоб не казаться белым». Монады для меня — как раз такой экран: сначала вы вытаскиваете ввод/вывод, потом пытаетесь его скрыть обратно — и в итоге непонятно, есть побочные эффекты или нет.

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

Сейбел: Вы пишете много прозы, этот род деятельности вам также интересен. Как по-вашему, писать прозу и код — занятия одного порядка или нет?

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

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

У меня есть любимый скетч из передачи «Saturday Night Live» — тот, где Эд Эснер изображает сотрудника АЭС, который уезжает в отпуск на две недели. Перед уходом он говорит: «Всем пока! Помните, в ядерном реакторе не может быть слишком много теплоносителя». И дальше минуты три все обсуждают, что же он хотел сказать.

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

Стил: О, это так.

Сейбел: Значит, в этом плане программисту полезно писать прозу?

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

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

Сейбел: По мере того как языки улучшаются или хотя бы становятся дружественными к программисту — в сравнении с временами ассемблера и перфокарт, — избегать ошибок в программах вроде бы становится легче. Есть компиляторы, сигнализирующие об ошибках, и так далее. Так можно ли отдать предпочтение — пусть небольшое — читаемости кода перед правильностью? Как говорят разработчики на Haskell: «Если ваша программа на Haskell выполняет проверку типов, можно спать спокойно».

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

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

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

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

Стил: Да. И, кроме того, люди сосредоточены на главном, они не думают о пограничных случаях, о сложных случаях, о маловероятных случаях. А именно в таких случаях начинаются разногласия — как сделать правильно.

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

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

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

Сейбел: Возьмем «Fantasia» — она в том числе об опасности рекурсии.

Стил: «Fantasia» и рекурсия, да. Или «Я хочу быть самым богатым человеком в стране», — в итоге все становятся бедняками, а он остается при своем. Такое в волшебных сказках случается, потому что люди забывают о разных путях, ведущих к цели. Если думать только о главном желании, пренебрегая деталями, много чего не стыкуется.

Сейбел: Каков же урок волшебных сказок? Гэндальфы становятся великими магами путем тяжких трудов и зубрежки заклинаний, а легкого пути нет?

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

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

Стил: Я уже говорил: нельзя пренебрегать точностью. В то же время мы можем создавать инструменты для повышения точности. Мы не можем сделать эту процедуру тривиальной, но можем помочь избежать некоторых ошибок. Вместо того чтобы допускать циклическое переполнение 32-битных целых, можно сделать обнаружение арифметического переполнения или предоставить возможность работы с большими числами. Сейчас реализация всего этого обходится дороже, но я считаю, что работа с настоящими большими числами дает немного меньше ошибок для некоторых видов программирования. Программисты и создатели алгоритмов для операционных систем все время попадают в одну и ту же ловушку. Они говорят: «Нам нужно синхронизировать фазы, так что будем брать по одному числу. При начале новой фазы вычисления будем увеличивать на единицу какую-нибудь переменную, получать новое число — и тогда все участники будут уверены, что работают в одной и той же фазе, пока не начнется какая-нибудь операция». Это работает на практике, но с 32-битными числами вы досчитаете до 4 миллиардов довольно быстро. Что будет в случае циклического переполнения? Все будет в порядке? Или нет? Многие подобные алгоритмы в компьютерной литературе содержат эту небольшую ошибку. Что если какой-нибудь поток застопорится со 2-й по 32-ю итерацию? Маловероятно, но все-таки возможно. Надо или как-то смягчить эту проблему в смысле корректности, или все просчитать и показать, что такой вариант настолько маловероятен, что беспокоиться не о чем. Или, возможно, для вас приемлем один компьютерный сбой в день. Суть в том, что надо проанализировать проблему, а не игнорировать ее. Тот факт, что счетчик может переполниться, — проблема еле заметная, большинство программистов она не затронет. Но для остальных это ловушка в их алгоритмах.

Сейбел: Кстати о сбоях. Какова худшая ошибка, с которой вы имели дело?

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

Когда я был зеленым программистом и работал на IBM 1130, решение, как исправить ошибку, однажды явилось мне во сне. Или сразу после пробуждения. Я бился над ней пару дней, ничего не получалось. И вот посреди ночи — озарение. Оказалось, я кое-что пропустил в спецификации интерфейса.

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

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

А вот другая занятная история. Я отвечал за Maclisp, a Maclisp поддерживал большие числа — целые числа произвольной точности. Они у нас были уже несколько лет, считалось, что они хорошо отлажены. Они широко использовались в Macsyma, пользователи Macsyma все время с ними работали. И вот приходит сообщение от Билла Госпера: «Частное двух этих целых чисел неверно». Он заметил это, поскольку частное примерно равнялось Пи.

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

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

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

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

Сейбел: Как обычно — ошибка не ходит одна.

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

Сейбел: Кроме ночных озарений, каков ваш излюбленный метод отладки? Что вы предпочитаете — символьные отладчики, вывод на печать, утверждения, формальные доказательства или все сразу?

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

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

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

Сейбел: Раз уж вы упомянули контрактное программирование: как вы используете утверждения в собственном коде?

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

Сейбел: А как насчет пошаговой отладки? Если все прочее не помогает, вы прибегаете к ней?

Стил: Зависит от длины программы. И, конечно, помогают инструменты, которые позволяют пропускать целые заведомо безошибочные куски. В Common Lisp есть отличная функция STEP. Я много раз пошагово отлаживал код на Common Lisp. Это очень здорово — возможность пропустить функции, в которых уверен. И еще возможность расставить ловушки и сказать себе: «Сюда мне смотреть не нужно, до тех пор пока этот цикл не повторится семнадцать раз». На PDP-10 были для этого аппаратные средства, это было здорово, особенно в MIT. Тогда они любили модернизировать свои машины, добавляя в них что-нибудь. Можно долго рассказывать про выполнение одного и того же кода разными способами.

Сейбел: Вы применяете к своему коду формальные доказательства?

Стил: Зависит от кода. Если в нем есть сложные математические инварианты, я использую доказательства. Я не стану писать функцию сортировки, пока не подберу какой-нибудь инвариант и не докажу его.

Сейбел: Питер ван дер Линден в своей книге «Expert С Programming» (Программирование на Си для экспертов) отвел доказательствам целую главу в пренебрежительном духе. Вот вам доказательство того-то, но смотрите — оно с ошибками! Ха-ха-ха!

Стил: Да, и доказательства могут быть с ошибками.

Сейбел: Но, по крайней мере, встретить ошибки в них меньше шансов, чем в проверяемом коде?

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

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

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

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

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

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

Сейбел: Итак, алгоритм содержал ошибки, и доказательство их пропустило.

Стил: Да, получилось, что доказательство некорректно. Что-то где-то было упущено. Какие-то детали работы с формулой — формула была почти правильна, но именно «почти». Дело было лишь в том, чтобы поменять местами, например, две строки кода.

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

Стил: Вряд ли бы я даже понял, что там есть ошибка. Алгоритм был довольно мудреным: я посмотрел бы на код, понял его общий смысл — и не заметил бы неточности. Нужна была очень маловероятная последовательность действий, чтобы она проявилась.

Сейбел: И все выявилось в процессе доказательства, то есть вам не надо было рассматривать различные сценарии типа «что, если» для обнаружения проблемы.

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

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

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

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

Сейбел: И программа автоматической проверки, проверенная вручную, может сэкономить 25 часов, потраченные вами на изучение доказательства фрагмента кода?

Стил: Да. Точно.

Сейбел: О чем еще вы бы хотели поговорить?

Стил: Мы не коснулись такой темы, как красота программ. А мне хочется сказать об этом пару слов. Некоторые программы буквально поражали меня своей красотой. Например, ТеХ, то есть исходный код ТеХ. METAFONT — в меньшей степени, но не знаю, почему: то ли потому, что я работал с ним меньше, то ли потому, что мне и вправду меньше нравятся структура кода и архитектура программы.

Есть великолепные алгоритмы, которыми я просто восхищался. Я видел чудесные программы для сжатия кода — в те времена у тебя был всего один мегабайт памяти, и это имело значение. Было важно, сколько слов ты используешь — 40 или 30, и люди временами серьезно работали над тем, чтобы ужать программу. Билл Госпер писал эти шедевры из четырех строк, и они творили потрясающие вещи, например с усилителем, подключенным к младшим битам аккумулятора, который в это время тасовал биты.

Это может показаться бессмысленной тратой сил, но одним из лучших моментов в моей карьере был тот, когда я смог сократить программу Госпера из 11 слов до 10. И это — ценой лишь небольшого увеличения времени выполнения программы, ценой малой доли машинного цикла. Я нашел способ сократить код на одно слово, и на это у меня ушло всего лишь 20 лет.

Сейбел: И вот, спустя 20 лет, вы сказали: «Привет, Билл, а представляешь?..»

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

Сейбел: Прямо «История Мэла, настоящего программиста».

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

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

С IBM 1130 трудность состояла в том, что на перфокарте 12 строк, а в машинном слове было 16 бит. Так что на 16-битную инструкцию приходилось 12 бит, то есть некоторые инструкции не помещались на перфокарте. Такие инструкции приходилось собирать с помощью тех инструкций, которые помещались на перфокарте. Возникала сложная система компромиссов, какие инструкции можно использовать: если я использую вот эту инструкцию, мне нужны будут еще вот эти инструкции на перфокарте, просто чтобы собрать ее. Огромная нагрузка плюс размер функции не мог превышать 80 слов. Поэтому приходилось использовать некоторые инструкции и как данные, использовать данные повторно для других целей. Если удавалось впихнуть эту функцию в память, то ее адрес мог использоваться как константа. Такой вот стиль программирования — не то оригами, не то хайку. Я этим занимался несколько лет.

Сейбел: Как вы думаете, те, кто прошел через это, сейчас справляются лучше или хуже других программистов?

Стил: Они приучены работать с ограниченными ресурсами и умеют точно их оценивать.

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

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

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

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

Сейбел: Вы говорили о ТеХ Кнута, — она существенно больше по размерам. Что придает ей красоту?

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

Сейбел: Значит, здесь красота в том ощущении, что есть блок и клей, и можно сказать: «Да, это глубоко правильная идея, я проникся ее красотой и хочу видеть ее в других программах». Вы проникаетесь ее красотой в процессе чтения кода, глядя на соотношение его элементов? Или же, прочитав код, говорите: «Великолепно, все основано на этой простой, но не упрощенной идее»?

Стил: И то и другое. Кнут замечательно умеет рассказывать о коде. Можно целый день читать «Искусство программирования», погружаться в алгоритмы. Он объясняет вам их, показывает, как их можно использовать, дает упражнения, и создается ощущение, что вас увлекают в интересное путешествие. И попутно показывают вам очень занятные вещи. Если пробираться через код ТеХ, ощущения сходные. Я много чего понял о программировании. Одни куски кода вполне обычны, порой даже поверхностны. А при виде других говоришь себе: «Даже не подозревал, что можно сделать так». Есть и то, и то.

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

Стил: Да. В комитете по разработке Common Lisp мы часами обсуждали конец строки, чтобы добиться совместимости с UNIX, где есть только символ перевода строки, и с системами PDP-10, где используется CR LF. Надо было приспособить перевод строки для обоих систем. Настоящий кошмар.

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

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