<p>Если мы посмотрим на определение из энциклопедии, то узнаем, что программирование — это что-то вроде преобразования формально проставленной задачи в исполняемый код, как правило, путём имплементации какого-то алгоритма на определённом языке программирования. И действительно, курсы обучения соответствующим дисциплинам большей частью и состоят из инструкций, как перевести словесно поставленную задачу в код и запрограммировать тот или иной алгоритм.</p>
<p>Этот алгоритмический подход — пожалуй, худшее, что могло случиться с программистами. Определять программирование через имплементацию алгоритмов — это как определять работу композитора как написание специальных значков на нотном стане, математика — как написание формул, астронома — как смотрение в телескоп.</p>
<p>Разумеется, написание кода — неотъемлемая часть процесса программирования. Но позвольте мне высказаться резко: это наименее важная и наиболее простая часть работы программиста. К сожалению, реальность такова, что множество людей, так или иначе связанных с программированием — включая самих программистов и их непосредственное руководство, — этого не понимают. Результат — огромное количество откровенно плохих, неудобных, небезопасных и дырявых как решето приложений, которые окружают человека в XXI веке.</p>
<h3>Об уровнях восприятия</h3>
<p>Программирование как трансляция формальной задачи с человеческого языка на машинный — это лишь первый (скорее даже, нулевой) уровень восприятия процесса разработки. Нет алгоритма, который был бы как остров, сам по себе; каждый алгоритм откуда-то берётся и для чего-то предназначен. Иными словами, существует некоторый контекст, в котором будет работать этот небольшой фрагмент программного кода. В первую очередь таким контекстом является программный продукт целиком, в составе которого имплементируется алгоритм.</p>
<p>Чем больше, масштабнее и сложнее программный продукт, тем менее важна собственно алгоритмизация конкретной подзадачи и тем более важна архитектура продукта в целом. Правильно ли произведена декомпозиция? Будет ли этот код понятен другому разработчику через несколько лет? Можно ли потом эффективно расширять и поддерживать этот код?</p>
<p>Это следующий уровень восприятия проекта: понимание <em>контекста</em> существования конкретного фрагмента кода. На этом уровне появляется понимание того, что интерфейс должен быть сначала спроектирован, и что с ним надо «переспать», т.е. всесторонне обдумать; что «переключение контекстов» — дорогая операция, и что браться за другую задачу в другом проекте, на самом деле, очень сложно, даже если требуется написать всего 10 строчек кода.</p>
<p>Но и этот уровень не последний. Помимо контекст продукта существует также и ещё более широкий контекст — внешний, в котором живёт сам продукт. Связи внешнего мира с внутренним миром разработки широки и разнообразны: внешний мир определяет функциональность продукта, внешний мир диктует свои правила. Помимо прямой связи существует и обратная: кто-то во внешнем мире будет пользоваться вашим продуктом и, возможно, вашим программным кодом — т.е. вы сами привносите нечто во внешний контекст, и это «нечто» какое-то время будет там жить.</p>
<p><em>Существуют ли иные, более масштабные уровни восприятия — наверняка хочет поинтересоваться пытливый читатель. Мне, во всяком случае, о них неизвестно; если что, я напишу о них в следующем, исправленном и дополненном, издании.</em></p>
<p>Свою задачу как автора этой книги я вижу в том, чтобы помочь вам мыслить на более высоких уровнях понимания. «При чём же здесь тогда API?» — наверняка удивится пытливый читатель.</p>
<p>При том, что API это ничто иное, как средство связи контекстов. Например, когда вы реализуете какой-то алгоритм — вы используете API языка программирования. Компилятор языка программирования, в свою очередь, использует API команд процессора. Это API, в случае CISC-процессоров, в свою очередь построено поверх ещё более низкоуровнего API микрокоманд; и так далее. Только представьте себе, какое огромное количество API разных уровней должно отработать — и отработать правильно! — для того, чтобы вы смогли, скажем, увидеть страничку в браузере: тут и весь сетевой стек с его 6-7 уровнями абстракции; и серверное ПО — веб-сервер, генерация страницы, база данных, кэш; и клиентское ПО — браузер — имплементирующее буквально десятки разных API.</p>
<p>В современном мире роль API трудно преувеличить, и она продолжает расти. Всё новые и новые устройства — от часов и розеток до кораблей и ветряных турбин — включаются в «игру контекстов» и обзаводятся своим API. При этом, к сожалению, качество этих API всё больше оставляет желать лучшего.</p>
<p>Именно эти соображения и побудили меня написать эту книгу. Разработка хорошего API требует, во-первых, определённого системного мышления; а, во-вторых, понимания того, что API — это не просто какой-то командный код, но и большая ответственность. Я надеюсь, что с помощью этой книги окружающие меня API станут чуть лучше.</p>
<h3>Об относительности критериев качества</h3>
<p>Несмотря на всё написанное выше, я вовсе не призываю вас подходить к каждой задаче глобально; чрезмерное переусложнение ничуть не лучше чрезмерного упрощения.</p>
<p>У каждого инструмента, включая и API, есть своя область применимости, и это важно понимать. Допустим, вы пишете, скажем, скрипт импорта данных из Википедии для того, чтобы залить в базу данных вашего продукта какие-то тестовые данные, и вы собираетесь воспользоваться им один раз. В этом случае, очевидно, совершенно неважно, правильная ли у него архитектура и понятен ли его API. Нет, разумеется, вы можете спроектировать и реализовать этот скрипт «правильно», гибко и универсально, чтобы вы могли им гордиться. Но зачем?</p>
<p>Любая красота сложна. Правильная архитектура требует больших затрат времени и сил, часто — неоднократного полного переписывания базовых модулей. Правильное API требует времени и сил ещё больше.</p>
<p>Понимать, где ваши усилия полезнее и нужнее — пожалуй, не менее важно, чем их (усилий) приложение.</p>
<p>Этот принцип, в общем и целом, относится не только к архитектуре и красоте кода, но и ко многим другим вещам. Применение любого паттерна, любого принципа разработки имеет свою стоимость — и, к сожалению, мало кто всерьёз берётся анализировать эту стоимость до начала разработки.</p>
<p>Например, бессмысленно примернять MVC-подход к вашему скрипту. Нет, конечно, можно, но зачем? Накладные расходы на описание моделей и представлений, будут разменены на что?</p>
<p>Бессмысленно пытаться внедрять и agile или waterfall практики. Зачем и для чего нужны переоценки и анализы требований для скрипта, который один раз отработает и будет забыт?</p>
<p>Более того, применение даже таких базовых принципов, как, скажем, ООП для этой задачи вовсе не выглядит обоснованным.</p>
<p>Эти вещи кажутся очевидными на простых примерах; со сложными же проектами, поверьте, то же самое. Достоинства и недостатки любой методологии, как правило, отлично известны, однако редко когда выбор конкретного фреймворка, паттерна или подхода опирается на анализ соответствия их достоинств и недостатков проекту.</p>
<p>Есть ещё один момент, который хочется подчеркнуть: инструменты должны соответствовать конкретным обстоятельствам — предметной области, планируемому жизненному циклу продукта и, что немаловажно, компетенциям команды. Во многих случаях неидеальный инструмент, с которым команда привыкла работать, предпочтительнее неидеального, который придётся осваивать по ходу.</p>
<h3>API как универсальный паттерн</h3>
<p>На фоне сказанного выше этот заголовок должен вызывать некоторые сомнения. Универсального паттерна не существует, разве нет?</p>
<p>Тем не менее, API таковым паттерном является. Чтобы пояснить этот парадокс, давайте для начала поймём, что такое API.</p>
<p>В первом разделе мы говорили о том, что API — это связь, интерфейс взаимодействия двух программируемых контекстов. Вспомним, что мы живём в мире, очень сильно завязанном на правильное взаимодействие целых иерархий таких контекстов. Что же тогда самое важное, что такое API по смыслу?</p>
<p>API — это обязательство. Обязательство поддерживать связь контекстов. Всё остальное вторично.</p>
<p>Поэтому любой сложный проект требует как минимум внутреннего API. Невозможно разработать что-то сложнее скрипта импорта данных, исполняемого один раз, без предоставления некоторых обязательств.</p>
<p>Надо понимать, что ничего бесплатного не существует. Написание продукта как API требует существенно больших затрат, и, разумеется, для многих проектов эти затраты никогда не окупятся. С другой стороны, и преуменьшать ценность API не стоит: поддержание взятых на себя обязательств — гигантское конкурентное преимущество в современном мире.</p>
<p>Решение «API-зации» того или иного функционала — также всецело вопрос баланса; это всегда размен времени и сил сейчас на стабильный и поддерживаемый код в будущем.</p>
<p>При этом, надо сразу оговорить, что наличие API вовсе не гарантирует отсутствие ошибок в коде (или хотя бы меньшее их количество); код, написанный как API, при прочих равных будет скорее содержать больше ошибок вследствие избыточности интерфейсов. Основная цель написания API — добиться стабильной, поддерживаемой и расширяемой архитектуры, которая способна покрыть все потребности пользователя.</p>
<h3>Более чем код</h3>
<p>Итак, API — это механизм связывания программируемых контекстов и обязательство поддерживать эту связь. Из чего же состоит это связывание и эта поддержка?</p>
<p>Понятно, что API имеет некоторую программную составляющую, иначе зачем бы оно было кому-то нужно. Но, исходя из вышесказанного, очевидно, что сам программный интерфейс — это далеко не всё.</p>
<p>Прежде всего, API обязано обладать некоторой гарантированной стабильностью. Это — ключевое свойство, без него API лишено смысла.</p>
<p>Во-вторых, API должно быть понятно пользователям. Это касается как архитектуры интерфейсов, т.е. номенклатуры программных сущностей, так и публичной документации. API без документации не существует так же, как и без программного кода.</p>
<p>В-третьих, помимо документации, должна осуществляться поддержка пользователей: решение их проблем и обратная связь. Идеальных API не существует, всегда есть какой-то поток вопросов и проблем.</p>
<p>И то, и другое, и третье кажется, на первый взгляд, понятным. Однако, когда теория сталкивается с практикой, часто оказывается, что разработчики API уделяют непростительно мало внимания этим проблемам.</p>
<h2>О проектировании API</h2>
<h3>Целесообразность</h3>
<p>Если попытаться одной фразой описать основной принцип проектирования API, то можно ограничиться отним словом «зачем» и вопросительным знаком.</p>
<p>Попробуем объяснить на конкретном примере. Допустим, мы располагаем огромным и хорошо структурированным сервисом, предоставляющем метаинформацию, скажем, о . (Лично я никогда не делал подобных сервисов, но уже много лет испытываю в них нужду.)</p>
<p><em>Зачем</em> такому сервису API?</p>
<p>Чтобы люди могли написать свои новые-кленовые аудиоплееры, использующие ваш сервис? Вряд ли.</p>
- решать проблемы
- не делать конкурентов самому себе (если ты не амазон)
- множество разнородных задач:
- сделать хорошо конечному пользователю
- сделать хорошо разработчику
<h3>О неважности кода</h3>
- в правильном апи любой код заменяем и он неважен
<h3>Уровни абстракции</h3>
<p>Концепция уровней абстракции довольно широко известна. Классическим примером разделения уровней является, скажем, модель OSI, описывающая 7 уровней сетевых взаимодействий.</p>
<p>К сожалению, в реальных системах выделить уровни абстракции гораздо сложнее, нежели в таких чистых случаях; множество успешных и существующих API и вовсе не оперируют такими терминами. Классический случай — веб-платформа, в которой правильное разделение абстракций фактически никогда не существовало, и лишь Extensible Web Manifesto впервые постулировал необходимость не экстенсивно наращивать номенклатуру методов, а строить иерархию абстракций.</p>
<p>Связано это, по-видимому, со сложностью подобного разделения в абсолютном большинстве предметных областей. Классический алгоритм «берём все объекты и разбиваем их на слои» в реальном мире работает очень слабо: всегда есть сущности, которые не ложатся в иерархию, будучи либо чересчур глобальными, либо, напротив, чересчур утилитарными.</p>
<p>Тем не менее, говорить об иерархии абстракций можно и нужно.</p>
<h4>Иерархия кейсов</h4>
<p>Самый простой из критериев деления уровней абстракции — это отделение сущностей, работающих в терминах, понятных конечному пользователю, от сущностей, понятных лишь разработчику. Такое дерево объектов можно построить даже для столь неструктурированной платформы, как веб: высший уровень абстракции реализуют те объекты, которые напрямую взаимодействуют с пользователем — скажем, интерфейс HTMLAudioElement. Методы, как play(), pause(), свойство controls — напрямую соответствуют конкретным кейсам использования сущности пользователем. Напротив, базовые сущности низкоуровневого WebAudio API — аудио буферы и потоки данных — для непрограммиста никакого интереса не представляют.</p>
<p>«Абстрактность» при таком подходе означает иерархию кейсов: есть набор кейсов пользователя (проиграть видео, поставить на паузу, прибавить громкость), которые покрываются сущностями верхнего уровня; однако верхний уровень иерархии, таким образом, сам предъявляет некоторый набор кейсов: обеспечить воспроизведение из некоторого источника, обеспечить микширование данных, обеспечить синхронизацию, встроить плеер в определённое место на странице — которым будет удовлетворять следующий «слой» иерархии — аудиоконтексты в WebAudio API и методы работы с DOM-деревом. Процесс, таким образом, можно повторять итеративно вплоть до «железа».</p>
<p>Что важно, этот подход работает не только для API, предоставляющих некоторые интерфейсы взаимодействия с конечным пользователем. Даже API доступа к базе данных, таким образом, хорошо разбивается на уровни абстракции, исходя из кейсов использования API.</p>
<h4>Иерархия знаний</h4>
<p>Уровни абстракций можно выделять и снизу вверх: самыми низкоуровневыми сущностями объявляются те, которые работают в терминах нижележащей платформы. Интерфейсы, оперирующие байтами, буферами и флагами — самые примитивные. Чем более специфичный, т.е. требующий знаний об устройстве платформы, код требуется написать — тем ниже уровень абстракции. Напротив, объекты, интерфейс которых не содержит никаких знаний о среде исполнения, образуют верхний уровень. В том же примере с аудио в вебе: сущности, работающие напрямую с «сырыми» данными — аудио буферы — низкоуровневые. Сущности, оперирующие буферами — различные операции над звуком — стоят чуть выше: чтобы работать с ними, уже не нужно знать о разрядности или частоте дискретизации. Ещё чуть выше — функциональность группировки операций и аудио контексты; и так далее. На вершине пирамиды — HTMLAudioElement, интерфейс которого не содержит никаких знаний и легко может быть быть применён не к воспроизведению звука в вебе, а, скажем, к API патефона, если кто-то вдруг задумает его сделать.</p>
<p>Иными словами, тот объект выше, интерфейс которого не поменяется, если вся нижележащая логика полностью изменится вплоть до смены платформы и физических принципов работы.</p>
<p>Если говорить и чисто программной составляющей API, то, пожалуй, именно именование важнее всего. Всё остальное, в конечном счёте, можно подправить или переписать, но вот номенклатура сущностей — это лицо вашего API, это фасад, с которым будет работать ваш пользователь.</p>
<p>Удивительно, но заставить разработчиков называть сущности правильно — гораздо более сложная задача, чем кажется.</p>
— делать именно то, что нужно
— сайдэффекты
- build и update
<h3>Тестирование</h3>
<p>Тесты обязаны быть в любом API; это, в общем, вытекает из определения API.</p>
<p>Вместе с тем, </p>
— покрытие в зависимости от проекта
<h3>Workflow</h3>
— эджайл vs здравый смысл
<h3>Версионирование</h3>
- зачем?
- semver
<h3>Уровни абстракции</h3>
- как?
- подход "от железа"
<h3>Интерфейсы</h3>
- зачем?
- 5 плюс минус 2
- объект в разных разрезах
<h3>Связность</h3>
<h4>Code reuse</h4>
- хороший копипаст
- плохой копипаст
<h4>Модульность</h4>
<h4>События</h4>
<h4>Паттерны</h4>
<h2>О видении продукта</h2>
<h3>Стабильность</h3>
<h3>Точки расширения</h3>
<h3>Внешние зависимости</h3>
<h3>Управление процессом</h3>
<p>Дело в том, что разработчик API, по определению, обладает относительно полным знанием о том, как API устроено, какие в нём есть крутые фишки и какие сложные задачи оно способно решать.</p>
<p>В реальности же 90%, если не больше, обращающихся за поддержкой пользователей будут решать совершенно тривиальные задачи и у них в принципе нет никакой мотивации разбираться с внутренним устройством API. Пользователь хочет решить свою задачу с наименьшим напряжением сил, и было бы странным его за это упрекать.</p>
<p>Люди не читают документацию. Не ищут ответов на свои вопросы на StackOverflow. Не являются гуру разработки. Даже напротив, абсолютное большинство ваших клиентов — начинающие и просто малоквалифицированные специалисты. Даже если вашим API пользуются ребята из соседнего отдела, квалификация которых не уступает вашей, — вряд ли даже у них есть желание глубоко разбираться в вашей архитектуре.</p>
<p>Поэтому любой поток обращений, как правило, делится на две неравные категории: вопросы от начинающих (часто глупые или тривиальные) и очень редкие запросы от профессионалов. Запросов первого типа абсолютное большинство, и, при том, найти проблему в коде пользователя частенько бывает весьма нетривиальной задачей.</p>