Перейти к содержимому

14. Диффы

Каждая модель Archlang живёт в git. Ветки — это будущие состояния, коммиты — архитектурные решения, рабочее дерево — настоящее. Это вы получаете даром. Интересная часть — как Archlang превращает построчные диффы git в структурные диффы — диффы, говорящие о модулях, интерфейсах и процессах, а не о добавленных и удалённых строках.

Эта глава о том, как это работает, что с этим можно делать и в чём ограничения.

Что такое структурный дифф

Дифф git двух файлов .arch показывает добавленные и удалённые строки. Это полезно для чтения файла, но бесполезно для чтения архитектуры. Переименуйте модуль из Payments в PaymentsService, и дифф git покажет:

service #pay001 Payments {
service #pay001 PaymentsService {

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

Структурный дифф говорит примерно так:

  • Переименовано: PaymentsPaymentsService (тот же #pay001).
  • Добавлена метка: criticality: High на PaymentsService.
  • Добавлен интерфейс: PaymentsService.Void.
  • Добавлен модуль: FraudCheck (#frd001).
  • Удалён модуль: LegacyBilling (#leg001).
  • Изменён процесс: BasicPayment — вставлен один шаг.

Глава 3 прошлась по одному из таких диффов целиком. Эта глава фокусируется на механике и инструментарии.

Как сопоставляется идентичность

Движок диффа сопоставляет старое с новым в таком порядке:

  1. По стабильному идентификатору. Всё, что имеет #id, сопоставляется по идентификатору. Модуль с #pay001 в старом файле сопоставляется с модулем #pay001 в новом, независимо от имени. Здесь определяются переименования.
  2. По структурному пути внутри стабильного родителя. Фасеты и интерфейсы не несут идентификаторов (Глава 13). Их идентичность — точечный путь внутри родителя, который их несёт: Payments.Authorize сопоставляется с PaymentsService.Authorize, потому что родитель #pay001 сопоставился первым.
  3. По эвристикам формы контракта. Внутри сопоставленного родителя интерфейсы с разными именами проверяются на схожесть — вид, поля, описание, схожесть имён. Сильные совпадения сообщаются как переименования; слабые откатываются к удалению плюс добавлению.

Специальные проекции диффа

Режим диффа просмотрщика рендерит структурную дельту прямо на диаграмме. Интерфейс просмотрщика предлагает несколько сохранённых проекций диффа:

  • Было / Стало — добавленные узлы окрашиваются в зелёный, удалённые в красный, изменённые в жёлтый.
  • Что изменилось — только список, без диаграммы, просто маркированная форма структурной дельты.
  • Новые зависимости — фокус на рёбрах, введённых диффом. Полезно, когда рефакторинг добавляет вызовы между командами и хочется убедиться, что они намеренные.
  • Удалённые зависимости — обратное. Показывает то, что раньше общалось, а теперь нет.

Каждая — это просто настроенный рендер диффа; движок один и тот же.

Что сравнивается

У диффа две стороны. Это могут быть:

  • Две git-ревизии в одной ветке — например, «что изменилось в последнем коммите?».
  • Две ветки — что фича-ветка вводит поверх main.
  • Рабочее дерево против HEAD — незакоммиченные изменения.
  • Два произвольных каталога — пример из Главы 3.

Все четыре сводятся к двум разобранным снимкам; один и тот же движок их сравнивает. Разные поверхности инструментария (размещённый просмотрщик, плагины редакторов, скрипты, использующие основную библиотеку диффа) предоставляют разные удобные точки входа для выбора двух сторон.

Что заменяет TO-BE / AS-IS

Старые средства моделирования (RSM, Sparx) используют поля-маркеры — state: as-is, state: to-be, new, changed, existing — чтобы указать, что модель содержит и текущую, и предлагаемую структуру. Archlang в них не нуждается.

Эту роль играют ветки. Ваша основная ветка — это текущая архитектура. Фича-ветка — это предлагаемая архитектура. Дифф между ними — «что изменится, если мы внедрим это предложение». Ревьюеры видят структурную дельту и принимают решение.

Никаких флагов. Никакой второй копии того же модуля с другими аннотациями. Нет риска забыть удалить маркер state: to-be после внедрения.

Ограничения

  • Переименования интерфейсов вместе со сменой вида могут победить эвристику. command Authorizequery Validate в одном коммите выглядит как удаление плюс добавление. Смягчите, разделив изменение на два коммита (сначала переименование, потом смена вида).
  • Перемещения интерфейсов между модулями в настоящее время трактуются как удаление плюс добавление. Если вы переместите OrderEvents из Orders в EventBus, дифф скажет, что одно событие удалено, а другое добавлено. Будущая итерация может отслеживать такие случаи.
  • Переименования целого пакета требуют ручной заботы. Имя пакета в package.archspace не несёт идентификатора; если переименовать acme.shop в acme.commerce, загрузчик трактует результат как новый пакет. На практике большинство переименований происходит в каталогах и путях зависимостей, где разрешение зависимостей по пути это допускает.

Стабильность при рефакторингах

Поскольку движок диффа уважает стабильные идентификаторы и точечные пути, крупные рефакторинги остаются ревьюабельными:

  • Разделение модуля на два: переместите половину интерфейсов в новый модуль с новым идентификатором. Дифф покажет: один интерфейс удалён из OldModule, добавлен один новый модуль, содержащий эти интерфейсы. Ревьюеры видят перемещение; эвристика по форме интерфейса связывает перемещённые интерфейсы с их прежними определениями.
  • Реорганизация вложенности: меняем service Orders { ... } на service Orders in CommerceSystem { ... }. Идентификатор модуля остаётся прежним; дифф сообщает о смене родителя, а не об удалении.
  • Переименование меток: меняем domain: Payments на domain: PaymentDomain во многих файлах. Дифф сообщает об изменениях значений полей по каждому модулю; структурная форма не затронута.

За пределами просмотрщика

Движок диффа доступен как функция библиотеки в @archlang/core. Конвейеры и инструменты ревью потребляют его структурированный вывод — список переименований, добавлений, удалений и модификаций — и подают в:

  • Автоматизацию ревью кода (блокировать PR, вводящие межкомандные зависимости без согласования).
  • Записи архитектурных решений (автогенерировать разделы «что изменилось» в ADR).
  • Отчёты о соответствии требованиям (любая модификация, затрагивающая модуль с security.zone: PCI, помечается для проверки).

Контрольная точка для повседневного читателя

Если вы потребляете только виды, определённые вашей стандартной библиотекой или платформенной командой, у вас уже есть полная картина:

  • Вы знаете, что такое модуль, интерфейс, фасет, процесс и проекция.
  • Вы знаете, как поля, метки и описания их описывают и классифицируют.
  • Вы знаете, как пакеты, стабильные идентификаторы и диффы делают изменения управляемыми.

Здесь можно остановиться. Оставшиеся главы посвящены определению метамодели — созданию собственных видов, управлению тем, как распространяются их поля, и созданию пользовательских виджетов. Они существенны для платформенных команд и авторов расширений языка. Они необязательны для прикладных команд, потребляющих виды стандартной библиотеки.

Резюме

  • Дифф Archlang структурный — он говорит о переименованных модулях и добавленных интерфейсах, а не о добавленных строках.
  • Стабильные идентификаторы позволяют диффу распознавать переименования; интерфейсы используют эвристики формы контракта.
  • Просмотрщик поставляет несколько проекций диффа: Было/Стало, Что изменилось, Новые/Удалённые зависимости.
  • Ветки заменяют маркеры TO-BE / AS-IS; ревью PR — это ревью архитектурной дельты.
  • Движок диффа доступен как функция библиотеки для конвейеров и инструментов ревью.

Что дальше

Глава 15: Зачем типы? → — первая глава Части IV и сдвиг мышления, после которого остальной язык обретает смысл.