Программирование в абсолютном двоичном коде
Первые программисты писали в абсолютном двоичном коде: каждая инструкция & каждый адрес в сыром двоичном коде. Одна инструкция может выглядеть как 01100101 00001010 — код инструкции & адрес памяти в двоичном виде.
Проблема спагетти-кода
Когда ошибка требовала вставки новой инструкции, программисты сталкивались с дилеммой. Вставка на место означала, что каждый последующий адрес инструкции сдвигался на один — что требовало от программиста обновления каждой ссылки на адрес во всей программе. Катастрофально.
Решение: заменить инструкцию прямо перед точкой вставки на переход к пустой памяти. В том месте пустой памяти: написать перезаписанную инструкцию, добавить новые инструкции, затем прыгнуть назад. Когда ошибки появлялись в исправлениях, применить один и тот же трюк снова, используя другую пустую память.
Результат: путь выполнения через программу прыгал на кажущиеся случайными места. Хэмминг назвал это 'банкой спагетти'. Путь потока управления, нарисованный на бумаге, выглядел ровно как запутанные спагетти.
Маршруты выхода
Два немедленных улучшения: восьмеричная нотация (группировка двоичных цифр по трём) & шестнадцатеричная (группировка по четырём, использование A–F для значений больше 9). Они сократили ошибки в написании, но не решили фундаментальную проблему адреса.
Символическая ассемблерная программа (например, SAP (Symbolic Assembly Program) IBM & SOAP (Symbolic Optimizing Assembly Program) на IBM 650) позволила программистам писать имена инструкций (ADD, MOVE) & символические метки адресов вместо двоичного кода. Ассемблер переводил в двоичный код во время ввода, автоматически управляя назначением адресов.
SOAP выполнял дополнительную оптимизацию: он располагал инструкции на вращающемся барабане так, чтобы следующая инструкция прибыла в головку чтения ровно когда предыдущая завершилась — кодирование с минимальной задержкой. SOAP даже компилировал себя: программа A обрабатывалась как данные для создания B, B запускалась на A для измерения того, насколько самокомпиляция улучшила его.
Библиотеки & перемещаемый код
Хэмминг отметил, что идея переиспользуемого программного обеспечения (математических библиотек) пришла очень рано — Бэббидж её задумал. Проблема: библиотека с абсолютным адресом требовала, чтобы каждая подпрограмма занимала одни и те же места в памяти каждый раз при её использовании. Когда общий размер библиотеки вырос слишком большим, программы начали конкурировать за одни и те же адреса.
Решение: перемещаемый код. Ассемблер генерирует инструкции, которые ссылаются на память относительно — смещения от базового адреса — а не на абсолютные адреса. Компоновщик разрешает финальные адреса во время загрузки.
Неопубликованные отчёты фон Неймана (широко распространённые) описывали необходимые приёмы программирования. Первая опубликованная книга по программированию (Wilkes, Wheeler & Gill, EDSAC, 1951) кодифицировала эти методы.
Развилка дизайна языка
FORTRAN (1957, IBM) & ALGOL (1958, международный комитет) представляют две философии дизайна, которые произвели радикально различные результаты.
FORTRAN
Джон Бэкус возглавлял проект FORTRAN (FORmula TRANslation) в IBM. Цель дизайна: сделать язык лёгким в использовании для учёных & инженеров. FORTRAN принимал математическую нотацию, которая чувствовалась естественной для своих пользователей: A = B + C * D вместо ADD B, C; STORE T; MULTIPLY T, D; STORE A.
FORTRAN выжил более 60 лет. Он остаётся в активном использовании в научных вычислениях, гидродинамике, моделировании климата & вычислительной физике. Хэмминг отметил эту долговечность как доказательство успешного дизайна.
ALGOL
ALGOL (ALGOrithmic Language) был разработан комитетом логиков & компьютерных учёных, нацеленных на математическую строгость: логически чистый, формально определяемый язык. Нотация Backus-Naur Form (BNF) для описания грамматик была изобретена для спецификации ALGOL.
ALGOL потерпел неудачу на практике. Несмотря на его логическую элегантность & огромное влияние на последующий дизайн языков (Pascal, C, & почти каждый современный язык происходит из концепций грамматики ALGOL), сам ALGOL никогда не был широко развёрнут. Вердикт Хэмминга: логически разработанный, но человечески невозможен в использовании.
Иерархия языков
Хэмминг описал естественную иерархию от машинного кода через ассемблер, языки более высокого уровня, & в конечном счёте 'язык, ориентированный на проблему' близкий к тому, как практики думают о своей проблемной области. Каждый уровень добавляет читаемость человеком ценой машинной эффективности.
Четыре критерия языка программирования Хэмминга
Хэмминг дистиллировал урок FORTRAN vs ALGOL в четыре критерия для успешного языка программирования:
1. Легко учиться — новичок может стать продуктивным быстро
2. Легко использовать — рутинные задачи требуют минимальных формальностей
3. Легко отлаживать — ошибки создают осмысленные, находимые сообщения об ошибке
4. Легко использовать подпрограммы — переиспользование & абстракция не требуют героических усилий
Он добавил структурное наблюдение: человеческий язык содержит около 60% избыточности; письменный язык около 40%. Языки с низкой избыточностью (как APL) создают элегантные однострочники, которые эксперты находят красивыми & новички находят непрозрачными — & которые содержат необнаружимые ошибки, когда один символ меняет значение.
Следствие: язык, разработанный для логической элегантности, оптимизирует для неправильного читателя. Программист — человек; люди нуждаются в избыточности для ловли ошибок & коммуникации намерения.
Психологический vs логический дизайн языка
Хэмминг вернулся к контрасту FORTRAN/ALGOL как к уроку в институциональной & человеческой динамике, не только дизайне языка.
FORTRAN был разработан психологически — для людей, которые его будут использовать, специально для учёных, которые думают в математической нотации. ALGOL был разработан логически — для формальной правильности & теоретической элегантности.
Парадокс, который Хэмминг определил: логически правильный язык, который люди сопротивляются, — неудача; прагматически разработанный язык, который люди принимают, — успех, даже если он логически более беспорядочен.
Он привёл APL как крайний случай: логически элегантный, выразимый однострочниками, со своим собственным специальным набором символов. Эксперты его любили. Нормальные программисты нашли его нечитаемым. Один символ может молча трансформировать значение программы. APL имеет небольшое преданное сообщество & почти нулевое основное использование.
Аргумент человеческой избыточности: разговорный язык ~60% избыточен (повторённый контекст, уточняющие слова, предсказуемая структура). Письменный язык ~40% избыточен. Эта избыточность служит обнаружению ошибок — люди ненадёжны, так что язык эволюционировал для несения достаточно повторённой информации для ловли & исправления ошибок. Язык с низкой избыточностью удаляет эту сеть безопасности.
Иерархия компилятора
Хэмминг описал наслаивание компилятора/интерпретатора: программа может читать язык более высокого уровня & переводить его на более низкоуровневый. Наложите эти слои — каждый переводит на один уровень вниз. На вершине: язык, ориентированный на область, который эксперты в области (биология, финансы, физика) пишут естественно. На дне: машинный код. Каждый переход — это компилятор или интерпретатор.
Предсказание выживаемости языка
К 1993 году Хэмминг видел, как много языков преуспели & потерпели неудачу. FORTRAN (1957) выжил. ALGOL (1958) потерпел неудачу. COBOL (1959) выжил десятилетия в деловых вычислениях. LISP (1958) выжил в исследованиях AI. PL/I (1964) попытался унифицировать всё & потерпел неудачу.
Повторяющийся паттерн
Глава истории программного обеспечения Хэмминга содержит повторяющуюся структуру:
1. Болезненное ограничение существует (абсолютные адреса, двоичная нотация, неуправляемый код)
2. Кто-то изобретает слой абстракции, который скрывает ограничение
3. Абстракция позволяет новый масштаб, который создаёт новые болезненные ограничения
4. Повторить
Двоичный → восьмеричный/шестнадцатеричный → символический ассемблер → FORTRAN → структурированное программирование → объектно-ориентированные языки → языки, ориентированные на область. Каждый слой разрешает самую острую боль предшественника, пока вводит новый класс проблемы.
Проблема спагетти-кода (абсолютные адреса) привела к символическому ассемблеру. Большие программы ассемблера привели к FORTRAN. Большие программы FORTRAN привели к структурированному программированию & затем объектной ориентации. Лекция Хэмминга закончилась до этих более поздних переходов, но паттерн продолжается.
Его урок для инженеров: вы всегда решаете боль, обнаруженную предыдущей абстракцией. Понимание слоя, на котором вы находитесь, требует знания, почему слой ниже существует.