Коммиты и pull request: короткий текст, который читают чаще всего
Есть тексты, которые инженер пишет раз в год и мучает неделю: RFC, дизайн-док, письмо в поддержку вендора. И есть текст, который он пишет по пять раз в день не задумываясь — строку сообщения коммита и абзац описания pull request. Второй тип читают на два порядка чаще.
Это не преувеличение. Одну строку git log --oneline увидят все, кто когда-либо будет искать причину регрессии, запускать git bisect, смотреть blame в редакторе, собирать changelog к релизу или объяснять клиенту, что изменилось. README откроют один раз при онбординге, комментарий в коде прочитают те двое, кто откроет файл, а заголовок коммита живёт в истории столько же, сколько сам репозиторий, и всплывает в местах, о которых автор не думал. Поэтому именно здесь английский окупается быстрее всего. И здесь же он самый выполнимый: жанр телеграфный, словарь узкий, конструкции повторяются, а хорошее сообщение коммита можно писать, владея активно тремя сотнями слов — если знать, какими именно.
Для кого эта глава и что нужно знать заранее
Честно, как и договаривались в главе «Инженерный английский: карта трека»: если вы не читаете техническую документацию без словаря, начните с чтения документации — писать всегда труднее, чем читать, и эта глава подразумевает, что вы уже узнаёте разбираемые конструкции, когда встречаете их в чужом коде.
Но есть и хорошая новость, которая делает эту главу самой доступной в треке. Сообщение коммита — это не сочинение. Это жанр с жёсткой формой, где:
- предложения короткие, придаточных почти нет;
- время глагола фактически одно (настоящее/императив), прошедшее и будущее нужны редко;
- артикли часто опускаются вовсе, и это норма жанра, а не ошибка;
- есть готовый каркас, который вы заполняете фактами.
То есть здесь можно писать на уровне носителя, не будучи носителем: достаточно понять, какие формулировки читаются как сигнал «автор понимает, что делает», а какие — как шум. Механика git (что такое коммит, ветка, squash-merge, как переписывать историю) в этой главе не объясняется — она разобрана в треке git: ежедневный рабочий процесс и совместная работа. Здесь — только язык.
Куда попадает ваш текст
Заголовок коммита не живёт в одном месте. Он рендерится:
- в
git log --onelineрядом с коротким хешем; - в списке коммитов на GitHub/GitLab — с обрезкой примерно на 50–72 символах;
- в аннотации blame внутри редактора — там места ещё меньше;
- в теме письма-уведомления и в сообщении бота в мессенджере;
- в автогенерируемом CHANGELOG, если команда использует Conventional Commits;
- в описании релиза, которое читает не инженер.
Практический вывод один: смысл кладут в начало строки. Не «После обсуждения с командой исправлена проблема авторизации» (в переводе на английский — то же самое, только длиннее), а сразу глагол и объект. Всё, что не влезло в первые 30–40 символов, читатель увидит только если специально раскроет коммит, — а он не раскроет.
Анатомия сообщения коммита
Форма сложилась в почтовых списках рассылки ядра Linux и с тех пор не менялась, потому что она машиночитаема: первая строка — «тема письма», дальше пустая строка, дальше «письмо», в конце — структурированные поля.
Границы 50 и 72 символа — не эстетика. 72 — это ширина терминала 80 минус четыре пробела отступа, которые git log добавляет к телу; всё, что длиннее, ломается некрасивым переносом. 50 — эмпирический предел, после которого интерфейсы начинают резать заголовок.
Заголовок — это метка, а не предложение
Три следствия, которые нужно принять один раз:
- Точка в конце не ставится. Это заголовок, а не фраза. Точка — самый быстрый маркер «человек пишет коммиты впервые».
- Начинается с заглавной буквы — если в репозитории не принят стиль Conventional Commits, где всё после двоеточия строчными. Смотрите, как принято рядом.
- Артикли и служебные слова опускаются.
Fix race in scheduler— нормальный английский для этого жанра, хотя как предложение он неполон. Это телеграфный стиль, тот же, что в заголовках газет:Fed cuts rates. Носитель прочтёт это без запинки.
Императив: Add, а не Added и не Adds
Единственное правило английской грамматики, которое в этой главе действительно нужно выучить.
Официальная формулировка — в Documentation/process/submitting-patches ядра Linux и в git SubmittingPatches: описывайте изменение в повелительном наклонении, как команду, которую вы отдаёте кодовой базе.
Проверочная фраза, которую придумал Крис Бимс в классической заметке How to Write a Git Commit Message:
If applied, this commit will _______
Подставляем заголовок:
| Вариант | Как читается | Тест «If applied…» |
|---|---|---|
Add retry to payment webhook |
команда системе | «…will add retry to payment webhook» — работает |
Added retry to payment webhook |
отчёт о проделанной работе | «…will added…» — грамматически ломается |
Adds retry to payment webhook |
описание третьим лицом | «…will adds…» — ломается |
Adding retry to payment webhook |
процесс, ещё не завершён | «…will adding…» — ломается |
Почему это не вкусовщина: сам git пишет автосообщения в императиве — Merge branch 'main', Revert "Add retry to payment webhook". Ваши коммиты стоят в одном списке с ними, и разнобой времён читается как разнобой.
Честная оговорка: в огромном количестве живых репозиториев пишут прошедшим временем, и мир не рухнул. Правило, которое стоит соблюдать по-настоящему, — консистентность внутри репозитория. Перед первым коммитом в новом проекте выполните git log --oneline -30 и пишите как соседи. Если соседи пишут Added, ваш идеально правильный Add будет выглядеть выпендрёжем.
Разбор реальных заголовков
Ниже — формулировки, которые встречаются в репозиториях русскоязычных команд. Слева то, что написано; в разборе — что слышит англоязычный ревьюер.
1. fix bug. Строго говоря, ошибок нет. Информации — ноль. Читается как «я не хочу говорить, что чинил». Через полгода при git bisect этот коммит будет стоить получаса. Переписываем, добавляя объект и место: fix: stop double-charging on webhook retry.
2. Fixed the problem with the users which was described in the task. Прошедшее время, длинное придаточное, «the task» без номера, «problem with the users» — размытое до бессмысленности. Плюс which вместо that в ограничительном придаточном (мелочь, но заметная). Всё это укладывается в: Fix duplicate emails in user export (JIRA-4127).
3. Realize new payment provider. Классический ложный друг. Realize — это «осознать, понять», а не «реализовать». Носитель читает «Осознать нового платёжного провайдера». Нужное слово — implement или просто add: Add Stripe as a payment provider.
4. Add possibility to filter orders by date. Грамматически возможно, но «possibility» — калька с «возможность». В английском для функциональности говорят иначе: Allow filtering orders by date или, ещё короче и естественнее, Add date filter to orders. Конструкция allow + -ing — рабочая лошадка для описания новых фич, запомните именно её, а не слово «possibility».
5. Correct mistake in authorization. Школьная лексика. Mistake — это ошибка человека (описка, неверное решение), дефект в программе называется bug, defect, regression, реже issue. И чинят его глаголом fix, а не correct (correct уместно для данных: Correct the exchange rate in the seed data). Правильно: Fix token check in the authorization middleware.
6. Refactoring of OrderService. Refactoring — существительное-процесс, неисчисляемое; «сделать рефакторинг» по-английски не «make a refactoring». В заголовке нужен глагол: Refactor OrderService to use the repository port. И сразу полезная привычка: писать, во что отрефакторили, — иначе ревьюер не знает, что искать в диффе.
7. Change logic of work with cache. Прямая калька «изменить логику работы с кэшем». «Logic of work» для носителя — набор слов. Пишем, что конкретно изменилось: Invalidate the product cache on price change.
8. Some improvements, update, wip, final fix 2. Отдельный класс. Это не английский, это отсутствие сообщения. Если такие коммиты нужны в процессе работы — это нормально, но перед отправкой их схлопывают: git rebase -i и осмысленное сообщение (см. переписывание истории).
Глаголы: где на самом деле нужна точность
Списки «полезных фраз» бесполезны — они запоминаются как заклинания и звучат фальшиво. А вот полтора десятка глаголов, которые покрывают почти все коммиты, выучить стоит: каждый несёт конкретный технический смысл, и ревьюер по глаголу заранее понимает характер изменения.
- fix — устранён дефект. Если поведение раньше не считалось багом,
fixвводит в заблуждение: вы не чините, вы меняете. Тогдаchangeилиswitch. - resolve — снять конфликт, разрешить ссылку, зарезолвить промис. Про баг — только в трейлере (
Resolves #12), не в описании работы. - address — «отреагировал на замечание», не обязательно исчерпывающе. Честное слово в ответах на ревью:
Address review comments on error handlingне обещает, что вопрос закрыт полностью. - handle — добавлена обработка случая:
Handle empty response from the pricing service. - prevent / guard against — изменение не даёт ситуации возникнуть:
Prevent duplicate submissions on double click. - drop / remove / delete — три разных оттенка. Drop — перестать поддерживать или использовать (
Drop support for Python 3.8), remove — вынуть код или элемент (Remove the unused retry wrapper), delete — удалить сущность физически (Delete stale migration files). - bump / pin / unpin — только про версии зависимостей:
Bump axios to 1.7.4,Pin postgres image to 16.3. Словоupdateдля версии тоже понятно, ноbump— родное. - extract / inline / rename / move — словарь рефакторингов Фаулера. Ревьюер узнаёт их мгновенно и понимает, что поведение не менялось. Это сильный сигнал:
Extract PriceCalculator from OrderService. - introduce — тяжелее, чем
add: вводится новая абстракция или механизм, у которого будут последствия.Introduce a retry policy interface. - wire up — соединить уже существующие части:
Wire up the audit logger in the checkout flow. - relax / tighten — про ограничения, валидацию, таймауты:
Relax the schema validation for legacy payloads. - revert / roll back — откат (существительное —
rollback, глагол — два слова).
Как проверять себя без словаря — прямо в репозитории, над которым работаете:
# какие глаголы реально используют в этом проекте
git log --no-merges --format='%s' | awk '{print tolower($1)}' | sort | uniq -c | sort -rn | head -20
# 20 случайных заголовков для калибровки стиля
git log --no-merges --format='%s' | shuf -n 20
# как здесь формулируют конкретный тип изменения
git log --no-merges --format='%s' --grep='deprecat' -i
Тот же приём работает на чужих репозиториях, где английский заведомо хороший: curl, PostgreSQL, Rust. Полчаса чтения git log даёт больше, чем учебник.
Как собирается заголовок
Тело сообщения: сюда пишут «почему»
Правило, которое стоит повесить над столом: дифф отвечает на вопрос «что», сообщение — на вопрос «почему». Пересказывать в теле список изменённых файлов бессмысленно, читатель видит их рядом.
Рабочая структура из четырёх движений — её узнают по коммитам PostgreSQL и ядра:
- Problem. Что не так. Настоящее время, если смотрите на код до патча (
The refresh path ignores iat), прошедшее — если описываете, как было (stayed valid for up to 15 minutes). Оба варианта нормальны, нельзя только смешивать их в одном абзаце. - Cause. Почему так было — если причина неочевидна.
- Change. Что сделано — в императиве или настоящем времени.
- Consequence. Что теперь изменится для пользователей, миграций, производительности.
fix(auth): reject tokens issued before password reset
Access tokens minted before a password reset stayed valid for up to
15 minutes. The refresh path checked the exp claim but never compared
iat against the account's last_password_reset_at, so a stolen token
survived the one action that is supposed to revoke it.
Compare iat with last_password_reset_at on every refresh and reject
anything older. Sessions created before the reset are dropped on the
next refresh, not immediately, so users may stay signed in on other
devices for up to one refresh cycle.
Fixes: #4127
Разбор по строкам — что именно делает этот текст английским, а не переведённым:
stayed valid for up to 15 minutes— прошедшее время для описания сломанного поведения до фикса. Конструкцияfor up to N(«вплоть до») точнее, чемabout 15 minutes.checked the exp claim but never compared— параллельная конструкция: два глагола в одном времени, соединённыеbut. Простейший способ показать контраст, не выстраивая придаточное.so a stolen token survived the one action that is supposed to revoke it— вот это предложение и есть ценность сообщения. Оно объясняет, почему баг важен, а не что в коде. Оборотthe one action that…(«то единственное действие, которое…») — сильный и совершенно обычный английский.Compare iat with … and reject anything older— императив в описании изменения. Тот же залог, что и в заголовке. НикакихI have comparedиit was decided to compare.Sessions … are dropped on the next refresh, not immediately— самая недооценённая часть. Автор сам называет ограничение своего решения. Для ревьюера это признак, что человек думал; для будущего читателя — ответ на вопрос, который иначе стал бы багом.
Чего в теле быть не должно: оборота This commit fixes… (читатель и так знает, что перед ним коммит; он уместен только при противопоставлении — This commit only changes the reader; the writer is fixed in #4130), будущего времени (will be refactored later — история не место для планов, для этого есть задачи) и оценок с извинениями (sorry for the huge diff, quick and dirty — это нужно ревьюеру сейчас, в описании PR, а не в вечной истории).
Conventional Commits и его английские ловушки
Conventional Commits — соглашение о машиночитаемом формате заголовка: type(scope)!: description. Что тут ломают чаще всего именно на уровне языка:
- scope — существительное в единственном числе, строчными. Имя модуля, а не действие:
fix(cache):, а неfix(caching):и точно неfix(fixCache):. - description начинается со строчной буквы и с глагола в императиве, без точки в конце.
feat(orders): allow filtering by date— да.feat(orders): Added date filter.— три ошибки в одной строке. !перед двоеточием означает ломающее изменение, и его дублируют футеромBREAKING CHANGE:заглавными буквами с двоеточием — регистр здесь значим, парсеры сравнивают буквально.- Ваша строка попадёт в CHANGELOG и её прочитает не инженер. Значит,
fix(deps): bump lodashдля пользователя бесполезно, аfix(export): stop truncating CSV files over 1 MB— полезно. Пишите description на языке эффекта, а не на языке кода.
# .commitlintrc.yml — минимальная проверка формата в CI
extends:
- "@commitlint/config-conventional"
rules:
subject-case: [2, "always", "lower-case"] # description строчными
subject-full-stop: [2, "never", "."] # без точки в конце
header-max-length: [2, "always", 72] # жёсткий предел заголовка
Локальная проверка без внешних инструментов — хук на shell:
#!/bin/sh
# .git/hooks/commit-msg — режет три самых частых огреха заголовка
subject=$(head -n 1 "$1")
case "$subject" in
Merge*|Revert*|fixup!*|squash!*) exit 0 ;; # служебные пропускаем
*.) echo "Заголовок не должен заканчиваться точкой" >&2; exit 1 ;;
esac
[ "${#subject}" -gt 72 ] && { echo "Заголовок длиннее 72: ${#subject}" >&2; exit 1; }
# прошедшее время в первом слове — самая частая ошибка русскоязычного автора
first=$(printf '%s' "$subject" | sed 's/^[a-z]*([^)]*)!*: *//' | awk '{print $1}')
case "$first" in
Added|Fixed|Updated|Removed|Changed|Refactored|Implemented)
echo "Пишите в императиве: не '$first', а '${first%ed}'" >&2; exit 1 ;;
esac
exit 0
Трейлеры: строки, которые читают машины
Нижний блок сообщения — пары Key: value, каждая с новой строки, без пустых строк между ними. Регистр и двоеточие значимы.
Fixes: #4127,Closes #4127,Resolves #4127— GitHub закроет задачу при мерже в основную ветку. Формулировки перечислены в документации GitHub.Refs #4127илиRelated to #4127— ссылка без закрытия. Разница на практике огромна: написалиFixesв промежуточном коммите — задача закроется раньше времени.Co-authored-by: Name <email>— формат жёсткий, GitHub показывает второго автора в истории.Signed-off-by: Name <email>— не «подпись что я молодец», а Developer Certificate of Origin: юридическое заявление о праве отдать код. В проектах с DCO без неё патч не примут.Reviewed-by,Reported-by,Suggested-by,Tested-by— стиль ядра Linux, атрибуция участия. В корпоративных репозиториях встречается редко, но встретив — вы теперь знаете.
Pull request: заголовок
Заголовок PR подчиняется тем же правилам, что и заголовок коммита, плюс два обстоятельства.
Первое: PR читают люди, которые дифф ещё не открывали, — тимлид в списке из тридцати PR, релиз-менеджер, менеджер продукта. Поэтому «что» здесь важнее «где»: Add date filter to the orders list понятнее, чем Update OrdersController.
Второе, и о нём часто забывают: при squash-merge заголовок PR становится заголовком коммита в main. То есть текст, который вы писали как рабочую переписку, навсегда уезжает в историю. Классические жертвы этого механизма — Fixes after review (в main это коммит-загадка), Task 4127 (номер без смысла: трекер сменят, ссылки протухнут, а строка останется) и [WIP] do not merge!!!, которое доезжает до main чаще, чем хочется. Префиксы вроде WIP: вообще не нужны там, где есть черновики: GitHub и GitLab умеют draft PR, и это машиночитаемо, в отличие от восклицательных знаков.
Pull request: описание
Каркас, который работает в любой команде, — четыре заголовка и ни одного лишнего слова:
## What
Adds a date range filter to the orders list. The API accepts `from` and `to`
as ISO-8601 dates; both are optional.
## Why
Support spends ~2 hours a day exporting orders and filtering them in a
spreadsheet (JIRA-4127). This removes that step.
## How to verify
1. Open /orders as a support user.
2. Set `from` to yesterday, leave `to` empty.
3. Expected: only orders created since yesterday, total count updates.
Edge case: an invalid date returns 400 with `INVALID_DATE_RANGE`, it does not
fall back to an unfiltered list.
## Risks
Adds an index on `orders.created_at` — the migration takes ~40s on production
volume and runs concurrently, so no table lock. Rollback: drop the index, the
filter degrades to a sequential scan.
Что тут сделано на уровне языка и почему это читается как английский инженера:
Adds a date range filter— настоящее время, третье лицо, подлежащее опущено. Подразумевается «this PR adds». Так пишет руководство Google по описаниям изменений: описываем, что делает изменение, а не что делал автор.both are optional— короткое утверждение вместоit is not necessary to pass both of them. Английский инженерных текстов любит прилагательные-предикативы:optional,required,idempotent,backwards-compatible.Support spends ~2 hours a day…— причина в фактах, а не в оценках. Неit is very inconvenient.Expected: only orders created since yesterday— конструкцияSteps → Expectedуниверсальна и совпадает с языком критериев приёмки, о которых говорят в приёмке требований и в баг-репортах.it does not fall back to an unfiltered list— явно сказано, чего система не делает. Отрицательные утверждения экономят ревьюеру целый круг вопросов.Rollback: drop the index— раздел, который отличает джуна от сеньора сильнее, чем код.
Секцию «How to verify» пишут реже всего и зря: она напрямую превращается в чек-лист ревьюера и в тест-кейс. Если у вас в проекте настроены проверки в CI, ссылайтесь на них, а не пересказывайте (см. тесты в CI).
Разбор целого описания: до и после
Реалистичный текст, каких много:
Hi! In this PR I want to add new functionality for our users. As we discussed
earlier, the actual behaviour of the filter is not correct and I fixed it. Also
I made some refactoring and delete unused code. Please, review it ASAP, I need
to merge it today because of the deadline. Thanks in advance!
Разбор по фразам — что видит ревьюер:
| Фрагмент | Что не так | Как читается |
|---|---|---|
Hi! |
PR — не письмо | Мелочь, но задаёт тон переписки вместо документа |
In this PR I want to add |
намерение вместо факта | «Автор ещё не сделал?» PR уже содержит изменение |
new functionality for our users |
ноль информации | Ревьюер вынужден читать дифф с нуля |
As we discussed earlier |
«мы» — это кто | Ревьюер мог не быть на той встрече; нужна ссылка на тред |
the actual behaviour |
ложный друг | actual = «фактический», не «актуальный». Нужно current |
is not correct |
размытая формулировка | Какое именно поведение и в чём именно неверно |
I made some refactoring |
неисчисляемое + калька | По-английски Refactor X; «some» усиливает неопределённость |
and delete unused code |
рассогласование времён | После made ожидается deleted. Но главное — сигнал: PR делает три разные вещи, его просят разбить |
Please, review it ASAP |
запятая + давление | В английском после please запятая не ставится; ASAP читается как «бросай всё» |
because of the deadline |
чей дедлайн | Ревьюеру он неизвестен и ничего не объясняет |
Thanks in advance |
спорно | В части команд нейтрально, в части читается как «я заранее считаю, что ты обязан». Безопаснее убрать |
Тот же PR, переписанный:
## What
Fixes the orders filter: dates were compared as strings, so `2026-09-01`
sorted before `2026-10-01` but after `2026-1-9`.
## Why
Support reported wrong results for any range crossing a month boundary
(JIRA-4127).
## How to verify
Filter orders from 2026-09-25 to 2026-10-05. Expected: 14 orders,
previously 3.
## Notes
The unrelated cleanup of `OrderMapper` is in #4131, this PR only touches the
comparison. I'd like to merge before Thursday's release — the report goes out
Friday. If that timing is a problem, tell me and I'll ask someone else.
Обратите внимание на последний абзац. Просьба поторопиться никуда не делась, но вместо давления там факт («релиз в четверг, отчёт в пятницу») и выход для собеседника («скажи, и я попрошу другого»). Это и есть разница между «ASAP» и нормальной рабочей просьбой; подробнее о том, как формулировать несогласие и просьбы, — в главе комментарии на ревью.
Жизненный цикл PR и язык на каждом шаге
Аббревиатуры, которые встретятся в этих переходах и которые надо просто знать:
- LGTM — looks good to me, «одобряю». Не «выглядит неплохо», а полноценный аппрув.
- PTAL — please take another look, «посмотри ещё раз».
- nit (от nitpick) — придирка, которую можно проигнорировать:
nit: typo in the comment. - WFM — works for me. IIRC — if I recall correctly. TL;DR — краткое резюме.
- Superseded by #N — «заменён другим PR». Вежливый способ закрыть свою работу.
Типичная переписка вокруг PR выглядит так:
Три вещи, которые здесь делает автор и которые стоит скопировать:
- Сразу говорит, куда смотреть — «одно изменение поведения, остальное механическое переименование». Это экономит ревьюеру самый дорогой ресурс, внимание.
- Объясняет красный CI сам, не дожидаясь вопроса. Молчащий красный билд читается как «автор не смотрел».
- Отвечает на «почему не иначе» аргументом, а не защитой.
Shorter lifetime would hit every user— сравнение вариантов по последствиям. НеI think it's better.
Английский вокруг: ветки, идентификаторы, ссылки
- Имена веток — латиницей, kebab-case, с номером задачи и глаголом:
feature/JIRA-4127-add-date-filter. Транслит (fix-oshibki-v-filtre) в общем репозитории выглядит ровно так, как выглядит. - Идентификаторы не переводят и оборачивают в бэктики. Если класс называется
OrderService, в описании пишутOrderService, а не «the orders service»: ревьюер ищет грепом, и точное совпадение экономит ему минуту.`created_at`в бэктиках читается однозначно; без них читатель тратит долю секунды на разбор, где слово, а где имя колонки. - Ссылки вместо пересказа.
See the discussion in #4090лучше трёх абзацев реконструкции. Если решение архитектурное, его место — в ADR, а PR только ссылается (см. архитектурные решения).
Типичные ошибки русскоязычных инженеров именно в этом жанре
Общий разбор ошибок будет в отдельной главе, здесь — только то, что бьёт по коммитам и PR. Первое место с большим отрывом занимает прошедшее время в заголовке, разобранное выше. Второе — ложные друзья, которые в этом жанре встречаются постоянно:
| Пишут | Имеют в виду | Что это значит на самом деле | Нужное слово |
|---|---|---|---|
actual behaviour |
актуальное, текущее | фактическое (в противопоставлении ожидаемому) | current |
realize the feature |
реализовать | осознать | implement |
control the value |
контролировать, проверять | управлять, регулировать | check, verify, monitor |
the decision of the problem |
решение проблемы | решение как выбор | solution |
it works normal |
нормально | обычно, штатно (наречие — normally) |
as expected, fine |
eventually it fails |
возможно | в конце концов | possibly, in some cases |
accurate migration |
аккуратная | точная | careful, safe |
pretend to be a fix |
претендует | притворяется | is intended as |
Неисчисляемые существительные. information, feedback, software, refactoring, progress, research не имеют множественного числа и не берут артикль a. I have some feedbacks — самая узнаваемая ошибка русскоязычного автора после Added.
Пунктуация вежливости. В английском запятая после please в начале предложения не ставится: Please review the migration first. Мелочь, но она в каждом втором PR. Туда же восклицательные знаки и капслок: DO NOT MERGE!!! читается как крик, а не как акцент, — для этого есть draft PR и метка blocked.
Пассив и «мы». It was decided to use a queue — кем решено? Some changes were made — кем? Английский инженерный текст активнее русского: We chose a queue because… или просто Uses a queue to smooth the write spikes. О том, почему инженерный английский проще разговорного, — в главе про ясность.
Present perfect там, где он не нужен. I have added the index and I have updated the tests — грамматически верно, но для описания изменения избыточно. Adds the index, updates the tests.
Прямые кальки предлогов. in the project вместо on the project, on the picture вместо in the screenshot, according to the task вместо per JIRA-4127 или as described in JIRA-4127. Предлоги учатся только чтением, поэтому — снова git log чужих проектов.
Let's в описании. Let's rewrite the module звучит как приглашение к совместному действию, а не как описание сделанного. В описании PR — Rewrites the module.
Инструменты, которые снимают половину проблем
- commitlint (commitlint.js.org) — проверка формата заголовка в pre-commit и в CI. Не проверяет английский, но убирает точки, длину и прошедшее время (через кастомные правила).
- codespell (github.com/codespell-project/codespell) — ловит опечатки в коде, комментариях и сообщениях коммитов. Дёшево и почти без ложных срабатываний.
- Vale (vale.sh) — линтер прозы с готовыми правилами по Google developer documentation style guide и Microsoft Writing Style Guide. Ставится на описания PR и документацию; для коммитов избыточен. Грамматику в редакторе закрывает LanguageTool с плагинами для VS Code и JetBrains.
- LLM как редактор, а не как переводчик. Разница принципиальная. Просьба «переведи на английский» даёт текст, который читается как перевод: длинные предложения, лишние вводные, кальки. Работает другое: написать самому как получится и попросить конкретную правку — «rewrite this commit subject in the imperative mood, under 50 characters, keep the identifier names unchanged» или «point out the three least natural phrases here and explain why». Так вы ещё и учитесь, а не получаете текст, который не сможете защитить на созвоне.
Автоматика не заменяет чтения, но связка «commitlint + codespell» в CI закрывает форму и оставляет вам работу над смыслом.
Мини-практикум
Переписать не глядя в разбор, потом сверить.
| Было | Стало | Почему |
|---|---|---|
Fixed bug with orders. |
Fix duplicate orders on webhook retry |
императив, без точки, назван дефект |
Add possibility to export in Excel |
Allow exporting orders to XLSX |
allow + -ing вместо кальки possibility |
Refactoring + small fixes |
два коммита: Extract PriceCalculator from OrderService и Fix rounding in tax calculation |
один коммит — одно изменение |
Update libs |
Bump axios to 1.7.4 for CVE-2024-XXXX |
bump + причина |
Delete not needed code |
Remove the unused legacy import path |
unused вместо кальки not needed |
Change behaviour of cache for better performance |
Cache product prices for 5 minutes |
конкретика вместо обещания |
Please, look at my PR, it's urgent!!! |
This blocks the Friday report — happy to walk through it on a call if that is faster. |
факт вместо давления, предложен выход |
Чек-лист перед отправкой
- Заголовок влезает в 50–72 символа, работает с «If applied, this commit will…», без точки в конце, первое слово — глагол в императиве.
- Заголовок совпадает по стилю с последними тридцатью коммитами репозитория.
- Если «почему» неочевидно — есть тело через пустую строку, строки не длиннее 72 символов.
- Тело объясняет причину и последствия, а не пересказывает дифф.
- Трейлеры на месте:
Fixesтолько там, где задача действительно закрывается. - В описании PR есть What / Why / How to verify, а для рискованных изменений — Rollback.
- Идентификаторы написаны точно как в коде и обёрнуты в бэктики; ни одного
actualв значении «текущий», ни одногоfeedbacks, ни одной запятой послеplease. - Заголовок PR не стыдно увидеть в main после squash-merge.
Мини-итог
Коммиты и pull request — тот случай, когда высокий уровень английского не нужен, а нужна дисциплина формы. Императив вместо прошедшего времени, глагол вместо существительного, факт вместо оценки, «почему» вместо «что», конкретика вместо вежливого давления. Пять привычек, каждая проверяется за секунду, и вместе они дают текст, по которому вас читают как инженера, а не как человека, который переводит с русского. Дальше по возрастанию сложности идут тексты, где формы меньше, а свободы больше: описания задач, баг-репорты, переписка. Форма всё ещё помогает, но думать придётся больше.
Источники
- Git — SubmittingPatches — первоисточник правила об императиве.
- Linux kernel — Submitting patches — трейлеры, структура тела, Signed-off-by.
- Chris Beams. How to Write a Git Commit Message — семь правил и тест «If applied…».
- Conventional Commits 1.0.0 — спецификация формата.
- Google. Writing good CL descriptions — как формулировать описание изменения.
- GitHub Docs — Linking a pull request to an issue — какие ключевые слова закрывают задачу.
- curl — CONTRIBUTE.md — образцовые требования к сообщениям коммитов в живом проекте.
- Google developer documentation style guide и Microsoft Writing Style Guide — что считать нормой тона.
- commitlint, codespell, Vale — автоматические проверки.
Что дальше
Задачи и баг-репорты: как описать, чтобы поняли с первого раза — следующий шаг по свободе формы: там нет 50 символов и шаблона из четырёх глаголов, зато есть читатель, который должен воспроизвести проблему по вашему тексту, не задав ни одного вопроса.