From f0c7fe0c745349d907f2e8a6bad04c5ade4e0cae Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 7 Sep 2026 07:31:08 -0400 Subject: [PATCH 1/4] =?UTF-8?q?docs(labs):=20=D1=84=D0=B0=D0=BA=D1=83?= =?UTF-8?q?=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=B8=D0=B2=D1=8B=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=83=D1=8E=20=D0=BF=D0=B0=D0=BF?= =?UTF-8?q?=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - расположение файла должно отличать задания основной программы от факультативов. - Что: - существующее ТЗ Superset перенесено в `docs/labs/optional/`, ссылки обновлены. - правило размещения записано в `AGENTS.md`. - выбор формы витрин объяснен учебной целью стенда. - Проверка: - `git diff --check`. - проверены все упоминания `superset-dashboard` и относительные ссылки. --- AGENTS.md | 11 +++++----- docs/architecture/dm.md | 21 +++++++++---------- docs/labs/README.md | 5 +++-- .../labs/{ => optional}/superset-dashboard.md | 2 +- docs/research/2026-08-31-labs-inventory.md | 4 ++-- docs/specs/2026-09-01-labs-program.md | 2 +- 6 files changed, 23 insertions(+), 22 deletions(-) rename docs/labs/{ => optional}/superset-dashboard.md (99%) diff --git a/AGENTS.md b/AGENTS.md index 5d97c7b..383dfa7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -157,14 +157,15 @@ Airflow) и названия из кода. Если для понятия ес дефис (`clickstream-event.md`). Ни даты, ни номера: документ не событие истории, а текущее описание живого формата — и собирается из кода, а не пишется руками. -- Имена файлов в `docs/labs/` — `S-N-slug.md`: номер ступени, номер задания - внутри ступени и слаг строчными латинскими буквами через дефис - (`1-2-top-pages-daily.md`), индекс папки — `README.md`. Здесь живут ТЗ +- Имена файлов основной программы в `docs/labs/` — `S-N-slug.md`: + номер ступени, номер задания внутри ступени и слаг строчными латинскими + буквами через дефис (`1-2-top-pages-daily.md`). Здесь живут ТЗ учебных заданий. Номера повторяют порядок программы из `README.md`, чтобы папка читалась как оглавление; даты нет: задание — живой документ и правится вместе с тем, о чем говорит. Слаг называет предмет задания. - Факультативы порядка не имеют и живут под одним слагом - (`superset-dashboard.md`). + Факультативы живут в `docs/labs/optional/`, без номеров, под одним слагом + (`superset-dashboard.md`). Общий индекс обеих частей — `docs/labs/README.md`; + отдельного индекса в `optional/` нет. - Имена файлов в `docs/handbook/` — слаг строчными латинскими буквами через дефис, индекс папки — `README.md`. Здесь живет путеводитель по стенду: учебные главы для менти. Ни дат, ни номеров: глава — живой документ и diff --git a/docs/architecture/dm.md b/docs/architecture/dm.md index 452a68a..e3a4442 100644 --- a/docs/architecture/dm.md +++ b/docs/architecture/dm.md @@ -7,20 +7,19 @@ ## Форма: по зерну, не по цене запроса -Витрина, меняющая зерно источника — дневной агрегат, сверка по заказам, — +На этом стенде форма витрины выбрана ради учебной задачи. Витрина, меняющая +зерно источника — дневной агрегат, сверка по заказам, — материализуется: `dm.<имя>_rep` / `_dist` и публичное `dm.<имя>_v` с явным списком колонок, по общим [конвенциям хранилища](storage.md). Наполняет её -`dm_transform`; ритм и механика пересборки — ниже. Витрина, сохраняющая зерно -источника — переименование, обогащение строки, — остаётся обычным -представлением: копию событий ради других имён колонок не держит ни один -боевой склад. +`dm_transform`; ритм и механика пересборки — ниже. Так менти учится пересобирать +агрегат конвейером. Витрина, сохраняющая зерно источника — переименование, +обогащение строки, — остаётся обычным представлением и не копирует события. -Правило выбрано учебной целью, а не ценой запроса. На объёмах стенда +Цена запроса здесь не определяет форму. На объёмах стенда представлениями обошлось бы всё: условия сквозь цепочку представлений проходят ([исследование](../research/2026-08-22-view-predicate-pushdown.md)). -Но пересборка агрегатов конвейером — производственный паттерн, ради -которого стенд и строится, а пара «таблица рядом с представлением» -показывает: форма — решение по роли витрины, у одного слоя законны обе. +Пара «таблица рядом с представлением» показывает, что форма зависит от роли +витрины: в одном слое уместны обе формы. ## Кто строит: стенд и менти @@ -47,8 +46,8 @@ Дашборд этапа 8 стоит только на витринах стенда: приёмка этапа не зависит от выполненных домашек. Свой дашборд по витринам менти — -[необязательная лаба](../labs/superset-dashboard.md) после этапа 8. Устройство -стендового дашборда — в [доке зоны Superset](superset.md). +[необязательная лаба](../labs/optional/superset-dashboard.md), доступная после +ступени 2. Устройство стендового дашборда — в [доке зоны Superset](superset.md). ## Ритм пересборки diff --git a/docs/labs/README.md b/docs/labs/README.md index 46e2315..822128b 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -122,8 +122,9 @@ DM](../architecture/dm.md). ### Superset и BI -Открывается после ступени 2. [Свой дашборд в Superset](superset-dashboard.md): -свои объекты BI живут отдельно от стендовых. +Открывается после ступени 2. +[Свой дашборд в Superset](optional/superset-dashboard.md): свои объекты BI живут +отдельно от стендовых. ### Инженерия стенда diff --git a/docs/labs/superset-dashboard.md b/docs/labs/optional/superset-dashboard.md similarity index 99% rename from docs/labs/superset-dashboard.md rename to docs/labs/optional/superset-dashboard.md index d3081f4..c488cbb 100644 --- a/docs/labs/superset-dashboard.md +++ b/docs/labs/optional/superset-dashboard.md @@ -23,7 +23,7 @@ Не добавляйте графики в стендовый дашборд и не правьте его датасеты. При инициализации Superset импорт с `--overwrite` заменит ручные правки стендовых объектов файлами из репозитория. `make up` свой дашборд не стирает; срок жизни -таких объектов описан в [доке Superset](../architecture/superset.md), «Импорт +таких объектов описан в [доке Superset](../../architecture/superset.md), «Импорт при каждой инициализации». Заведите отдельные датасеты и графики, соберите из них свой дашборд. diff --git a/docs/research/2026-08-31-labs-inventory.md b/docs/research/2026-08-31-labs-inventory.md index e4bce60..2204dbb 100644 --- a/docs/research/2026-08-31-labs-inventory.md +++ b/docs/research/2026-08-31-labs-inventory.md @@ -116,7 +116,7 @@ **17. Свой дашборд в Superset (готовая необязательная лаба).** Урок: пользовательские объекты BI хранятся отдельно от стендовых и переживают перезапуск. Стендовые объекты при импорте перезаписываются. -Материал: `docs/labs/superset-dashboard.md`, `docs/architecture/superset.md`. +Материал: `docs/labs/optional/superset-dashboard.md`, `docs/architecture/superset.md`.\ Форма: построить свое. Проверка: глазами — счетного инварианта у раскладки панелей нет. @@ -276,4 +276,4 @@ STG и прием событий нельзя отдавать в форме «удали и перепиши»: код там служит несущей частью стенда. Для этих участков подходят расследования и форма «собери рядом». -У всех счетных проверок остается общая дыра — раскладка по шардам. Поэтому пересчет ключа по `hostName()` должен входить в ТЗ каждого вычитания отдельной строкой. \ No newline at end of file +У всех счетных проверок остается общая дыра — раскладка по шардам. Поэтому пересчет ключа по `hostName()` должен входить в ТЗ каждого вычитания отдельной строкой. diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index a0380b8..424b87d 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -172,7 +172,7 @@ Открывается после ступени 2 основной программы. - **Свой дашборд.** Готовая лаба - [`docs/labs/superset-dashboard.md`](../labs/superset-dashboard.md) учит + [`docs/labs/optional/superset-dashboard.md`](../labs/optional/superset-dashboard.md) учит хранить пользовательские объекты отдельно от стендовых. - **Почему дашборд не видит объект на второй ноде.** Забытый `ON CLUSTER` может остаться незаметным до тех пор, пока дашборд не обратится ко второй ноде. -- 2.54.0 From af3f23f9ea2a2b7cdfada179466acd4b727cb523 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 7 Sep 2026 07:42:40 -0400 Subject: [PATCH 2/4] =?UTF-8?q?docs(dm):=20=D0=B8=D0=B7=D0=BB=D0=BE=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - объяснение формы витрин читалось искусственно и использовало неуместное слово «менти». - Что: - выбор таблицы или представления объяснён через конкретные действия в задании. - слово «менти» убрано из dm.md и AGENTS.md. - описание факультатива и ссылка на раздел DM приведены к новому тексту. - Проверка: - git diff --check. - проверено отсутствие слова «менти» в изменённых документах и наличие целей ссылок. --- AGENTS.md | 10 +++--- docs/architecture/dm.md | 72 ++++++++++++++++++++++------------------- docs/labs/README.md | 8 ++--- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 383dfa7..204b153 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,15 +8,15 @@ Стенд строится по спеке [«Боевой реализм стенда (v2)»](docs/specs/2026-07-30-stand-v2-realism.md). -Отсюда вопрос к любой доработке — коду, проверке, документу: **чему на ней -научится менти?** Ответ называется одной фразой до того, как писать. Не +Отсюда вопрос к любой доработке — коду, проверке, документу: **чему она +учит?** Ответ называется одной фразой до того, как писать. Не складывается фраза — это вопрос владельцу, а не строчка кода: проверки в этом репозитории однажды уже переросли продукт. **Усложнение не бесплатно, и платит за него учебная ценность.** Чем сложнее -стенд, тем хуже он как учебный материал: внимание менти конечно, и каждая +стенд, тем хуже он как учебный материал: внимание читателя конечно, и каждая конструкция, которую он обязан расшифровать по дороге к уроку, списывается с -этого счёта. Поэтому вопрос выше имеет вторую половину: **что менти платит, +этого счёта. Поэтому вопрос выше имеет вторую половину: **что читатель платит, чтобы это прочесть, и что получает взамен?** Ответ «вопрос — и ничего» означает, что писать не нужно. @@ -168,7 +168,7 @@ Airflow) и названия из кода. Если для понятия ес отдельного индекса в `optional/` нет. - Имена файлов в `docs/handbook/` — слаг строчными латинскими буквами через дефис, индекс папки — `README.md`. Здесь живет путеводитель по стенду: - учебные главы для менти. Ни дат, ни номеров: глава — живой документ и + учебные главы для читателя. Ни дат, ни номеров: глава — живой документ и правится вместе с тем, что описывает. Исходники интерактивной карты лежат подпапкой `architecture-map/`, а ее рендер — файлом `architecture-map.html` рядом с главами. diff --git a/docs/architecture/dm.md b/docs/architecture/dm.md index e3a4442..4dc98f7 100644 --- a/docs/architecture/dm.md +++ b/docs/architecture/dm.md @@ -5,23 +5,27 @@ колонок каждой витрины задаёт раздел 7 мастер-спеки; здесь — форма, наполнение и контроль. -## Форма: по зерну, не по цене запроса +## Таблица или представление -На этом стенде форма витрины выбрана ради учебной задачи. Витрина, меняющая -зерно источника — дневной агрегат, сверка по заказам, — -материализуется: `dm.<имя>_rep` / `_dist` и публичное `dm.<имя>_v` с явным -списком колонок, по общим [конвенциям хранилища](storage.md). Наполняет её -`dm_transform`; ритм и механика пересборки — ниже. Так менти учится пересобирать -агрегат конвейером. Витрина, сохраняющая зерно источника — переименование, -обогащение строки, — остаётся обычным представлением и не копирует события. +Дневной агрегат и сверка по заказам меняют зерно источника. На этом стенде такие +результаты хранятся в `dm.<имя>_rep` / `_dist` и читаются через публичное +представление `dm.<имя>_v` с явным списком колонок. Имена и устройство объектов +следуют общим [конвенциям хранилища](storage.md). Таблицы наполняет +`dm_transform`; ритм и механика пересборки описаны ниже. В задании этот путь +приходится пройти целиком: рассчитать агрегат, загрузить его и включить +обновление в конвейер. -Цена запроса здесь не определяет форму. На объёмах стенда -представлениями обошлось бы всё: условия сквозь цепочку представлений -проходят ([исследование](../research/2026-08-22-view-predicate-pushdown.md)). -Пара «таблица рядом с представлением» показывает, что форма зависит от роли -витрины: в одном слое уместны обе формы. +Переименование колонок и обогащение строки зерно не меняют. Такие витрины на +стенде остаются обычными представлениями и не хранят вторую копию событий. -## Кто строит: стенд и менти +На объёмах стенда все витрины могли бы остаться представлениями: условия +проходят сквозь их цепочку +([исследование](../research/2026-08-22-view-predicate-pushdown.md)). +Мы все равно оставили материализованные агрегаты ради практики: без них в +программе не было бы загрузки и пересборки витрин. Представления дают другой +случай: преобразование строк на прежнем зерне. + +## Что готово в стенде, а что остаётся заданием Стенд привозит готовыми витрины трёх сюжетов дашборда этапа 8 и инфраструктуру контроля: @@ -34,20 +38,20 @@ [картой идентичностей](dds/identity.md); - `dq_summary` — сводка проверок качества (ниже). -Остальные витрины спеки — учебные задания: менти строит их сам по ТЗ с -образцом. Разминка — представление `events_enriched_v` (зерно события), -затем агрегаты `top_pages_daily`, `session_overview` и `dq_errors_daily`, -последней — самая содержательная `utm_effectiveness` с уроком `declared_*`. -Построенная витрина встаёт в конвейер руками менти: финальный пункт ТЗ -каждого агрегата — добавить задачу своей витрины в `dm_transform`. До этого -конвейер о ней не знает и остаётся зелёным, после — витрина пересобирается -наравне со стендовыми. У разминочного представления такого пункта нет: -пересобирать нечего. Тексты заданий — в [`docs/labs/`](../labs/README.md). +Остальные витрины спеки нужно построить в учебных заданиях по готовому ТЗ и +образцу. Разминка — представление `events_enriched_v` (зерно события), затем +агрегаты `top_pages_daily`, `session_overview` и `dq_errors_daily`, последней — +самая содержательная `utm_effectiveness` с уроком `declared_*`. В финале +каждого задания с агрегатом его задачу нужно добавить в `dm_transform`. До +этого конвейер о витрине не знает, после — пересобирает её вместе со +стендовыми. Представление из разминки добавлять некуда: его не нужно +пересобирать. Тексты заданий лежат в [`docs/labs/`](../labs/README.md). -Дашборд этапа 8 стоит только на витринах стенда: приёмка этапа не зависит -от выполненных домашек. Свой дашборд по витринам менти — -[необязательная лаба](../labs/optional/superset-dashboard.md), доступная после -ступени 2. Устройство стендового дашборда — в [доке зоны Superset](superset.md). +Дашборд этапа 8 читает только готовые витрины стенда, поэтому его приёмка не +зависит от домашних заданий. Отдельный дашборд по учебным витринам можно +собрать в [факультативном задании](../labs/optional/superset-dashboard.md), +доступном после ступени 2. Устройство стендового дашборда описано в +[справочнике Superset](superset.md). ## Ритм пересборки @@ -214,8 +218,8 @@ business_key`. ## Даг самопроверки -Домашки контролирует `homework_check` — отдельный даг без расписания: менти -запускает его рукой. По задаче на каждое из +Домашние задания проверяет `homework_check` — отдельный даг без расписания, +который запускают вручную. В нём есть задача для каждого из [пяти заданий по витринам](../labs/README.md). Задача красная, пока витрины нет или счёт не сходится, и зелёная, когда построено верно; сообщение об ошибке говорит, что не так и куда смотреть. Проверки — инварианты против источников витрины, @@ -223,8 +227,8 @@ business_key`. лабы, а не история качества конвейера. Приём проверен на соседнем учебном стенде Airflow + Greenplum: -валидационный даг с группами проверок по слоям давал менти обратную связь -по каждому шагу домашки, не трогая рабочий конвейер. +валидационный даг с группами проверок по слоям давал обратную связь по каждому +шагу задания, не трогая рабочий конвейер. ## Отклонённые варианты @@ -232,7 +236,7 @@ business_key`. производственный паттерн слоя — пересборка агрегатов — остался бы невидим, а `dm_transform` — без работы. - **Материализовать всё.** Копия событий ради переименования колонок — - антипаттерн, который менти увёз бы в бой. + лишняя; такой пример приучал бы без нужды дублировать исходные данные. - **Гибрид по цене запроса.** Выбор формы по стоимости, которой на объёмах стенда нет: сторож без настоящей опасности. - **Проверки домашек в конвейерном звене DQ.** Конвейер стоял бы красным до @@ -241,8 +245,8 @@ business_key`. - **«Жёлтый» статус недостроенной витрины.** У задачи Airflow нет такого состояния; изображать его логами — семантика, которую пришлось бы расшифровывать. Красное «ещё не построено» честнее. -- **Самопроверка пишет в `dq_summary`.** Попытки менти смешались бы с - историей качества конвейера. +- **Самопроверка пишет в `dq_summary`.** Результаты учебных попыток смешались бы + с историей качества конвейера. - **Окно заказов для `daily_traffic` — прошлое замерзает.** Дни за окном хранили бы число известных на момент закрытия — витрина тихо расходится с картой и протаскивает обратно «знание на дату», отвергнутое развилкой карты diff --git a/docs/labs/README.md b/docs/labs/README.md index 822128b..b3316e1 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -53,7 +53,7 @@ источнику. Создайте `dm.<имя>_rep` / `_dist`, донор `dm.<имя>_stage_rep` / `_stage_dist` и публичное `dm.<имя>_v` с явным списком колонок. При прежнем зерне достаточно представления: таблица дублировала бы данные. Доводы — -раздел «Форма: по зерну, не по цене запроса» [справочника +раздел «Таблица или представление» [справочника DM](../architecture/dm.md). **Ключ шардирования — ваше решение.** Объясните, почему выбранный ключ не @@ -122,9 +122,9 @@ DM](../architecture/dm.md). ### Superset и BI -Открывается после ступени 2. -[Свой дашборд в Superset](optional/superset-dashboard.md): свои объекты BI живут -отдельно от стендовых. +Открывается после ступени 2. В задании +[Свой дашборд в Superset](optional/superset-dashboard.md) вы собираете +отдельный дашборд, не меняя объекты стенда. ### Инженерия стенда -- 2.54.0 From 30ddcf470eab9439ed924e0c932f508d00b9e4b3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 7 Sep 2026 07:51:39 -0400 Subject: [PATCH 3/4] =?UTF-8?q?docs(agents):=20=D1=86=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=B2=D0=B0=D1=8F=20=D0=B0=D1=83=D0=B4=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - агентам нужен точный портрет читателя и проверяемый результат обучения. - Что: - описана подготовка начинающих инженеров данных перед работой со стендом. - зафиксирована цель: освоить общепринятые приёмы и суметь построить похожее хранилище. - добавлена ссылка на программу учебных заданий. - Проверка: - git diff --check. --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 204b153..8baa396 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,13 @@ Стенд строится по спеке [«Боевой реализм стенда (v2)»](docs/specs/2026-07-30-stand-v2-realism.md). +Стенд рассчитан на начинающих инженеров данных. Они недавно изучили ClickHouse, +но почти не работали с ним руками. Здесь они разбирают и достраивают хранилище +небольшого интернет-магазина: проходят путь от загрузки данных до витрин и BI. +Программа дает практику общепринятых инженерных приемов. После нее участники +должны уметь построить похожее хранилище без готового образца. Состав и порядок +работы задает [программа учебных заданий](docs/specs/2026-09-01-labs-program.md). + Отсюда вопрос к любой доработке — коду, проверке, документу: **чему она учит?** Ответ называется одной фразой до того, как писать. Не складывается фраза — это вопрос владельцу, а не строчка кода: проверки в этом -- 2.54.0 From 8771e2d675050e12c7291571e957142248b5dc30 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Mon, 7 Sep 2026 07:53:02 -0400 Subject: [PATCH 4/4] =?UTF-8?q?docs(agents):=20=D0=BD=D0=B0=D0=B7=D0=BD?= =?UTF-8?q?=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0=20=D1=83=D1=82=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - эксплуатационные улучшения не должны становиться самостоятельной целью учебного проекта. - Что: - поддержка стенда ограничена сохранением учебного пути и учебными задачами. - зафиксировано отсутствие отдельной эксплуатационной ценности. - Проверка: - git diff --check. --- AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 8baa396..4b11367 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,10 @@ должны уметь построить похожее хранилище без готового образца. Состав и порядок работы задает [программа учебных заданий](docs/specs/2026-09-01-labs-program.md). +Стенд существует только ради обучения. Его поддержка имеет смысл, когда +сохраняет возможность пройти программу или сама становится учебной задачей. +Отдельной эксплуатационной ценности у стенда нет. + Отсюда вопрос к любой доработке — коду, проверке, документу: **чему она учит?** Ответ называется одной фразой до того, как писать. Не складывается фраза — это вопрос владельцу, а не строчка кода: проверки в этом -- 2.54.0