Джо Армстронг известен прежде всего как создатель языка программирования Erlang и Open Telecom Platform (OTP) — платформы для создания приложений на Erlang.
Среди современных компьютерных языков Erlang выглядит чудаком. Он старше и одновременно моложе многих популярных языков: Армстронг начал работу над ним в 1986 году — за год до появления Perl, — но язык был доступен лишь в качестве коммерческого продукта и использовался только в изделиях компании Ericsson, пока не был выпущен с открытым исходным кодом в 1998 году — через три года после появления Java и Ruby. Истоки Erlang кроются скорее в логическом языке Пролог, чем в каком-либо из языков семейства Алгол. Кроме того, он был разработан для специфического вида ПО с высокой доступностью и надежностью, как например АТС. Свойства, благодаря которым этот язык применялся на АТС, почти неожиданно сделали его пригодным также для написания программ, предназначенных к использованию в параллельных системах. Это привлекло к Erlang внимание программистов, когда стало понятно, что будущее за многоядерными процессорами.
Армстронг в каком-то смысле тоже чудак. Будучи физиком, он стал осваивать информатику, когда во время написания диссертации по физике, испытывая финансовые затруднения, нанялся исследователем в команду Дональда Мичи — одного из британских первопроходцев в области искусственного интеллекта. В лаборатории Мичи Армстронг познакомился со всеми достижениями в этой сфере, стал членом-основателем Британской робототехнической ассоциации и написал несколько статей о зрении роботов.
Когда финансирование работ в области искусственного интеллекта иссякло благодаря известному «Отчету Лайтхилла», Армстронг на несколько лет вернулся к программированию для целей физики, работая в научной ассоциации EISCAT и затем в Шведской космической корпорации, пока наконец не оказался в Лаборатории информатики компании Ericsson, где и создал Erlang.
Сидя на кухне стокгольмской квартиры Армстронга, мы несколько дней обсуждали подход к параллелизму, реализованный в Erlang, необходимость поиска более простых и эффективных способов связи между программами, а также важность вскрытия «черных ящиков».
Сейбел: Как вы научились программированию? Когда все это началось?
Армстронг: В школе. Я родился в 1950 году — тогда компьютеров было мало. В последнем классе, когда мне было примерно 17, местный совет приобрел мэйнфрейм — вероятно, производства IBM. На нем можно было писать программы на Фортране. Тогда все писали программы на специальных бланках и отсылали их. Через неделю программные бланки и перфокарты возвращались к вам, и надо было их одобрить. Но те, кто набивал перфокарты, часто ошибались, так что они путешествовали туда-сюда, иногда по два раза. В конце концов все это поступало в компьютерный центр.
Из компьютерного центра их опять присылали к вам, и компилятор Фортрана, споткнувшись о первую же синтаксическую ошибку, с остальной частью программы вообще не работал. Чтобы запустить свою первую программу, требовалось месяца три. Тогда я научился вместо одной программы писать несколько параллельных подпрограмм, и отсылал уже их. Я написал программку, которая выводила на печать изображение шахматной доски. Но все подпрограммы были решены как параллельные задачи, поскольку время обработки задания было невероятно большим.
Сейбел: Значит, приходилось писать подпрограммы, по сути, с модульными тестами для их проверки?
Армстронг: Да, и потом соединять их. Не знаю, можно ли это назвать обучением программированию. Потом я поступил на физическое отделение Лондонского университетского колледжа. Кажется, программирование там начиналось уже на первом курсе. Время обработки заданий составляло часа три. Но при запуске одновременно четырех-пяти программ все шло быстрее.
Сейбел: А в старших классах были уроки информатики?
Армстронг: Нет, был факультатив — что-то вроде компьютерного клуба. Помню, как мы ходили смотреть на компьютер. Множество серьезных, взрослых людей в белых халатах, с ручками в кармане, двигались торжественно, как в церкви. Компьютер был очень дорогой.
Сейбел: Вы изучали физику. А как вы перешли к программированию?
Армстронг: Когда я был на последнем курсе, на некоторых занятиях требовалось писать программы. Мне это нравилось. Я очень хорошо освоил отладку и делал ее для других, если ничто больше не помогало. Стандартной таксой была бутылка пива. Дальше все зависело от сложности — двухбутылочная задача, трехбутылочная задача и так далее.
Сейбел: То есть сколько человек должен был вам за отладку его программы — две, три бутылки?
Армстронг: Да. Исправляя ошибки, я обычно читал программу и говорил: «Тут можно бы сделать и попроще», — и переписывал ее. Меня поражало, что народ пишет такие сложные программы. Я понимал, как обойтись пятью строчками, а другие писали их десятками! И я удивлялся: как можно не видеть простого решения.
Но по-настоящему я занялся программированием после того, как стал бакалавром и решил, что буду писать диссертацию. И вот я начал трудиться над диссертацией по физике высоких энергий и присоединился к группе, которая работала с пузырьковой камерой. У них был компьютер Honeywell DDP-516. Я мог работать с ним сам! Да, там были перфокарты, но я мог запускать программы — вставляешь карту, нажимаешь кнопку — трррр! — и машина тут же выдает ответ. Просто восторг! Я написал для этого компьютера шахматную программку.
Тогда применялась память на магнитных сердечниках, которые соединяли вместе пожилые тетеньки. Если вы заглядывали внутрь, то видели магнитики и кучу проводов. Страшно дорогая штука — двадцать дисков весом под пятнадцать кило, на которых умещалось мегабайт десять. И телетекстовый интерфейс, чтобы настукивать программы.
А потом появился один из первых дисплеев — можно было набирать программы и редактировать их. Фантастика! Никаких больше перфокарт. Помню, мы разговорились с сотрудником, который обслуживал компьютер, и я сказал: «Когда-нибудь такой будет у каждого». А он мне в ответ: «Ты с ума сошел, Джо!» — «Почему?» — «Да потому, что это слишком дорого».
Вот тогда я и научился программировать всерьез. Мой научный руководитель говорил: «Тебе надо бросить диссертацию по физике и заняться компьютерами, раз ты так их любишь». Я возражал, что хочу закончить ее, если уж начал. Но он был прав, как видим.
Сейбел: Так вы написали диссертацию или нет?
Армстронг: Нет. У меня закончились деньги, и я поехал в Эдинбург. Когда я изучал физику, то, как и другие, занимался в физической библиотеке. В уголке там прятался стеллаж с компьютерными книгами. Среди них были четыре коричневых тома под названием «Machine Intelligence» (Машинный интеллект), изданные отделением машинного интеллекта Эдинбургского университета. Я в принципе изучал физику, но с жадностью поглощал эти книги и думал: «До чего же занятно!» Поэтому я написал Дональду Мичи, который возглавлял это отделение, о том, что все это мне очень интересно, и спросил, нет ли у него какой-нибудь работы. Мне пришел ответ. Мичи писал, что работы пока нет, но он был бы рад познакомиться, посмотреть, кто я такой.
Через несколько месяцев Мичи не то позвонил, не то написал мне, что в ближайший вторник будет в Лондоне и хотел бы встретиться со мной. Так как он пересаживался на эдинбургский поезд, то предлагал увидеться на вокзале. Мы встретились, и Мичи сказал: «Хм, здесь не очень-то поговоришь — пойдемте в паб». Мы поболтали в пабе, и чуть позже он написал мне: «У меня в Эдинбурге есть место в лаборатории, не хотите ли поработать?» Я переехал в Эдинбург и стал помощником Мичи по исследовательской части. Вот так я бросил физику ради компьютерной науки.
Во время Второй мировой Мичи работал с Тьюрингом в Блетчли-парке, и ему достались все бумаги Тьюринга. У меня был стол в библиотеке Тьюринга, так что я сидел среди тьюринговских бумаг. Год я проработал в Эдинбурге. Потом все там рухнуло. Дело в том, что правительство поручило математику Джеймсу Лайтхиллу выяснить, как в Эдинбурге обстоят дела с искусственным интеллектом. Лайтхилл выяснил и заявил, что ничего коммерчески ценного из этого не вырастет.
Вообще, все это напоминало гигантский детский манеж. Я был членом-основателем Британской робототехнической ассоциации, и нам казалось, что все это — дело чрезвычайной важности. Но те, кто финансировал нашу работу, не хотели и слышать о роботах! Где-то около 1972 года финансирование прекратилось. Народ стал говорить: «Что ж, занятная была работа, теперь надо искать что-то другое».
Итак, надо было возвращаться к физике. Я приехал в Швецию и стал заниматься физическими компьютерными программами в научной ассоциации EISCAT. Мой начальник, старше меня, пришел из IBM; я должен был разрабатывать спецификацию, аон- реализовывать ее. Мы спорили по этому поводу. Он говорил: «Плохо то, что у нас нет описания задания и подробной спецификации». А я отвечал: «Так это же прекрасно, что нет описания, — можно самому определять задание для себя». Через год он ушел, а я занял его место — место ведущего разработчика.
Я спроектировал то, что сейчас бы назвали прикладной операционной системой, то, что работает как надстройка над обычной ОС. К тому времени компьютеры уже заметно подешевели. У нас были норвежские NORD-10 — кажется, они задумывались для конкуренции с PDP-11.
Там я провел почти четыре года, а потом получил работу в Шведской космической корпорации, разработал еще одну прикладную операционную систему для управления «Викингом», первым шведским спутником. Проект был интересный. Я работал на машине, названия которой не помню, но это был клон компьютера Amdahl. Только строковые редакторы, никаких полноэкранных. Все программы хранились в одном каталоге. Не больше десяти букв в имени файла, не больше трех в расширении. Плюс компилятор Фортрана или ассемблер — вот и вся система.
При этом я думаю, что все эти современные штуки с наворотами вряд ли делают вас продуктивней. Как работа может улучшиться при иерархической файловой системе? Как бы то ни было, программы создаются главным образом в голове. Мне кажется, что работа с такой довольно простой системой дисциплинирует мышление. Если все файлы надо складывать в один каталог, нужна строжайшая дисциплина. Если нет системы управления версиями, нужна строжайшая дисциплина. И если работать дисциплинированно, то, по-моему, нет особой нужды ни в иерархической файловой системе, ни в системе управления версиями. Они не решают главную проблему. Пожалуй, с ними удобнее работать вместе нескольким людям. Если же работать одному, то разницы я не вижу.
Кроме того, сейчас у нас что-то вроде избытка выбора. У меня имелся только Фортран — помнится, не было даже консольных скриптов. Командные файлы для запуска программ, компилятор Фортрана — и все! Ну и ассемблер, наверное, если кому-то он был нужен. Никакого мучительного выбора, как сейчас. Думаю, быть сегодня молодым программистом просто жутко: двадцать языков, десятки фреймворков и операционных систем — можно погибнуть, выбирая. А в то время — ничего подобного. Просто начинаешь работать, потому что язык и программы выбирать не приходится. Просто берешь и работаешь.
Сейбел: Разница еще и в том, что сегодня не понять, как устроена система сверху донизу. Проблема не только в выборе, но и в том, что не во всех используемых «черных ящиках» хочется разбираться.
Армстронг: Ну да. Если «черные ящик» работает неважно и надо что-то в нем подкручивать, то легче взять и написать все самому. А вот что точно не работает, так это повторное использование кода. С этим совсем плохо.
Сейбел: Вы создали не только Erlang, но и Open Telecom Platform — платформу для разработки приложений. Что вы скажете о ее многократном использовании?
Армстронг: В какой-то мере это возможно. Но возникает все та же проблема. Если эта платформа полностью решает вашу задачу, если какой-нибудь программист, ничего не знающий о структуре ОТР, посмотрит на нее через несколько лет и скажет: «Вот именно то, что мне нужно», — отлично, вы уложились в эту меру повторного использования. Если же этого не случится, проблема останется.
Не так давно мне говорили: «Все это как-то искусственно, мы все время стараемся впихнуть код в этот ОТР». Я отвечал: «Ну что ж, переделайте ОТР». Но ведь этого не сделаешь. При этом фреймворк — всего лишь программа, довольно простая по устройству. Действительно простая. Я залезаю в нее, и она делает то, что нужно этим людям, после чего они соглашаются: «И правда, просто». Но при этом заявляют: «Наши менеджеры не хотят, чтобы мы возились с фреймворком». Ну так назовите это по-другому, и все.
Сейбел: А как по-вашему, реально ли на самом деле вскрыть все эти «черные ящики», заглянуть внутрь, понять, как они работают, и приспособить их для своих нужд?
Армстронг: С годами я, кажется, все чаще допускаю вот эту типичную ошибку: боюсь вскрыть «черный ящик». Он кажется мне таким непроницаемым, таким сложным, что я не хочу открывать его. Один-два я все же открыл; я хотел сделать оконную графическую программу для Erlang и подумал: «А не запустить ли ее на X Windows». Что такое X Windows? Это сокет с протоколом поверх. Открываешь сокет и транслируешь через него сообщения. Зачем библиотеки? Erlang основан на сообщениях. Идея в чем? Вы посылаете сообщения по какому-то адресу, и там что-то делается. То же и в X Windows — у вас есть окно, вы посылаете сообщение, и начинается выполнение. Если же вы делаете что-то в окне, сообщение вам возвращается. Очень похоже на Erlang. Однако программирование в X Windows происходит при помощи библиотек с обратными вызовами. Это не философия Erlang. Послать сообщение, чтобы начала выполняться команда, — вот философия Erlang. Итак, оставайтесь подключенными и обращайтесь непосредственно к сокету, без библиотек.
И знаете, это очень легко. Х-протокол принимает 80-100 сообщений, а вам нужно, скажем, только 20. Вы отображаете их в Erlang, стоит чуть поколдовать — и вы можете отправлять сообщения прямо в окна, а те уже займутся всем. К тому же это быстро работает. Правда, выглядит не очень — я мало заботился о графике, о внешнем виде, над этим надо еще поработать. Но главное — это легко.
Кроме того, я создал программу верстки, где границей абстракции, которую я перешел, был PostScript. Подходя к этой границе, боишься ее пересекать, поскольку думаешь, что за ней что-то невероятно сложное. Но и тут все оказывается легко. Это язык программирования. Хороший язык программирования. Границу абстракции пересечь легко, и это очень полезно.
Когда издавалась моя книга по Erlang, издатель сказал: «У нас есть софт для рисования схем». Но все такие программы обычно ставят стрелку не совсем туда, куда надо. Да и рука после них болит. Я подумал, что это должно занять всего несколько часов — программа, которая бы выдавала PostScript-файлы и ставила стрелку точно в нужное место. Времени тратится на создание схем с помощью программ примерно столько же, сколько в системах WYSIWYG. Но у последних есть два преимущества. Во-первых, рука не болит и, во-вторых, при увеличении даже в 10 000 раз стрелка показывает туда, куда надо.
Конечно, начинающий программист не обязан осваивать все эти абстракции. Но надо держать в уме такую возможность, не отвергать ее, понимая, что прямой путь может быть эффективнее пакетного. Вообще, думаю, приобретая написанную кем-то программу, обычно тратишь много времени, чтобы приспособить ее к своим нуждам: программа делает не совсем так, как надо, а чуть по-другому.
Сейбел: Вы сказали, что с повторным использованием кода дело обстоит «совсем плохо». Но ведь открытие каждого «черного ящика» и возня с его содержимым едва ли исправит эту ситуацию.
Армстронг: Думаю, проблемы с повторным использованием кода есть в объектно-ориентированных, а не в функциональных языках. Ведь объектно-ориентированные языки тянут за собой всю неявно окружающую их среду. Вы хотели только банан, а получили еще и гориллу, которая держит этот банан, и все джунгли впридачу.
Если у вас образцово прозрачный код, если у вас чистые функции — все данные вводятся через их аргументы, все возвращается и не оставляет за собой никакого состояния, — программа более чем пригодна для повторного использования. Можно использовать ее где угодно. Если надо применить ее в другом проекте, вы просто вырезаете код и вставляете его в другой проект.
Программисты научились пользоваться разными языками, но не научились пользоваться легкими способами склейки программ. Конвейеры UNIX — «А переходит в Б переходит в В» — банально простой способ склейки. Пользуются ли ими программисты? Нет, они берут несколько API и связывают их в одной и той же области памяти, что очень сложно и не позволяет поддерживать разные языки. Если языки из одного семейства, это проще — с императивными языками, например, все прекрасно. А если, допустим, у нас есть Пролог и Си? У них совершенно разное представление об управлении памятью. Их невозможно скомпоновать. То есть повторное использование кода тоже невозможно. Вероятно, тут замешаны крупные коммерческие интересы: кому-то очень не хочется, чтобы все это работало вместе. Появляются тысячи рабочих мест для консультантов, тысячи утилит для решения проблем, которых нет, — они решены много лет назад.
Удивительно, но очень мало языков программирования описывают взаимодействие между программами. Вернемся к склейке программ и способам описания протоколов. У нас нет способов описания межпрограммных протоколов: если я шлю вам вот это, вы присылаете мне вон то. У нас есть способы описания пакетов и типов пакетов, но что касается протоколов — таких способов очень мало.
Программирование полностью отличается от того, как мы конструируем вещи в реальном мире. Допустим, вы автопроизводитель. Вы покупаете компоненты у субподрядчиков — батареи у Lucas, генераторы еще у кого-то — и соединяете их вместе, последовательно. Когда вы строите дом, то кладете кирпичи один на другой и помещаете дверь там-то. Именно так делаются чипы: есть готовая плата, обеспечивающая эти соединения. Покупая чипы, вы соединяете их ножки проводами. Хорошо бы и программы делать так, но мы не делаем.
Причина, по которой мы этого не делаем, связана с параллелизмом. Чипы, соединенные последовательно, выполняют операции параллельно. И они посылают сообщения. Они основаны на парадигме обмена сообщениями — той парадигме программирования, в которую я верю. Но сегодня она не применяется для создания программ. Erlang мог пойти по этому пути — во всяком случае, я бы этого хотел: эволюционировать в сторону компонентного строения. Пока я еще этим не занялся, но хотел бы разработать графический внешний интерфейс для создания компонентов и начать писать программы для связывания их воедино. Потоковое программирование крайне декларативно. В нем нет такого понятия, как последовательные состояния. В нем нет программного счетчика, который в них переходит. Что-то просто есть, и все. Декларативная модель легка для понимания. Но в большинстве языков она не реализована.
Я не хочу этим сказать, что содержимое «черного ящика» не может быть сложным. Возьмите, например, такую утилиту, как grep. Если глядеть извне, это что-то вроде квадратика. На входе — поток данных, файл. Можно сказать cat foo | grep, и grep получает некие аргументы, регулярное выражение, которому нужно найти соответствие. Хорошо. И grep выдает все строки, соответствующие этому регулярному выражению. На уровне восприятия то, что делает grep, до крайности просто. На входе — файл. Регулярное выражение. На выходе — набор (поток) строк, соответствующих этому выражению. Но это не означает, что действующий внутри «черного ящика» алгоритм прост — он может быть предельно сложным.
Происходящее внутри «черного ящика» может быть предельно сложным. А процесс склеивания сложных компонентов необязательно сложен. Использовать grep совсем несложно. Вот чего я не вижу в архитектуре систем: четкого различия между склеиванием составных частей и устройством, порой очень сложным, самих частей.
Соединяя программы при помощи API языка программирования, мы не получаем абстракцию «черного ящика». Мы отправляем их в одну и ту же область памяти. Если grep есть модуль, который предоставляет вызовы в своем API, и вы снабжаете его указателем char*, и вам надо выделять для этого память, и вы занимаетесь глубоким копированием этой строки — можно ли создать параллельный процесс, делающий все это? В таком случае это становится трудным для понимания. Не знаю, почему люди склеивают программы таким замысловатым образом. Надо выбирать методы попроще.
Сейбел: Если сравнить то, что вы думаете о программировании сейчас, и то, что думали в начале карьеры, в чем главное отличие?
Армстронг: Главные отличия в том, как я думаю о программировании, не имеют ничего общего с аппаратным обеспечением. Да, оно становится быстрее и мощнее, но наш мозг в миллион раз мощнее лучших программных инструментов. Я могу писать программу и вдруг, через много времени, обнаружить, что в ней ошибка: если произойдет вот это, и вот это, и вот это, случится сбой. Смотрю в код — и правда, ошибка! Хотя ничто в работе программы не говорит об этом. А теперь скажите, какая среда разработки на такое способна? Поэтому перемены, которые со мной произошли, — это перемены в моей голове.
Есть две перемены, связанные с опытом программирования. Вот первая: когда я был моложе, то обычно, закончив писать программу, переставал работать над ней. Ведь все закончено! Потом наступало озарение: «Что за бред?! Я идиот! Надо переписать». И потом снова то же самое.
Помню, как размышлял: не лучше ли сначала все продумать, а потом записать? Не лучше ли испытать озарение, еще не написав программу? Думаю, теперь у меня получается. Поэтому я считаю, что в течение двадцати лет учился программированию. Сейчас я знаю, как это делается. Я ставил эксперименты, чтобы научиться. Теперь я умею программировать и не нуждаюсь в экспериментах.
Иногда, правда, я ставлю совсем небольшие эксперименты — пишу крошечные программы, только чтобы ответить на какой-то вопрос. Я все продумываю, и программа более-менее работает по моему плану, потому что я продумал ее. Это, конечно, занимает много времени. Пишешь программу, потом наступает озарение, все переписываешь — написание программы может занять год. Поэтому теперь я предпочитаю целый год думать над ней. Просто я ничего не набираю на клавиатуре.
Это первое. Второе — это интуиция. Когда был моложе, я мог программировать ночь напролет, до четырех утра, и страшно уставал. Как настоящий крутой программист, я писал код, час за часом. Выходило неважно, но я упорно продолжал работать, когда интуиция уже покинула меня.
И я усвоил вот что: если устал, то выходит дерьмово, и на другой день это выбрасываешь. Двадцать лет назад я продолжал работу, даже чувствуя, что с кодом не все в порядке. С годами я заметил, что по-настоящему хорошо получается, когда я полностью в потоке — не забочусь о времени, мало думаю о самой программе, просто сижу расслабленно, что-то набираю и гляжу, что там на экране. Вот тогда код выходит хорошим. Раньше я не понимал, что это такое, когда что-то подсказывает тебе: «Нет-нет, все не так». Сейчас, если что-то говорит «нет», я останавливаюсь. Я знаю по опыту, что надо на время завязать с кодом — оставить эту задачу, подумать о чем-то другом.
Я хорошо успевал по математике в школе и считал, что у меня логический склад ума. Но потом психологические тесты показали, что у меня прекрасно развита интуиция, а вот с логикой хуже. Не то чтобы плохо — я могу заниматься математикой и программированием вполне на уровне. Но поскольку я хорошо успевал по математике, то думал, что наука вся построена на логике и математике. Теперь не скажу этого, потому что знаю, как много значит интуиция, уверенность в правильности.
Сейбел: Итак, теперь вы намного больше размышляете о коде. Что именно вы делаете на этом этапе?
Армстронг: Ну, я не просто сижу и думаю — я делаю пометки на бумаге. Пожалуй, это слабо связано с кодом — если взглянуть со стороны, я о чем-то думаю, что-то черчу. Еще один очень важный момент — я спрашиваю своих коллег: «А как бы вы решили эту задачу?» Нередко бывает такое: приходишь к кому-то и говоришь: «Прямо не знаю, как сделать вот это — так или так, выбрать А или Б», — рассказываешь про А и Б и вдруг на полуслове хлопаешь себя ладонью по лбу: «Конечно, Б! Большое, большое тебе спасибо».
Если просто написать все это на доске, ничего не выйдет — нужна обратная связь. Нужен человек, выступающий в роли доски. Вы объясняете что-то, рисуете альтернативные решения, человек вступает в разговор и подсказывает оригинальный ход. И вы внезапно видите решение. Для меня это не распространяется на само написание кода, но диалог с коллегами, решающими сходные проблемы, может быть очень полезен.
Сейбел: Что тут главное — реплики других, вопросы, сам процесс объяснения?
Армстронг: Думаю, вот что: вы переводите задачу из той части мозга, которая ее решает, в ту часть, которая вербализует. Это две разные части. Вы форсируете решение. Я, правда, никогда не пробовал говорить вслух в пустой комнате.
Сейбел: Я слышал, что на одном факультете компьютерных наук в кабинете преподавателя был плюшевый медведь, которому следовало изложить вопрос, прежде чем побеспокоить преподавателя. «Значит так, мистер Медведь, я работаю над такой-то задачей и думаю, как лучше сделать... ага, понял!»
Армстронг: Да? Надо попробовать.
Сейбел: Поговорите со своими кошками.
Армстронг: О, само собой! Я работал с одним парнем чуть постарше меня, очень умным. Всякий раз, когда я приходил к нему в офис с каким-то вопросом, он говорил: «Программа — это черный ящик. Есть вход, выход и функциональная связь между ними. Какой у тебя вход, какой выход, какая функциональная связь?» В ходе беседы я вдруг выкрикивал: «Да ты гений!» — и выбегал из комнаты, а тот удивленно качал головой: «Он даже не объяснил мне, в чем проблема». Так что тот парень был вроде медведя.
Сейбел: А что вы чертите — куски кода или просто линии, фигуры?
Армстронг: Чаще всего кружки со стрелками. Когда рисуешь что-то на доске, объясняя людям, то это кружки со стрелками, уравнения, разные обозначения, но не код. Только иногда это куски кода, потому что так экономнее всего что-то выразить. Это в период обдумывания. Код пишу лишь изредка, чтобы понять, сколько времени что займет. Написав строк десять кода, я оцениваю время.
Сейбел: То есть сколько времени займет выполнение программы на компьютере?
Армстронг: Да. Я не знаю, будет это микросекунда или миллисекунда. Я могу что-то предполагать, но предположение должно подтвердиться. Поэтому я смотрю только на те части, про которые ничего не понимаю. Но у меня большой опыт программирования, связанный с Erlang, и я примерно понимаю, что будет делать программа. За сколько-то лет пути решения задач не изменились: определить самые трудные части, написать небольшие прототипы, выявить области, в которых не уверен, написав совсем небольшие кусочки кода. В принципе, сейчас я делаю то же самое, но уже не так нуждаюсь в этих небольших экспериментах, если пишу на Erlang. Если же пишу на Ruby или Java, то вспоминаю прошлое и провожу эксперименты, так как не знаю, что может быть дальше.
Сейбел: И посреди обдумывания вы вдруг понимаете, каким должен быть код?
Армстронг: Да, все части складываются воедино. Но, вероятно, объяснить другим я не смогу — просто приходит сильнейшее чувство, что если начать писать программу сейчас, она заработает. Я не знаю в точности, что это за решение. Это как с яйцом. Цыпленок готов вылупиться из яйца. Я тоже готов.
Сейбел: Теперь вы в потоке, и вас нельзя прерывать.
Армстронг: Да-да.
Сейбел: Как я понимаю, остается множество вещей, которые нужно разобрать на уровне кода. Вам нужно сосредоточиться.
Армстронг: Именно так. Но есть два типа сосредоточения. Вещи, которые требуют реального сосредоточения, не те, что можно сделать автоматически, — это вещи, которые надо обдумывать. Это своего рода хитрая сборка мусора, нужно понять, что именно и где разметить. Нужно крепко подумать. Ты знаешь, что решение найдется, потому что ты его уже ограничил со всех сторон. И знаешь, что оно в этом маленьком черном ящике.
Когда Микеланджело расписывал, к примеру, потолок Сикстинской капеллы, у него была команда помощников. Он сперва рисовал общий вид фрески — вот здесь все закрасить голубым, а здесь зеленым. Это напоминает создание программ. Сперва общий набросок, где все расставлено по местам. Некоторые области можно закрасить одним цветом и довольно быстро — это не требует размышления.
А вот рисовать глаза — хитрое дело. Знаешь, что можешь сделать это и что глаза на верном месте, потому что набросок правилен. Начинаешь прорисовывать глаза в деталях. Это не так-то просто, надо всерьез сосредоточиться. А если взять лоб или щеки, особого сосредоточения не нужно — это однородные области. Допустим, на щеках пробиваются волосы, но все равно можно сосредоточиться наполовину.
Затем все набираешь, исправляешь синтаксические ошибки, запускаешь несколько небольших тестов, чтобы проверить, как работает программа. Тут уже можно расслабиться. Увидел мелкую ошибку компиляции, исправил. Поднаторев в языке, можно даже не читать диагностику. Там все равно только номера строк. Ага, такая-то строка неправильная, нужно ее переписать.
Когда я читал курс лекций по Erlang в Чикаго, то бродил по классу и понимал: так, здесь что-то не то. Здесь пропущена запятая или программа даст сбой еще до такого-то события, а у вас нет связей. Моя жена — отличный корректор, сразу видит ошибки. Пропущенная запятая, орфографическая ошибка буквально бросаются ей в глаза. Мне бросаются в глаза ошибки в чужом коде. Это почти бессознательно: смотришь на экран — и бац! — видишь ошибку. Так что речь идет об исправлении несущественных погрешностей.
Сложнее найти небольшие ошибки в написании имен переменных. Чтобы избежать их, я специально подбираю для разных переменных очень непохожие имена. Например, легко спутать переменную person-Name и список имен personNames, поэтому переменную я называю person-Name, а список — listOf People. А вот пунктуацию я вижу хорошо — все, что касается запятых, скобок и так далее. Плюс к тому Emacs сам все раскрашивает, сам делает абзацные отступы, разные скобки выходят разного цвета. Работа сильно облегчается.
Сейбел: Когда вы переходите к написанию кода, то откуда начинаете — сверху, снизу, с середины?
Армстронг: Снизу. Я пишу небольшой фрагмент и тестирую его, потом еще один и так далее. Сейчас пишу сначала модульные тесты, а потом уже код. И твердо уверен в своем подходе.
Сейбел: Вернемся в прошлое. После Шведской космической корпорации вы работали в исследовательской лаборатории компании Ericsson?
Армстронг: Да. Это было счастливое время, на редкость счастливое — кажется, 1984 год. Я пришел в лабораторию года через два после ее создания, там царил оптимизм. Мы считали, что решим все задачи, убедим руководство запустить новые проекты, производительность компании взлетит. Эти взгляды не имели ни малейшего отношения к реальности. Мы полагали, что придумаем новые полезные программы, и мир встретит нас с распростертыми объятиями. Позже мы поняли, что открывать новые вещи не так-то легко. И невероятно трудно убедить людей пользоваться новыми, более совершенными вещами.
Сейбел: Erlang относился к этим новым, более совершенным вещам?
Армстронг: Да, разумеется. Вначале был Пролог. Я создал нечто вроде небольшого языка, люди стали им пользоваться. Роберт Вирдинг поглядел и сказал: «Прикольно», — а потом спросил, можно ли слегка изменить мой Пролог. Вообще, это довольно опасно — Роберт пишет в комментарии к программе: «Джо придумал, я кое-что изменил», — но на самом деле меняется все. Так что мы переписали код от и до и крепко спорили: «Не могу прочесть твой код, сплошные пробелы после запятых», — в таком духе.
Наконец мы обнаружили кое-кого в Ericsson, кому нужен был новый язык программирования — или, скорее, им нужен был более лучший подход в программировании телефонии. Мы встречались с ними раз в неделю, так продолжалось полгода, может быть, месяцев девять. Общая идея была такова, что они узнают от нас о программировании, а мы от них — о телефонии, о том, что тут за проблема. Это было очень трудно, но при этом стимулировало нас. Язык изменился, поскольку перед нами были живые люди, которые пользовались им, проводили исследования. По результатам они говорили: «Хорошо, но слишком медленно», — и указывали, что все должно работать в 70 раз быстрее. Итак, мы взялись выполнить их требования, заставить язык работать в 70 раз быстрее за два года или около того.
У нас было несколько фальстартов, были и тяжелые моменты. И была одна крупная ошибка. Никогда не обещайте людям, что все будет работать с такой-то скоростью, не осуществив реализации. Но наконец мы поняли, как это сделать. Я написал компилятор на Прологе, Роб занимался библиотеками и разными программами. Примерно после двух лет работы я подумал, что смогу реализовать эту абстрактную машину на Си, — так мне впервые довелось писать на Си. Майк Уильяме, случившийся рядом, посмотрел мой Си-код и сказал: «Это самый плохой Си-код, который я видел в жизни. Никуда не годится». Не думаю, что было настолько плохо, но Майку не нравилось. Поэтому Майк создал виртуальную машину на Си, а я — компилятор на Прологе. Затем компилятор скомпилировал сам себя, произвел байт-код, мы вставили его в машину, поменяли грамматику и синтаксис, скомпилировали компилятор в нем самом и получили нечто такое, что могло самозагружаться. Дело пошло. Это был уже не Пролог, а новый язык.
Сейбел: Было что-нибудь, что оказалось трудно встроить в Erlang?
Армстронг: Да. Мы полностью абстрагировались от памяти. Если вы переводите картинку из JPEG в двоичное изображение, а это очень сильно зависит от точного места размещения данных, получается так себе. Неважно получаются алгоритмы, работающие через деструктивное изменение состояния.
Сейбел: Если бы вы создавали большую программу для последовательной обработки изображений, то писали бы код для преобразования картинок на другом языке?
Армстронг: Да, на Си, или на ассемблере, или на чем-то еще. Или на диалекте Erlang с последующей кросс-компиляцией между Erlang и Си. Создать диалект — своего рода предметно-ориентированный язык. Можно было бы также написать программы на Erlang, которые бы генерировали программы на Си, а не писать последние от руки. Но целевой язык — Си, или ассемблер, или что-то еще. Писать от руки или генерировать? Интересный вопрос. Я склоняюсь к генерированию — это проще.
Но я бы использовал структуру Erlang. У меня есть кое-что для создания семейного альбома и всего такого. Там я использую ImageMagik и несколько консольных скриптов, но управляю всем из Erlang. Я просто пишу обертки вокруг них, запускаю osxommand и потом команду в ImageMagik. Обертки — это удобно. Я бы не хотел обрабатывать картинки в Erlang, писать это на Erlang глупо. Си для этого подходит куда больше.
Сейбел: И, кроме того, уже написан ImageMagik.
Армстронг: Вот это меня совершенно не волнует. Если бы я писал это на OCaml, я бы взял и написал это, потому что в OCaml можно достичь эффективности такого рода, но Erlang делать этого не может. Будь я OCaml-программистом, что бы я сделал? Переписать ImageMagik? Поехали!
Сейбел: Просто потому, что написать свое увлекательнее?
Армстронг: Я люблю программировать, так почему бы и нет? Я всегда говорил, что Erlang не годится для обработки изображений, — я и не пробовал делать в нем это. Наверное, ничего не выйдет, хотя кто знает? Надо попробовать. Хм, занятно. Не искушайте меня.
По-настоящему хороший программист много программирует. Исключений я не встречал. Если я не программирую два-три дня, то ощущаю зуд. И потом, чем больше делаешь, тем быстрее работаешь. Побочный эффект написания всяких дополнительных программ в том, что рутинные вещи начинают выполняться намного быстрее.
Сейбел: Вы делали что-нибудь специально для улучшения своих программистских навыков?
Армстронг: Да нет. Я изучал незнакомые мне языки, но вовсе не с целью совершенствоваться в программировании. Может быть, с целью улучшить навыки проектирования языков.
Мне нравится разбираться в том, как все работает. Хороший способ проверки — реализовывать все самому. Для меня программирование — это не ввод кода в машину, это процесс понимания. Программирование есть понимание. Я люблю понимать, как все устроено. Почему бы мне не реализовать программу для JPEG, о которой мы говорили? Ведь мне нравится разбираться в вейвлет-преобразованиях. А программирование как раз позволяет в них разобраться. Зачем я создаю интерфейс для X Windows? Чтобы разобраться, как работает Х-протокол.
Реализация чего-нибудь — сильный мотивирующий фактор. Рекомендую всем. Хотите понять Си — напишите для него компилятор. Хотите понять Лисп — напишите для него компилятор или интерпретатор. Некоторые говорят: «Компилятор — это же так трудно». Совсем нет. Это легко. Есть масса мелочей, которые не трудны и которые нужно освоить. Надо разбираться в структурах данных. Надо разбираться в хеш-таблицах и в парсинге. В генерировании кода. В техниках интерпретации. Каждый из этих предметов не особенно сложен. Начинающие думают, что это все большие и сложные темы, и поэтому к ним не подступают. Все, что вы не делаете, трудно, все, что вы уже сделали, легко. Люди даже не пытаются ничего делать, и я думаю, это ошибка.
Сейбел: Кое-кто из моих собеседников советовал учить разные языки, потому что в них применяются разные подходы к решению задач.
Армстронг: Да, но только если эти языки делают разные вещи. Нет смысла учить несколько языков, делающих одно и то же. Я немало писал на JavaScript, и на Tel, и на Си, и на Прологе — в действительности на Прологе, на Фортране и на Erlang даже очень много. Чуть-чуть на Ruby и на Haskell. Я знаком со всеми языками, но не на всех могу программировать. Хотя я могу писать программы на довольно многих языках.
Сейбел: Но не на C++?
Армстронг: Нет. На C++ я едва могу читать и писать. Не люблю C++; плохо его чувствую, он слишком переусложнен. Мне по душе компактные небольшие языки, а этот большой и громоздкий.
Сейбел: Какие языки повлияли на структуру Erlang?
Армстронг: Пролог. Разумеется, он вырос из Пролога.
Сейбел: Сегодня в нем уже непросто распознать то, что идет от Пролога.
Армстронг: Унификация, сопоставление образцов — все это напрямую идет от Пролога. Как и структуры данных. Синтаксис кортежей и списков слегка различается, но тем не менее очень похож. Еще теория взаимодействующих последовательных процессов Тони Хоара. Я также читал об охраняемых командах Дейкстры. Вот почему я требую, чтобы сопоставление с образцом всегда было явным, не должно быть случая по умолчанию, всегда должна быть ветка с совпадением. Вот основные влияния.
Сейбел: А функциональный облик языка — он откуда?
Армстронг: После внедрения параллелизма в Пролог надо было сделать так, чтобы после какого-нибудь действия не происходил возврат назад. В Прологе можно вызвать что-нибудь и вернуться назад, полностью уничтожая эффект от вызова. Поэтому идея была та, чтобы после команды, скажем «Пуск ракет», они вылетают — ррраз! — и отменить уже нельзя. Чистые Пролог-программы обратимы. Но при взаимодействии с реальным миром все действия совершаются только в одном направлении. Подали команду «Пуск» — ракеты пущены. Подали команду «Зеленый сигнал светофора» — загорается зеленый, и нельзя потом пересмотреть решение, сочтя его плохим.
Итак, у нас есть язык с поддержкой параллельных процессов, и уже внутри них мы имеем самый настоящий Пролог с возвратами и всем прочим. Пролог, в котором там и сям сделаны срезы, чтобы избежать возврата, стал весьма детерминированным.
Сейбел: Где необратимые программы могут посылать сообщения другим процессам?
Армстронг: Да. Но это просто вызов функции, хотя, возможно, и не функции, запускающей ракеты. Это вызов функции, которая вызывает другую, а та — функцию, запускающую ракеты. По этой причине код, который вы создаете, становится все более функциональным, превращаясь в диалект Пролога — функциональное подмножество. А функциональное подмножество можно сделать полноценным функциональным языком.
Сейбел: Erlang сильно отличается от прочих функциональных языков, поскольку в нем есть динамическая типизация. Вы ощущаете свою принадлежность к сообществу функциональных программистов?
Армстронг: Безусловно. На конференциях по функциональному программированию мы спорим, выпячивая расхождения. Мы спорим о «жадных» и «ленивых» вычислениях, о динамической и статической типизации. Но несмотря ни на что, остается центральная идея функционального программирования: х не обозначает место в памяти, это неизменяемое значение. Мы задаем х равным 3, и дальше с этим ничего не сделаешь. Различные функциональные сообщества сходятся на том, что это крайне полезно для понимания программ, для параллелизма, для отладки. Однако есть функциональные языки с динамической типизацией, такие как Erlang, и функциональные языки со статической типизацией. У тех и других свои сильные и слабые стороны.
Было бы здорово, если бы Erlang пользовался преимуществами статической типизации. Возможно, в отдельных местах мы сумеем аннотировать программы так, чтобы типы стали более явными, тогда компилятор сможет порождать типы и генерировать более качественный код.
Сторонники статической типизации говорят: «Мы оцениваем сполна плюсы динамического подхода при маршалинге структур данных». Мы не можем послать любую программу по проводу и воссоздать ее на другом конце — нам нужно знать тип. И вот мы имеем то, что Карделли назвал перманентно неконсистентной системой. Система, которая постоянно растет и меняется, при этом ее части могут быть временно неконсистентными. Если я меняю код, он не ведет себя как неделимый объект. Часть узлов меняются, остальные нет. Они общаются друг с другом — ведь в какие-то периоды времени они консистентны. А когда мы переходим через коммуникационную границу, как определить, что она проведена правильно? Тут надо кое-что проверять.
Сейбел: Когда-то вы отлаживали чужие программы за пиво. Почему вы считали себя большим специалистом по отладке?
Армстронг: Мне страшно нравилось заниматься отладкой. В какой-то точке программы распечатываешь переменные, что-то еще и смотришь, совпадает ли результат с ожидаемым. В одной точке программа может быть правильной, а в другой — нет. Надо смотреть посередине, потом еще раз посередине и так далее. При условии, что можно воспроизвести ошибку. Невоспроизводимые ошибки с трудом поддаются отладке. Но я сталкивался только с воспроизводимыми. Делишь участок кода пополам каждый раз, пока не найдешь. В конце концов ошибка обнаружится.
Сейбел: Считаете ли вы, что у вас был более систематический подход?
Армстронг: Да, другие попросту сдавались, неясно почему. Я не понимал, почему они не могут отладить. Как вы полагаете, отладка — трудное дело? По-моему, нет. Просто останавливаешь программу и прокручиваешь в замедленном режиме. Я сейчас говорю о пакетном Фортране.
Конечно, если говорить об отладке систем реального времени или программ чистки памяти, то я помню, как однажды «упал» Erlang. Это было в самом начале, сразу после запуска, я сидел и что-то настукивал — и Erlang «упал». У него в оболочку было встроено что-то вроде команд Emacs. Набираешь erl, чтобы запустить его, и оказываешься в REPL. Я набрал четыре-пять символов и сделал орфографическую ошибку. Потом я несколько раз сдвинул курсор назад, исправил, и он «упал» с ошибкой сборки мусора. Я знал, что это была очень, очень серьезная ошибка. Я попытался в точности вспомнить, что я настукивал, — там было около 12 символов — начал сначала, набил их, и все пошло без сбоя. Я сидел часа полтора, пробуя сотню разных штук. И вот снова сбой! Тут я все записал и смог приступить к отладке.
Сейбел: А чем вы пользуетесь? Операторами печати?
Армстронг: Да. Великие боги программирования говорят: «Вставь оператор печати в программу там, где, по-твоему, допущена ошибка, ре-компилируй и запусти».
Еще есть Закон отладки Джо — не помню, вычитал я его где-то или сам придумал. Звучит он так: «Все ошибки будут не дальше трех операторов в ту или другую сторону от места последнего изменения программы». Когда я работал в Шведской космической корпорации, мой начальник там раньше занимался «железом». Мы были вместе с ним в Эсранге на севере Швеции — там площадка для запуска ракет и станция слежения за спутниками. Как-то раз он ломал голову, отлавливая ошибку в оборудовании, подсоединяя осциллографы, что-то меняя. Я спросил: «Может, я могу чем-нибудь помочь?» От ответил: «Нет, Джо, это ведь железо». Тогда я сказал: «Это должно быть как в программах — ошибка недалеко от места последнего изменения». Он подумал и сказал: «Ты гений! Я же поменял конденсатор». Дело в том, что он заменил конденсатор на другой, более крупный. Мой шеф отпаял его, поставил тот, что был вначале, и все заработало. И это верно для всего. Ремонтируешь машину, что-то не так — причина в последнем действии. Всегда вспоминайте, что вы поменяли.
Сейбел: Вы доказывали когда-нибудь корректность своих программ? Импонирует ли вам такой формализм?
Армстронг: И да и нет. Я преобразовывал программы алгебраически, чтобы показать, что они эквивалентны, но никогда не применял доказательство через теоремы как таковое. Помню, когда я читал курс денотационной семантики, то отказался от этой идеи. Было дано упражнение: пусть х = 3иу = 4вх + у; докажите, что схема жадного вычисления, заданная уравнением таким-то, и схема ленивого вычисления, заданная уравнением таким-то, обе приводятся к 7.
Четырнадцать страниц лемм и прочего. Потом я подумал: «Ну как нее х = 3, у = 4, х + у = 7». В то время я писал компилятор для Erlang. Если бы пришлось на десятках страниц доказывать, что 3 + 4 = 7, то доказательство правильности компилятора заняло бы не одну тысячу страниц.
Сейбел: Вы предпочитаете работать один или в команде?
Армстронг: Я люблю рабочую атмосферу в компании программистов: в целом я человек общительный. Но работать предпочитаю один. Нет, конечно, мне нравится сотрудничать с другими в смысле обсуждения проблем. Я всегда считал, что мысли, которые приходят в голову во время кофе-брейков, особенно по дороге туда и обратно, очень ценны. Бывает масса озарений. Отличная возможность объяснить свои идеи другим. Для меня важно переместить их из одной области мозга в другую. Часто, объясняя что-то, начинаешь лучше понимать задачу.
Сейбел: Вы занимались парным программированием, когда садишься за компьютер и начинаешь писать код вместе с коллегой?
Армстронг: Да, с Робертом. Робертом Вирдингом. Мы делали это, когда оба продирались через дебри, не понимая толком, что делаем. А если не знать толком, что делаешь, очень полезно работать с человеком, который и сам в таком же положении. Если один программист сильнее другого, то для более слабого это плюс — он наблюдает за работой более опытного. Можно многому научиться. Но если разница между ними слишком велика, чтобы чему-то научиться, то попросту чувствуешь себя дураком. Ну, а заниматься парным программированием с программистом твоего уровня, когда оба не знают, что делают, довольно прикольно.
Есть еще то, что я назову особыми задачами. Я не возьмусь за них, если у меня простуда или если я в плохой физической форме. Я знаю, что написание программы займет три дня, что мне надо спланировать свой день, не читать почту, работать, не отвлекаясь, четыре часа подряд. Я делаю это дома, зная, что меня не потревожат. Мне требуется полное сосредоточение. И не думаю, что парное программирование тут поможет. Слишком много помех.
Сейбел: А что это за особые задачи?
Армстронг: Представьте, что у вас есть сборщик мусора — это императивное кодирование, — где надо не забыть пометить все регистры. Или, допустим, вы делаете лямбда-поднятие в компиляторе, где черт ногу сломит. Вы даете новые ярлыки всем переменным, у вас есть четыре-пять слоев абстрактных типов данных, перемешанных между собой, фреймы стека с разными вещами — и вы понимаете, что вам надо очень серьезно поразмыслить. Нужна концентрация.
Я работаю над задачами по настроению. Иногда совсем нет вдохновения — тогда думаю, кого бы пойти отвлечь, или читаю почту. А иногда чувствую, что вот сейчас время писать какой-нибудь трудный код. Чтобы работать над кодом, надо быть в нужном состоянии. А разве это возможно, если работаешь в паре? Из двоих всегда один не в настроении, хочет читать почту и все такое.
Сейбел: Скажите, а с Робертом Вирдингом вы занимались последовательным парным программированием — когда посылают друг другу куски кода и переписывают их?
Армстронг: Да, когда работают поочередно. Иногда у меня была программа, которая требовала двух-трех недель. Я говорил Роберту: «Ну, с меня достаточно, забирай». И он забирал. Каждый раз, когда это происходило, обратно возвращалось нечто неузнаваемое. Роберт безжалостно кромсал код, потом пересылал мне, и я его так же безжалостно кромсал.
Сейбел: Но это приносило пользу?
Армстронг: О, да. Я был в восторге, когда ему удавалось что-то улучшить. Мы продвигались очень хорошими темпами. Роберт был склонен к обобщению. Однажды я нашел переменную — отслеживал ее в 45 подпрограммах, и в конце концов выяснилось, что она ни разу не использовалась, хотя присутствовала в 45 различных функциях. Я спросил: «Зачем эта штука — она ни разу не используется?» И Роберт ответил: «Зарезервирована под будущее расширение». Я ее убрал.
Я решил написать специализированный алгоритм, убирающий все, что не нужно в данной конкретной программе. Чем более специализированными они были, тем короче становились. А когда Роберт брал мою программу, она удлинялась, делаясь более универсальной. Думаю, это философия UNIX: программа должна делать то, что от нее требуется, и ничего больше. У Роберта была другая философия: программа должна быть частным случаем некоей более общей программы. Поэтому мы попеременно добавляли то общего, то специального.
Сейбел: Похоже на глубокое философское расхождение. Была ли польза от того, что программа металась между двумя крайностями?
Армстронг: Была. Каждый цикл оказывался улучшен. Думаю, программа в целом очень выиграла — больше, чем если бы ее писал один из нас.
Сейбел: Расскажите, как вы проектируете программы. Может быть, приведете пример, связанный с ОТР?
Армстронг: ОТР проектировали я, Мартин Бьёрклунд и Магнус Фрё-берг. Над первоначальным проектом работали только мы трое. Мы собирались каждое утро за чашкой кофе, долго беседовали — час, два часа — и за это время исписывали всю доску. Я сразу же писал всю документацию, а они — весь код. Правда, иногда я тоже выдавал фрагмент-другой кода. И когда я писал документацию и понимал, что не могу что-то описать, мы это меняли. Или они приходили ко мне со словами: «Программа не будет работать, мы поняли сегодня утром, потому что вот это не так, и это, и это». К концу дня у нас была вся нужная документация и весь нужный код — или достаточно того и другого, чтобы работать. И на этом мы успокаивались.
Иногда что-то не срабатывало, и мы откладывали все на завтра. На второй заход в тот день уже не было времени, но один заход в день — то, что нужно. Около двух часов уходило на дискуссии, столько же — на документацию и код. Четыре часа плотной умственной работы, нормальный трудовой день. Поэтому все шло просто отлично. Не помню, как долго это длилось — может, недель десять-двенадцать. После этого у нас уже была базовая конструкция, появилось больше народу. Мы определились с архитектурой, можно было наращивать программу. Привлекли к проекту еще троих-четверых.
Сейбел: Как распределялась работа между новоприбывшими?
Армстронг: Мы знали, какими будут прототипы и какими — окончательные версии. Я всегда занимал позицию проектировщиков систем: в первую очередь делай трудное. Выявляй сложные проблемы и решай их. Ну, а легкие уладятся сами. Конечно, нужен опыт, чтобы разделить проблемы на простые и сложные. Например, что-нибудь вроде программы переключения на резервный IP — это сложно, а разбор файла конфигурации — это легко. В прототипе должен быть файл конфигурации, который просто считывается. Не надо проверять его синтаксис, так как еще нет грамматики. В конечном продукте этот файл, наверное, будет в XML, со всей грамматикой, прошедшей проверку. Но это делается на автомате. У компетентного программиста на это может уйти несколько недель. Но это все выполнимо, предсказуемо, тут нет неприятных сюрпризов. А вот правильно настроить коммуникационные протоколы, чтобы они работали как надо при отказе программы, — здесь нужна небольшая группа программистов.
Сейбел: В этом случае вы писали документацию еще до кода или, по крайней мере, одновременно с ним. Вы так делаете всегда?
Армстронг: Зависит от сложности задачи. Если она очень сложная, я часто начинаю с документации. Чем сложнее, тем больше я склонен начинать с документации.
Мне нравится делать документацию. По-моему, до появления документации в нормальном виде программу нельзя считать готовой. Писать спецификации мне тоже нравится. Некоторые говорят: «Хотите знать, что делает программа? Читайте код». Думаю, это непрофессиональный подход. Код показывает мне, что программа делает, а не то, что она должна по идее делать. Код — это решение задачи. Если нет спецификации или какой-либо документации, приходится догадываться о задаче по решению. Догадка может быть неверной. Я хочу иметь объяснение — в чем состоит задача.
Сейбел: На этой стадии вы пишете внутреннюю документацию для других программистов или документацию для пользователя?
Армстронг: Для пользователя. Мышление при этом переключается в другой режим. Для этого я создаю каталог с таким-то именем, сохраняю в нем такой-то файл, переименовываю его таким-то образом — я описываю структуру. Я как бы обдумываю вопрос. Кнут бы наверняка сказал: «Любое программирование по сути литературно». То есть вы не пишете код, а потом документацию, — вы пишете их одновременно: это литературное программирование. Но я так не считаю. Не знаю, насколько его взгляды сформировались благодаря тому, что он публикует свои программы.
Может, это переключение между полушариями мозга, а может, еще что, но при написании документации думаешь о программе по-другому, чем при написании кода. Пожалуй, литературное программирование способствует такому переключению и поэтому может быть очень продуктивным. Я ввел кое-какие литературные элементы в Erlang, хотя использовал их мало. Вообще, это любопытная мысль — написать что-нибудь, пользуясь литературными элементами Erlang. Я не против самой идеи, просто я нетерпелив, рвусь писать код, а не документацию. Но если хотите что-то хорошо понять, создание документации — необходимый шаг.
Если бы я программировал на Haskell, то с самого начала задумался бы о типах, написал для них документацию. Работая с Лиспом или Erlang, можно начать с кода, не особенно заботясь о типах. В каком-то смысле написание документации — тоже забота о типах. Начинается со связки «есть». «Мелодия есть последовательность нот». Хорошо. Мелодия есть последовательность аккордов, каждый из которых является сочетанием нот равной длины. Простыми определениями терминов в документации — такое-то есть то-то — вы делаете своего рода анализ типов и декларативно размышляете о структурах данных.
Сейбел: Как вы полагаете, языки программирования в целом становятся лучше? Мы уже встали на путь, когда можем вооружиться новыми идеями, усвоив уроки прошлого?
Армстронг: Да. Новые языки вполне хороши. Haskell и ему подобные, Erlang. Есть интересные языки, которые надо использовать активнее.
Например, Пролог — прекрасный язык, но малоиспользуемый. Коваль-ски назвал его решением в поисках задачи.
Сейбел: Дэн Ингаллс говорит, что мы должны пересмотреть свои взгляды на Пролог, после того как уже несколько десятилетий испытываем действие Закона Мура.
Армстронг: Пролог сильно отличается от других языков. Там реализован любопытный способ мышления, и он подходит не для всех задач, хотя и для очень многих. Он мало распространен, к нашему стыду, — ведь программы на нем выходят очень короткими. Когда я написал первую свою программу на Прологе, то испытал нечто вроде шока. Поразительное было ощущение. Смотри — где программа, которую ты написал? Ты всего лишь рассказал немного фактов о системе, о своей задаче, а он сообразил, что делать. Просто чудесно! Надо бы бросить Erlang и вернуться к Прологу.
Сейбел: Есть ли навыки, не связанные прямо с программированием, которые тем не менее помогли вам лучше делать вашу работу? Или такие навыки, которыми должен обладать программист?
Армстронг: Умение писать. Кто-то из компьютерных теоретиков сказал: «Если у вас плохо с английским, вы никогда не станете хорошим программистом».
Сейбел: Кажется, Дейкстра.
Армстронг: Со мной время от времени советуются университеты насчет учебных планов по компьютерным наукам. Я ведь работаю в компании — вот они и хотят знать, что нужно на практике. Я говорю: «Учите их писать и подбирать убедительные доводы». Большинство выпускников, имеющих степень по компьютерным наукам, не сильны в писании текстов.
Думаю, учить этому очень тяжело, потому что это очень индивидуально. Кто-нибудь перечеркивает твой текст красной ручкой и объясняет, в чем ты неправ. Это отнимает очень много времени. Вы знакомы с советом Хэмминга молодым исследователям?
Сейбел: Из доклада «You and Your Research» (Вы и ваше исследование)?
Армстронг: Хэмминг говорит примерно так: «Делайте правильные вещи. Если вы не делаете правильные вещи в правильных областях, то неважно, что именно вы делаете». Еще он говорит: «Один день в неделю я обязательно осваиваю что-то новое. То есть трачу на освоение нового на 20% больше, чем мои коллеги. Выходит, через 4,5 года я буду знать вдвое больше них, а с учетом сложных процентов получается, что через 5 лет я буду знать втрое больше». Не помню точно, какие там были цифры. По-моему, это верно. Поскольку я занимаюсь исследованиями, то на освоение чего-то нового трачу не 20% времени, а 40%. Я занимался ими 30 лет. И понял, что знаю очень много. Вы спрашивали, как улучшить навыки программирования? Тратьте 20% своего времени на узнавание чего-нибудь нового. Прочтите Хэмминга, он очень хорошо пишет.
Сейбел: Бывало так, что вы находили какой-то код просто красивым?
Армстронг: Да, но почему — не знаю. Любопытно, что если двум программистам дать одну и ту же задачу — конечно, смотря какую, я говорю о задачах скорее математического свойства, — то они с большой вероятностью напишут одинаковый код. Если произвести форматирование, переименовать переменные и функции, получится одно и то же — одинаковые алгоритмы. Мы создаем что-то — или просто снимаем покрывало? Это как со статуей. Мы снимаем покрывало и обнаруживаем, что алгоритм всегда был там. Изобретаем ли мы новый алгоритм или уже существующую структуру? С некоторыми алгоритмами, в основном математическими, такое случается. Но, скажем, если я занимаюсь реализацией протокола для телефонии, такого чувства нет. Нет ощущения, что я снимаю покрывало со статуи.
Сейбел: Похожий случай с красотой математических задач — это часть природы. Но есть и другие уровни, на которых код доставляет эстетическое наслаждение.
Армстронг: Да. Это вроде фэн-шуй. Я люблю компактный код, хорошо сбалансированный и структурированный. Удаляешь одно, другое, и настает момент, когда удалить больше ничего нельзя — программа не будет работать. Вот в этот момент она прекрасна. Любое возможное изменение может ухудшить алгоритм, но сейчас он прекрасен.
Сейбел: Вы говорили о том, как с Робертом Вирдингом пересылали друг другу куски кода. Как каждый из вас изменял низкоуровневые детали форматирования, по поводу которых программисты без конца спорят?
Армстронг: Они не затрагивают красоту алгоритма.
Сейбел: Но это часть эстетики, это вкус.
Армстронг: Да, но я не назову код уродливым, потому что в нем есть пробел после запятой. Уродливое — это линейный поиск, когда можно применить деление пополам, или логарифмический метод. Делайте линейный код, если у вас список из десяти элементов, — пожалуйста! Но при больших структурах данных применяйте бинарный поиск. В линейном виде это выходит некрасиво. Математические алгоритмы — как платоновские идеальные сущности. Что-то близкое к архитектуре. Вы любуетесь красивым зданием, которое не есть математический объект. Это не шар, не сфера, не призма — это небоскреб. Но выглядит он великолепно.
Сейбел: Что делает человека хорошим программистом? Нанимая на работу программиста, на что вы смотрите?
Армстронг: Как человек выбирает задачи. Что для него важнее — задача или решение? Мне симпатичнее человек, который говорит: «У меня была вот такая занятная задача». Вы спрашиваете, в каком самом интересном проекте он участвовал, просите показать соответствующий код, спрашиваете, как он решал задачу. Для меня не столь важно, хорошо ли соискатель знает язык А или язык Б. Обычно программисты всеми языками владеют одинаково хорошо — или плохо. Хороший Си-программист будет успешно работать и с Erlang, это очень надежный показатель. Есть, конечно, исключения, но умственные навыки, необходимые для работы с одним языком, можно применить и к другим.
Сейбел: В некоторых компаниях соискателям дают решать логические задачи. Вы так делаете?
Армстронг: Нет. Есть очень хорошие программисты, которые в них соображают туго. Помню одного из парней, работавших с Erlang, — у него была степень по математике, и он напоминал алмазный бур, прогрызающий гранит. Как-то раз он загрипповал и взял домой листинги из Erlang. Потом он пришел, добавил в код атомарное выражение и сказал: «Это поставит эмулятор на замкнутый цикл». Он обнаружил, что изначальное хеш-значение этого выражения равно нулю, и взял какой-то модуль для перехода к следующему выражению — оно тоже оказалось равным нулю. И он декомпилировал хеш-алгоритм для одного патологического случая. Он даже не запускал программы — посмотреть, как они работают: он читал их. Но медленно. Довольно медленно. Не знаю, как бы он решил по-быстрому логическую задачу.
Сейбел: Есть ли еще навыки, необходимые хорошему программисту?
Армстронг: Где-то я читал, что нужно иметь хорошую память. Думаю, это так.
Сейбел: Билл Гейтс однажды сказал, что до сих пор мог бы выйти к доске и написать большие фрагменты кода на Бейсике, который писал для Altair, хотя прошло уже лет десять. А вы можете вот так вспомнить свой старый код?
Армстронг: Да, кое-что восстановить по памяти могу. Иногда я забываю какой-нибудь старый код и совершенно не беспокоюсь — просто пишу его заново, и получается примерно то же самое. Могут измениться имена переменных, расстановка функций. Но код выйдет изоморфным. Или даже лучше, чем раньше, потому что я дополнительно его обдумал.
Возьмем, к примеру, сопоставление образцов в моем компиляторе десятилетней давности — я могу сесть и набрать его. Он будет отличаться от старой версии, но в лучшую сторону. Код как бы самоулучшается со временем. Но структура остается прежней.
Я не беспокоюсь о потере кода — если шаблоны в голове, я все вспомню. Даже не столько вспомню, сколько сделаю заново. Не думаю, что это припоминание, скорее воссоздание. Если Билл может вспомнить именно сам текст, то я — нет. Но, конечно, я долго помню структуру кода.
Сейбел: Скажите, обмен сообщениями в духе Erlang поможет раз и навсегда решить проблему параллельного программирования?
Армстронг: Нет, конечно. Это всего лишь усовершенствование, пусть и большое, по сравнению с разделяемой памятью. Думаю, Erlang выполнил, по крайней мере, одну задачу — продемонстрировал это. Работая над Erlang, мы всегда говорили на конференциях: «Надо будет копировать все данные». И слушатели, кажется, воспринимали наши доводы насчет безотказной работы системы — копирование всех данных вводилось для этого. Нам возражали: «Но ведь тогда все будет работать страшно медленно», — а мы отвечали: «Зато безотказно».
Удивительно, но наш язык при определенных обстоятельствах оказывается еще и более быстрым. И то, что мы делали ради безотказной работы, во многих случаях было так же эффективно, а то и лучше, чем с разделяемой памятью. Мы спросили себя: «Почему так происходит?» Потому что улучшается распараллеливаемость. В системе с общей памятью нужно блокировать данные при обращении к ним. А блокировка обходится дорого. И потом, возможно, копировать придется не так уж много данных. Если данных немного, то копирование лучше, чем бесчисленные обновления, обращения, блокировки. А на многоядерниках, если принять старую разделяемую модель, блокировка может остановить работу всех ядер. У вас тысячеядерный процессор, одна программа делает глобальную блокировку — и тысяча ядер прекращают работу.
Я также очень сомневаюсь в неявном параллелизме. В языке могут быть параллельные конструкции, но если они не отображаются на па-рале льном процессоре, а только эмулируются системой, — преимущества никакого. Вообще, есть три вида процессорного параллелизма.
Есть конвейерный параллелизм, когда в чипе создается более длинный конвейер, чтобы делать вещи параллельно. Это предопределено при создании чипа. Обычный программист не может сделать ничего с параллелизмом на уровне инструкций.
Есть параллелизм данных; это не настоящий параллелизм, скорее он связан с процессами в кэше. Если вы хотите, чтобы программа на Си выполнялась эффективно, и если *р находится на 16-байтовой границе, то при доступе к *р доступ к *(р+1) практически бесплатен, поскольку строка кэша вытягивает его. Вам надо знать, насколько широки строки кэша, сколько байтов вы сможете утянуть при одном перемещении в кэш? Этот вид параллелизма можно успешно использовать, если быть очень осторожными со структурами данных и точно знать, где что находится в памяти. Все запутанно, и разбираться с этим не очень охота.
И наконец, многоядерные процессоры. К концу десятилетия они будут 32-ядерные, а к 2019 году ядер будут миллионы. Поэтому вам нужно взять параллельные участки своей программы и отобразить на ядра компьютера. Согласен, это довольно громоздкая операция. Вычисление начинается в другом ядре, из него потом приходит ответ — это требует времени. Если надо просто сложить два числа, это не стоит усилий — больше времени потратится на перенос данных между ядрами, чем на то, чтобы сделать все на месте.
Erlang неплохо приспособлен для этого. Программист говорит: «Мне нужен процесс, и еще, и еще». Процессы распределяются между ядрами. Может быть, стоит подумать над их физическим распределением между ядрами. Не исключено, что процесс, порождающий другой процесс, обменивается с ним данными. Имеет смысл поместить его в то ядро, которое ближе. Если же обмен данными не очень интенсивен, то можно поместить и подальше. Процессы, занимающиеся вводом/выводом, стоило бы расположить у края чипа, с другими процессами, занимающимися вводом/выводом. Чипы становятся все больше, и надо задуматься над тем, что размещение данных в середине чипа обходится дороже, чем на краю. Если есть три сервера и база данных, то ее мы поместим в середину, а серверы, которые общаются с клиентами, на край. Но все это подлежит исследованию.
Сейбел: Вам дорога мысль о том, что Erlang — это средство, позволяющее организовать параллельные процессы. Что вам дороже — идея параллелизма на основе обмена сообщениями без разделения данных или Erlang как язык?
Армстронг: Конечно, идея. Меня спрашивают: «Что будет с Erlang? Станет ли он популярным языком?» Этого я не знаю. Думаю, он уже стал важным языком. Он может повторить судьбу Smalltalk — очень важного языка, который имел горячих сторонников, но широко не применялся. С Erlang может произойти то же самое. Возможно, заложенные в нем идеи придут к миллионам пользователей, если их реализует Microsoft в Common Language Runtime, добавив там-сям фигурных скобок.