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

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

Lo-fi

Однако больше абстракции не обязательно хорошо. Если ваши абстракции скрывают важные детали, когнитивная нагрузка на разработчиков увеличивается (поскольку они отслеживают важную информацию, не выраженную в API) или их программное обеспечение становится хуже (если они игнорируют эти детали). Это может принимать разные формы, но обычно это делает дорогие вещи бесплатными, перегружая конструкции языка программирования. Вот несколько примеров…

Геттеры и сеттеры

Геттеры и сеттеры могут неявно вводить неожиданные важные побочные эффекты. Код, который выглядит так:

foo.bar = x;
y = foo.baz;

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

  • неожиданная мутация — получение или установка поля изменяет некоторые другие аспекты объекта. Например, обновляет ли установка person.fullName поля person.firstName и person.lastName?
  • отсутствие идемпотентности — многократное чтение одного и того же поля объекта не должно изменять его значение, но с геттером может. Часто даже удобнее иметь геттер nextId, чем возвращать увеличивающийся идентификатор или что-то в этом роде.
  • отсутствие симметрии — если вы записываете в поле, выходит ли одно и то же значение при немедленном чтении из него? Некоторые геттеры или сеттеры очищают данные — полезно, но неожиданно.
  • низкая производительность — установка поля в структуре — это едва ли не самая дешевая вещь, которую вы можете сделать в высокоуровневом коде. Вызов геттера или сеттера может сделать что угодно. Дорогостоящая проверка полей для сеттеров, дорогие вычисления для геттеров и, что еще хуже, чтение из базы данных или запись в нее — это неожиданное, но распространенное явление.

Геттеры и сеттеры действительно полезны для разработчиков API. Они позволяют нам представить нашим потребителям простой интерфейс, но они создают риск сокрытия деталей, которые повлияют на них или их пользователей.

Автоматическое управление памятью

Автоматическое управление памятью — один из больших шагов вперед для повышения производительности труда программиста. Управление памятью с помощью malloc и free сложно сделать правильно, часто неэффективно (поскольку мы ошибаемся в сторону простой предсказуемости) и является источником многих ошибок. Автоматическое управление памятью создает свои проблемы.

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

Поведение памяти API скрыто от вызывающих, поэтому неясно, какова будет их стоимость. Хуже того, в более странных системах автоматического управления памятью, таких как автоматический подсчет ссылок в современных версиях Objective-C, неясно, сохранят ли API объекты, переданные им или возвращенные из них — часто даже разработчикам API (например) .

МПК и РПЦ

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

Но…

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

При разработке API сделайте шаг назад и подумайте о том, как разработчики будут использовать ваш API. Делайте дешевые вещи простыми, а дорогие заведомо дорогими.

Первоначально опубликовано на сайте Ian McKellar.