Чтение онлайн

на главную - закладки

Жанры

Кодеры за работой. Размышления о ремесле программиста
Шрифт:

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

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

Стил: Я уже говорил: нельзя пренебрегать точностью. В то же время мы можем создавать инструменты для

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Поделиться:
Популярные книги

Наследник чародея. Школяр. Книга первая

Рюмин Сергей
1. Наследник чародея
Фантастика:
городское фэнтези
5.00
рейтинг книги
Наследник чародея. Школяр. Книга первая

Проданная невеста

Wolf Lita
Любовные романы:
любовно-фантастические романы
5.80
рейтинг книги
Проданная невеста

Гардемарин Ее Величества. Инкарнация

Уленгов Юрий
1. Гардемарин ее величества
Фантастика:
городское фэнтези
попаданцы
альтернативная история
аниме
фантастика: прочее
5.00
рейтинг книги
Гардемарин Ее Величества. Инкарнация

Черт из табакерки

Донцова Дарья
1. Виола Тараканова. В мире преступных страстей
Детективы:
иронические детективы
8.37
рейтинг книги
Черт из табакерки

Новый Рал 9

Северный Лис
9. Рал!
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Новый Рал 9

Росток

Ланцов Михаил Алексеевич
2. Хозяин дубравы
Фантастика:
попаданцы
альтернативная история
фэнтези
7.00
рейтинг книги
Росток

Пять попыток вспомнить правду

Муратова Ульяна
2. Проклятые луной
Фантастика:
фэнтези
эпическая фантастика
5.00
рейтинг книги
Пять попыток вспомнить правду

Александр Агренев. Трилогия

Кулаков Алексей Иванович
Александр Агренев
Фантастика:
альтернативная история
9.17
рейтинг книги
Александр Агренев. Трилогия

Наследник с Меткой Охотника

Тарс Элиан
1. Десять Принцев Российской Империи
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Наследник с Меткой Охотника

Хозяйка старой пасеки

Шнейдер Наталья
Фантастика:
попаданцы
фэнтези
7.50
рейтинг книги
Хозяйка старой пасеки

Выйду замуж за спасателя

Рам Янка
1. Спасатели
Любовные романы:
современные любовные романы
7.00
рейтинг книги
Выйду замуж за спасателя

Искатель 2

Шиленко Сергей
2. Валинор
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Искатель 2

Мастер Разума III

Кронос Александр
3. Мастер Разума
Фантастика:
героическая фантастика
попаданцы
аниме
5.25
рейтинг книги
Мастер Разума III

Родословная. Том 2

Ткачев Андрей Юрьевич
2. Линия крови
Фантастика:
городское фэнтези
аниме
фэнтези
5.00
рейтинг книги
Родословная. Том 2