mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-01-05 10:20:22 +02:00
Описание конечных интерфейсов - продолжение
This commit is contained in:
parent
57b5e35e48
commit
af53dab172
138
docs/API.ru.html
138
docs/API.ru.html
@ -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>
|
BIN
docs/API.ru.pdf
BIN
docs/API.ru.pdf
Binary file not shown.
@ -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-объекте) означает, что в поле находится именно то, что ожидается — т.е. в данном примере какая-то дата начала.
|
@ -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: простые вещи делаются просто без бойлерплейта
|
Loading…
Reference in New Issue
Block a user