Абстракции — естественная составляющая разработки программного обеспечения, и они вполне могут протекать.

image

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

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

В этой статье вы узнаете, что такое «дырявая абстракция», почему она возникает и стоит ли вам — серьезному программисту — беспокоиться об этом.

Давайте представим абстракции (снова)


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

Вот как это устроено. Типичный автомобиль имеет простой приборный щиток, который выглядит примерно так: кнопка запуска, руль для управления движением и две педали для контроля скорости. Вы используете кнопку зажигания (или ключ), чтобы завестись, и при этом совсем не обязательно разбираться в работе стартера и двигателя внутреннего сгорания. (Версия для программистов: вы вызываете метод Car.Start()). Вы крутите руль, чтобы поехать в нужную сторону, хотя не понимаете, как приводится в действие рулевая колонка и как соединены колеса. И так далее.

image

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

image

Идет время, и мы создаем новые абстракции и дополняем существующие. Когда люди только создавали автомобили, на приборном щитке не было спидометра (программист сказал бы: у машины не было свойства Car.CurrentSpeed). Но оказалось, что без спидометра очень трудно заставить «объекты» автомобиля удовлетворять «статическому свойству» Road.MaxSpeed. Поэтому спидометр пришлось добавить в следующей версии, и с тех пор жизнь необратимо изменилась.

image

Проектировать что-то — все что угодно — значит продумывать создание правильной абстракции. Хорошая абстракция — такая, которая раскрывает все важные детали и сверх них ничего лишнего. Та, которая обеспечивает баланс между контролем и сложностью. Она предоставляет методы и свойства, легко соотносимые с задачами, которые вы хотите выполнить. Если абстракция разработана хорошо, то она проста и логична в использовании.

image

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

Именно абстракции -позволяют написать в HTML-документе, а не рисовать отдельные пиксели. Благодаря абстракциям вы можете написать SQL-запрос, чтобы вывести историю заказов клиента, не зная размера каждой записи и места ее хранения. Благодаря абстракциям вы можете распечатать файл, не понимая языка принтера, воспроизвести видеофайл, не разбираясь в видеокодеках, прочитать текстовый файл, не переключаясь вручную между кластерами на жестком диске, и хранить коллекции данных, не управляя адресами памяти (если только вы действительно этого не хотите).

Было бы небольшим преувеличением сказать, что все хорошее в программировании происходит от абстракции… но только небольшим.

Как абстракции дают течь


Вот главная истина: все абстракции дырявые.

image

Иногда это называют "Законом Дырявых Абстракций", он был предложен одним из создателей Stack Overflow Джоэлом Спольски. Но почему это правило существует? Почему мы не можем создать идеальную абстракцию?

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

Вспомните наш пример с автомобилем. Можно создать красивый интерфейс с методами fillGas() и changeOil(). Но потом радиатор засорится, двигатель перегреется, и настанет время копаться в кишках автомобиля, задыхаясь от дыма. Через приборный щиток эту работу не сделать. Теперь вам нужен механик, который понимает те самые детали, которые скрывает эта абстракция.

image

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

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

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

Числа JavaScript. Что вы получите, если выполните console.log(0.1+0.34)? Одну дырявую абстракцию с плавающей точкой. Чтобы исправить это, вам нужно изучить числа с плавающей точкой, рассмотреть возможность округления или, возможно, использовать BigInt.

Объектно-реляционное отображение в базе данных. Используйте ORM-фреймворк, и вы сможете работать с объектами вместо таблиц, запросов и хранимых процедур (которые сами по себе уже являются абстракцией поверх хранилища данных). Но базы данных используются интенсивно, важность производительности растет, и вскоре вам понадобится тонкий контроль, который заставит вас обойтись без ORM-абстракции.

ASP.NET Web Forms. Это всего лишь один из примеров серверного фреймворка, который пытается заставить вас забыть о том, что ваш код на самом деле выполняется на другом компьютере (веб-сервере). Проблема в том, что большинство веб-приложений вскоре потребуют тщательно исследовать вопросы скорости отклика, задержки и размера страницы. Когда это произойдет, вам придется узнать все подробности о реализации модели исполнения вашего веб-фреймворка и о разнице между кодом на стороне клиента и на стороне сервера.

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

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

Комментарии (2)


  1. playermet
    27.11.2022 16:24

    Числа JavaScript

    Но ведь почти во всех языках этот же самый double... Причем реализованный в железе CPU.


  1. YungFlaeva
    27.11.2022 23:56

    Хочется добавить, что тема необходимости парадигмы ООП — это постоянный холивар, однако нет идеальной парадигмы программирования. Разумеется, со временем, когда возникает необходимость развивать проект, гибкость кода всё больше и больше сходит на нет, поэтому в том числе бывает полезно что-то "отремонтировать" или "пересобрать", как было сказано в статье. Подытожить хочется тем, что начинающим программистам не стоит останавливаться на одной парадигме, намного лучшим вариантом будет развивать своё мышление в разных парадигмах и познавать для себя новое, что сфера программирования и требует!