Кодеры за работой. Размышления о ремесле программиста - Питер Сейбел 32 стр.


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

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

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

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

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

Но после этого у каждого должна быть возможность воспользоваться результатом его/их труда через простейший интерфейс. И вот это, по-моему, здорово. Мне бы не хотелось, чтобы каждому требовался подобный уровень знаний. Вот пример, который когда-то приводил Морис Херлихи, я только вчера на него ссылался: очередь с двухсторонним доступом, и вам нужно вставлять и удалять элементы.

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

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

Хочу сказать еще кое о чем - мы опять возвращаемся к функциональному программированию. Транзакционная память, конечно, не имеет к нему прямого отношения. Она касается изменения разделяемого состояния - не самой функциональной вещи.

Дело в том, что когда-то я пошел на лекцию Тима Харриса о транзакционной памяти в Java. До этого о транзакционной памяти я вообще не слышал. Тим описывал "атомарные транзакции" и, в общем, больше ничего такого.

Я сказал: "Здорово, но тогда придется протоколировать все побочные эффекты в памяти, все инструкции по загрузке и хранению. А сколько их в Java!" Но в Haskell, благодаря монадам, их почти нет. Загрузка и сохранение в Haskell выражены явно, и программисты считают их важными операциями.

И я подумал, что надо ввести в Haskell все эти атомарные операции, - будет классно. Потом, после лекции, я стал объяснять Тиму, как это можно сделать. Вскоре, поскольку у нас была чистая, элегантная инфраструктура, мы придумали retry и orElse. Механизм retry позволяет осуществлять блокировку внутри транзакции, а о г Else - выбор внутри транзакции. Тиму и его коллегам при разработке транзакционной памяти такое не пришло в голову, поскольку они имели дело с довольно сложной средой.

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

Это то, что у них было. Это работало для небольших программ, но в целом было неудовлетворительно. Поэтому в Haskell мы ввели retry и о г Else, а затем внедрили их в контекст распространенного сейчас императивного программирования, и они широко используются. Отличная работа.

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

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

Сейбел: Какие книги по программированию вы бы взяли с собой на необитаемый остров?

Пейтон-Джонс: Обязательно "Programming Pearls" Джона Бентли. По поводу жемчужин: в чудной книге "Beautiful Code" есть глава Брайана Хэйеса "Создание программ для "Книги"". Думаю, под книгой Брайан понимает вечно прекрасную программу. Даны три точки, надо найти, с какой стороны линии между двумя точками окажется третья. Есть решения, которые работают неважно, а потом находится идеальное простое решение.

Еще, конечно же, "The Art of Computer Programming" Дона Кнута. Это не та книга, чтобы читать ее от и до, но одно время я активно ею пользовался. "Purely Functional Data Structures" (Чисто функциональные структуры данных) Криса Окасаки - просто фантастика: нечто вроде курса Артура Нормана, из которого сделана целая книга. Про то, как делать очереди, поисковые таблицы и кучи без всяких побочных эффектов и с хорошими ограничениями алгоритмической сложности. Великолепная книга, читать каждому. К тому же невелика по объему и доступно написана. "Structure and Interpretation of Computer Programs" Абельсона и Сассмана - мне очень понравилось. "Compiling with Continuations" (Компиляция на продолжениях) Эндрю Аппеля - о том, как компилировать функциональную программу, используя стиль передачи продолжений. Тоже превосходная вещь.

Есть одна важная для меня книга, которую я читал мало, - "A Discipline of Programming" Дейкстры. Дейкстра заботится о красоте программ. Его программы полностью императивны, но обладают "свойством Хо-ара": вместо того чтобы не иметь очевидных ошибок, они совершенно очевидно не имеют ошибок. Он очень хорошо и изящно рассуждает об этом. Я впервые понял, как это - рассуждать о программах, когда тебе невозможно возразить. Наконец, на меня сильно повлияла книга Пера Бринча Хансена о написании параллельных операционных систем. Я постоянно перечитываю ее.

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

Пейтон-Джонс: Конечно. Ни дня без кода. Ну, не то чтобы так и было, но это моя мантра. Мне кажется, тем, кто что-то делает хорошо, угрожает движение вверх по карьерной лестнице, пока они совсем не перестанут заниматься тем, что делают хорошо. Поэтому работа в Microsoft Research и в исследовательской сфере вообще хороша тем, что я могу заниматься компилятором, над которым тружусь с 1990 года. Это большая по объему вещь, и есть длинные фрагменты кода, которые я знаю лучше всех остальных.

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

Сейбел: Вам это доставляет такое же удовольствие, как и в начале?

Пейтон-Джонс: Да, конечно. Это лучшая в мире вещь. Мне кажется, у большинства программистов есть чутье: "здесь должен быть какой-то хороший выход". Работа в исследовательской сфере хороша еще и тем, что надо мной не стоит менеджер, которому нужен результат через неделю. Я могу спокойно сесть и подумать: "Здесь должен быть какой-то хороший выход".

Поэтому я много времени уделяю рефакторингу, вожусь с интерфейсами, создаю новые типы или полностью переписываю большие куски, чтобы их улучшить. GHC довольно велик - не по промышленным стандартам, а по понятиям функционального программирования: в нем 80 000 строк кода на Haskell, а может, и больше. Это компилятор-долгожитель - ему уже пятнадцать лет. Он активно развивается, большие куски переписываются, и нет мест, которые нельзя трогать. Поэтому меня так приятно возбуждает возможность сесть и сказать себе: "Здесь должен быть какой-то хороший выход". Иногда я оставляю что-нибудь на несколько недель - не могу найти хороший выход, зная, что он есть. Это мучительно. Потому что красивый способ должен быть.

Сейбел: Что происходит в эти несколько недель?

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

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

Пейтон-Джонс: Скорее второе. Озарения по утрам случаются редко. На исследовательской работе есть время подумать над сделанным, записать свои мысли. Иногда получается настолько интересно, что я пытаюсь соорудить статью. Одна из таких статей называется "The Secrets of the GHC Inliner" (Секреты GHC). В ней описываются техники реализации, примененные нами для некоторых элементов GHC и полезные, как мы считаем, другим разработчикам. Как ученый, я имею возможность абстрагироваться от кода, которому мы на четвертый раз наконец-то придали нужный вид, и написать о нем, чтобы другие смогли применять наши методы.

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

Пейтон-Джонс: Вы знакомы со статьей Фреда Брукса на этот счет -"The Computer Scientist as Toolsmith" (Компьютерный исследователь как системный программист)? Я недавно ее перечитал - отлично написано. Нельзя забывать, что прежде всего мы конструируем разные вещи. Поэтому программировать так увлекательно.

Но одновременно мне очень хочется вывести некие постоянно действующие принципы. У меня есть статья о том, как написать хорошую статью или сделать хороший доклад об исследовании, и одно из главных правил в ней - "не описывайте артефакт". Артефакт есть продукт реализации некой идеи. А что такое идея, этот предмет многократного использования, который вы пытаетесь вручить своим читателям или слушателям? Нечто такое, что может быть для них полезно. Задача ученого как раз в том, чтобы извлекать идеи, пригодные для многократного использования, из конкретных артефактов. Это не наука в смысле открытия законов. Но это перевод ежедневной жизненной суеты в абстракции многократного использования. И я считаю это важным.

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

Пейтон-Джонс: Это ложное противопоставление. Тут нет никакого "или-или". Для проектировщиков и разработчиков ПО очень сложно внутренне прочувствовать размер артефакта, над которым они работают. Если смотреть на Эмпайр-стейт-билдинг через отверстие в один квадратный фут, очень непросто ощутить его истинные размеры и внутреннюю структуру.

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

И здесь, как мне кажется, функциональное программирование может многое нам дать. Оно позволяет создавать более крепкие конструкции, более легкие для понимания и рассуждения о них. И здесь мы, функциональные программисты, запаздываем - много говорим об описаниях функциональных программ, но мало делаем таких описаний. Я бы хотел видеть больше инструментов, позволяющих понимать программы на языке Haskell, формально рассуждать о них и давать гарантии за пределами его системы типизации. Мы уже поднялись выше других и способны подняться еще выше.

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

Вероятно, это что-то вроде инварианта. Если что-то умеешь делать, то раздвигаешь это до тех пор, пока не перестает получаться. Поэтому в программировании всегда будет что-то от работы ремесленника, ведь наши амбиции непрерывно растут. А для инженерных сооружений всегда есть физические пределы, дальше которых наращивать структуру невозможно. В ближайшее время вряд ли построят мост через Атлантику, а если его построить, он может рухнуть. Но его не строят просто потому, что это слишком дорого. А что в программировании? Мы научились быстро и с малыми затратами строить мосты через Ла-Манш, это пройденный этап - давайте попробуем взяться за мост через Атлантику. И он разваливается.

Сейбел: Гай Стил говорил, что закон Мура действовал на протяжении всей его карьеры программиста, но вот насчет всей карьеры своего сына он уже не уверен. Он также рассуждал насчет программирования в ближайшем будущем. Не пора ли уже прекращать эти разговоры -"Построили мост через Ла-Манш, давайте строить через Атлантику"?

Пейтон-Джонс: Нет-нет. С ПО все иначе. Если вы написали программу в десять раз больше предыдущей, это не значит, что вам нужно запускать ее на компьютере, работающем в десять раз быстрее. Программный счетчик тратит 90% времени на 10% кода или как-то так. Участки программы, критичные с точки зрения быстродействия, могут быть невелики по объему.

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

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

Сейбел: Что вам особенно нравится в программировании?

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

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

Системы слишком уж распухли. Если вы хотите сделать веб-сервис на ASP.NET, то должны освоить API и инструменты, уметь писать на трех разных языках, знать Silverlight и LINQ - и вы можете создавать акронимы до конца света. И про каждый из них есть толстая книга.

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

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

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

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

Назад Дальше