Кодеры за работой. Размышления о ремесле программиста
Шрифт:
Осенью 1968 года, когда начали работать Дэйв, Уилли и остальные, я работал над другим проектом. Думаю, мой контракт был делом решенным, но к работе я приступил только в январе. К этому моменту сделано было не так много. Помнится, ребята написали часть кода, но ничего еще не работало. Когда я пришел, Дэйв и Уилли как раз начали вчерне разрабатывать алгоритм работы всей системы в целом и делить фронт работ. Я тоже попросил себе кусок-другой. Как программисты мы все были очень разные, но каждый должен был точно знать, как работает каждая строка кода, в том числе потому, что это была относительно небольшая программа. Сложная, но небольшая.
Я знал, что к моему приходу они не могли далеко продвинуться, потому что процесс
После этого на PDP-1 появилась возможность редактировать файлы, ассемблировать их, распечатывать результат, запускать ТЕСО-макросы и так далее. Единственное, что надо было набивать, - относительно небольшая бинарная исполняемая программа, которая предназначалась для машины Honeywell.
Сейбел: Что было самым трудным в написании программ для IMP - заставить их работать быстрее?
Козелл: Интересный вопрос. Так, давайте посмотрим. Мы не слишком много думали о размере программы - предполагалось, что в системе будет достаточно памяти для буферизации. И что кода будет не так уж много. Если бы он был, скажем, на 10% больше, чем он получился в итоге после максимального сжатия, рабочих буферов памяти стало бы немногим меньше. Поэтому мы не беспокоились о том, сколько инструкций все займет.
Сейбел: То есть какой объем.
Козелл: Да. Какой объем. Но нас чрезвычайно заботила скорость, поскольку приходилось учитывать пропускную способность. Перед нами стоял вопрос: как организовать систему, чтобы она была отказоустойчивой и сама восстанавливала свою работу, вместо того чтобы умереть?
Второй проблемой было заставить все это работать. В системе было много новых, непроверенных элементов. Мы не знали заранее, будут ли работать протоколы. Уилл предложил несколько новых идей, связанных с алгоритмами маршрутизации, - будут ли они удачными? Таких вопросов было очень много. Например, по поводу отслеживания заторов. Могли ли мы быть уверены, например, в том, что если пользователи всего мира отправят пакеты одному бедолаге, то мы сможем отбрасывать их в правильном порядке, не дав ему утонуть?
Сейбел: С подобными проблемами до вас никто не сталкивался?
Козелл: Совершенно верно. Это был в то же время и исследовательский проект - много новой теории. Было написано несколько диссертаций, многие думали, что разбираются в вопросе. Пришло время воплотить эту теорию на практике. Нам предстояло увидеть, работают ли теория очередей и алгоритмы маршрутизации на самом деле.
Третьим большим вызовом была отладка системы, устранение неисправностей. Например, внезапно пропала связь с городом Цинциннати (штат Огайо). Что случилось? Как это исправить? Звонишь в Цинциннати, а тебе отвечает заспанный ночной охранник - там сейчас три часа ночи. Нужно, чтобы он поднялся и посмотрел на ту мигающую коробочку в углу. Что он расскажет? Что с этим делать? Даже если система вновь заработала, как выяснить, что пошло не так? Как избежать этой проблемы в будущем? Не забывайте, что я считался великим-отладчиком-всего-и-вся, мастером-на-все-руки.
Помню, как был удивлен Уилл, когда я нашел в программе ошибку, которую не мог найти никто. Она скрывалась в одном из модемных протоколов и посылала неправильный пакет в неправильное время. Я сделал несколько заплаток, так что смог поместить маркер в пакет, и когда
Была еще одна проблема из разряда неприятных, но не смертельных. Порча памяти из-за неправильного указателя. Сама по себе порча была незначительной, но спустя несколько тысяч машинных циклов программа вылетала, потому что одна из структур данных оказывалась поврежденной. Та поврежденная структура данных использовалась постоянно, поэтому мы не могли просто добавить в код команду “Остановить программу, когда она изменится”. Я немного поразмыслил и наконец придумал двух- или трехступенчатую систему заплаток. Как только память портилась, первая заплатка активировала вторую, которая работала с другой частью кода. Это, в свою очередь, активизировало запись третьей заплатки в еще один участок. Наконец, когда эта третья заплатка замечала, что происходит что-то не то, она останавливала систему. Я придумал, как откладывать проверку до нужного момента с помощью такой динамической системы заплаток. К счастью, это сработало, и мы смогли быстро устранить неполадку.
Сейбел: Что наталкивает на подобные удачные идеи?
Козелл: Если я досконально разобрался в системе, будь то ПО для IMP или система разделения времени для PDP-1, несмотря на то, что она мультипрограммная, многослойная и основана на работе с прерываниями, все равно вся ее динамика находится у меня в голове. Я знаю, что и в каком порядке должно происходить и что не должно происходить. Это позволяет всегда представить примерную модель происходящего.
Надо сказать, что многие из возникавших тогда проблем относились к двум разным моделям компьютеров, и их анализ требовал недюжинной фантазии. Например, что-то происходит не так на первой машине, а последствия проявляются на второй. Я не могу остановить работу, первая машина уже обработала на 6000 пакетов больше, чем вторая, и вдруг вторая говорит, что один из пакетов поврежден. Что тогда делать? Всем нам троим приходилось садиться и прослеживать весь процесс в ретроспективе, чтобы найти ошибку и “вылечить” систему.
Сейбел: Был ли в систему встроен отладочный код?
Козелл: Нет.
Сейбел: Значит, с каждым сложным багом приходилось возиться отдельно?
Козелл: Насколько помню, мы не добавляли никакого отладочного кода. Сейчас я не устаю повторять: программу нужно писать так, чтобы она была тестируемой. А для того чтобы она была такой, нужно подумать об этом заранее, еще до того, как напишешь первую строку кода. Когда программа заработала, в нее уже поздно вставлять блокировки, утверждения или тестовые модули.
Но тогда мы даже не думали об этом. Мы просто старались написать эту чрезвычайно сложную систему реального времени так, чтобы она работала быстро. Это само по себе было трудной задачей. Мы не закладывали никаких проверок на непротиворечивость; к чему тратить на это время? Так что все исправления вносились потом бессистемно. Загляни в такой-то раздел памяти, просмотри код, чтобы разобраться с тем, или другим, или третьим, потом вернись и начни сначала.
Иногда эту работу удавалось несколько упорядочить. Точно помню, что написал однажды утилиту, позволявшую добавлять в систему заплатку, которая изымала один буфер из оборота и использовала его для фиксирования кода на определенных этапах. Делалось и такое, но тоже без всякой системы. Когда появлялся очередной баг, мы ломали головы, пытаясь его вычислить.