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

From The Joel on Software Translation Project

Jump to: navigation, search

Автор: Джоэл Сполски
Переводчик: Семён Хавкин
Редактор: Маргарита Исаева
В оригинале статья называется The Law of Leaky Abstractions и была написана 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* и арифметику указателей, а можно было сразу перейти к строкам из STL. Но в один прекрасный день они напишут "foo"+"bar", и возникнут странные проблемы, а мне придётся всё равно объяснить им, что такое char*. Или они попытаются вызвать функцию Windows с параметром типа LPTSTR и не смогут, пока не выучат char* и указатели и Юникод и wchar_t и хедерные файлы TCHAR — все то, что просвечивает через дырки в абстракциях.

Когда я обучаю кого-то программированию COM, было бы здорово ограничиться визардами Visual Studio и автоматической генерацией кода, но если что-то выйдет не так, у них не будет ни малейшего понятия, что случилось и как это исправить. Значит, надо рассказывать им про IUnknown и CLSID и ProgIDS и... о боги!

При обучении программистов ASP.NET было бы здорово сказать: мол, дважды кликните мышкой на штучку, а затем пишите код, который будет обрабатываться на сервере, когда пользователь кликнет на эту штучку. И правда, ASP.NET абстрагирует разницу между написанием кода HTML для отработки нажатия на гиперссылку (<a>) и кода для отработки нажатия на кнопку. Проблема: разработчикам ASP.NET пришлось скрыть тот факт, что в HTML нету способа отсылать форму из гиперссылки. Они обходят это, генерируя несколько строчек на JavaScript и добавляя к гиперссылке функцию onclick. Но эта абстракция дырява. Если пользователь отключит JavaScript, то приложение на ASP.NET не будет правильно работать; и если программист не знает, что именно абстрагировалось ASP.NET'ом, он не поймёт, в чём там дело.

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

Отсюда парадоксальное следствие: в то время как инструментарий программиста забирается на всё более высокие уровни сложности со всё более развитыми абстракциями, стать высококвалифицированным программистом становится всё труднее.

Во время моей первой стажировки в Microsoft я писал строковые библиотеки для Макинтоша. Типичное задание: написать версию функции strcat, которая возвращает указатель на конец новой строки. Несколько строчек кода на C. Всё, что я делал, пришло прямо со страниц К&Р, одной тоненькой книжки про язык C.

Сегодня же для работы над CityDesk мне нужно знать Visual Basic, COM, ATL, C++, InnoSetup, внутренности Internet Explorer, регулярные выражения (RegExp), DOM, HTML, CSS и XML. Всё это инструменты более высокого уровня по сравнению со старым K&Р, а всё ж таки мне и его надо знать, не то беда.

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

Закон дырявых абстракций крепко держит нас за штаны.

Комментарии переводчика:

  • ) "Остапу Бендеру из Восточной Африки". Или из Западной; жулик - он везде жулик. Но ссылка на Африку неслучайна. Во-первых, подчёркивается проникновение высоких технологий в самые отдалённые и технически отсталые уголки населённого мира. А во-вторых, как раз в период написания статьи из этой самой Африки по всему миру прокатилась волна вопиющего спама.
  • ) Спам. Казалось бы, spam — простая тушёнка. Но благодаря одному из номеров популярного среди древних программистов сатирического коллектива «Монти Питон» (Monty Python), так стали называть всякое ненужное барахло, рассылаемое по электронной почте вовсе того не желающим абонентам.
  • ) Хаб — сетевой узел, в котором встречаются провода, по которым передаётся информация.
  • ) В случае с поленом речь идёт не о Буратино, а о волокне древесины; по-английски grain of the wood. Подобный термин (шуточный, но точный) в русском языке, насколько мне известно, не устоялся. Речь идёт о том, что перебор элементов массива в том порядке, в котором они физически лежат в памяти, бывает значительно эффективнее (т.е. быстрее). Похожим образом, колоть дрова проще, когда лезвие топора ложится вдоль волокон, а не поперёк.
  • ) Отказ памяти у компьютеров происходит, когда искомого не находится в ближней памяти, и приходится лезть за ним в память более далёкую. Это может происходить на различных уровнях иерархии: если нужной информации нет в кэше, надо обращаться в оперативную память, при отказе оперативной памяти — во вторичную память (например, жёсткий диск), и так далее. Такой процесс проваливания на следующие уровни стоило бы по-русски назвать провалами памяти, но увы.
  • ) Гражданами первого класса называются элементы языка программирования, имеющие значения, с которыми можно обращаться как с обычными переменными, а именно: присваивать, передавать в качестве параметров, возвращать из функций-подпрограмм.
  • ) К&Р (K&R) — так программисты нежно называют классическую книжку Кернигана и Ричи, «Язык программирования C». Джоель использует этот термин и в более широком смысле - как стиль программирования.


---

Personal tools