Единый язык: говорить и писать код словами предметной области
Что такое единый язык DDD и где он должен звучать — от разговора с экспертом до имён классов, событий и тестов; как вырабатывать его через интервью, примеры и глоссарий; как узнать синонимы, жаргон и двусмысленные термины. Главный артефакт урока — глоссарий онлайн-школы, на котором стоит вся программа.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Методист онлайн-школы приходит к разработчику:
— Курс по Python стартует первого ноября. Если человек забронировал место и за двое суток не оплатил, бронь сгорает.
— Понял, — отвечает разработчик. — Крон будет удалять неоплаченные заказы старше 48 часов.
Оба уверены, что договорились. Но «курс» у методиста — конкретный запуск с датой старта, а у разработчика в базе
courses — описание курса без дат. «Бронь сгорает» для методиста значит «место освобождается», а разработчик
удаляет строку — и вместе с ней историю. Через месяц поддержка не может ответить студенту, записывался ли он вообще.
Такие недопонимания стоят дорого, потому что их не видно. Каждый говорит на своём языке и переводит в голове, а при переводе смысл теряется. В уроке DDD.1.1 мы видели, как одно правило о возврате, понятое двумя разработчиками по-разному, дало студентам разные деньги. Лекарство DDD — единый языкединый языкEN ubiquitous languageОбщий язык экспертов и разработчиков, построенный на модели, который используют везде: в разговорах, документах, именах классов и методов, событиях и тестах. Изменение языка — это изменение модели и кода. Встречаются переводы «вездесущий» и «повсеместный язык».: одни и те же слова у экспертов, в документах и в коде.
В этом уроке мы заведём глоссарий доменаглоссарий доменаEN domain glossaryЖивой документ рядом с кодом: термин единого языка, его имя в коде, определение, пример и запрещённые синонимы. Меняется в одном коммите с кодом. онлайн-школы. Им будут пользоваться все следующие уроки программы DDD и программы SDD, поэтому читайте его внимательно — к нему придётся возвращаться.
Что такое единый язык
Заголовок раздела «Что такое единый язык»Определение
Заголовок раздела «Определение»Эрик Эванс описывает единый язык (ubiquitous language) так: возьмите модель предметной области за основу языка и договоритесь всей командой говорить на нём везде — в разговорах, документах, диаграммах и в коде. Английское ubiquitous значит «вездесущий»; в русских текстах встречаются переводы «единый», «вездесущий» и «повсеместный язык». Мы говорим «единый».
Единый язык — не словарь бизнеса «как есть». Эксперты часто говорят расплывчато: «бронь», «заявка», «запись» и «заказ» у них могут означать одно и то же, а могут — разное. Работа команды — договориться о точных словах. Эванс распределяет роли так: эксперты возражают против терминов, которые неудобны или неверно передают смысл, разработчики ищут двусмысленности и противоречия, которые потом сломают код. Язык вырабатывают вместе, и он меняется вместе с моделью.
Где звучит единый язык
Заголовок раздела «Где звучит единый язык»| Где | На едином языке | Без единого языка |
|---|---|---|
| Разговор | «запись ожидает оплаты» | «заказ в нулевом статусе» |
| Задача в трекере | «отменять неоплаченную запись через 48 часов» | «крон чистит orders» |
| Класс | Enrollment, Cohort | Order, UserGroupLink |
| Метод | cohort.enroll(student_id, now) | manager.process(data) |
| Событие | EnrollmentCancelled | order_status_changed |
| Тест | test_enrollment_is_refused_when_no_free_seats | test_case_7 |
| Ошибка | «На потоке python-2026-11 нет свободных мест» | IntegrityError, err: no_slots |
| Промпт ИИ-агенту | «добавь отмену записи на поток по правилам Набора» | «сделай отмену заказа» |
Имя теста из третьего столбца ничего не говорит о бизнесе, а имя из второго — готовое правило. Авторы «Cosmic Python» формулируют проверку так: имена тестов и классов должны быть взяты из языка бизнеса, чтобы код можно было показать коллеге без технического образования, и он подтвердил бы, что поведение описано верно.
Изменение языка — это изменение модели
Заголовок раздела «Изменение языка — это изменение модели»Слова языка и есть модель. Если эксперты начинают различать то, что раньше не различали, — например, «счёт» и «платёж», — модель изменилась, и код должен измениться вместе с ней. Если бизнес переименовал «поток» в «группу» по серьёзной причине, переименовывают всё: глоссарий, классы, события, тесты. «Просто помнить, что Cohort — это теперь группа» нельзя: через полгода в команде будут люди, которые этого не помнят.
У языка есть границы
Заголовок раздела «У языка есть границы»Единый язык однозначен только внутри своей области. В Наборе человек, который учится, — «студент». В Уведомлениях тот же человек — «получатель письма», и его там интересует только адрес. В Оплате говорят о «плательщике» — им может быть и компания, которая оплачивает обучение сотрудника. Попытка заставить все части школы говорить одним словарём ломает язык: слова начинают значить слишком много. Границы, внутри которых язык однозначен, называются ограниченными контекстами — это тема урока DDD.2.1. Глоссарий ниже — язык Набора и соседних частей школы в том объёме, который нужен программе.
Глоссарий домена онлайн-школы
Заголовок раздела «Глоссарий домена онлайн-школы»Понятия
Заголовок раздела «Понятия»| Термин | В коде | Что это | Пример, правила | Не говорим |
|---|---|---|---|---|
| Курс | Course | учебный продукт: тема, программа занятий, цена | «Python для аналитиков». Курс сам не стартует — стартуют его потоки | «продукт», «товар» |
| Поток | Cohort | конкретный запуск курса: дата старта и вместимость | «Python для аналитиков, старт 2 ноября, 30 мест» | «группа», «курс» в смысле запуска |
| Студент | Student | человек, который записывается на потоки и учится | один студент может учиться на нескольких потоках | «пользователь», «клиент», «покупатель» |
| Запись на поток | Enrollment | факт, что студент занимает место в потоке; у записи есть статус | статусы — в таблице ниже | «заказ», «бронь», «заявка», «покупка» |
| Лист ожидания | Waitlist | очередь студентов на поток без свободных мест | кто раньше встал, тому раньше предложат место | «очередь», «резерв» |
| Счёт | Invoice | требование оплатить запись: сумма и срок | выставляется, когда студент записался | «оплата», «чек», «заказ» |
| Платёж | Payment | факт, что деньги по счёту поступили через внешнюю платёжную систему | счёт может быть выставлен, а платежа ещё нет | «оплата», «транзакция» |
| Сертификат | Certificate | документ о прохождении курса | выдаётся, когда студент выполнил условия | «диплом» |
| Деньги | Money | сумма в копейках вместе с валютой | 30 000 руб. — это 3 000 000 копеек в валюте RUB | число с плавающей точкой без валюты |
| Период | Period | интервал времени: начало и конец | даты обучения потока | две отдельные даты «с» и «по» |
Обратите внимание на «оплату»: в разговоре это слово означает то счёт («оплата создана»), то платёж («оплата
прошла»). Поэтому в глоссарии его нет вовсе — вместо него два точных термина. Деньги хранятся в копейках целым числом:
так не возникает ошибок округления вроде . Как устроены Money и Period в коде,
разберём в уроке DDD.3.1.
Статусы записи на поток
Заголовок раздела «Статусы записи на поток»| Статус | В коде | Занимает место? | Когда наступает |
|---|---|---|---|
| ожидает оплаты | PENDING_PAYMENT | да | сразу после записи |
| подтверждена | CONFIRMED | да | платёж по счёту получен |
| отменена | CANCELLED | нет | студент отменил запись или 48 часов прошли без оплаты |
stateDiagram-v2 [*] --> PENDING_PAYMENT : записать PENDING_PAYMENT --> CONFIRMED : платёж получен PENDING_PAYMENT --> CANCELLED : отмена или 48 ч без оплаты CONFIRMED --> CANCELLED : отмена студентом CANCELLED --> [*]
Действия и события
Заголовок раздела «Действия и события»Действия — глаголы, это команды «сделай». События — факты в прошедшем времени, «это случилось». В названии события всегда прошедшее время: событие нельзя отменить, можно только совершить новое действие.
| Действие | В коде | Событие | Имя события |
|---|---|---|---|
| записать студента на поток | enroll | СтудентЗаписан | StudentEnrolled |
| подтвердить запись | confirm | ЗаписьПодтверждена | EnrollmentConfirmed |
| отменить запись | cancel | ЗаписьОтменена | EnrollmentCancelled |
| освободить место | release | МестоОсвободилось | SeatReleased |
| получить платёж (делает платёжная система) | receive | ПлатёжПолучен | PaymentReceived |
| выдать сертификат | issue | СертификатВыдан | CertificateIssued |
| предложить место из листа ожидания | offer | — | — |
| вернуть деньги | refund | — | — |
Свойства тоже договорены: вместимость — capacity, место — seat, старт потока — starts_at, сумма — amount,
валюта — currency, статус — status. Для двух последних действий событий пока нет: понадобятся ли они, решит
Event Storming в уроке DDD.2.3.
Правила Набора на едином языке
Заголовок раздела «Правила Набора на едином языке»Числа — условия учебного примера, а не отраслевой стандарт.
- Записей со статусами «ожидает оплаты» и «подтверждена» не больше вместимости потока.
- Студент не может записаться на один поток дважды.
- Запись на поток закрывается в момент старта потока.
- Неоплаченная запись отменяется через 48 часов после записи.
- При отмене за 7 суток (7 × 24 часа) и более до старта потока — полный возврат, позже — без возврата.
- Освободившееся место предлагается первому в листе ожидания (кто раньше встал), у него 24 часа на подтверждение.
У глоссария всегда есть открытые вопросы — то, что эксперты ещё не решили. Для школы, например: может ли студент, отменивший запись, снова записаться на тот же поток? Что происходит, если платёж пришёл через минуту после автоматической отмены? Что именно подтверждает студент из листа ожидания за 24 часа? Их записывают прямо в глоссарий, чтобы не додумать ответ за экспертов. Часть из них закроем в модулях DDD.2 и DDD.3.
Как хранить глоссарий
Заголовок раздела «Как хранить глоссарий»Глоссарий — файл в репозитории рядом с кодом, например docs/glossary.md, а не страница в забытой вики. Правило
простое: изменение термина и переименование в коде идут в одном коммите и проходят одно ревью. Одна запись
выглядит так:
## Запись на поток (Enrollment)
**Что это:** факт, что студент занимает место в потоке.**Статусы:** ожидает оплаты (PENDING_PAYMENT), подтверждена (CONFIRMED), отменена (CANCELLED).**Правила:** занимают место только «ожидает оплаты» и «подтверждена»; неоплаченная отменяетсячерез 48 часов после записи.**Пример:** Анна записалась на поток «Python для аналитиков, 2 ноября» 1 октября в 10:00.**Не говорим:** заказ, бронь, заявка, покупка.**Контекст:** Набор.**Открытые вопросы:** можно ли записаться снова после отмены?Похожее поле есть и в канве ограниченного контекста от DDD Crew: раздел «Ubiquitous Language» — ключевые термины контекста и их значения. С этой канвой вы поработаете в уроке DDD.2.1.
Как вырабатывать единый язык
Заголовок раздела «Как вырабатывать единый язык»Интервью с экспертами
Заголовок раздела «Интервью с экспертами»Эксперты предметной областиэксперт предметной областиEN domain expertЧеловек, который глубоко знает бизнес-процессы и их правила: методист, менеджер набора, бухгалтер, опытный сотрудник поддержки. Необязательно начальник. школы — методист, менеджер набора, бухгалтер, старший сотрудник поддержки. Несколько правил разговора:
- Просите истории, а не определения. «Расскажите про последний случай, когда студент отменял запись» даёт больше, чем «как у вас устроены отмены?».
- Записывайте точные слова. Не переводите сразу в свои термины: «бронь сгорает» — ценная цитата, даже если в глоссарий попадёт другое слово.
- Уточняйте различия: «чем запись отличается от брони?», «счёт и оплата — одно и то же?».
- Ищите края: «а если платёж пришёл в последнюю минуту?», «а если поток перенесли?».
- Пересказывайте на языке модели: «правильно ли я понял: запись в статусе „ожидает оплаты“ занимает место?» Если эксперт поправляет — модель стала точнее.
Фрагмент такого интервью:
— Как человек попадает на поток?
— Записывается на сайте. Место за ним держится двое суток, пока он не оплатит.
— А если не оплатил?
— Бронь сгорает, место отдаём тому, кто ждёт.
— Кто ждёт — это кто?
— Ну, лист ожидания. Кто раньше встал, тому и предлагаем. У него сутки, чтобы решить.
— А если и он не решил?
— Следующему. Хм, а это у нас где-то записано?..
Из одного разговора — пять кандидатов в глоссарий («поток», «место», «лист ожидания», «отмена» вместо «бронь сгорает», «предложить место») и два правила с числами. А последняя реплика эксперта — частый и очень ценный результат: правило существовало только в головах.
Примеры вместо общих фраз
Заголовок раздела «Примеры вместо общих фраз»Общая формулировка «неоплаченная запись отменяется через 48 часов» кажется ясной, пока не начнёшь подставлять числа. Подход «спецификация на примерах»спецификация на примерахEN specification by exampleСпособ описывать правила конкретными примерами с числами и ожидаемым результатом вместо общих фраз (книга Гойко Аджича, 2011). Примеры вскрывают неясности и затем становятся тестами. (specification by example — так называется книга Гойко Аджича, 2011) предлагает описывать правила конкретными примерами с ожидаемым результатом:
| Поток (вместимость) | Что уже есть | Что происходит | Ожидаемый результат |
|---|---|---|---|
| 2 места | Анна — ожидает оплаты, Борис — подтверждена | Вера записывается | отказ: мест нет; Вере предлагают лист ожидания |
| 2 места | Анна записалась 1 октября в 10:00, не оплатила | наступает 3 октября 10:00 | запись Анны отменена, место освободилось |
| 2 места | то же, Вера первая в листе ожидания | место освободилось | Вере предложено место, у неё 24 часа |
| старт 2 ноября 10:00 | мест много | Глеб записывается 2 ноября в 10:00 | отказ: запись закрыта |
Каждая строка — вопрос к эксперту, на который нельзя ответить «ну, как обычно». Вторая строка сразу рождает новый: а если платёж Анны придёт 3 октября в 10:05? Вернуть деньги или восстановить запись? Пока ответа нет, это открытый вопрос в глоссарии. Позже такие примеры почти дословно становятся тестами.
Удобный формат встречи — карта примеровкарта примеровEN Example MappingКороткая встреча по одной истории (около 25 минут): карточки правил, примеров к каждому правилу и вопросов, на которые пока нет ответа. Технику предложил Мэтт Уинн из сообщества BDD. (Example Mapping) Мэтта Уинна. На стол кладут карточку истории («студент записывается на поток»), под ней — карточки правил, под каждым правилом — карточки примеров, а сбоку — карточки вопросов, на которые никто в комнате не может ответить. Одна история — одна короткая встреча. Много карточек вопросов значит, что историю рано отдавать в разработку.
Примеры можно записывать и в формате «Дано / Когда / Тогда» (его подробно разберёт урок SDD.2.1):
Сценарий: неоплаченная запись отменяется через 48 часов Дано поток «Python для аналитиков» со стартом 2 ноября и вместимостью 2 И Анна записалась на поток 1 октября в 10:00 и не оплатила счёт Когда наступает 3 октября 10:00 Тогда запись Анны отменена И на потоке 2 свободных местаПризнаки беды
Заголовок раздела «Признаки беды»| Признак | Как выглядит в школе | Чем опасен | Что делать |
|---|---|---|---|
| Синонимы | Order в одном модуле, Booking в другом, Enrollment в третьем; в разговоре — «группа» и «поток» | люди думают, что это разные вещи; поиск по коду находит треть мест | выбрать одно слово, остальные внести в «не говорим», переименовать |
| Технический жаргон | OrderManager.process(data), st == 2, flag, handle_item | эксперт не может прочитать код; правило спрятано за техникой | глаголы и существительные домена: cohort.enroll, EnrollmentStatus.CANCELLED |
| Один термин в двух смыслах | «курс» — и описание курса, и его запуск; «оплата» — и счёт, и платёж | баги на стыке: доступ к занятиям открыли, когда счёт выставили, а не когда пришли деньги | развести на два термина: курс и поток, счёт и платёж |
| Слово без определения | «активный студент», «VIP», «скидка» в коде, которых нет в глоссарии | каждый понимает по-своему | спросить экспертов: новое понятие — в глоссарий, случайное — убрать |
| Переводчик в голове | на встрече одни слова, в коде другие | каждое обсуждение требует перевода, ошибки перевода незаметны | язык один — и в разговоре, и в коде |
Последний признак легко проверить на себе: если, рассказывая эксперту о коде, вы переводите («ну, Order — это ваша
запись»), языка ещё нет.
Практика
Заголовок раздела «Практика»Переписываем фрагмент кода на единый язык
Заголовок раздела «Переписываем фрагмент кода на единый язык»Вот фрагмент записи на поток из «старой» школы. Логика здесь простая, но прочитать её может только автор:
class OrderManager: """Работа с заказами."""
def __init__(self): self.orders = []
def process(self, user, group, data): taken = [o for o in self.orders if o["group"] == group["id"] and o["st"] != 2] if data["now"] >= group["start"]: return {"ok": False, "err": "closed"} if len(taken) >= group["max"]: return {"ok": False, "err": "no_slots"} self.orders.append({"user": user, "group": group["id"], "st": 0, "ts": data["now"]}) return {"ok": True}Та же логика на едином языке. Enum из стандартной библиотеки — это перечисление, фиксированный набор именованных
значений: EnrollmentStatus.CANCELLED читается лучше, чем 2, а опечатку в имени Python поймает сразу. Классы с
@dataclass и исключения вы знаете по урокам 0.2.3 и
0.2.4.
from dataclasses import dataclass, fieldfrom datetime import datetimefrom enum import Enum
class EnrollmentStatus(Enum): """Статус записи на поток — ровно три, как в глоссарии.""" PENDING_PAYMENT = "ожидает оплаты" CONFIRMED = "подтверждена" CANCELLED = "отменена"
class NoFreeSeats(Exception): """На потоке нет свободных мест."""
class EnrollmentClosed(Exception): """Запись на поток закрыта: поток уже стартовал."""
@dataclassclass Enrollment: """Запись на поток.""" student_id: str enrolled_at: datetime status: EnrollmentStatus = EnrollmentStatus.PENDING_PAYMENT
@dataclassclass Cohort: """Поток курса: дата старта и вместимость.""" cohort_id: str starts_at: datetime capacity: int enrollments: list = field(default_factory=list)
def free_seats(self): """Свободные места: вместимость минус записи «ожидает оплаты» и «подтверждена».""" taking_seats = [e for e in self.enrollments if e.status != EnrollmentStatus.CANCELLED] return self.capacity - len(taking_seats)
def enroll(self, student_id, now): """Записать студента на поток.""" if now >= self.starts_at: raise EnrollmentClosed(f"Запись на поток {self.cohort_id} закрыта: поток стартовал " f"{self.starts_at:%d.%m.%Y %H:%M}") if self.free_seats() == 0: raise NoFreeSeats(f"На потоке {self.cohort_id} нет свободных мест") enrollment = Enrollment(student_id, enrolled_at=now) self.enrollments.append(enrollment) return enrollment
cohort = Cohort("python-2026-11", starts_at=datetime(2026, 11, 2, 10, 0), capacity=2)now = datetime(2026, 10, 1, 12, 0)anna = cohort.enroll("anna", now)cohort.enroll("boris", now)print("Анна:", anna.status.value, "| свободных мест:", cohort.free_seats())try: cohort.enroll("vera", now)except NoFreeSeats as error: print("Вера:", error)anna.status = EnrollmentStatus.CANCELLED # пока вручную; правила отмены — в модуле DDD.3print("Анна отменила запись | свободных мест:", cohort.free_seats())try: cohort.enroll("gleb", datetime(2026, 11, 2, 10, 0))except EnrollmentClosed as error: print("Глеб:", error)Анна: ожидает оплаты | свободных мест: 0Вера: На потоке python-2026-11 нет свободных местАнна отменила запись | свободных мест: 1Глеб: Запись на поток python-2026-11 закрыта: поток стартовал 02.11.2026 10:00Что поменялось при переписывании:
OrderManager,orders,group→Cohort,enrollments,Enrollment— слова глоссария вместо синонимов;process(user, group, data)→cohort.enroll(student_id, now)— глагол домена вместо жаргона, и сразу видно, кто что делает;st != 2→status != EnrollmentStatus.CANCELLED,max→capacity,no_slots→ исключениеNoFreeSeatsс русским текстом, понятным и поддержке;- докстринги пересказывают правила словами методиста.
Строка anna.status = EnrollmentStatus.CANCELLED — честно помеченная временная мера: кто угодно может поставить
любой статус в обход правил. Как сделать, чтобы правила нельзя было нарушить, — тема агрегатов в уроке DDD.3.2.
Тесты тоже пишутся на едином языке — имя теста формулирует правило, докстринг повторяет его по-русски:
from datetime import datetime
import pytest
from enrollment import Cohort, EnrollmentClosed, NoFreeSeats
STARTS_AT = datetime(2026, 11, 2, 10, 0)
def test_enrollment_is_refused_when_no_free_seats(): """Записей «ожидает оплаты» и «подтверждена» не больше вместимости потока.""" cohort = Cohort("python-2026-11", STARTS_AT, capacity=1) cohort.enroll("anna", datetime(2026, 10, 1, 12, 0)) with pytest.raises(NoFreeSeats): cohort.enroll("boris", datetime(2026, 10, 1, 13, 0))
def test_enrollment_closes_when_cohort_starts(): """Запись закрывается в момент старта потока.""" cohort = Cohort("python-2026-11", STARTS_AT, capacity=30) with pytest.raises(EnrollmentClosed): cohort.enroll("anna", STARTS_AT)Если сохранить код записи в enrollment.py, а тесты — в test_enrollment.py рядом, pytest -q покажет
2 passed. Список имён тестов при этом читается как перечень правил Набора — его можно отправить методисту на проверку.
Задание: проверка языка автоматически
Заголовок раздела «Задание: проверка языка автоматически»Глоссарий полезен, только если код ему следует. Следить за этим глазами на ревью утомительно, поэтому часть проверки
можно автоматизировать. В задании вы напишете маленький «линтер языка»: он разбивает имена классов, методов, полей и
констант на слова, сверяет каждое слово с глоссарием и находит синонимы, жаргон и слова, которых в глоссарии нет.
Синонимы он умеет исправлять сам, а жаргон оставляет человеку: переименовать OrderManager.process осмысленно может
только тот, кто понимает, что этот метод делает.
Функция public_names уже готова: она разбирает исходный код модулем ast из стандартной библиотеки, не выполняя
его, и возвращает публичные имена. Вам нужно написать split_words, classify_word, to_domain_name и audit.
import ast
import re # понадобится в split_words
# Глоссарий домена онлайн-школы: русский термин → имя в коде (урок DDD.1.2)
GLOSSARY = {
"Курс": "Course",
"Поток": "Cohort",
"Студент": "Student",
"Запись на поток": "Enrollment",
"Лист ожидания": "Waitlist",
"Счёт": "Invoice",
"Платёж": "Payment",
"Сертификат": "Certificate",
"Деньги": "Money",
"Период": "Period",
}
GLOSSARY_WORDS = {name.lower() for name in GLOSSARY.values()} # все имена — из одного слова
# Свойства, действия и статусы, о которых договорились с экспертами (тоже из глоссария урока)
DOMAIN_WORDS = {
"capacity", "seat", "start", "starts", "end", "ends", "status", "amount", "kopecks", "currency",
"enroll", "confirm", "cancel", "release", "offer", "issue", "receive", "refund",
"pending", "confirmed", "cancelled", "enrolled", "released", "issued", "received",
}
# Служебные слова, которые можно использовать в любых именах
COMMON_WORDS = {"id", "at", "by", "to", "of", "for", "from", "is", "can", "has", "max", "min",
"count", "total", "free", "default", "days", "hours"}
# Синонимы, которыми эксперты НЕ говорят, → слово глоссария
SYNONYMS = {
"order": "enrollment", "booking": "enrollment", "purchase": "enrollment", "registration": "enrollment",
"group": "cohort", "stream": "cohort",
"user": "student", "client": "student", "customer": "student", "learner": "student",
"bill": "invoice",
"transaction": "payment",
"diploma": "certificate",
"queue": "waitlist",
"slot": "seat", "place": "seat",
"product": "course",
}
# Технический жаргон: эксперт не поймёт, о чём речь
JARGON = {"data", "info", "item", "obj", "record", "row", "entity", "manager", "helper", "handler", "handle",
"process", "proc", "do", "flag", "util", "utils", "tmp", "st", "dt", "ts", "trx", "usr", "grp", "calc"}
KIND_OK = "в глоссарии"
KIND_SYNONYM = "синоним"
KIND_JARGON = "жаргон"
KIND_UNKNOWN = "нет в глоссарии"
TECHNICAL_CODE = '''
MAX_USERS = 30
class Group:
"""Группа курса."""
start_dt: str
max_users: int
def free_slots(self):
...
class OrderManager:
"""Всё про заказы."""
def process(self, user, group, data):
...
def cancel_order(self, order_id):
...
def handle_payment_data(trx):
...
def calc_discount(usr):
...
'''
DOMAIN_CODE = '''
DEFAULT_CAPACITY = 30
class Cohort:
"""Поток курса: дата старта и вместимость."""
starts_at: str
capacity: int
def free_seats(self):
...
def enroll(self, student_id):
...
class Enrollment:
"""Запись на поток."""
status: str
def confirm(self):
...
def cancel(self, cancelled_at):
...
def receive_payment(payment):
...
'''
def split_words(name):
"""Слова идентификатора в нижнем регистре.
snake_case и UPPER_CASE делим по «_», CamelCase — по заглавным буквам:
"EnrollmentStatus" → ["enrollment", "status"], "MAX_USERS" → ["max", "users"],
"starts_at" → ["starts", "at"]. Пустые куски (от «_» в начале имени) пропускаем.
"""
# TODO: ваш код
...
def classify_word(word):
"""Вид слова — одна из констант KIND_OK, KIND_SYNONYM, KIND_JARGON, KIND_UNKNOWN.
1) Приведите слово к нижнему регистру.
2) Слово есть в GLOSSARY_WORDS, DOMAIN_WORDS или COMMON_WORDS → KIND_OK;
в SYNONYMS → KIND_SYNONYM; в JARGON → KIND_JARGON.
3) Не нашли, а слово оканчивается на «s», — повторите шаг 2 для слова без «s»
(множественное число: users → user, seats → seat).
4) Не нашли нигде → KIND_UNKNOWN.
"""
# TODO: ваш код
...
def to_domain_name(name):
"""Заменить в имени синонимы словами глоссария, сохранив стиль имени.
Каждое слово из split_words: синоним → SYNONYMS[слово]; синоним во множественном
числе (orders) → слово глоссария + «s» (enrollments); остальные слова не трогаем —
жаргон автоматически не исправить.
Стиль: всё имя заглавными (MAX_USERS) → слова заглавными через «_»;
первая буква заглавная (OrderStatus) → CamelCase; иначе → snake_case.
"""
# TODO: ваш код
...
def public_names(source):
"""Публичные имена модели: классы, их методы и поля, функции и константы верхнего уровня.
Имена, которые начинаются с «_», пропускаем. Готовая функция: модуль ast разбирает
исходный код в дерево, не выполняя его.
"""
names = []
for node in ast.parse(source).body:
if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
names.append(node.name)
if isinstance(node, ast.ClassDef):
for item in node.body:
if isinstance(item, ast.FunctionDef):
names.append(item.name)
elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
names.append(item.target.id)
elif isinstance(node, ast.Assign):
names.extend(t.id for t in node.targets if isinstance(t, ast.Name))
return [n for n in names if not n.startswith("_")]
def audit(source):
"""Аудит языка: {имя: [(слово, вид), ...]} только для имён, где есть слова не из глоссария.
Для каждого имени из public_names(source) разбейте его на слова (split_words),
определите вид каждого слова (classify_word) и оставьте пары, где вид не KIND_OK.
Слова идут в порядке появления в имени. Имена без замечаний в отчёт не попадают.
"""
# TODO: ваш код
...
print("Слова имени OrderManager:", split_words("OrderManager"))
print("cancel_order →", to_domain_name("cancel_order"))
print("\nЗамечания к «техническому» коду:")
for name, problems in (audit(TECHNICAL_CODE) or {}).items():
print(f" {name:20} " + "; ".join(f"{w} — {kind}" for w, kind in problems))
print("\nЗамечания к коду на едином языке:", audit(DOMAIN_CODE))
Посмотрите на итоговый отчёт: после автоматического переименования остаются OrderManager, process,
handle_payment_data, calc_discount и start_dt. Жаргон требует понять, что делает код. А слово discount —
отдельный случай: это не жаргон и не синоним, а новое понятие, о котором никто не договорился. Правильная реакция —
не придумывать слово самому, а спросить экспертов, есть ли в школе скидки, и если есть — внести термин в глоссарий.
Единый язык, промпты и спецификации
Заголовок раздела «Единый язык, промпты и спецификации»Всё сказанное о людях верно и для ИИ-агентов, только сильнее. Агент выбирает имена по статистике кода, на котором
училась модель, а там чаще всего встречаются Order, User, process_data. Без глоссария код, написанный агентом,
дрейфует к самому распространённому словарю интернета — то есть к синонимам и жаргону из таблицы выше.
Что помогает на практике:
- Глоссарий в контексте агента. Положите
docs/glossary.mdв репозиторий и сошлитесь на него из файла памяти проекта (CLAUDE.md, AGENTS.md — подробно в уроке SDD.1.2): «имена в коде — только из глоссария; новое понятие — сначала вопрос мне». - Промпты и спецификации на едином языке. Сравните «сделай отмену заказа» и «добавь отмену записи на поток: при отмене за 7 суток и более до старта потока — полный возврат, позже — без возврата; событие EnrollmentCancelled». Во втором случае агенту нечего додумывать.
- Проверка, а не надежда. Линтер из задания можно запускать в тестах или в CI: агент, который назвал класс
Booking, получит понятное сообщение и исправится сам. - Новое слово от агента — сигнал. Если агент ввёл
discount_rate, это либо синоним, либо понятие, о котором забыли спросить экспертов. В обоих случаях решает человек.
Проверь себя
Заголовок раздела «Проверь себя»- Единый язык — общий язык экспертов и разработчиков, построенный на модели. Он звучит везде: в разговорах, задачах, именах классов и методов, событиях, тестах, сообщениях об ошибках и промптах. Изменение языка — это изменение модели и кода, одним коммитом с глоссарием.
- Глоссарий онлайн-школы — основа всей программы: десять понятий (Course, Cohort, Student, Enrollment, Waitlist, Invoice, Payment, Certificate, Money, Period), три статуса записи, действия и события, шесть правил Набора и открытые вопросы.
- Язык вырабатывают вместе с экспертами: истории вместо определений, точные слова, пересказ на языке модели, примеры с числами и карта примеров. Примеры потом становятся тестами.
- Признаки беды — синонимы, технический жаргон, один термин в двух смыслах, слова без определения. Часть проверки можно автоматизировать линтером, но жаргон и новые понятия решает человек вместе с экспертами.
- Для ИИ-агента глоссарий — лучший контекст: без него код дрейфует к
Orderиprocess_data, с ним промпты и спецификации не оставляют места для догадок.
Дальше: урок DDD.1.3 — поддомены. Разберём, почему Набор и Обучение — сердце школы, а Оплату и Уведомления лучше купить, где допустим простой CRUD и как описать ядро системы на одной странице.