1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-01-05 10:20:22 +02:00

style fix

This commit is contained in:
Sergey Konstantinov 2020-10-23 17:34:55 +03:00
parent 4070313067
commit 65153b205b

View File

@ -30,17 +30,21 @@
Иными словами, десятки, если не сотни, различных API должны отработать корректно для выполнения базовых действий типа просмотра web-страницы; без надёжной работы каждого из них современные информационные технологии попросту не могли бы существовать.
API — _это обязательство_. Формальное обязательство связывать между собой различные программируемые контексты.
**API — это обязательство**. Формальное обязательство связывать между собой различные программируемые контексты.
Когда меня просят привести пример хорошего API, я обычно показываю фотографию римского виадука:
* он связывает между собой две области
* обратная совместимость нарушена ноль раз за последние две тысячи лет.
Отличие римского виадука от хорошего API состоит лишь в том, что API предлагает _программный_ контракт. Для связывания двух областей необходимо написать некоторый _код_. Цель этой книги — помочь вам написать код, так же хорошо выполняющий свою задачу, как и римский виадук.
Отличие римского виадука от хорошего API состоит лишь в том, что API предлагает _программный_ контракт. Для связывания двух областей необходимо написать некоторый _код_. Цель этой книги — помочь вам разработать API, так же хорошо выполняющий свою задачу, как и римский виадук.
Виадук также хорошо иллюстрирует другую проблему разработки API: вашими пользователями являются инженеры. Вы не поставляете воду напрямую потребителю: к вашей инженерной мысли подключаются заказчики путём пристройки к ней каких-то своих инженерных конструкций. С одной стороны, вы можете обеспечить водой гораздо больше людей, нежели если бы вы сами подводили трубы к каждому крану. С другой — качество инженерных решений заказчика вы не может контролировать, и проблемы с водой, вызванные некомпетентностью подрядчика, неизбежно будут валить на вас.
Поэтому проектирование API налагает на вас несколько большую ответственность; API выступает как мультипликатором ваших возможностей, так и мультипликатором ваших ошибок.
Поэтому проектирование API налагает на вас несколько большую ответственность. **API является как мультипликатором ваших возможностей, так и мультипликатором ваших ошибок**.
### Замечание о терминологии
Разработка программного обеспечения характеризуется, помимо прочего, наличием множества различных парадигм разработки, адепты которых зачастую настроены весьма воинственно. Поэтому при написании этой книги мы намеренно избегаем слов «метод», «объект», «функция» и так далее, используя нейтральный термин «сущность». Под сущностью понимается некоторая атомарная единица функциональности — класс, метод, объект, нужное подчеркнуть.
## I. Проектирование API
@ -54,13 +58,13 @@ API — _это обязательство_. Формальное обязате
* API должно быть читаемым: в идеале разработчик, просто глядя в номенклатуру методов, сразу пишет правильный код, не углубляясь в детали (особенно — детали реализации!); немаловажно уточнить, что из интерфейсов объектов должно быть понятно не только решение задачи, но и возможные ошибки и исключения;
* API должно быть консистентно: при разработке новой функциональности, т.е. при обращении к каким-то незнакомым сущностям в API, разработчик может действовать по аналогии с уже известными ему концепциями API, и его код будет работать.
Однако статическое удобство и понятность API — это простая часть. В конце концов, никто не стремится специально сделать API нелогичным и нечитаемым — всегда при разработке мы начинаем с каких-то понятных концепций и пытаемся их развивать. При минимальном опыте проектирования сложно сделать ядро API, не удовлетворяющее критериям очевидности, читаемости и консистентности.
Однако статическое удобство и понятность API — это простая часть. В конце концов, никто не стремится специально сделать API нелогичным и нечитаемым — всегда при разработке мы начинаем с каких-то понятных базовых концепций. При минимальном опыте проектирования сложно сделать ядро API, не удовлетворяющее критериям очевидности, читаемости и консистентности.
Проблемы начинаются, когда мы начинаем API развивать. Добавление новой фунциональности рано или поздно приводит к тому, что некогда простое и понятное API становится наслоением разных концепций, а попытки сохранить обратную совместимость приводят к нелогичным, неочевидным и попросту плохим решениям. Отчасти это связано так же и с тем, что невозможно обладать полным знанием о будущем: ваше понимание о «правильном» API тоже будет меняться со временем, как в объективной части (какие задачи решает API и как лучше это сделать), так и в субъективной — что такое очевидность, читабельность и консистентность для вашего API.
Принципы, которые я буду излагать ниже, во многом ориентированы именно на то, чтобы API правильно развивалось во времени и не превращалось в нагромождение разнородных неконсистентных интерфейсов. Важно понимать, что такой подход тоже небесплатен: необходимость держать в голове варианты развития событий и закладывать возможность изменений в API означает избыточность интерфейсов и возможно излишнее абстрагирование. И то, и другое, помимо прочего, усложняет и работу программиста, пользующегося вашим API; усложнение API, закладывание перспектив «на будущее» имеет смысл, только если это будущее у API есть, иначе это просто оверинжиниринг.
Принципы, которые я буду излагать ниже, во многом ориентированы именно на то, чтобы API правильно развивалось во времени и не превращалось в нагромождение разнородных неконсистентных интерфейсов. Важно понимать, что такой подход тоже небесплатен: необходимость держать в голове варианты развития событий и закладывать возможность изменений в API означает избыточность интерфейсов и возможно излишнее абстрагирование. И то, и другое, помимо прочего, усложняет и работу программиста, пользующегося вашим API. **Закладывание перспектив «на будущее» имеет смысл, только если это будущее у API есть, иначе это попросту оверинжиниринг**.
#### Обратная совсместимость
#### Обратная совместимость
### 1. Пирамида контекстов API
@ -74,28 +78,28 @@ API — _это обязательство_. Формальное обязате
Может показаться, что наиболее полезные советы и best practice приведены в последнем разделе, однако это не так; цена ошибки, допущенной на разных уровнях весьма различна. Если исправить плохое именование довольно просто, то исправить неверное понимание того, зачем вообще нужно API, практически невозможно.
**NB**. Здесь и далее мы будем рассматривать концепции разработки API на примере некоторого гипотетического API заказа кофе в городских кофейнях. На всякий случай сразу уточним, что пример является синтетическим; в реальной ситуации, если бы такое API пришлось проектировать, оно вероятно было бы совсем не похоже на наш выдуманный пример.
_NB_. Здесь и далее мы будем рассматривать концепции разработки API на примере некоторого гипотетического API заказа кофе в городских кофейнях. На всякий случай сразу уточним, что пример является синтетическим; в реальной ситуации, если бы такое API пришлось проектировать, оно вероятно было бы совсем не похоже на наш выдуманный пример.
### Определение области применения
Ключевой вопрос, который вы должны задать себе четыре раза, выглядит так: какую проблему мы решаем? Задать его следует четыре раза с ударением на каждом из четырёх слов.
1. _Какую_ проблему мы решаем? Можем ли мы чётко описать, в какой ситуации гипотетическим потребителям-разработчикам нужно наше API?
1. _Какую_ проблему мы решаем? Можем ли мы чётко описать, в какой ситуации гипотетическим потребителям-разработчикам нужно наше API?
2. Какую _проблему_ мы решаем? А мы правда уверены, что описанная выше ситуация — проблема? Действительно ли кто-то готов платить (в прямом и переносном смысле) за то, что ситуация будет как-то автоматизирована?
2. Какую _проблему_ мы решаем? А мы правда уверены, что описанная выше ситуация — проблема? Действительно ли кто-то готов платить (в прямом и переносном смысле) за то, что ситуация будет как-то автоматизирована?
3. Какую проблему _мы_ решаем? Действительно ли решение этой проблемы находится в нашей компетенции? Действительно ли мы находимся в той позиции, чтобы решить эту проблему?
3. Какую проблему _мы_ решаем? Действительно ли решение этой проблемы находится в нашей компетенции? Действительно ли мы находимся в той позиции, чтобы решить эту проблему?
4. Какую проблему мы _решаем_? Правда ли, что решение, которое мы предлагаем, действильно решает проблему? Не создаём ли мы на её месте другую проблему, более сложную?
4. Какую проблему мы _решаем_? Правда ли, что решение, которое мы предлагаем, действильно решает проблему? Не создаём ли мы на её месте другую проблему, более сложную?
Итак, предположим, что мы хотим предоставить API автоматического заказа кофе в городских кофейнях. Попробуем применить к ней этот принцип.
1. Зачем кому-то может потребоваться API для приготовления кофе? В чем неудобство заказа кофе через интерфейс, человек-человек или человек-машина? Зачем нужна возможность заказа машина-машина?
* Возможно, мы хотим решить проблему выбора и знания? Чтобы человек наиболее полно знал о доступных ему здесь и сейчас опциях.
* Возможно, мы оптимизируем время ожидания? Чтобы человеку не пришлось ждать, пока его заказ готовится.
* Возможно, мы хотим минимизировать ошибки? Чтобы человек получил именно то, что хотел заказть, не потеряв информацию при разговорном общении либо при настройке незнакомого интерфейса кофе-машины.
1. Зачем кому-то может потребоваться API для приготовления кофе? В чем неудобство заказа кофе через интерфейс, человек-человек или человек-машина? Зачем нужна возможность заказа машина-машина?
* Возможно, мы хотим решить проблему выбора и знания? Чтобы человек наиболее полно знал о доступных ему здесь и сейчас опциях.
* Возможно, мы оптимизируем время ожидания? Чтобы человеку не пришлось ждать, пока его заказ готовится.
* Возможно, мы хотим минимизировать ошибки? Чтобы человек получил именно то, что хотел заказть, не потеряв информацию при разговорном общении либо при настройке незнакомого интерфейса кофе-машины.
Вопрос «зачем» — самый важный из тех вопросов, которые вы должны задавать себе. Не только глобально в отношении целей всего проекта, но и локально в отношении каждого кусочка функциональности. Если вы не можете коротко и понятно ответить на вопрос «зачем вот эта сущность нужна» — значит, она не нужна.
Вопрос «зачем» — самый важный из тех вопросов, которые вы должны задавать себе. Не только глобально в отношении целей всего проекта, но и локально в отношении каждого кусочка функциональности. **Если вы не можете коротко и понятно ответить на вопрос «зачем эта сущность нужна» — значит, она не нужна**.
Здесь и далее предположим (в целях придания нашему примеру глубины и некоторой упоротости), что мы оптимизируем все три фактора в порядке убывания важности.
@ -165,7 +169,7 @@ API — _это обязательство_. Формальное обязате
1. Для решения задачи «заказать лунго» разработчику нужно обратиться к сущности «рецепт» и выяснить, что у каждого рецепта есть объём. Далее, нужно принять концепцию, что приготовление кофе заканчивается в тот момент, когда объём сравнялся с эталонным. Нет никакого способа об этой конвенции догадаться: она неочевидна и её нужно найти в документации. При этом никакой пользы для разработчика в этом знании нет.
2. Мы автоматически лишаем себя возможности варьировать объём кофе. Нам придётся или заводить ложные рецепты типа «большой лунго, средний лунго, маленький лунго», либо вводить ещё одну неявную конвенцию: если при создании заказа указать объём кофе, то готовность надо определять именно по нему. Обратите внимание, что тем самым при введении концепции объёма кофе в заказе разработчику так же надо будет изменить код функции, определяющей готовность кофе — а это уже совсем контринтуитивно, и обязательно приведёт к проблемам в будущем.
2. Мы автоматически лишаем себя возможности варьировать объём кофе. Нам придётся или заводить ложные рецепты типа «большой лунго», «средний лунго», «маленький лунго», либо вводить ещё одну неявную конвенцию: если при создании заказа указать объём кофе, то готовность надо определять именно по нему. Обратите внимание, что тем самым при введении концепции объёма кофе в заказе разработчику так же надо будет изменить код функции, определяющей готовность кофе — а это уже совсем контринтуитивно, и обязательно приведёт к проблемам в будущем.
3. Вся эта схема полностью неработоспособна, если разные модели кофе-машин производят лунго разного объёма. Для решения задачи «объём лунго зависит от вида машины» нам придётся сделать совсем неприятную вещь: сделать рецепт зависимым от id машины. Тем самым мы начнём активно смешивать уровни абстракции: одной частью нашего API (рецептов) станет невозможно пользоваться без другой части (информации о кофе-машинах). Если разработчик хочет сделать какое-то абстрактное приложение про кофе (без его приготовления) — у него не получится это сделать.
@ -207,7 +211,7 @@ API — _это обязательство_. Формальное обязате
Дублирование функций на каждом уровне абстракций позволяет добиться важной вещи: возможности сменить нижележащие уровни без необходимости переписывать верхнеуровневый код. Мы можем добавить другие виды кофе-машин с принципиально другими физическими способами определения готовности напитка, и наш метод `GET /orders?order_id={id}` продолжит работать, как работал.
Да, код, который работал с физическим уровнем, придётся переписать. Но, во-первых, это неизбежно: изменение принципов работы физического уровня стэка автоматически означает необходимость переписать код. Во-вторых, такое разделение ставит перед нами четкий вопрос: до какого момента API должно предоставлять публичный доступ? Стоило ли предоставлять пользователю методы физического уровня? В-третьих, грамотное проектирование интерфейсов помогает избежать и этой проблемы, о чем будет подробнее рассказано ниже.
Да, код, который работал с физическим уровнем, придётся переписать. Но, во-первых, это неизбежно: изменение принципов работы физического уровня автоматически означает необходимость переписать код. Во-вторых, такое разделение ставит перед нами четкий вопрос: до какого момента API должно предоставлять публичный доступ? Стоило ли предоставлять пользователю методы физического уровня? В-третьих, грамотное проектирование интерфейсов помогает избежать и этой проблемы, о чем будет подробнее рассказано ниже.
### Разграничение областей ответственности