Закон Дырявых Абстракций


Автор: Джоэл Сполски
Переводчик: Семён Хавкин
Редактор: Маргарита Исаева
23 марта 2000

Есть в Интернете инженерное волшебство, на работу которого мы с вами полагаемся каждый день. Оно заключено в сетевом протоколе TCP, одном из основных кирпичей, из которых выстроен Интернет.
TCP — способ пересылки данных, который считаетсянадёжным. Это значит: если вы с его помощью отсылаете сообшение по сети, оно обязательно прибудет на место в неискажённом виде.
Мы все пользуемся TCP для повседневных нужд: загрузить страничку с веба, послать электронную почту. Надёжность TCP позволяет всякому Остапу Бендеру из Восточной Африки рассылать по миру спам наивысшего качества. О счастье, о радость!
Посмотрим теперь на другой, ненадёжный, метод пересылки данных под названием IP. Тут уже никто не обещает, что посылка доедет до места назначения, и что по дороге с ней ничего не случится. Отправляя через IP кучу сообщений, не удивляйтесь, если половина из них потеряется, а из остальных часть окажется совсем не тем, что посылалось: может, они будут содержать фотографии прелестных котят, но скорее всего — просто нечитаемый мусор, вроде столь любимого нами всеми тайваньского спама.
Волшебство же состоит в том, что TCP основан на IP. Иными словами, TCP обязуется работать надёжно, используя лишь ненадёжные детали.
Для иллюстрации волшебства, рассмотрим аналогичный, хотя и не вполне обычный, сценарий из реальной жизни.
Предположим, некая дама отправляла подводами из Петербурга в Москву диван, чемодан, саквояж и т.д. Часть подвод сломалась и до Москвы не доехала. Часть подвод опрокинулась по пути, разбив картину, картонку и зеркала. Подводы добирались до Москвы не в том порядке, в каком выезжали из Петербурга, поскольку некоторых задержали страшные лесные разбойники, и вообще возницы выбирали разные маршруты. А теперь представим, что даме предлагается новая услуга: Красная Стрела, которая гарантирует, что багаж (а) прибудет на место (б) в целости и сохранности (в) и в нужном порядке. Но волшебным образом Красная Стрела не использует, как вы подумали, железной дороги, а нанимает тех самых возниц с подводами. Красная Стрела организует работу возниц следующим образом. Состояние багажа каждой подводы тщательно проверяется. В случае повреждения диван, чемодан и проч. заменяются со склада точно такими же. Подводы выстраиваются в правильном порядке. Если страшные лесные разбойники сумели захватить Бологое и перерезать дорогу, то Красная Стрела перенаправляет подводы другим путём, и дама ничего не подозревает. Ей просто кажется, что багаж прибывает немного медленнее, чем обычно; а об ужасных событиях в Бологом даме знать необязательно.
Примерно так TCP и работает. По-учёному это называетсяабстракция: упрощённое описание процесса, механизм которого остаётся скрытым. На самом деле, значительная часть программирования заключается в построении абстракций. Что такое, допустим, строковая библиотека? Это способ сделать вид, что компьютеры умеют легко работать со строками, яко бы с числами. Что такое файловая система? Это способ сделать вид, что жёсткий диск состоит не из быстро вращающихся намагниченных тарелок, которые умеют сохранять биты в определённых местах, а якобы представляет из себя иерархическую систему папок внутри папок, внутри которых отдельные файлы содержат, в свою очередь, байтовые цепочки.
Вернёмся к TCP. Я тут для простоты слегка загнул, и у кого-то, может быть, от этого уже пар из ушей пошёл. Короче, я сказал, будто TCP гарантирует, что сообщение прибудет на место. На самом деле, это не так. Если ваш любимый хомячок перегрызёт сетевой кабель, так что никакие пакеты IP не дойдут до компьютера, то TCP ничего не сможет поделать, и сообщение не придёт. Если же вы поругались с сетевым администратором, который в отместку включил ваш компьютер в перегруженный хаб, то много пакетов IP потеряется, и хотя TCP будет работать, но так медленно, что за время пути собачка, сами понимаете, того.
Вот это я и называю дырявой абстракцией. TCP пытается абстрагироваться от ненадёжной сети полностью, но иногда эта сеть все-таки просвечивает сквозь дыры в абстракции, так что абстракция не всегда защищает от необходимости иметь дело с глубокими подробностями. Это всего лишь один пример того, что я назвал Законом Дырявых Абстракций:
Все нетривиальные абстракции дырявы.
В абстракциях обнаруживаются дыры. В одних немного, в других целая куча. Эти дыры постоянно просвечивают, протекают, абстракции не срабатывают. Вот ещё примеры.
  • Простой пример: итерация по большому двумерному массиву может идти с совершенно разной скоростью, смотря как он обходится: горизонтально или вертикально. Как и в случае с поленом, которое легче раскалывать вдоль волокна, а не поперёк, одно направление может вызывать значительно большеотказов памяти, чем другое, а отказы обслуживаются долго. Даже программистам на ассемблере приходится делать вид, что у компьютера большая плоская память, но в системе виртуальной памяти это всего лишь абстракция, в которой при отказе памяти образуется дырка, так что отдельные обращения к памяти могут занимать значительно больше наносекунд, чем обычно.
  • Язык SQL был создан, чтобы абстрагироваться от процедурных шагов, нужных для запросов к базе данных. Вместо этого он позволяет описать, что именно запрашивается, и пусть база данных сама догадается, какие процедурные шаги для этого нужны. Но в иных случаях некоторые запросы SQL в тысячи раз медленнее, чем другие, логически им эквивалентные. Известный пример: некоторые сервера SQL значительно быстрее отрабатывают запрос where a=b and b=c and a=c , чем where a=b and b=c , хотя результат, конечно, тот же самый. Программисту на SQL вроде бы и не следует заботиться о процедуре, только о спецификациях. Но иногда абстракция протекает, что приводит к страшным потерям в производительности, так что приходится лезть во внутренности планировщика запросов и смотреть, что там не так, и как заставить его работать эффективнее.
  • Хотя сетевые библиотеки, вроде NFS и SMB, позволяют работать с файлами на других машинах как на своей, иногда связь становится очень медленной или просто падает, и дальний файл перестаёт прикидываться местным; а ведь программисту надо писать код так, чтобы и в этой ситуации всё работало. Значит, в абстракции "всё равно, где лежит этот файл" естьдырки.Вот пример для системных администраторов Юникса. Если домашние директории лежат на дисках, подмонтированных по NFS (одна абстракция), а пользователи создают файлы .forward для автоматической пересылки почты в другое место (вторая абстракция), и сервер NFS падает, а почта всё прибывает, то никуда она не перешлётся, поскольку файл .forward будет недоступен. Так сквозь дырку в абстракции письма могут просыпаться на пол.
  • Строковые классы должны представлять строчки в виде граждан первого класса. Они абстрагируются от того, что строки — штуки сложные, и дают возможность работать с ними легко, ну прям как с числами. Почти все строковые классы C++ перегружают оператор +, и для конкатенации строчек можно писать s+"bar". Но как ни старайся, никакой на свете строковый класс C++ не даст вам написать"foo"+"bar", поскольку строковые литералы в C++ всегда имеют тип char*, а не string. Абстракция прохудилась так, что языком C++ её не заткнёшь. (Интересно, что историю развития C++ можно описать как историю затыкания дырок в абстракции строк. Уж не знаю, отчего бы не добавить к языку элементарный класс строчек.)
  • И ещё: несмотря на дворники, мощные фары, крышу и обогреватель, которые защищают (абстрагируют) от непогоды, под дождём быстро ехать нельзя; приходится иметь дело с водяной подушкой, а иногда ливень такой, что на дороге ничего не видно, и надо остановиться; так что и погоду, из-за закона дырявых абстракций, полностью не абстрагируешь.
Закон дырявых абстракций означает, к сожалению, что абстракции не так сильно упрощают нашу жизнь, как хотелось бы. Если я обучаю программистов C++, было бы здорово, если бы мне не нужно было рассказывать им проchar* и арифметику указателей, а можно было сразу перейти к строкам из стандартной библиотеки темплейтов. Но в один прекрасный день они напишут "foo"+"bar", и возникнут странные проблемы, а мне придётся всё равно объяснить им, что такое char*. Или они попытаются вызвать функцию Windows с параметром типа LPTSTR и не смогут, пока не выучат char* и указатели и Юникод и wchar_t и хедерные файлы TCHAR — все то, что просвечивает через дырки в абстракциях.
Когда я обучаю кого-то программированию COM, было бы здорово ограничиться визардом Студии и автоматической генерацией кода, но если что-то выйдет не так, у них не будет ни малейшего понятия, что случилось и как это исправить. Значит, надо рассказывать им про IUknown иCLSID и ProgIDS и... о боги!
При обучении программистов ASP.NET было бы здорово сказать: мол, дважды кликните мышом на штучку, а затем пишите код, который должен отрабатываться на сервере, когда пользователь кликнет на эту штучку. И правда, ASP.NET абстрагирует разницу между написанием кода HTML для отработки нажатия на гиперссылку (<a>) и кода для отработки нажатия на рисованную клавишу. Проблема: разработчикам ASP.NET пришлось скрыть тот факт, что в HTML нету способа отсылать форму из гиперлинка. Они обходят это, генерируя несколько строчек на JavaScript и добавляя к гиперлинку функцию onclick. Но эта абстракция дырява. Если пользователь отключит JavaScript, то приложение на ASP.NET не будет правильно работать; и если программист не знает, что именно абстрагировалось ASP.NET'ом, он не поймёт, в чём там дело. Из-за закона дырявых абстракций вот что получается: придумает кто-нибудь чудесный новый генератор кода, с которым у программиста работа наконец-то станет эффективной, а ему и говорят: "Сперва научись делать это руками, а потом уж пользуйся генератором, чтобы сэкономить время". Генераторы кода, абстрагирующие разработку кусков кода, так же дырявы, как и все прочие абстракции. А единственный компетентный способ залатать эти дыры - выучить, как работают абстракции, и какие подробности они скрывают. Итак, абстракции экономят наше рабочее время, но не экономят учебное время.
Отсюда парадоксальное следствие: в то время как инструментарий программиста забирается на всё более высокие уровни сложности со всё более развитыми абстракциями, подготовить высококвалифицированного программиста становится всё труднее.
Во время мой первой стажировки в Microsoft я писал строковые библиотеки для Макинтоша. Типичное задание: написать версию функции strcat, которая возвращает указатель на конец новой строки. Несколько строчек кода на C. Всё, что я делал, пришло прямо со страниц К&Р, одной тоненькой книжки про язык C.
Сегодня же для работы над CityDesk'ом мне нужно знать Вижуал Бэйсик, COM, ATL, C++, InnoSetup, внутренности Эксплорера, регулярные выражения (RegExp), DOM, HTML, CSS и XML. Всё это инструменты более высокого уровня по сравнению со старым K&Р, а всё ж таки мне и его надо знать, не то беда.
Десять лет назад можно было мечтать, что на сегодняшний день новые компьютерные концепции облегчат труд программиста. И правда: созданные за эти годы абстракции позволяют работать с проектами на порядки более сложными, чем десять или пятнадцать лет назад, типа программирования GUI и сетевого программирования. Но хотя замечательные инструменты, вроде современных объектных языков визуальных форм, позволяют сделать много и очень быстро, вдруг в один злосчастный день приходится искать течь в абстракции, и на это уходит пара недель. А когда вам нужно найти себе программиста восновном на Вижуал Бэйсике, совершенно недостаточно нанять программиста только на Вижуал Бэйсике, потому что каждый раз, когда абстракции Бэйсика потекут, он не сможет сделать ни шага.
Закон дырявых абстракций крепко держит нас за штаны.
Комментарии переводчика:
*) Отказ памяти у компьютеров происходит, когда искомого не находится в ближней памяти, и приходится лезть за ним в память более далёкую. Это может происходить на различных уровнях иерархии: если нужной информации нет в кэше, надо обращаться в оперативную память, при отказе оперативной памяти — во вторичную память (например, жёсткий диск), и так далее. Такой процесс проваливания на следующие уровни стоило бы по-русски назвать провалами памяти, но увы.
*) К&Р — так программисты нежно называют классическую книжку Кернигана и Ричи, «Язык программирования C». Джоель использует этот термин и в более широком смысле - как стиль программирования.

Текут абстракции? Хотите знать, почему?


Часто в обсуждениях или даже статьях можно встретить понятие «Дырявые (или протекающие) абстракции», которое описал в своей статье Джоэл Спольски. Понятие обычно применяется в моменты, когда что-то перестает работать, начинает выполняться очень медленно или совсем ломается.

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

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

Поясню, как я сам понимаю суть подобных утечек. На самом деле все просто и даже очевидно. Любое решение, любая вещь имеет область применимости и область применения. Так получается потому, что ресурсы ограничены, потому что жизнь коротка, потому что лень заниматься работой, которая все равно в 99,999% никому не будет нужна.

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

Какова причина протечек? Мы так увлеклись решением нашей задачи, что подошли к краю области применения решения. Это может быть и «медленный ORM», и «тормозящая Java», и «сложный Linux», и «ненадежный Windows». Появление протечек абстракций в ваших решениях — это признак того, что вы подошли к краю обрыва, приближаетесь к запретной зоне, возможно, идете не туда или выбрали неправильный инструмент (или неправильно его применяете) для решения для своих задач. Что будет при пересечении границы применимости? Может быть все, что угодно. Если нет четкой границы, есть риск незаметно ее пересечь и оказаться не в самом лучшем положении.

Что же делать? Понять, что является причиной появления препятствий на вашем пути. Не пытаться решать задачу о маятнике с большой амплитудой «формулой малых колебаний», понимать ограничения применяемого решения, переосмыслить задачу или поменять инструмент.

На изображении:
Это гравюра Гравюра Фламмариона (в оригинале она монохромная), которую раскрасил Хуго Хайкенвельдер.
… На ней изображён человек, одетый в средневековую одежду пилигрима с посохом в руке. Сквозь занавес небесного свода он рассматривает внутреннее устройство вселенной...