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

Описание конечных интерфейсов - продолжение

This commit is contained in:
Sergey Konstantinov 2020-11-11 15:42:04 +03:00
parent 57b5e35e48
commit af53dab172
4 changed files with 215 additions and 82 deletions

View File

@ -124,7 +124,8 @@ h4, h5 {
<p>Выражения «мажорная версия API» и «версия API, содержащая обратно несовместимые изменения функциональности» тем самым следует считать эквивалентными.</p><div class="page-break"></div><h3 id="6">Глава 6. Условные обозначения и терминология</h3>
<p>Разработка программного обеспечения характеризуется, помимо прочего, существованием множества различных парадигм разработки, адепты которых зачастую настроены весьма воинственно по отношению к адептам других парадигм. Поэтому при написании этой книги мы намеренно избегаем слов «метод», «объект», «функция» и так далее, используя нейтральный термин «сущность». Под «сущностью» понимается некоторая атомарная единица функциональности — класс, метод, объект, монада, прототип (нужное подчеркнуть).</p>
<p>Для составных частей сущности, к сожалению, достаточно нейтрального термина нам придумать не удалось, поэтому мы используем слова «поля» и «методы».</p>
<p>Большинство примеров API в общих разделах будут даны в виде JSON-over-HTTP-эндпойтов. Это некоторая условность, которая помогает описать концепции, как нам кажется, максимально понятно. Вместо <code>GET /orders</code> вполне может быть вызов метода <code>orders.get()</code>, локальный или удалённый; вместо JSON может быть любой другой формат данных. Смысл утверждений от этого не меняется.</p><div class="page-break"></div><h2>I. Проектирование API</h2><h3 id="7api">Глава 7. Пирамида контекстов API</h3>
<p>Большинство примеров API в общих разделах будут даны в виде JSON-over-HTTP-эндпойтов. Это некоторая условность, которая помогает описать концепции, как нам кажется, максимально понятно. Вместо <code>GET /orders</code> вполне может быть вызов метода <code>orders.get()</code>, локальный или удалённый; вместо JSON может быть любой другой формат данных. Смысл утверждений от этого не меняется.</p>
<p>Также в примерах часто применяется следующая конвенция. Запись <code>{ "begin_date" }</code> (т.е. отсутствие значения у поля в JSON-объекте) означает, что в поле находится именно то, что ожидается — т.е. в данном примере какая-то дата начала.</p><div class="page-break"></div><h2>I. Проектирование API</h2><h3 id="7api">Глава 7. Пирамида контекстов API</h3>
<p>Подход, который мы используем для проектирования, состоит из четырёх шагов:</p>
<ul>
<li>определение области применения;</li>
@ -336,54 +337,107 @@ h4, h5 {
<p>// TODO: простые вещи делаются просто без бойлерплейта</p><div class="page-break"></div><h3 id="11">Глава 11. Описание конечных интерфейсов</h3>
<p>Определив все сущности, их ответственность и отношения друг с другом, мы переходим непосредственно к разработке API: нам осталось прописать номенклатуру всех объектов, полей, методов и функций в деталях. В этой главе мы дадим сугубо практические советы, как сделать API удобным и понятным.</p>
<p>Важное уточнение под номером ноль:</p>
<ol start="0">
<li>Правила — это всего лишь обобщения. Они не действуют безусловно и не означают, что можно не думать головой. У каждого правила есть какая-то рациональная причина его существования. Если в вашей ситуации нет причин следовать правилу — значит, следовать ему не надо.<ul>
<li>например, требование консистентности номенклатуры существует затем, чтобы разработчик тратил меньше времени на чтение документации; если вам <em>необходимо</em>, чтобы разработчик обязательно прочитал документацию по какому-то методу, вполне разумно сделать его сигнатуру нарочито неконсистентно;</li>
<li>важно понимать, что вы вольны вводить свои собственные конвенции. Например, в некоторых фреймворках сознательно отказываются от парных методов <code>set_entity</code> / <code>get_entity</code> в пользу одного метода <code>entity</code> с опциональным параметром. Важно только проявить последовательность в её применении — если такая конвенция вводится, то абсолютно все методы API должны иметь подобную полиморфную сигнатуру, или по крайней мере должен существовать принцип именования, отличающий такие комбинированные методы от обычных вызовов.</li></ul></li>
</ol>
<h4 id="0">0. Правила — это всего лишь обобщения</h4>
<p>Правила не действуют безусловно и не означают, что можно не думать головой. У каждого правила есть какая-то рациональная причина его существования. Если в вашей ситуации нет причин следовать правилу — значит, следовать ему не надо.</p>
<p>Например, требование консистентности номенклатуры существует затем, чтобы разработчик тратил меньше времени на чтение документации; если вам <em>необходимо</em>, чтобы разработчик обязательно прочитал документацию по какому-то методу, вполне разумно сделать его сигнатуру нарочито неконсистентно.</p>
<p>Это соображение применимо ко всем принципам ниже. Если из-за следования правилам у вас получается неудобное, громоздкое, неочевидное API — это повод пересмотреть правила (или API).</p>
<ol>
<li><p>Явное лучше неявного. Из названия любой сущности должно быть очевидно, что она делает и к каким сайд-эффектам может привести её использование.</p>
<p>Важно понимать, что вы вольны вводить свои собственные конвенции. Например, в некоторых фреймворках сознательно отказываются от парных методов <code>set_entity</code> / <code>get_entity</code> в пользу одного метода <code>entity</code> с опциональным параметром. Важно только проявить последовательность в её применении — если такая конвенция вводится, то абсолютно все методы API должны иметь подобную полиморфную сигнатуру, или по крайней мере должен существовать принцип именования, отличающий такие комбинированные методы от обычных вызовов.</p>
<h4 id="1">1. Явное лучше неявного</h4>
<p>Из названия любой сущности должно быть очевидно, что она делает и к каким сайд-эффектам может привести её использование.</p>
<ul>
<li>плохо: <code>GET /orders/cancellation</code> отменяет заказ<br />
— неочевидно, что достаточно просто обращения к сущности <code>cancellation</code> (что это?), тем более немодифицирующим методом <code>GET</code>, чтобы отменить заказ;<br />
хорошо: <code>POST /orders/cancel</code>;</li>
<li>плохо: <code>GET /orders/statistics</code> агрегирует статистику заказов за всё время<br />
— даже если операция немодифицирующая, но вычислительно дорогая — следует об этом явно индицировать, особенно если вычислительные ресурсы тарифицируются для пользователя; тем более не стоит подбирать значения по умолчанию так, чтобы операция без параметров максимально расходовала ресурсы;<br />
хорошо: <code>POST /orders/statistics/aggregate</code> с обязательным указанием периода агрегации в запросе.</li></ul></li>
<li><p>Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно:</p>
<li><p>плохо: </p>
<pre><code>GET /orders/cancellation
отменяет заказ
</code></pre>
<p>Неочевидно, что достаточно просто обращения к сущности <code>cancellation</code> (что это?), тем более немодифицирующим методом <code>GET</code>, чтобы отменить заказ; </p>
<p>Хорошо: </p>
<pre><code> POST /orders/cancel
отменяет заказ
</code></pre></li>
<li><p>плохо:</p>
<pre><code>GET /orders/statistics
Возвращает агрегированную статистику заказов за всё время
</code></pre>
<p>Даже если операция немодифицирующая, но вычислительно дорогая — следует об этом явно индицировать, особенно если вычислительные ресурсы тарифицируются для пользователя; тем более не стоит подбирать значения по умолчанию так, чтобы вызов операции без параметров максимально расходовал ресурсы.</p>
<p>Хорошо:</p>
<pre><code>POST /orders/statistics/aggregate
{ "start_date", "end_date" }
Возвращает агрегированную статистику заказов за указанный период
</code></pre></li>
</ul>
<p><strong>Стремитесь к тому, чтобы из сигнатуры функции было абсолютно ясно, что она делает, что принимает на вход и что возвращает</strong>. Вообще, при прочтении кода, работающего с вашим API, должно быть сразу понятно, что, собственно, он делает — без подглядывания в документацию.</p>
<h4 id="2">2. Сущности должны именоваться конкретно</h4>
<p>Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно:</p>
<ul>
<li>плохо: <code>user.get()</code>
— неочевидно, что конкретно будет возвращено;<br />
хорошо: user.<code>get_id()</code>;</li></ul></li>
<li><p>Не экономьте буквы. В XXI веке давно уже нет нужды называть переменные покороче:</p>
<li>плохо: <code>user.get()</code> — неочевидно, что конкретно будет возвращено;<br />
хорошо: <code>user.get_id()</code>;</li>
</ul>
<h4 id="3">3. Не экономьте буквы</h4>
<p>В XXI веке давно уже нет нужды называть переменные покороче.</p>
<ul>
<li>плохо: <code>order.time()</code><br />
— неясно, о каком времени идёт речь: время создания заказа, время готовности заказа, время ожидания заказа?…<br />
<li>плохо: <code>order.time()</code> — неясно, о каком времени идёт речь: время создания заказа, время готовности заказа, время ожидания заказа?…<br />
хорошо: <code>order.get_estimated_delivery_time()</code></li>
<li>плохо: <code>strpbrk</code> ищет вхождение любого из списка символов в строке<br />
— возможно, автору этого API казалось, что аббревиатура <code>pbrk</code> что-то значит для читателя, но он явно ошибся;<br />
хорошо: <code>string_search_for_characters</code>; однако необходимость существования такого метода вообще вызывает сомнения, достаточно было бы иметь удобную функцию поиска подстроки с нужными параметрами.</li></ul></li>
<li><p>Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — <code>objects</code>, <code>children</code>; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений:</p>
<li>плохо:
<code>
strpbrk (str1, str2)
возвращает положение первого вхождения в строку str2 любого символа из строки str2
</code>
Возможно, автору этого API казалось, что аббревиатура <code>pbrk</code> что-то значит для читателя, но он явно ошибся. К тому же, невозможно сходу понять, какая из строк <code>str1</code>, <code>str2</code> является набором символов для поиска.
Хорошо:
<code>
str_search_for_characters(lookup_character_set, str)
</code>
Однако необходимость существования такого метода вообще вызывает сомнения, достаточно было бы иметь удобную функцию поиска подстроки с нужными параметрами. Аналогично сокращение <code>string</code> до <code>str</code> выглядит совершенно бессмысленным, но, увы, является устоявшимся для большого количества предметных областей.</li>
</ul>
<h4 id="4">4. Тип поля должен быть ясен из его названия</h4>
<p>Если поле называется <code>recipe</code> — мы ожидаем, что его значением является сущность типа <code>Recipe</code>. Если поле называется <code>recipe_id</code> — мы ожидаем, что его значением является идентификатор, который я могу найти в составе сущности <code>Recipe</code>.</p>
<p>Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — <code>objects</code>, <code>children</code>; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений:</p>
<ul>
<li>плохо: <code>GET /news</code>
— неясно, будет ли получена какая-то конкретная новость или массив новостей;
хорошо: <code>GET /news-list</code>.</li></ul></li>
<li><p>Аналогично, если ожидается булево значение, то из названия это должно быть очевидно, т.е. именование должно описывать некоторое качественное состояние, например, <code>is_ready</code>, <code>open_now</code>:</p>
<li>плохо: <code>GET /news</code> — неясно, будет ли получена какая-то конкретная новость или массив новостей;
хорошо: <code>GET /news-list</code>.</li>
</ul>
<p>Аналогично, если ожидается булево значение, то из названия это должно быть очевидно, т.е. именование должно описывать некоторое качественное состояние, например, <code>is_ready</code>, <code>open_now</code>:</p>
<ul>
<li>плохо: <code>"task.status": true</code>
— неочевидно, что статус бинарен, плюс такое API будет нерасширяемым<br />
хорошо: <code>"task.is_finished": true</code></li></ul></li>
<li><p>Сущности, выполняющие подобные функции, должны называться подобно и вести себя подобным образом.</p>
<li>плохо: <code>"task.status": true</code> — неочевидно, что статус бинарен, плюс такое API будет нерасширяемым;<br />
хорошо: <code>"task.is_finished": true</code></li>
</ul>
<p>Отдельно следует оговорить, что на разных платформах эти правила следует дополнить по-своему с учетом специфики first-class citizen-типов. Например, объекты типа <code>Date</code>, если таковые имеются, разумно индицировать с помощью, например, постфикса <code>_at</code> (<code>created_at</code>, <code>occurred_at</code>, etc).</p>
<h4 id="5">5. Подобные сущности должны называться подобно и вести себя подобным образом</h4>
<ul>
<li>плохо: <code>begin_transition</code> / <code>stop_transition</code><br />
<li><p>плохо: <code>begin_transition</code> / <code>stop_transition</code><br />
<code>begin</code> и <code>stop</code> — непарные термины; разработчик будет вынужден рыться в документации;<br />
хорошо: <code>begin_transition</code> / <code>end_transition</code> либо <code>start_transition</code> / <code>stop_transition</code>;</li>
<li>плохо:<br />
<code>strpos(haystack, needle)</code> — ищет позицию строки <code>needle</code> внутри строки <code>haystack</code>;<br />
<code>str_replace(needle, replace, haystack)</code> — заменяет вхождения строки <code>needle</code> внутри строки <code>haystack</code> на строку <code>replace</code>;<br />
— здесь нарушены сразу несколько правил: написание неконсистентно в части знака подчеркивания; близкие по смыслу методы имеют разный порядок аргументов <code>needle</code>/<code>haystack</code>; наконец, один из методов находит первое вхождение, а другой — все вхождения, и это никак не отражено в именовании.</li></ul></li>
</ol>
<p>// TODO: единицы измерения, различные стандарты, консистентность
// TODO: простые вещи делаются просто без бойлерплейта</p><div class="page-break"></div></article>
хорошо: <code>begin_transition</code> / <code>end_transition</code> либо <code>start_transition</code> / <code>stop_transition</code>;</p></li>
<li><p>плохо: </p>
<pre><code>strpos(haystack, needle)
Находит первую позицию позицию строки `needle` внутри строки `haystack`
</code></pre>
<pre><code>str_replace(needle, replace, haystack)
Находит и заменяет все вхождения строки `needle` внутри строки `haystack` на строку `replace`
</code></pre>
<p>Здесь нарушены сразу несколько правил:</p>
<ul>
<li>написание неконсистентно в части знака подчеркивания;</li>
<li>близкие по смыслу методы имеют разный порядок аргументов <code>needle</code>/<code>haystack</code>; </li>
<li>первый из методов находит только первое вхождение строки <code>needle</code>, а другой — все вхождения, и об этом поведении никак нельзя узнать из сигнатуры функций.</li></ul>
<p>Упражнение «как сделать эти интерфейсы хорошо» предоставим читателю.</p></li>
</ul>
<h4 id="6">6. Использованные стандарты указывайте явно</h4>
<p>К сожалению, человечество не в состоянии договориться о таких простейших вещах, как «начинать ли неделю с понедельника или с воскресенья», что уж говорить о каких-то более сложных стандартах.</p>
<p>Поэтому <em>всегда</em> указывайте, по какому конкретно стандарту вы отдаёте те или иные величины. Исключения возможны только там, где вы на 100% уверены, что в мире существует только один стандарт для этой сущности, и всё население земного шара о нём в курсе.</p>
<ul>
<li>Плохо: <code>"date":"11/12/2020"</code> — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц;<br />
хорошо: <code>"iso_date":"2020-11-12"</code>.</li>
<li>Плохо: <code>"duration":5000</code> — пять тысяч чего?<br />
Хорошо:<br />
<code>"duration_ms":5000</code><br />
либо<br />
<code>"duration":"5000ms"</code><br />
либо<br />
<code>"duration":{"unit":"ms","value":5000}</code></li>
</ul>
<p>Отдельное следствие из этого правила — денежные величины <em>всегда</em> должны сопровождаться указанием кода валюты.</p>
<p>Также следует отметить, что в некоторых областях ситуация со стандартами настолько плоха, что как ни сделай — кто-то останется недовольным. Классический пример такого рода — порядок геокоординат ("широта-долгота" против "долгота-широта"). Здесь, увы, вам остаётся только смириться и проявлять выдержку при нападках на ваше API.</p>
<h4 id="7">7. Сохранение точности дробных чисел</h4>
<p>Там, где это позволено протоколом, дробные числа с фиксированной запятой — такие, как денежные суммы, например — должны передаваться в виде специально предназначенных для этого объектов, например, Decimal или аналогичных.</p>
<p>Если в протоколе нет Decimal-типов (в частности, в JSON нет чисел с фиксированной запятой), следует либо привести к целому, либо использовать строковый тип.</p><div class="page-break"></div></article>
</body></html>

Binary file not shown.

View File

@ -4,4 +4,6 @@
Для составных частей сущности, к сожалению, достаточно нейтрального термина нам придумать не удалось, поэтому мы используем слова «поля» и «методы».
Большинство примеров API в общих разделах будут даны в виде JSON-over-HTTP-эндпойтов. Это некоторая условность, которая помогает описать концепции, как нам кажется, максимально понятно. Вместо `GET /orders` вполне может быть вызов метода `orders.get()`, локальный или удалённый; вместо JSON может быть любой другой формат данных. Смысл утверждений от этого не меняется.
Большинство примеров API в общих разделах будут даны в виде JSON-over-HTTP-эндпойтов. Это некоторая условность, которая помогает описать концепции, как нам кажется, максимально понятно. Вместо `GET /orders` вполне может быть вызов метода `orders.get()`, локальный или удалённый; вместо JSON может быть любой другой формат данных. Смысл утверждений от этого не меняется.
Также в примерах часто применяется следующая конвенция. Запись `{ "begin_date" }` (т.е. отсутствие значения у поля в JSON-объекте) означает, что в поле находится именно то, что ожидается — т.е. в данном примере какая-то дата начала.

View File

@ -4,51 +4,128 @@
Важное уточнение под номером ноль:
0. Правила — это всего лишь обобщения. Они не действуют безусловно и не означают, что можно не думать головой. У каждого правила есть какая-то рациональная причина его существования. Если в вашей ситуации нет причин следовать правилу — значит, следовать ему не надо.
* например, требование консистентности номенклатуры существует затем, чтобы разработчик тратил меньше времени на чтение документации; если вам _необходимо_, чтобы разработчик обязательно прочитал документацию по какому-то методу, вполне разумно сделать его сигнатуру нарочито неконсистентно;
* важно понимать, что вы вольны вводить свои собственные конвенции. Например, в некоторых фреймворках сознательно отказываются от парных методов `set_entity` / `get_entity` в пользу одного метода `entity` с опциональным параметром. Важно только проявить последовательность в её применении — если такая конвенция вводится, то абсолютно все методы API должны иметь подобную полиморфную сигнатуру, или по крайней мере должен существовать принцип именования, отличающий такие комбинированные методы от обычных вызовов.
#### 0. Правила — это всего лишь обобщения
Правила не действуют безусловно и не означают, что можно не думать головой. У каждого правила есть какая-то рациональная причина его существования. Если в вашей ситуации нет причин следовать правилу — значит, следовать ему не надо.
Например, требование консистентности номенклатуры существует затем, чтобы разработчик тратил меньше времени на чтение документации; если вам _необходимо_, чтобы разработчик обязательно прочитал документацию по какому-то методу, вполне разумно сделать его сигнатуру нарочито неконсистентно.
Это соображение применимо ко всем принципам ниже. Если из-за следования правилам у вас получается неудобное, громоздкое, неочевидное API — это повод пересмотреть правила (или API).
1. Явное лучше неявного. Из названия любой сущности должно быть очевидно, что она делает и к каким сайд-эффектам может привести её использование.
* плохо: `GET /orders/cancellation` отменяет заказ
— неочевидно, что достаточно просто обращения к сущности `cancellation` (что это?), тем более немодифицирующим методом `GET`, чтобы отменить заказ;
хорошо: `POST /orders/cancel`;
* плохо: `GET /orders/statistics` агрегирует статистику заказов за всё время
— даже если операция немодифицирующая, но вычислительно дорогая — следует об этом явно индицировать, особенно если вычислительные ресурсы тарифицируются для пользователя; тем более не стоит подбирать значения по умолчанию так, чтобы операция без параметров максимально расходовала ресурсы;
хорошо: `POST /orders/statistics/aggregate` с обязательным указанием периода агрегации в запросе.
Важно понимать, что вы вольны вводить свои собственные конвенции. Например, в некоторых фреймворках сознательно отказываются от парных методов `set_entity` / `get_entity` в пользу одного метода `entity` с опциональным параметром. Важно только проявить последовательность в её применении — если такая конвенция вводится, то абсолютно все методы API должны иметь подобную полиморфную сигнатуру, или по крайней мере должен существовать принцип именования, отличающий такие комбинированные методы от обычных вызовов.
2. Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно:
* плохо: `user.get()`
— неочевидно, что конкретно будет возвращено;
хорошо: user.`get_id()`;
#### 1. Явное лучше неявного
3. Не экономьте буквы. В XXI веке давно уже нет нужды называть переменные покороче:
* плохо: `order.time()`
— неясно, о каком времени идёт речь: время создания заказа, время готовности заказа, время ожидания заказа?…
хорошо: `order.get_estimated_delivery_time()`
* плохо: `strpbrk` ищет вхождение любого из списка символов в строке
— возможно, автору этого API казалось, что аббревиатура `pbrk` что-то значит для читателя, но он явно ошибся;
хорошо: `string_search_for_characters`; однако необходимость существования такого метода вообще вызывает сомнения, достаточно было бы иметь удобную функцию поиска подстроки с нужными параметрами.
Из названия любой сущности должно быть очевидно, что она делает и к каким сайд-эффектам может привести её использование.
* плохо:
```
GET /orders/cancellation
отменяет заказ
```
Неочевидно, что достаточно просто обращения к сущности `cancellation` (что это?), тем более немодифицирующим методом `GET`, чтобы отменить заказ;
4. Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — `objects`, `children`; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений:
* плохо: `GET /news`
— неясно, будет ли получена какая-то конкретная новость или массив новостей;
хорошо: `GET /news-list`.
Хорошо:
```
POST /orders/cancel
отменяет заказ
```
* плохо:
```
GET /orders/statistics
Возвращает агрегированную статистику заказов за всё время
```
Даже если операция немодифицирующая, но вычислительно дорогая — следует об этом явно индицировать, особенно если вычислительные ресурсы тарифицируются для пользователя; тем более не стоит подбирать значения по умолчанию так, чтобы вызов операции без параметров максимально расходовал ресурсы.
5. Аналогично, если ожидается булево значение, то из названия это должно быть очевидно, т.е. именование должно описывать некоторое качественное состояние, например, `is_ready`, `open_now`:
* плохо: `"task.status": true`
— неочевидно, что статус бинарен, плюс такое API будет нерасширяемым
хорошо: `"task.is_finished": true`
Хорошо:
```
POST /orders/statistics/aggregate
{ "start_date", "end_date" }
Возвращает агрегированную статистику заказов за указанный период
```
6. Сущности, выполняющие подобные функции, должны называться подобно и вести себя подобным образом.
* плохо: `begin_transition` / `stop_transition`
`begin` и `stop` — непарные термины; разработчик будет вынужден рыться в документации;
хорошо: `begin_transition` / `end_transition` либо `start_transition` / `stop_transition`;
* плохо:
`strpos(haystack, needle)` — ищет позицию строки `needle` внутри строки `haystack`;
`str_replace(needle, replace, haystack)` — заменяет вхождения строки `needle` внутри строки `haystack` на строку `replace`;
— здесь нарушены сразу несколько правил: написание неконсистентно в части знака подчеркивания; близкие по смыслу методы имеют разный порядок аргументов `needle`/`haystack`; наконец, один из методов находит первое вхождение, а другой — все вхождения, и это никак не отражено в именовании.
**Стремитесь к тому, чтобы из сигнатуры функции было абсолютно ясно, что она делает, что принимает на вход и что возвращает**. Вообще, при прочтении кода, работающего с вашим API, должно быть сразу понятно, что, собственно, он делает — без подглядывания в документацию.
#### 2. Сущности должны именоваться конкретно
Избегайте слов-«амёб» без определённой семантики, таких как get, apply, make. Сущности должны именоваться конкретно:
* плохо: `user.get()` — неочевидно, что конкретно будет возвращено;
хорошо: `user.get_id()`;
#### 3. Не экономьте буквы
В XXI веке давно уже нет нужды называть переменные покороче.
* плохо: `order.time()` — неясно, о каком времени идёт речь: время создания заказа, время готовности заказа, время ожидания заказа?…
хорошо: `order.get_estimated_delivery_time()`
* плохо:
```
strpbrk (str1, str2)
возвращает положение первого вхождения в строку str2 любого символа из строки str2
```
Возможно, автору этого API казалось, что аббревиатура `pbrk` что-то значит для читателя, но он явно ошибся. К тому же, невозможно сходу понять, какая из строк `str1`, `str2` является набором символов для поиска.
Хорошо:
```
str_search_for_characters(lookup_character_set, str)
```
Однако необходимость существования такого метода вообще вызывает сомнения, достаточно было бы иметь удобную функцию поиска подстроки с нужными параметрами. Аналогично сокращение `string` до `str` выглядит совершенно бессмысленным, но, увы, является устоявшимся для большого количества предметных областей.
#### 4. Тип поля должен быть ясен из его названия
Если поле называется `recipe` — мы ожидаем, что его значением является сущность типа `Recipe`. Если поле называется `recipe_id` — мы ожидаем, что его значением является идентификатор, который я могу найти в составе сущности `Recipe`.
Сущности-массивы должны именоваться во множественном числе или собирательными выражениями — `objects`, `children`; если это невозможно (термин неисчисляемый), следует добавить префикс или постфикс, не оставляющий сомнений:
* плохо: `GET /news` — неясно, будет ли получена какая-то конкретная новость или массив новостей;
хорошо: `GET /news-list`.
Аналогично, если ожидается булево значение, то из названия это должно быть очевидно, т.е. именование должно описывать некоторое качественное состояние, например, `is_ready`, `open_now`:
* плохо: `"task.status": true` — неочевидно, что статус бинарен, плюс такое API будет нерасширяемым;
хорошо: `"task.is_finished": true`
Отдельно следует оговорить, что на разных платформах эти правила следует дополнить по-своему с учетом специфики first-class citizen-типов. Например, объекты типа `Date`, если таковые имеются, разумно индицировать с помощью, например, постфикса `_at` (`created_at`, `occurred_at`, etc).
#### 5. Подобные сущности должны называться подобно и вести себя подобным образом
* плохо: `begin_transition` / `stop_transition`
`begin` и `stop` — непарные термины; разработчик будет вынужден рыться в документации;
хорошо: `begin_transition` / `end_transition` либо `start_transition` / `stop_transition`;
* плохо:
```
strpos(haystack, needle)
Находит первую позицию позицию строки `needle` внутри строки `haystack`
```
```
str_replace(needle, replace, haystack)
Находит и заменяет все вхождения строки `needle` внутри строки `haystack` на строку `replace`
```
Здесь нарушены сразу несколько правил:
* написание неконсистентно в части знака подчеркивания;
* близкие по смыслу методы имеют разный порядок аргументов `needle`/`haystack`;
* первый из методов находит только первое вхождение строки `needle`, а другой — все вхождения, и об этом поведении никак нельзя узнать из сигнатуры функций.
Упражнение «как сделать эти интерфейсы хорошо» предоставим читателю.
#### 6. Использованные стандарты указывайте явно
К сожалению, человечество не в состоянии договориться о таких простейших вещах, как «начинать ли неделю с понедельника или с воскресенья», что уж говорить о каких-то более сложных стандартах.
Поэтому _всегда_ указывайте, по какому конкретно стандарту вы отдаёте те или иные величины. Исключения возможны только там, где вы на 100% уверены, что в мире существует только один стандарт для этой сущности, и всё население земного шара о нём в курсе.
* Плохо: `"date":"11/12/2020"` — стандартов записи дат существует огромное количество, плюс из этой записи невозможно даже понять, что здесь число, а что месяц;
хорошо: `"iso_date":"2020-11-12"`.
* Плохо: `"duration":5000` — пять тысяч чего?
Хорошо:
`"duration_ms":5000`
либо
`"duration":"5000ms"`
либо
`"duration":{"unit":"ms","value":5000}`
Отдельное следствие из этого правила — денежные величины *всегда* должны сопровождаться указанием кода валюты.
Также следует отметить, что в некоторых областях ситуация со стандартами настолько плоха, что как ни сделай — кто-то останется недовольным. Классический пример такого рода — порядок геокоординат ("широта-долгота" против "долгота-широта"). Здесь, увы, вам остаётся только смириться и проявлять выдержку при нападках на ваше API.
#### 7. Сохранение точности дробных чисел
Там, где это позволено протоколом, дробные числа с фиксированной запятой — такие, как денежные суммы, например — должны передаваться в виде специально предназначенных для этого объектов, например, Decimal или аналогичных.
Если в протоколе нет Decimal-типов (в частности, в JSON нет чисел с фиксированной запятой), следует либо привести к целому, либо использовать строковый тип.
// TODO: единицы измерения, различные стандарты, консистентность
// TODO: простые вещи делаются просто без бойлерплейта