Закон дырявых абстракций - Академия Selectel

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

Владимир Туров Владимир Туров Разработчик 29 июля 2020

Текст, который установил «закон дырявых абстракций», был написан в 2002 году. Почему я перевожу его спустя почти 20 лет? Он до сих пор не потерял своей актуальности и достоин прочтения. Протокол TCP не получил лучшую альтернативу, а закон дырявых абстракций лишь укрепился в жизни разработчиков и рискует стать аксиомой. Добавлю, что я не пересчитывал все […]

Изображение записи

Текст, который установил «закон дырявых абстракций», был написан в 2002 году. Почему я перевожу его спустя почти 20 лет? Он до сих пор не потерял своей актуальности и достоин прочтения.

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

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

Использование протокола TCP — это способ надежно передать данные. Поясню: использование TCP при пересылке сообщения через сеть гарантирует, что оно будет доставлено в первоначальном виде.

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

Для сравнения, существует другой, ненадежный, протокол передачи данных под названием IP. Никто не дает гарантий, что данные будут доставлены. Если вы отправите последовательность сообщений с использованием IP, то не удивляйтесь, когда половина сообщений не будет доставлена, а вторая половина придет в случайном порядке. Более того, есть шанс, что часть сообщений превратится в изображения маленьких обезьянок или, что вероятнее, сообщения станут нечитаемым мусором.

Вот здесь-то и происходит настоящее волшебство: TCP работает на основе IP. Иными словами, TCP обязан доставить данные надежно, используя только ненадежный инструмент.

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

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

Теперь представьте сервис Hollywood Express, который гарантирует: (а) доставку; (б) в правильном порядке; (в) в идеальном состоянии. Чудеса заключаются в том, что у Hollywood Express нет иных способов трансфера актеров, кроме ненадежного — на машинах. Hollywood Express проверяет каждого приезжающего актера, и если его состояние неудовлетворительно, то сервис звонит на родину актера и просит выслать идентичного близнеца. Если актеры приезжают в случайном порядке, то Hollywood Express восстановит изначальный порядок. Даже если большой корабль пришельцев на своем пути в Зону 51 упадет и парализует скоростную магистраль в Неваде, актеры просто сменят маршрут и поедут через Аризону, а Hollywood Express ничего не скажет об инциденте продюсерам в Калифорнии. Для продюсеров все будет выглядеть так, будто актеры ехали чуть дольше обычного и никакого крушения НЛО не было.

Примерно так же работает TCP. Это то, что специалисты в Computer Science называют абстракцией — упрощением чего-то значительно более сложного, происходящего «под капотом». Большая часть программирования заключается в построении абстракций. Что такое библиотека для работы со строками? Это способ сделать работу со строками такой же простой и удобной, как и с числами. Что такое файловая система? Это способ представить жесткий диск не набором вращающихся магнитных пластин, хранящих биты в определенных местах, а иерархической структурой каталогов с файлами, которые содержат данные.

Но вернемся к TCP. Я немного приукрасил, чтобы облегчить понимание работы TCP. И осознаю, что некоторых такое упрощение может довести до белого каления. Я сказал, что TCP гарантирует доставку сообщения. Что ж, это не так. Если ваш домашний питомец перегрызет сетевой кабель вашего компьютера, то IP-пакеты перестанут его достигать. Вне зависимости от усилий TCP сообщение не будет доставлено. Если вы были невежливы с системными администраторами в вашей компании и вас решили наказать, подключив к перегруженному концентратору, то лишь часть ваших IP-пакетов будет доходить, в этом случае TCP будет работать, но чрезвычайно медленно.

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

Любая нетривиальная абстракция в некоторой степени дырявая.

Абстракции ломаются. Иногда немного, иногда значительно. Это и называется дырами, протечками. Что-то идет не по плану. Это происходит повсеместно, где используются абстракции. Вот несколько примеров:

  • Даже простой обход двумерного массива может выполняться с разной скоростью в зависимости от используемого направления обхода: вертикального или горизонтального. В одном из направлений будет значительно больше исключений вида «отказ страницы», а обработка таких исключений — медленное дело. Программисты на ассемблере думают, что у них есть большое и плоское адресное пространство. Но на самом деле используется виртуальная память, абстракция, которая протекает каждый раз, когда происходит отказ страницы, и необходимо тратить дополнительные несколько наносекунд на подгрузку запрашиваемой страницы.
  • Язык SQL абстрагирует от процедурных шагов, необходимых для извлечения информации из базы данных. Вы указываете, что нужно извлечь, а база данных решает, как это сделать. Но в ряде случаев некоторые запросы на некоторых SQL-серверах в тысячи раз медленнее, чем их логические эквиваленты. Широко известен пример, когда запрос «where a=b and b=c and a=c» выполняется быстрее, чем «where a=b and b=c» на одном и том же наборе данных. В идеале, о таких деталях должна беспокоиться только спецификация, а не вы. Но иногда в абстракции находится дыра, которая вызывает просадку по производительности. Это заставляет вас разобраться с анализатором запросов, а затем понять, что пошло не так и как это исправить.
  • Сетевые библиотеки типа NFS и SMB позволяют работать с файлами на удаленных машинах, как с локальными файлами. Однако в некоторых случаях на нестабильном соединении файлы перестают вести себя как локальные, а вам как программисту приходится решать эту проблему. Это дыра в абстракции «удаленный файл то же самое, что и локальный файл”. А вот конкретный пример для системных администраторов. Если вы размещаете домашние каталоги пользователей на сетевом диске (одна абстракция), а пользователь создает .forward файл для переадресации своих писем (еще одна абстракция), то при недоступности сетевого диска файл .forward не будет найден и письмо не будет переадресовано. В итоге дыра в абстракции приведет к тому, что несколько писем будут выброшены на пол.
  • Предполагается, что строковые классы в С++ позволяют вам использовать строки как объекты первого класса. Они пытаются абстрагировать факт, что строковые манипуляции сложные, и позволяют вам работать со строками, как с числами. Почти все строковые классы в С++ перегружают оператор +, так что вы можете писать s + “bar” для конкатенации строк. Но знаете что? Вне зависимости от того, как сильно они стараются, во всем мире не существует ни одного класса, который позволил бы написать “foo” + “bar”, потому что строки в С++ всегда типа char*. В абстракции возникает дыра, и язык не позволяет вам ее залатать. (Забавно, но история эволюции С++ со временем может быть описана как история заделывания дыр в абстракции строк. Почему нельзя было добавить нативный строковый тип — мне до сих пор не ясно.)
  • И более того, вы не можете быстро ездить в дождливую погоду, даже если ваша машина оборудована крышей, дворниками, фарами и обогревателем, которые защищают вас от непогоды. Вам все равно придется беспокоиться о гидропланировании. А иногда дальность видимости заметно снижается, и вам приходится снижать скорость, потому что полностью абстрагироваться от погоды не получается. Закон дырявых абстракций в действии.

Из закона дырявых абстракций следует, что абстракции не упрощают нашу жизнь настолько, насколько нам хотелось бы. Когда я преподаю С++, мне хотелось бы избежать рассказа про тип данных char* и арифметику указателей. Было бы замечательно рассказывать сразу про STL, но однажды ученики напишут “foo” + “bar” и испугаются, а мне придется рассказать про char*. Или когда-нибудь они попробуют вызывать функцию Windows API с аргументом OUT LPTSTR, и им все равно придется узнать про char, указатели, юникод и wchar_t, а так же TCHAR и все, что просачивается через абстракцию.

При программировании с COM (Component Object Model — прим.пер.) было бы неплохо изучать сразу помощников Visual Studio и всю магию кодогенерации. Но если хоть что-то пойдет не так, то у программистов не будет ни малейшей идеи, что произошло, где искать ошибку и как ее починить. И мне придется рассказывать про IUnknown, CLSID и ProgIDS и… О, человечество!

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

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

Это парадоксально, но с каждым разом, когда мы изобретаем все более высокоуровневые инструменты с более качественными абстракциями, становиться профессиональным программистом все сложнее.

В ходе своей первой стажировки в Microsoft я разрабатывал библиотеки для работы со строками на Macintosh. Вот пример типичного задания: написать версию strcat, которая возвращает указатель на конец новой строки. Всего несколько строк кода на Си. Все, что я делал, было прямиком из K&R (Керниган и Ритчи — прим. пер.) — одной тонкой книги про язык программирования С.

А после я устраиваюсь в CityDesk (компания закрылась в 2016 году — прим.пер.). Теперь мне необходимо знать Visual Basic, COM, ATL, C++, InnoSetup, внутреннюю организацию Internet Explorer, регулярные выражения, DOM, HTML, CSS и XML. Это все инструменты высокого уровня по сравнению с вещами из K&R, однако я все еще должен знать все вещи из K&R.

Десять лет назад мы могли представлять, что новые парадигмы программирования упростят нам разработку сейчас. На самом деле, абстракции, которые мы создали за эти десятилетия, позволяют нам легко ладить с новыми уровнями сложности, которые нам не поддавались 10-15 лет назад, как в случае с разработкой графических интерфейсов или работы с сетью. А сейчас у нас есть множество замечательных инструментов, таких как объектно-ориентированные языки с поддержкой форм, которые позволяют нам выполнять работу невероятно быстро. Пока однажды мы не столкнемся с проблемой, где абстракция «протекает», и нам потребуется две недели на решение. Когда вам нужно нанять программиста на Visual Basic, чтобы делать только VB-код, это не самая лучшая затея. Потому что такой программист будет застревать каждый раз, когда наткнется на дыру в абстракции Visual Basic.

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

Эта статья является переводом.
Оригинал:
The Law of Leaky Abstractions by Joel Spolsky