12. Кен Томпсон
12. Кен Томпсон
Кен Томпсон — один из первых бородатых великих хакеров UNIX. На протяжении своей карьеры он занимался всем, что только казалось ему интересным, в том числе — в разное время — аналоговыми вычислениями, системным программированием, регулярными выражениями и компьютерными шахматами.
Он работал исследователем в проекте MULTICS Bell Labs. Когда Bell Labs решила прекратить разработку MULTICS, Томпсон вместе с Денисом Ричи продолжил работу над UNIX — за что его вполне могли уволить. Также он изобрел язык программирования Би — предшественник языка Си, созданного Денисом Ричи.
Затем он заинтересовался компьютерными шахматами и создал Belle — первый специализированный шахматный компьютер, сильнейший среди компьютерных шахматистов того времени. Он также помог довести эндшпильные шахматные таблицы до такого уровня, что они охватили все четырех- и пятифигурные окончания.
Работая над операционной системой Plan 9 компании Bell Labs, он предложил ныне повсеместно используемую кодировку Юникода UTF-8.
В 1983 году Томпсон и Ричи получили премию Тьюринга «за развитие теории порождающих операционных систем и в особенности за внедрение операционной системы UNIX». Также Томпсон был награжден Национальной медалью за технологии и премией Tsutomu Kanai Института инженеров электротехники и электроники (IEEE) — оба раза за работу над UNIX.
В этом интервью он рассказал о своей давней любви к электронике, о довольно нетривиальной академической карьере, в ходе которой он еще студентом вел несколько курсов, а также о своих опасениях по поводу современного программирования.
Сейбел: Как вы научились программировать?
Томпсон: Меня всегда завораживала логика, и даже в школе я предпочитал решать арифметические примеры в двоичном исчислении и тому подобное. Мне это просто нравилось.
Сейбел: То есть так вы делали их для себя более интересными?
Томпсон: Нет-нет. Я разработал таким образом алгоритмы для сложения в разных базисах, узнал, что значит перенос, что обозначает каждый столбец и так далее. И у меня было небольшое десятичное арифметическое устройство, вроде счетов[71]. Вместо каждого разряда там был ползунок от нуля до девяти. Вычитать надо было с помощью нижней колонки, а добавлять — с помощью верхней. Берешь стилус и сдвигаешь им ползунок на четыре, например, деления, при переполнении разряда переходя в соседнюю колонку. Позже, основываясь на этом варианте, я сделал двоичное устройство, а потом обобщил его до личного.
Сейбел: Как вы впервые узнали о двоичной арифметике?
Томпсон: В классе, как раз когда я начал этим заниматься, нас познакомили с двоичным счислением.
Сейбел: Вы стали жертвой «новой математики»?
Томпсон: Нет-нет. Я был жертвой плохой математики. Мы переезжали каждый год, я учился в очень, очень плохих школах, а потом в хороших. Так что мне иногда приходилось за год проходить двухлетнюю программу, а потом я год ничего не делал. На математике я просто бездельничал, так что начальное математическое образование у меня просто ужасное. И вот в одном классе как раз рассказывали про двоичную арифметику. Я заинтересовался, расширил ее до любого базиса и стал с ней играть. Вот так все и началось.
Сейбел: И это было еще в школе?
Томпсон: Да, в седьмом классе. Позже, где-то в выпускном классе, я сильно увлекся электроникой — собирал радио, усилители, осцилляторы и терменвоксы. И очень заинтересовался аналоговыми вычислениями. Это и впрямь было потрясающе. Все это время электроника была моей страстью. Я поступил на соответствующий факультет в Беркли и только там на первом курсе впервые увидел настоящие цифровые компьютеры.
Сейбел: В каком году это было?
Томпсон: Я поступил рано, и тогда в году было три семестра. Поступил я в сентябре 1960 года, так что это, видимо, весна или осень 1962-го. У них был аналоговый компьютер, за которым мне нравилось работать. И была еще барабанная вычислительная машина — G15. Была одна учебная лаборатория с ними, и тогда она была всегда открыта. Там мог заниматься любой, но почти никто не хотел, так что всегда было свободно. И главным образом сидел там я один. Я писал на ней собственные программы по масштабированию — собственно, почти все аналоговое вычисление сводится к масштабированию.
Сейбел: В каком смысле «масштабирование»?
Томпсон: Масштабирование времени и амплитуды. Обычно все, что нужно было сделать, — это построить функцию. Вводишь некоторые данные, затем получаешь функцию для этих данных и связываешь результаты. И ни в один момент нельзя забираться слишком высоко, иначе наткнешься на отсечку.
Точно так же измеряешь время: делишь на два частоту в одних случаях или удваиваешь ее в других. И когда так делаешь, меняется и линейное масштабирование. Так что если есть несложная задача, при которой масштабирование не нужно, то аналог — это отличное решение. Но когда появлется масштабирование, все становится очень, очень сложным. И вот я написал цифровые компьютерные программы для масштабирования установок аналогового компьютера. Без вычисления точной формы сигнала считаешь амплитуду и частоту формы сигнала в каждой точке. И таким образом понимаешь, где что-то идет не так и какая операция выполняется в данный момент.
Сейбел: А программы для цифрового компьютера были написаны на ассемблере или на Фортране?
Томпсон: В основном это был ассемблер. Интерпретируемый язык, который там был, оказался очень медленным. Поэтому пришлось перейти на ассемблер, и так я на самом деле узнал, что такое компьютер.
Сейбел: То есть загружаем программу, нажимаем на кнопку «пуск» и уходим. Это работало на перфокартах?
Томпсон: Нет-нет. Это был флексорайтер, то есть нечто вроде телетайпа или бумажной перфоленты. Записываешь на бумажную перфоленту и суешь во флексорайтер.
Сейбел: В этой лаборатории вас учили ассемблеру?
Томпсон: Нет.
Сейбел: Когда вы в следующий раз встретились с программированием?
Томпсон: На той машине G15 был установлен интерпретатор Intercom 501. И группа электроинженеров на нем программировала. У меня был приятель-выпускник, который написал интерпретатор для Intercom на большой машине IBM, предоставляющей лучшие вычислительные возможности во всем кампусе. Я получил листинг этого кода и однажды на каникулах, рождественских или еще каких-то, прочитал и проанализировал его. Я не знал язык, на котором он был написан, — это оказался NELIAC. И написана программа была просто отлично. И по ней я изучал программирование, NELIAC, Intercom и то, как разобраться в чем угодно. Я сидел и читал все каникулы — неделю. Потом я вернулся и стал задавать приятелю вопросы, иногда находя некоторые ошибки. Теперь я знал, как программировать, и добился в этом успехов. Потом я получил свою первую работу как программист.
Я учился, работал в лаборатории и брался за странные подработки. Я был помощником исследователя — должность мелкой сошки для студента, которая помогала написать программу для дипломной работы. И я был ассистентом, вел занятия. Программировал для компьютерного центра. Главным образом в компьютерном центре надо было сидеть в маленькой такой будке и слушать, как люди заходят и говорят: «Я изменил только в одном месте». «Ну, давайте посмотрим, что это за место и что у вас в итоге случилось».
Сейбел: Это помогло вам отточить мастерство отладки или все это было сплошной глупостью?
Томпсон: Отточило отладку только в том отношении, что после этого хорошо начинаешь понимать стандартные ошибки. Человек несколько дней корпел над своей программой, он приходит к тебе, а ты так небрежно ему: «Вот тут!»
Сейбел: По образованию вы электроинженер? По компьютерным наукам в то время еще никого не готовили?
Томпсон: Нет, в то время на всей территории Соединенных Штатов компьютерные науки только зарождались, двумя путями. Теоретически — через математику и практически — через электронику. В Беркли компьютерные науки в то время преподавались в рамках курса по электроинжинирингу. Математика тоже пыталась прорваться, но у них не было такого политического влияния, чтобы соревноваться с умудренными жизнью конкурентами.
Сейбел: Университет Беркли в итоге стал известен прежде всего такими вещами, как Системная лаборатория Беркли, то есть практикой, а не вкладом в теорию.
Томпсон: Именно так. Есть источники зарождения компьютерных наук, например Корнелл, и есть подход Беркли к компьютерным наукам. Сама атмосфера места очень значима. И я провел год в Беркли не потому, что имел какие-то амбиции. Просто мне больше нечего было делать, а это нравилось.
Сейбел: Сразу после учебы?
Томпсон: Да. Честно говоря, я работал в университете и не собирался продолжать обучение, даже не подавал заявку. Это за меня сделал один из преподавателей, который потом и сообщил мне, что я теперь в магистратуре.
Сейбел: По-прежнему по направлению электроинжиниринга?
Томпсон: Да. И мои последние годы были просто замечательными. Я не делал ничего, чего не хотел бы делать. Никаких требований, ничего такого. Чтобы нормально выпуститься, я летом прослушал курс, кажется, по американской истории — было какое-то требование по получению степени. Но за исключением этого я преподавал почти половину всех курсов, которые должен был изучать.
Общая теория вычислений в то время как раз оформлялась как самостоятельная дисциплина. Сортировка методом Шелла только что появилась, и никто не мог понять, почему она быстрее, чем п2. Все гоняли тесты и пытались выяснить, в чем же дело; очень просто было увидеть, что она действительно сортирует, но было непонятно, почему же, собственно, она такая быстрая. Брали асимптоту и считали, почему тут n1,3, и так далее. А это же не натуральное число. И из всего этого — сортировки Шелла, интеллектуальной привлекательности сортировки Шелла и попытки выяснить, почему она такая быстрая, — вышла вся скорость вычислений. Разделяй и властвуй[72], все эти п log n и так далее. Поразительное, захватывающее было время.
У меня были друзья, в том числе весьма молодые преподаватели — по математике, я с ним очень дружил, и по электроинжинирингу, а также выпускник, на которого я работал. Для меня придумывали класс, и я его обучал.
Сейбел: Вы официально изучали предмет или только по документам числились его преподавателем?
Томпсон: Нет, конечно, нигде я преподавателем не числился. Везде стоял код электроинжиниринга 199, который означал индивидуальные или групповые исследования и все такое. Они изобретали предмет, давали ему название и передавали мне. И на него приходили три-четыре студента.
Сейбел: Одним из которых на бумаге были вы.
Томпсон: Да.
Сейбел: Вам нравилось преподавать?
Томпсон: В некотором отношении. Мне приходилось преподавать в своей жизни дважды. Один раз я взял отпуск на год и преподавал в Беркли в 1975/76 гг., а еще год я читал лекции в Сиднее в 1988 г. Это очень интересно. Мне очень, очень нравится. Я занимался лабораторными исследованиями, а потом поехал в Беркли преподавать и сам изучал те предметы, которые преподавал, поскольку у меня ведь не было образования в области компьютерных наук. Обычно внештатный преподаватель ведет один предмет. У меня их было пять. Некоторые курсы я читал по два раза, и они, думаю, были наилучшими, потому что в первый год я учился, а на второй уже понял, как преподавать, и организовал свои занятия более успешно, опережая на два шага студентов в этом предмете. На третий год курс становился просто унылым. Я взялся вести один предмет третий раз подряд, и это было ошибкой. Я никогда не смог бы стать педагогом, потому что там нужно преподавать каждый раз одно и то же. Это не по мне. Но я люблю преподавание: сперва тяжело, потом весело и интересно. И затем унылый третий раз.
Сейбел: Какую самую первую интересную программу вы написали?
Томпсон: Первая длинная вычислительная программа, которую я написал, относилась к проблеме игры в пентамино. Знаете такую?
Сейбел: С кирпичиками?
Томпсон: Да, с кирпичиками. Я запустил программу на компьютере IBM 1620, который имелся на физическом факультете. Я знал, где находятся самые лучшие компьютеры, и все их запускал на ночь для своих целей. Да и в главном компьютерном центре у меня было порядка двадцати учетных записей под разными псевдонимами. Имеется 12 пентамино — это разные типы фигур из пяти кирпичиков. И таких фигур может быть 12.
Сейбел: Примерно как в тетрисе.
Томпсон: Да. Но здесь каждая фигура состоит из пяти клеток. Если их все разложить на доске, то есть две, скажем так, наиболее приятные конфигурации. Одна — прямоугольник 10 на 6, а вторая — 8 на 8 с отверстием 2 на 2 посередине. И я решил все конфигурации для этих двух досок, то есть как разместить на них фигуры. В общем случае я решал это, сравнивая шаблон досок и шаблон фигур: как фигуры вкладываются в шаблон доски. Программа не знала, что это пентамино.
Сейбел: В принципе, это был поиск методом грубой силы?
Томпсон: Так и есть.
Сейбел: Наверное, на ассемблере?
Томпсон: Видимо, так. Да, наверное, на ассемблере. Не помню, честно говоря.
Сейбел: Где-то в это время вы должны были изучить Фортран.
Томпсон: Да, конечно, в компьютерном центре мне пришлось преподавать Фортран и отлаживать программы на нем. Но я никогда на Фортране не программировал. Довольно давно я написал компилятор Фортрана для UNIX, язык Би был неудачной попыткой написать компилятор Фортрана.
Сейбел: А я думал, Би — это ваша версия BCPL.
Томпсон: Отчасти. Начиналось все, как... впрочем, не знаю как. С семантической точки зрения, однако, это оказался BCPL. А когда я начинал, то это должен был быть Фортран. И как раз тогда у меня впервые появилось описание BCPL. Мне понравилась его четкая семантика. Я отложил Фортран, и в итоге вышел синтаксис Си и семантика BCPL.
Сейбел: Есть ли большие различия в том, как вы занимались программированием и теоретизировали по его поводу в начале карьеры и сегодня? Считаете ли вы, что программирование в каком-то смысле повзрослело или что вы повысили класс или узнали что-то такое, что заставляет вас оглянуться и сказать: «Господи, я же просто не знал, что творил!»?
Томпсон: Вовсе нет. Иногда я действительно оглядываюсь назад, но говорю: «Эх, а тогда ведь я был намного сильнее». Начиная с момента, когда я неделю читал ту программу, и до 30-35 лет я глубоко понимал каждую строку написанного мною кода. Я мог писать программу весь день, а ночью продолжать сидеть и читать строку за строкой в поисках ошибок. На следующий день я возвращался к нему и, разумеется, находил ляп.
Сейбел: А в 35 лет вы могли вспомнить то, что написали десятью годами раньше?
Томпсон: Да. Это позже моя память стала более избирательной.
Сейбел: Есть ли в изучении программирования то, что сейчас вы бы сделали по-другому? Сожалеете ли вы о пути, который избрали; может быть, хотели бы, чтобы что-то было сделано вами раньше?
Томпсон: Да-да, конечно. В школе мне обязательно следовало заниматься машинописью. Я плохо печатаю даже сейчас, но кто знал. Я никогда ничего не планировал. У меня нет дисциплины. Я делал то, что хотел делать завтра, на следующей неделе, всю жизнь. Если бы у меня были способности к планированию или предвидению, то курс машинописи я бы непременно по возможности прошел. И я бы, конечно, более углубленно изучил математику, потому что часто сталкивался с такими вещами, где помощи приходилось ждать только от математики. Вот такие частности, словом. Но если вернуться назад, то я уверен, что меня не хватило бы на то, чтобы сделать что-то принципиально по-иному. Обычно я ничего не планировал — просто делал следующий шаг. И начав заново, я точно так же делал бы следующий шаг.
Сейбел: Сразу после выпуска вы попали прямо в Bell Labs — как это вышло? На том этапе карьеры вы вроде не были классическим академическим исследователем.
Томпсон: Так уж меня занесло. Трудно сказать. На самом деле я, можно сказать, не учился. Формально — да, конечно. Один из моих преподавателей — собственно, мой хороший друг — прямо-таки натравил на меня рекрутера из Bell Labs. Но я не искал работу. У меня не было абсолютно никаких амбиций. И он назначил мне встречу на его маленьком рекрутерском стенде, а я то ли проспал, то ли сказал ему, что мне неинтересно. Но он не сдавался. В какой-то момент он позвонил мне и сказал, что хотел бы зайти. И пришел ко мне домой. И сказал, что хотел бы, чтобы я прошел собеседование в Bell Labs. Я отказался. Но он сказал: «Поездка бесплатная. Ты можешь делать все что угодно». А я ему: «Ладно, тогда вот что я тебе скажу. Работа мне не нужна. Но бесплатная поездка — это прикольно, у меня есть друзья на Восточном побережье. К ним и поеду». А он мне: «Отлично». Вот так я и попал на собеседование. Я приехал, два дня потратил на Bell Labs, а потом взял напрокат машину и поехал по Восточному побережью повидать школьных друзей, которые разъехались кто куда.
Сейбел: Очевидно, в вас было что-то, что заметили парни из Bell Labs и сказали: «Этот человек должен работать у нас».
Томпсон: С их точки зрения я смотреть не пытался. Я видел в них авторов статей, по которым я готовился к тем занятиям, что вел и где учился. Я знал их имена и репутацию. И они продолжали делать отличные вещи. По мне работа была работой, а эти парни не работали. Они просто развлекались. Как в школе.
Сейбел: Чем вы начали заниматься, поступив на работу?
Томпсон: Bell Labs занималась проектом MULTICS, и меня брали работать над MULTICS. Это я и делал. Я работал с машинами, загружал MULTICS, немного писал. В какой-то момент Bell Labs решила, что MULTICS не для них, и они отказались от проекта.
Но у них были машины специально под MULTICS. Они стояли без дела и ждали, пока их кто-нибудь не увезет. И примерно год я работал на одной из этих машин, она была чудовищная. Их использовали у нас только двое или трое. Я начал разрабатывать операционную систему, пытался создать маленькую операционную систему и запустить ее.
Это было безумно сложно, потому что сам компьютер был очень сложен. Но у меня получилось, и как-то я по этому компьютеру передал привет на 50 телетайпов по всему зданию. В итоге моя работа стала известна. Я побегал по помещению, нашел еще несколько неиспользуемых машин и, в принципе, таким образом и создал UNIX — на этих очень-очень маленьких компьютерах PDP.
Сейбел: Время на это у вас было — ваши боссы знали, что вы делаете, и одобряли этот проект как хорошее исследование, или вы занимались операционной системой сверхурочно?
Томпсон: Нет, честно говоря, я был просто неисправим. В принципе, меня могли даже и уволить, но меня это не беспокоило. Предполагалось, что мы должны заниматься фундаментальными исследованиями, но на деле выходило, что одними фундаментальными исследованиями мы заниматься будем, а другими нет. Только что мы выбрались из руин работы над MULTICS, так что операционные системы были одним из тех видов фундаментальных исследований, которые делать не надо. Поскольку мы пытались, не получилось, был большой провал, это дорого обошлось, так что давайте пошлем к черту. Так что я, можно сказать, ожидал, что за свои действия могу быть уволен. Но не уволили.
Сейбел: Как вы создаете программы? Царапаете на миллиметровке, запускаете утилиту для работы с UML или просто начинаете писать?
Томпсон: Зависит от масштаба проекта. Большую часть времени все хранится у меня в голове, никаких бумаг, и я концентрируюсь на сложных частях. Простые части отходят на второй план, их достаточно записать: они так и слетают с кончиков пальцев, когда все готово. Но над сложными я сижу и даю им некоторое время, чтобы созреть, — примерно месяц. В какой-то момент начинает складываться основание, образуется пирамида. И как только пирамида у меня в голове делается достаточно высокой, я начинаю с ее основания.
Сейбел: Но вы не просто создаете отдельные элементы — вы знаете, какова должна быть итоговая структура.
Томпсон: Допустим, кто-нибудь мне описывает что-то таким образом: «Вот это компьютер, а вот коды операций». Я могу представить себе структуру программ и то, насколько в соответствии с ней эффективны или неэффективны действия, основанные на этих кодах операций, потому что вижу основание и представляю себе иерархию. То же самое я могу делать и с программами. Если мне показывают функции из библиотеки или базовые вещи из нижнего уровня, я могу понять, как построить на этом разные программы и чего не хватает, какие программы будет сложно написать. Так что я могу представить себе всю пирамиду — останется только разобрать ее и определить, где основание.
Современное программирование во многих отношениях пугает меня: они пишут слой, за ним еще один, потом еще один, и все эти слои только и делают, что обращаются друг к другу. Меня смущает программа, которую обязательно читать сверху вниз. Там написано: «Сделай то-то». Отправляешься искать «то-то», находишь, а там написано: «Сделай еще что-то». Ищешь еще что-то, а там надо сделать еще вот это, и так до самого верха. И ничего не делается. Это просто перенос задачи на все более и более глубокий уровень. Я не могу этого ни понять, ни принять.
Сейбел: Почему же тогда не читать снизу вверх? Листья ведь где-то есть.
Томпсон: Но вы же не знаете, что является в данном случае листьями, а что нет. Если описание хорошее, то можно прочитать написанное английским языком и все понять, так что код можно не читать. Но если дается просто какой-то кусок кода и говорят: «Прочитай и сделай его лучше» или: «Прочитай и заставь его делать еще что-то». Тогда обычно приходится читать сверху вниз.
Сейбел: Вы что-нибудь записываете, прежде чем начать писать код?
Томпсон: Да, обычно это структуры данных. Я не записываю никаких алгоритмов или блок-схем, если вы это имеете в виду. Только то, к чему приходится обращаться практически в каждой строке кода, — структуры данных.
Сейбел: Если вы пишете программу на Си, значит ли это, что код на Си будет определять эти структуры данных?
Томпсон: Нет, это будут квадратики со стрелками и так далее.
Сейбел: Итак, у вас большая общая картина — пирамида. Насколько вы следуете плану в процессе написания кода?
Томпсон: Я не привязываюсь к коду. Если на полпути я понимаю, что другая декомпозиция лучше, то переключаюсь на нее. Я знаю многих, у которых написанная строка кода остается такой до конца жизни, если там, конечно, нет ошибки. Особенно если они пишут функцию для какого-то API, набросают этот API где-нибудь на бумажке или в списке рассылки — и все. Он никогда не меняется, как бы плох ни был. А я всегда с удовольствием все менял, если находил другой, более подходящий путь или иную декомпозицию. Я никогда не питал большой любви к имеющемуся коду. Код сам по себе — почти чепуха, его можно переписывать. Даже если ничего не изменяется, он все равно по какой-то причине портится.
Сейбел: Как вы понимаете, что вот этот код нужно отбросить?
Томпсон: Когда с ним становится тяжело работать. Я отбрасываю код гораздо быстрее, чем другие. Я отказываюсь от него, как только хочу к нему что-то добавить, но возникает ощущение, что добавить к нему что-то будет непросто. Тогда я выбрасываю его, начинаю заново и нахожу другую декомпозицию, при которой куда проще сделать то, что я собирался. Я не думаю долго над тем, выбросить код или нет.
Сейбел: Это же справедливо и для работы с чужим кодом?
Томпсон: Зависит от того, есть ли у меня на это право. Если да, то не важно, чей это код. Если нет и код чужой, то приходится терпеть. Или не делать это.
Сейбел: Если вы унаследовали чей-то код, то при его переписывании может возникнуть такая опасность: возможно, вы упустили какую-то тонкость в его работе или просмотрели какой-то функциональный элемент, который раньше был, а сейчас его не осталось. У вас так бывало?
Томпсон: Ну да, бывало, но это неизбежный элемент отладки. Если что-то забыл или не сделал, то сразу, как только это понял, доделываешь. Это просто элемент отладки. Код не получается с первого раза. Его расширяешь.
Сейбел: Построив систему, возвращаетесь ли вы, чтобы каким-то образом ее документировать?
Томпсон: Зависит от того, для чего она предназначена. Если она написана исключительно для меня, то нет, не возвращаюсь. Если я забуду аргументы, то добавлю строку о том, как это использовать. И в комментарии заголовка поясняю, что вся функция делает. Но очень кратко. Если это часть системы, или библиотеки, или еще чего-то, что должно быть опубликовано, то я потрачу время на документацию. В других же случаях нет.
Документация — это такое же искусство, как и само программирование. Редко когда документация оказывается на том уровне, какого бы мне хотелось. Обычно она намного изощреннее, чем нужно. Она содержит кучу маловажных подробностей и манящих возможностей, которые в данном случае неприменимы. Документирование — весьма сложный процесс, требующий больших временных затрат. Чтобы выполнить его правильно, нужно относиться к нему как к программированию. Нужно разобрать и снова собрать, но уже лучше, переписать, если дело идет неправильно. А этого не происходит.
Кроме того, я предпочитаю документирование снизу вверх, а как раз так мало кто делает. Если программа опирается на другие программы, файлы или структуры данных, мне хочется видеть ясную ссылку на них, чтобы я мог пройти по ней и их почитать, а такие ссылки нередко отсутствуют.
Сейбел: То есть вы бы хотели читать код так же, как его пишете, а именно снизу вверх?
Томпсон: Да. Таким образом я могу удержать его у себя в голове и запомнить. В противном случае я читаю его и могу даже понять, но сразу после чтения он выветривается у меня из головы. Если я понимаю структуру кода, то он становится частью меня и я воспринимаю его полностью.
Сейбел: В своей речи при получении премии Тьюринга вы упомянули, что если бы Дэна Боброу заставили использовать PDP-11, а не более мощный PDP-10, то в тот день награду вместо вас и Дениса Ричи мог бы получать он.
Томпсон: Я просто пытался сказать, что наша награда — результат счастливого случая.
Сейбел: Думаете, вам повезло быть привязанными к менее мощной машине?
Томпсон: Безусловной удачей оказалось то, что компьютер был мал и эффективен. Но, думаю, этот код мы бы написали в любом случае. Нам сильно помогло то, что дело происходило в самый разгар революции мини-компьютеров. «Десятка» была большим мэйнфреймом, управляемым компьютерным центром. Автономные вычисления вместо централизованных, думаю, и явились элементом счастливого случая. И это сыграло свою роль в PDP-11.
Сейбел: Выиграла ли UNIX от того, что была написана на Си, в то время как другие ОС — например TENEX и ITS — создавалась на языке ассемблера и, следовательно, их нельзя было так запросто переносить на другое оборудование, как UNIX?
Томпсон: Были и другие хорошие языки системного программирования, на которых писались такие вещи.
Сейбел: Например?
Томпсон: NELIAC был версией Алгола-58 для системного программирования.
Сейбел: BLISS тоже относится к той эре?
Томпсон: BLISS, кажется, был позже. В этих языках акцент делался на то, чтобы они хорошо компилировались. Думаю, с самого начала было вполне ясно, что из-за хорошей компиляции нельзя так уж убиваться. Нужно делать это хорошо, но вовсе не обязательно безупречно. Дело в том, что пока вы дорастаете до безупречной компиляции, закон Мура вас все равно обгонит. Вы можете повысить качество на 10%, но пока вы это делали, быстродействие компьютеров выросло вдвое и, возможно, появилось еще что-то более значимое для оптимизации, вроде кэшей. Думаю, по большей части стремление к совершенству здесь — пустая трата времени. Это очень тяжело: вы порождаете столько же ошибок, сколько устраняете. Нужно остановиться и не тратить 100% времени на 10% работы.
Сейбел: Вы, наверное, слышали о статье Ричарда Гэбриела «Worse Is Better» (Чем хуже, тем лучше).
Томпсон: Нет.
Сейбел: Он сравнивал два стиля — стиль Массачусетского технологического института (MIT), где на первом месте правильность, и стиль Нью-Джерси (то есть Bell Labs), где высоко ценят простоту реализации. Его теория состоит в том, что стиль Нью-Джерси, который он также называет «Чем хуже, тем лучше», обеспечивает возможность немедленного запуска проекта и исправления в нем ошибок на ходу.
Томпсон: Думаю, у MIT всегда был некий комплекс неполноценности по поводу UNIX. Я выступал с лекцией о UNIX в MIT, и меня представлял, кажется, Майкл Детрузос. Он объяснял, почему UNIX не была написана в MIT, хотя именно так и должно было быть. Почему у них были возможности, люди и все такое, но тем не менее ничего не произошло. И тут меня осенило, что все это время в них сидит дух соперничества. У меня ничего подобного не было. Мы сделали UNIX, а они сделали MULTICS, настоящего монстра. Это явно синдром второй системы.
Сейбел: Где MULTICS была вторичной по отношению к операционной системе MIT CTSS?
Томпсон: Да. Слишком много лишнего. Почти неприменимо. Они по-прежнему уверяют, что это был потрясающий успех, хотя очевидно обратное.
Сейбел: Как я понимаю, большинство специалистов MIT придерживается именно такого мнения о MULTICS. Они предпочитали построенные ими же системы ITS и те, что были основаны на Лиспе. Похоже, после MULTICS получилась вилка. UNIX вышла в свет, как вам хорошо известно, и в MIT все эти любители Лиспа стали срочно работать на PDP-10 и строить основанные на Лиспе системы, которые в конце концов, думаю, и породили Лисп-машины.
Томпсон: Да-да. Я знал всех этих ребят. Я считал, что это сумасшедшая работа. Не думал, что Лисп — настолько уникальный язык, чтобы делать под него свою машину. И я, видимо, был прав. Все это время я говорил: «Вы с ума сошли». Вот PDP-11 — отличная Лисп-машина. PDP-10 — отличная Лисп-машина. Зачем строить машину под Лисп, которая не будет быстрее? Да и вообще нет никакого смысла строить машину под Лисп. Это глупо, в конце концов.
Сейбел: Были ли у MULTICS такие черты, которые вам нравились, но тем не менее не были перенесены в UNIX?
Томпсон: Вещи, которые мне понравились настолько, что я их оттуда позаимствовал, — это иерархическая файловая система и оболочка — отдельный процесс, который можно было заменить другим процессом. До того во всех системах был «исполнительный компонент» — это так тогда называлось, — который был встроенным языком обработки. Он исполнялся в каждом процессе отдельно. А каждый раз когда вы что-то печатаете в оболочке, она порождает новый процесс и запускает то, что вы напечатали, а когда тот заканчивается, то вы возвращаетесь, и таким образом всегда находитесь недалеко от оболочки.
Сейбел: И все это вы перенесли в свою систему, там не осталось ничего, о чем бы вы жалели?
Томпсон: Нет.
Сейбел: Судя по тому, что я читал по истории UNIX, вы действительно использовали тот процесс проектирования, который здесь описали. Вы долго думали, а потом, когда жена и ребенок на месяц уехали, сказали: «Отлично! Теперь я могу написать код».
Томпсон: Да... Мы собрались группой и поговорили о файловой системе. Нас было трое или четверо. Единственный, кто сейчас не очень хорошо известен, — это Радд Кэнеди (Rudd Canady). В те дни в Bell Labs были потрясающие удобства: можно было позвонить по телефону и потом получить распечатку вызова. То есть оставляешь заявку на то, чтобы оно было записано, и на следующий день в твоем почтовом ящике появится распечатка. И после того как мы обсудили файловую систему с помощью доски и мела, Кэнеди снял трубку, набрал номер и зачитал данные с доски.
Распечатка пришла, и получившееся оказалось так близко к проектной документации, которую мы тоже сделали, что были просто невероятные совпадения. И я взял и реализовал эту файловую систему, только на PDP-7. В какой-то момент я решил, что мне необходимо тестирование. И написал разные загрузочные штуки. Но у меня были проблемы при написании программ для файловой системы. Хотелось чего-нибудь интерактивного.
Сейбел: И вам просто хотелось поиграть с написанием файловой системы? В тот момент, получается, вы не собирались писать именно ОС?
Томпсон: Нет, это была просто файловая система.
Сейбел: И вы, таким образом, написали ОС, чтобы создать наилучшую среду для тестирования файловой системы.
Томпсон: Да. Примерно на полпути я понял, что это была настоящая система разделения времени. Я писал оболочку для запуска файловой системы. И еще пару программ, запускавших ее. Тут я подумал: «Осталось только сделать редактор, и у меня есть целая операционная система».
Сейбел: Какую самую страшную ошибку вам приходилось отлавливать?
Томпсон: В основном это были ошибки, связанные с порчей памяти. Больше такого не бывает. Не знаю почему. Но поначалу мы постоянно работали с различным экспериментальным оборудованием, и в нем были ошибки.
Сейбел: То есть память портилась из-за аппаратных ошибок, а не из-за вышедшего из-под контроля указателя?
Томпсон: Мог быть указатель. Могло быть оборудование. Или и то, и другое. Та ошибка, которая мне сейчас вспомнилась как один из худших примеров, случилась на PDP-11. Там не было блока умножения, но его можно было докупить отдельно и подключить как периферию ввода/вывода. Сохраняешь числитель и знаменатель и запускаешь. Проходит цикл ожидания, затем выдается ответ — частное и остаток. И это было сделано под PDP-11 без управления памятью, а потом к нам пришло первое экспериментальное оборудование для PDP-11 с управлением памятью, и в результате блок умножения конфликтовал с управлением памятью.
Например, сохраняем, а потом прогоняем тест занятости. А в некоторых участках теста занятости отсылался физический адрес вместо виртуального, и часть памяти в итоге затиралась числителем того, на что мы пытаемся поделить. А обнаруживалось это далеко не сразу, причем в самых разных местах. Безусловно, это самая сложная ошибка, которую мне пришлось искать.
Сейбел: Как же в итоге удалось ее отследить?
Томпсон: Я написал программу, которая должна была поставить мировой рекорд по нахождению знаков числа е. Предыдущие мировые рекорды были ограничены не вычислениями — количеством циклов в секунду, — а вводом/выводом. Я придумал новый алгоритм, узким местом которого были вычисления (ввод/вывод был тривиальным). Он был феерически тяжелым в отношении умножения и деления. И мы заметили, что машина каждый раз буквально обрушивалась, когда я запускал на ней эту свою программу. А потом и установили связь.
Сейбел: И это дало вам возможность понять, что проблема в умножителе. Вы сразу же нашли причину?
Томпсон: Сначала мы решили, что ошибка возникает, когда сохраняешь множитель в блоке умножения, потом обращаешься за ним, а его там нет. Мы обратились в компанию DEC, там ничего не нашли и не захотели связываться. Их слишком нормальным сотрудникам не хотелось иметь дело с гибридной машиной. В те дни у нас были принципиальные схемы машин, и мы, собственно, нашли ошибку по этой схеме. Потом мы позвонили в DEC и сказали им: «Соедините вот этот и вон тот проводки».
Сейбел: К счастью, сейчас оборудование так не сыплется.
Томпсон: Да. Думаю, такие ошибки сейчас редки. К тому же сейчас компоненты гораздо более изолированы друг от друга: ошибку можно сделать, только если совсем уж безумствовать. Просто ошибиться на ассемблере: очень несложно что-то перепутать в каком-нибудь регистре при вызове подпрограммы. Но если вы имеете дело с высокоуровневым языком, где все аргументы должны подходить, такие ошибки становятся все более и более редкими.
Раньше в языках ассемблера их было множество. Если речь шла только о программах, а не о комбинации программ и оборудования, обычно они случались в одном месте и повреждалось только это место. Ошибка была с чем-то связана. Нужно было сидеть и мониторить операционную систему. И часто — или очень часто — мы видели, что случалась ошибка, как можно быстрее останавливали процесс и смотрели, что происходит в других местах, таким образом отслеживая ошибки. Их можно было так поймать.
Но вот ту, о которой я говорю, поймать было нельзя. Пока я не написал ту программу с тяжелым умножением/делением, благодаря которой стало видно, с какой частотой происходит ошибка. Вместо того чтобы падать раз в пару дней, теперь система рушилась раз в пару минут. И как только мы получили то, что обрушивает машину, у нас появился шанс это найти.
Сейбел: Сегодня некоторые говорят: «Да, конечно, у ассемблера больше всего шансов повредить память из-за программных ошибок, но и Си склонен к этому больше, чем некоторые другие языки». Можно сделать так, что указатели будут указывать черт знает куда, и получится выход за границу массива. Вы не считаете, что это проблема?
Томпсон: Нет, ее можно обойти языковыми оборотами. Некоторые пишут хрупкий код, а некоторые — очень крепкий структурно, и тут все зависит от человека. Думаю, хрупкий код можно написать на любом языке. Я определяю хрупкий код так: допустим, требуется добавить функциональность, так вот в хорошем коде ее нужно добавить только в одно место, а в хрупком придется затронуть сразу десять мест.
Сейбел: Если появляется брешь в безопасности из-за переполнения буфера, то что вы можете ответить на критику Си и C++, где утверждается, что они частично за это ответственны, что многих проблем можно было бы избежать, если использовать язык, который проверяет границы массивов или в котором есть сборка мусора?
Томпсон: Ошибки есть ошибки. Вы пишете код с ошибками, просто потому что вы так пишете. Если язык безопасен в смысле безопасности времени выполнения, то операционная система упадет, а не переполнит буфер, став при этом уязвимой. Например, ping of death — атака на IP-стек операционной системы. Мне кажется, что будут еще атаки такого рода. Это будут не атаки типа «полностью захватить машину и стать суперпользователем». Это будут ping of death.
Сейбел: Но ведь есть разница между отказом в обслуживании и уязвимостью, когда вы можете стать суперпользователем и делать, что захотите.
Томпсон: Есть две возможности стать суперпользователем: одна состоит в переполнении буфера, а другая — в том, чтобы указать программе сделать то, чего она делать не должна. В большинстве случаев реализуется вторая возможность, а не переполнение буфера. Вы можете стать суперпользователем без всяких переполнений. Так что ваш аргумент не работает. Все, что нужно сделать, — это уговорить su[73] дать вам оболочку; все пути и так уже там есть, без каких-либо ошибок времени выполнения.
Сейбел: Ладно. Не будем говорить о том, что ведет к краху программы, уязвимости или чему-то подобному; есть такой класс ошибок, которые случаются в Си и C++, но почему-то не случаются, скажем, в Java. Есть ли для определенных типов приложений какие-то преимущества, весомые по сравнению с тем, что эти ошибки будут происходить и вызывать неприятности?
Томпсон: Думаю, этот класс ошибок доставляет не так уж много проблем. Разумеется, каждый раз когда я пишу один из вызовов функции, которая не проверяет длину строки, strcpy и тому подобное, я знаю, что фактически пишу ошибку. Но каким-то образом я принимаю решение о том, оправданна ли эта ошибка, нужно ли экономить аргументы. Сейчас обычно я их экономлю. Но имеется семантическая проблема: если вы обрезаете строку и используете обрезанную строку, то появляется новая проблема. Ошибка по-прежнему там, просто буфер еще не переполнен.
Сейбел: Какие инструменты вы используете при отладке?
Томпсон: В основном вывод на печать. При разработке программы я размещаю очень много операторов вывода на печать. И пока я их не убрал или не закомментировал, вывод на печать достаточно надежен. Мне редко приходится возвращаться к этому снова.
Сейбел: И что именно вы печатаете?
Томпсон: Все, что нужно, все, что удобно иметь под рукой. Инварианты. Но по большей части я печатаю во время разработки. Так я и выполняю отладку. Я не пишу программы с чистого листа — я беру программу и изменяю ее. Даже в большой программе я вывожу: «main, left, right, print, hello». Да, «hello» — это не то, что я ожидаю от этой программы. Я вывожу на печать то, что ожидаю увидеть, и отлаживаю эту часть. При разработке я запускаю программу двадцать раз в час.
Сейбел: Вы печатаете инварианты; а используете ли вы утверждения, которые проверяют эти инварианты?
Томпсон: Редко. Мне проще убедить себя, что они правильны, и либо закомментировать вывод на печать, либо отбросить их.
Сейбел: Почему же тогда вам проще напечатать, что инвариант верен, чем использовать assert для автоматической проверки?
Томпсон: Потому что при печати вы действительно видите, что происходит, а не частные значения, и вы печатаете к тому же много всего, не только инварианты. Просто я делаю вот так. Я не предлагаю это как общую парадигму. Но сам я всегда делаю так.
Сейбел: Когда мы говорили о том, как вы создаете программы, вы упоминали процесс разработки снизу вверх. Вы строите эти кирпичики отдельно друг от друга?
Томпсон: Иногда.
Сейбел: А вы пишете тестовые модули для тестирования низкоуровневых функций?
Томпсон: Да, я так часто поступаю. Все зависит от программы, над которой я работаю. Если программа — это транслятор из А в Б, то я предложу целый спектр возможных А и соответствующих Б. Для регрессионного тестирования исполню все варианты А и проверю, насколько им будут соответствовать Б. Компилятор, транслятор, поиск регулярных выражений. Что-то вроде этого. Но есть программы, которые совсем не похожи на эти. Я никогда много не занимался тестированием и в этих программах мало понимаю. Я проведу несколько проверок, но они редко будут значительными, потому что, например, их будет тяжело проводить внутри самой программы. Главным образом это будут просто регрессионные тесты.
Сейбел: Под более сложными для тестирования программами вы понимаете, например, драйверы устройств и сетевые протоколы?
Томпсон: Вообще, они ведь запускаются каждый раз, когда вы запускаете систему.
Сейбел: То есть, по-вашему, таким образом можно избавиться от части ошибок?
Томпсон: Да, конечно. Что может быть лучше для тестирования операционной системы, чем постоянно запускать ее?
Сейбел: Еще один важный аспект программирования — оптимизация. Одни приступают к оптимизации с самого начала. Другие предпочитают сначала написать код, а потом уже думать о дальнейшей оптимизации. Каков ваш подход?
Томпсон: В первый раз я делаю программу насколько возможно простой. И очень часто этого достаточно. Строить очень сложный алгоритм для того, что никогда не будет запущено, просто глупо. Это трата времени и порождение ошибок. И поддерживать это невозможно, потому что придется исписать математическими формулами 50 страниц, чтобы рассказать другому парню, что ты, собственно, делаешь.
То, что сделано просто и методом грубой силы, 99% времени будет работать отлично. Если вы действительно создаете инструмент, который используется часто и местами проявляет квадратичную сложность, то иногда приходится вложиться в него как следует. Но чаще всего нет. Чем проще, тем лучше.
Сейбел: Некоторым просто нравится доводить код до блеска — так сказать, код для кода.
Томпсон: Да мне тоже нравится, но здесь во многом ради кода приходится жертвовать алгоритмом. Я имею в виду, что обычно сложный алгоритм требует сложного кода. А я лучше буду использовать простой алгоритм и простой код, чем какой-нибудь ужас. А если нужно как-то кратко охарактеризовать мой код, то могу сказать, что он простой, легко меняющийся и маленький. Ничего особо интересного. Его может прочесть любой.
Сейбел: Есть ли еще такие задачи, для решения которых в целях производительности приходится работать вручную с ассемблером?
Томпсон: Это редкость. Это музейная редкость — разве что вы можете таким образом получить порядковую разницу. Если вы можете усердно работать и заставить маленький кусочек большой программы исполняться в два раза быстрее, то и всю программу можно заставить исполняться в два раза быстрее, если только подождать год-другой. Если вы пишете компилятор, то, безусловно, 99% кода, который вы пишете, будут исполнены один или два раза. Но будет крохотная часть в операционной системе, которая работает 24 часа в день. А еще более крохотная — в самом внутреннем цикле такой системы. Так что, возможно, только 0,1% оптимизации, которую вы применили к компилятору, обернется для ваших пользователей каким-то эффектом. Но притом эффект может оказаться глубоким, так что, возможно, вы все равно захотите эту оптимизацию выполнить.
Сейбел: Но это будет результатом порождения лучшего кода в компиляторе, а не переписыванием всего компилятора на ассемблере.
Томпсон: Да, да.
Сейбел: Так что, возможно, часть причин, по которым программы пишут прямо на ассемблере, ныне менее важна, потому что компиляторы стали лучше.
Томпсон: Нет. Думаю, главным образом машины стали намного лучше. А компиляторы — все такой же отстой. Посмотрите на код, который выходит из GCC, это же ужасно. Просто плохо. И еще чертовски медленно. В самом компиляторе более 20 проходов. Это просто чудовищно медленно, но компьютеры-то стали в 1000 раз быстрее, с тех пор как вышел GCC. Поэтому кажется, что он стал быстрее: он ведь не стал настолько медленнее, насколько повысилось быстродействие компьютеров, на которых он запущен.
Сейбел: Кстати было бы упомянуть сборку мусора. С появлением Java сборка мусора наконец-то пошла в массы. Как однажды сказал Деннис Ричи, Си активно сопротивляется сборке мусора. Хорошо ли, что сейчас наблюдается явная тенденция к языкам со сборкой мусора? Заслуживает ли эта технология настолько массового использования?
Данный текст является ознакомительным фрагментом.