Глава 2. Грамматика документации

462 просмотров
0 лайков
0 в избранном
Войдите, чтобы поставить лайк. Лайков:

Введение

Хорошая новость: вся английская грамматика для чтения документации не нужна. Из двенадцати времён там живут два с половиной, из всего богатства наклонений — по сути одно. Плохая новость короче: то, что остаётся, надо знать до последней запятой. Разница между SHOULD и MUST — это разница между «можно и не делать» и «иначе твоя реализация несовместима». Незамеченное unless переворачивает абзац целиком.

Начнём с императива и Present Simple: на них написано подавляющее большинство технических текстов. Потом пассив — где он уместен, а где стайлгайды просят его убрать и почему. Отдельно и подробно возьмём модальность RFC 2119/8174: MUST, SHOULD, MAY лежат в фундаменте всех интернет-стандартов и почти любой внутренней спеки.

Дальше условные конструкции, артикли, инфинитив цели и цепочки существительных вроде database connection pool timeout. Последние выглядят непроходимыми ровно до того момента, когда узнаёшь одно правило чтения. К концу главы ты сможешь взять кусок спецификации и сказать точно: вот это требуется, это рекомендуется, а это просто разрешено.

Императив: язык инструкций

Когда речь о действиях читателя, документация переходит на повелительное наклонение. Форма проще некуда: глагол в словарной форме, подлежащего нет.

Run the migration before starting the server.
Open the configuration file in any text editor.
See the API reference for the full list of parameters.
Do not commit the `.env` file to version control.

Почему императив, а не вежливое you should run? Потому что инструкция обязана быть однозначной и короткой. Run the migration толкований не допускает. You may want to run the migration оставляет читателя гадать: так надо или можно пропустить?

Типовых глаголов, с которых начинаются инструкции, немного, и повторяются они везде — выучи список один раз: run, open, create, set, add, remove, replace, check, make sure, note, see, refer to, ensure, verify.

Теперь про порядок слов в инструкции с условием — мелочь, которая на практике дорого стоит. Стайлгайды требуют ставить условие в начало, действие в конец. Причина простая: читатель, наткнувшись на действие раньше условия, начнёт его выполнять и только потом узнает, что оно было не для него.

Хуже:  Restart the service if you changed the TLS certificate.
Лучше: If you changed the TLS certificate, restart the service.

Однажды по такому рунбуку я перезапустил сервис, который трогать было не надо: строчка «перезапусти сервис» шла первой, а «если менял TLS-сертификат» — второй. Пять минут простоя из-за порядка слов в рунбуке. Когда пишешь инструкции коллегам, помни об этом.

Present Simple: язык описания поведения

Как только речь заходит не о действиях читателя, а о том, что делает система, документация переключается в Present Simple. Это время описывает постоянные свойства и повторяющиеся действия — то есть ровно поведение кода.

The function returns `null` if the key is not found.
The scheduler runs every five minutes.
This endpoint accepts both JSON and form-encoded bodies.
The client retries failed requests up to three times.

Деталь, на которой сыпятся все: returns, runs, accepts, retries — везде окончание -s, потому что подлежащее в третьем лице единственного числа. Русскоязычные авторы теряют это -s чаще всего прочего, особенно когда между подлежащим и глаголом вклинивается длинное определение:

Плохо:  The list of users that match the filter are returned.
Хорошо: The list of users that match the filter is returned.

Подлежащее здесь list, единственное число, а не users. Ловушка классическая: глагол магнитом притягивается к ближайшему существительному.

Второе — Present Simple, а не Future. Руку тянет написать the function will return null, потому что по-русски мы говорим «функция вернёт». Но в английской документации будущее время значит «это случится однажды, потом», а не «таково свойство функции». Правильно — настоящее:

Плохо:  If the token expires, the API will return 401.
Хорошо: If the token expires, the API returns 401.

Will уместно, когда речь о реальном будущем: This method will be removed in version 4.0 — «будет удалён», это обещание на будущее, а не описание поведения.

Пассивный залог: где уместен, а где мешает

Пассив (is returned, are evicted, was created) в технических текстах встречается на каждом шагу, а отношение к нему двойственное. Стайлгайды, включая открытый гайд Google, по умолчанию просят активный залог. Причина в том, что пассив прячет действующее лицо, а в инструкции читателю жизненно важно знать, кто именно должен шевелиться.

Плохо:  The configuration file must be updated before deployment.
Хорошо: Update the configuration file before you deploy.

Кто обновляет? Читатель? Скрипт деплоя? Администратор? В первом варианте неизвестно, и однажды это заканчивается тем, что конфиг не обновил никто. Во втором вопросов нет.

Но есть три случая, где пассив не просто уместен, а лучше активного.

Действующее лицо неважно или очевидно. Когда речь о внутренней машинерии, называть исполнителя незачем:

Expired sessions are removed once per hour.
The password is hashed with Argon2id before it is stored.

Кто именно удаляет сессии — фоновая задача, триггер в БД, cron — читателю всё равно. Важен факт.

Действующее лицо неизвестно. В спецификациях протоколов зачастую нельзя сказать, кто сгенерирует значение: это дело реализации.

Логическое ударение на объекте. Если абзац про токен, то и подлежащим логично быть токену, даже когда действие над ним совершает кто-то другой: The token is signed by the authorization server.

Вывод без догматизма: пассив — инструмент, а не грех. В инструкциях читателю — активный залог и императив. В описании внутреннего поведения системы пассив совершенно нормален.

Модальность RFC 2119: MUST, SHOULD, MAY

Самая важная часть главы. RFC 2119 (позже уточнённый документом RFC 8174) закрепил точные значения нескольких слов, которые в спецификациях пишут заглавными буквами. Договорённость давно ушла за пределы интернет-стандартов: её берут в API-контракты, во внутренние инженерные спеки, в описания требований.

Ключевое правило из RFC 8174: значения ниже работают, только когда слово написано ЗАГЛАВНЫМИ. Обычное must в тексте — просто английское слово, никаких обязательств оно не создаёт.

СловоЗначениеЧто будет, если нарушить
MUST, REQUIRED, SHALLАбсолютное требованиеРеализация несовместима со спецификацией
MUST NOT, SHALL NOTАбсолютный запретРеализация несовместима со спецификацией
SHOULD, RECOMMENDEDРекомендация: отклониться можно, но нужно понимать последствияФормально совместим, но стоит уметь объяснить, почему
SHOULD NOT, NOT RECOMMENDEDНе рекомендуется, но в отдельных случаях допустимоТо же, но в обратную сторону
MAY, OPTIONALПолностью на усмотрение реализацииНичего: обе стороны обязаны корректно работать при любом выборе

Три предложения, различающиеся одним словом. Миры за ними — совершенно разные:

The client MUST send the `Authorization` header.
The client SHOULD send the `Authorization` header.
The client MAY send the `Authorization` header.

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

Тонкость про MAY, которую упускают чаще всего: свободу оно даёт одной стороне, а обязательство накладывает на другую. Спека говорит «клиент MAY прислать дополнительные поля» — значит, сервер обязан не падать, когда они придут. Встретив MAY, всегда спрашивай: а что из этого следует для моей стороны?

Однажды мы почти час спорили на ревью. Во внутренней спеке стояло the worker SHOULD retry the webhook at least three times, ревьюер разворачивал PR с комментарием «написано же — должен», я упирался, что SHOULD — это рекомендация. Спор кончился, когда кто-то открыл RFC 2119 и зачитал определение вслух. На следующий день в шапку спеки добавили строчку «keywords are to be interpreted as described in RFC 2119», и этот класс споров исчез навсегда.

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

can, may, might, could

Вне контекста RFC модальные глаголы живут по обычным правилам английского. Путаница у русскоязычных читателей возникает по банальной причине: все четыре переводятся словом «может».

ГлаголЗначениеПример
canспособность, техническая возможностьYou can filter results by date.
mayразрешение либо вероятностьThe response may include a `warnings` field.
mightвероятность, чуть ниже, чем у mayThis might break older clients.
couldгипотетическая возможность, вариант решенияWe could cache the result, but memory is tight.
cannotневозможно техническиYou cannot rename a column without a migration.

Разница чисто практическая. Can сообщает о существующей возможности: «так делать можно, вот как». May — либо о разрешении, либо о том, что нечто иногда случается. Фразу the request may fail читай как «запрос иногда падает, закладывайся на это», а не «запросу разрешено упасть». Разница между этими двумя прочтениями — наличие или отсутствие ретраев в твоём коде.

Условные конструкции: if, when, unless и компания

Условия — сердце технических текстов: поведение системы почти всегда от чего-нибудь зависит. Английский различает здесь оттенки, которые русское «если» бесследно смазывает.

КонструкцияСмысл
ifУсловие, которое может выполниться, а может нет
whenУсловие, которое обязательно выполнится, вопрос только когда
unless«Если НЕ»: правило действует, пока условие не наступило
in case«На случай, если»: подготовка заранее, а не реакция
provided that«При условии, что»: формальное ограничение
as long as«Пока выполняется»: условие должно держаться всё время

Разница между if и when не косметическая:

If the token expires, the client requests a new one.
When the token expires, the client requests a new one.

Первое допускает, что токен может и не истечь. Второе утверждает: истечёт обязательно, вопрос только когда, — и для токенов с ограниченным сроком жизни это чистая правда. Аккуратный автор выберет when.

Дальше in case — вечный источник ошибок, потому что похоже на if, а значит другое:

Take a backup in case the migration fails.   → бэкап делаем СЕЙЧАС, заранее
Take a backup if the migration fails.        → бэкап делаем ПОСЛЕ падения (бессмысленно)

Unless — самое пропускаемое слово в технических текстах, и одного упоминания ему мало. Оно равно if not и разворачивает условие:

Responses are cached for 60 seconds unless the request
includes a `Cache-Control: no-store` header.

Кэшируется всё, КРОМЕ запросов с этим заголовком. Читатель, проглотивший unless, получит ровно обратное правило и будет долго удивляться, почему приватные ответы прилетают чужому пользователю.

Ещё одна важная в спецификациях формулировка — if and only if, часто сокращаемая до iff. Обычное if задаёт достаточное условие. If and only if задаёт условие и достаточное, и необходимое:

The cache entry is refreshed if and only if both the ETag
and the `Last-Modified` value have changed.

Сказано две вещи сразу: при изменении обоих значений запись обновится — и ни при каких других обстоятельствах она не обновится. Обычное if вторую половину не гарантировало бы, и место для «а ещё вот в этом случае» осталось бы открытым.

И самое коварное — двойное отрицание. В спецификациях оно попадается регулярно, и на нём надо останавливаться:

A server MUST NOT reject a request solely because it does not
include the optional `X-Request-Id` header.

По шагам: MUST NOT reject — отвергать нельзя; solely because it does not include — только на том основании, что заголовка нет. Итог: отсутствие необязательного заголовка не может быть единственной причиной отказа. Половину смысла тут несёт solely — по другим причинам сервер отвергнуть запрос по-прежнему вправе. Выкинь это слово при чтении, и получится запрет, которого в спеке нет.

Артикли a/an и the

Артикли для чтения важнее, чем кажется: это указатели, новый перед тобой объект или уже упомянутый. Правило, закрывающее почти всю документацию: a/an при первом упоминании (какой-то, один из многих), the при последующих (тот самый, уже известный).

Create a service account and grant it read access.
The service account is used by the deployment pipeline.

В первом предложении аккаунт вводится в разговор, он новый — отсюда a. Во втором это уже конкретный, известный читателю аккаунт — the.

The ставится и тогда, когда объект единственный в контексте: the database, the main branch, the response body — тело ответа в рамках одного запроса одно. А ещё перед порядковыми и превосходными формами: the first request, the latest version.

Артикль отсутствует у неисчисляемых понятий и множественного числа в общем смысле: data, traffic, memory, latency, а также Requests are logged (запросы вообще, а не какие-то конкретные). Артикль не ставится и перед именами: Redis stores, а не the Redis stores.

Практический вывод для чтения один, зато рабочий: увидел the перед незнакомым существительным — ищи, где оно вводилось раньше. Почти наверняка ты проскочил определение, и оно в предыдущем абзаце.

Инфинитив цели и герундий

«Чтобы сделать X, сделай Y» — конструкция, на которой держится половина инструкций. По-английски она строится инфинитивом цели, и он выносится в начало предложения:

To create an API token, open Settings and click Generate.
To disable the cache, set `CACHE_TTL` to 0.
To roll back, run `make deploy REVISION=<previous-sha>`.

In order to create a token… значит то же самое, только на два слова длиннее; стайлгайды голосуют за короткую форму.

Герундий (форма на -ing) идёт туда, где действие работает существительным. Это заголовки разделов и подлежащие:

## Configuring TLS
## Migrating from v2 to v3

Restarting the service clears the in-memory cache.

Тонкость, на которой ломаются почти все: после предлога всегда герундий, а не инфинитив. Before deploying, after merging, without restarting, instead of polling. Одно из немногих правил, которое честно надо зазубрить: по-русски там инфинитив, и калька выдаёт ошибку автоматически. Не before to deploy, а before deploying.

Цепочки существительных: читаем справа налево

Английский умеет выстраивать существительные в шеренгу, где все, кроме последнего, работают определениями. Выглядит это пугающе:

database connection pool timeout
default request retry policy
user session expiration handler

Правило разбора одно, зато без исключений: главное слово — последнее, читай справа налево. Первая цепочка по шагам:

  • timeout — это тайм-аут. Главное слово, всё остальное его уточняет.
  • pool timeout — тайм-аут пула.
  • connection pool timeout — тайм-аут пула соединений.
  • database connection pool timeout — тайм-аут пула соединений с базой данных.

Тот же приём на второй: policyretry policy (политика повторов) → request retry policy (политика повторов запросов) → default request retry policy (политика повторов запросов по умолчанию).

Два замечания. Цепочка длиннее четырёх слов — уже плохой стиль: пишешь сам — разбей её предлогом, timeout for the database connection pool читается вдвое легче. И второе, приятное: имена переменных и настроек строятся по тому же принципу, так что навык разбора цепочек бесплатно переносится на чтение конфигов:

database:
  connection_pool_timeout: 30
  max_idle_connections: 5
  statement_timeout_ms: 5000

Кейс из реального проекта: разбираем фрагмент спецификации

Перед тобой фрагмент в духе спецификации авторизации по bearer-токенам — такой текст живёт в любом RFC и в описании любого внутреннего API-контракта. Задача одна: разложить его на «требуется», «рекомендуется» и «разрешено».

The client MUST send the access token in the `Authorization`
header using the Bearer scheme. The client SHOULD NOT include
the token in the query string, because query strings are commonly
written to server logs.

Resource servers MAY support additional authentication schemes.
A resource server that supports more than one scheme MUST
document the order in which schemes are evaluated.

If the presented token has expired, the resource server MUST
respond with 401 and MUST include a `WWW-Authenticate` header,
unless the endpoint is explicitly documented as accepting
unauthenticated requests, in which case the request is
processed as anonymous.

Раскладываем.

Абзац 1. MUST send … in the Authorization header — жёстко. Токен в заголовке, схема Bearer, вариантов нет. Дальше SHOULD NOT include the token in the query string — рекомендация, а не запрет. Формально прислать токен в query-параметре можно, спецификацию это не нарушит. Но последствия названы прямым текстом: query-строки утекают в логи. Вывод для реализации: токен кладём в заголовок; если приходится поддержать query-параметр ради древнего клиента, который не умеет заголовки, это допустимо — но требует осознанного решения и почти наверняка вырезания токена из логов.

Абзац 2. MAY support additional schemes — полная свобода: хочешь поддерживай, хочешь нет. И сразу следом условное требование: A resource server that supports more than one scheme MUST document the order. Смотри на конструкцию: MAY даёт опцию, и в тот момент, когда ты ею воспользовался, включается MUST. Паттерн в спецификациях частый — необязательная возможность с обязательными условиями. Добавил вторую схему авторизации и не описал порядок проверки — реализация несовместима, хотя добавлять схему тебя никто не заставлял.

Абзац 3. Самый плотный. По частям:

  • If the presented token has expired — условие. Заметь Present Perfect (has expired): к моменту проверки токен уже истёк.
  • MUST respond with 401 и MUST include a WWW-Authenticate header — два независимых обязательных требования, соединённых через and. Выполнять нужно оба. Вернуть 401 без заголовка — нарушение, и находят его обычно чужие клиенты, а не твои тесты.
  • unless the endpoint is explicitly documented as accepting unauthenticated requests — исключение из обоих требований. Слово explicitly критично: молчаливое допущение «ну этот эндпоинт вроде публичный» не годится, нужна явная запись в документации.
  • in which case the request is processed as anonymous — что делать в исключительном случае. Оборот in which case означает «и тогда», он относится к содержимому unless.

Соберём чек-лист реализации — если по этому фрагменту тебе писать код, читать его надо именно так:

GET /api/v1/orders HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Обязательно (MUST): токен только в заголовке; при истёкшем токене — 401 плюс WWW-Authenticate. Рекомендуется (SHOULD NOT): не принимать токен в query-строке. Разрешено (MAY): дополнительные схемы авторизации — но с обязательной документацией порядка. Исключение (unless): явно задокументированные публичные эндпоинты обрабатывают запрос как анонимный вместо 401.

Заметил, что произошло? Спецификация превратилась в набор задач с приоритетами. Это и есть цель чтения спеки — не «понять текст», а вытащить из него проверяемые требования, каждое из которых можно закрыть тестом.

Типичные ошибки

1. Трактовать SHOULD как MUST

Самая дорогая ошибка при чтении спецификаций — и самая незаметная, потому что ошибкой она не выглядит. Наоборот: кажется, что ты сделал «как надо, с запасом». Результат — команда реализует необязательную рекомендацию как жёсткое требование, кладёт на это спринт и вешает на продукт ограничения, которых стандарт не просил.

Обратная сторона не лучше: MUST, прочитанное как пожелание, даёт реализацию, которая проходит свои тесты и разваливается при интеграции с чужим клиентом. Лекарство дешёвое: перед началом работы выпиши все заглавные модальные слова спеки отдельным списком. Пять минут против недели неверных приоритетов.

2. Не замечать unless и другие развороты

Requests are retried automatically unless the response
status is 4xx.

Быстрое чтение выдаёт «запросы повторяются, если статус 4xx». Правильно — ровно наоборот: повторяются все, КРОМЕ 4xx. Что логично: клиентская ошибка от повтора не рассосётся. Слова-развороты, на которых надо тормозить сознательно: unless, except, other than, rather than, instead of, regardless of, however, otherwise, nevertheless.

Приём, который реально помогает: наткнулся на такое слово — перечитай предложение и проговори результат утвердительно, без отрицаний. «Повторяем всё, кроме 4xx». Теперь ошибиться сложно.

3. Путать will и Present Simple

Плохо:  The endpoint will return an empty list if no records match.
Хорошо: The endpoint returns an empty list if no records match.

Ошибка приезжает прямо из русского: «эндпоинт вернёт пустой список». Для носителя will в описании поведения звучит так, будто это случится однажды, потом, а не является постоянным свойством. При чтении оно работает так же: will be removed — это про будущее изменение, то есть про устаревание, а не про сегодняшнее поведение. Спутать эти два случая — значит либо затеять миграцию раньше времени, либо проспать объявление о выпиливании API.

4. Терять отрицание в длинном предложении

Чем длиннее предложение, тем выше шанс, что отрицание из начала не доживёт до конца:

The server does not validate the signature of tokens issued
by trusted internal services when the request originates from
the private network and the `X-Internal` header is present.

К концу читатель уже уверен, что сервер подпись проверяет. Сказано обратное: НЕ проверяет, а дальше перечислены условия, при которых он этого не делает. Разница, между прочим, в том, пропустишь ли ты в приватную сеть подделанный токен. Приём тот же: разбей предложение на части и проговори каждую. «Сервер не проверяет подпись» + «токенов от доверенных внутренних сервисов» + «когда запрос из приватной сети» + «и есть заголовок X-Internal».

Заодно перед тобой образец того, как не надо писать самому. Формулируешь требование — режь его на короткие предложения или на список. Читатель скажет спасибо, а споров на ревью станет меньше.

Итог

Грамматическое ядро документации невелико: императив для инструкций читателю, Present Simple для описания поведения системы, пассив там, где исполнитель неважен. Will в описании поведения не живёт — оно означает реальное будущее, то есть обычно грядущее изменение или устаревание.

Модальность RFC 2119 — то, ради чего эту главу стоит перечитать. MUST — обязательство. SHOULD — рекомендация с осознанными последствиями. MAY — свобода выбора, которая почти всегда вешает обязательства на другую сторону. Путаница дорого стоит в обе стороны, и особенно та, что кажется «сделал с запасом».

Условные конструкции требуют внимания к оттенкам: if против when, in case как подготовка заранее, unless как разворот условия, if and only if как строгая эквивалентность. Артикли подсказывают, новый перед тобой объект или уже упомянутый. Цепочки существительных разбираются справа налево, главное слово — последнее.

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

Комментарии 0

Для добавления комментариев необходимо войти или зарегистрироваться.

Пока нет комментариев. Станьте первым!