14. Диффы
Каждая модель Archlang живёт в git. Ветки — это будущие состояния, коммиты — архитектурные решения, рабочее дерево — настоящее. Это вы получаете даром. Интересная часть — как Archlang превращает построчные диффы git в структурные диффы — диффы, говорящие о модулях, интерфейсах и процессах, а не о добавленных и удалённых строках.
Эта глава о том, как это работает, что с этим можно делать и в чём ограничения.
Что такое структурный дифф
Дифф git двух файлов .arch показывает добавленные и удалённые строки. Это полезно для чтения файла, но бесполезно для чтения архитектуры. Переименуйте модуль из Payments в PaymentsService, и дифф git покажет:
service #pay001 Payments {service #pay001 PaymentsService {Ревьюер может это прочитать и понять, что это переименование. Но автоматизированному инструментарию нетривиально сказать «переименование Payments не вводит новых зависимостей» — для этого нужно знать, что это переименование, а не удаление плюс добавление.
Структурный дифф говорит примерно так:
- Переименовано:
Payments→PaymentsService(тот же#pay001). - Добавлена метка:
criticality: HighнаPaymentsService. - Добавлен интерфейс:
PaymentsService.Void. - Добавлен модуль:
FraudCheck(#frd001). - Удалён модуль:
LegacyBilling(#leg001). - Изменён процесс:
BasicPayment— вставлен один шаг.
Глава 3 прошлась по одному из таких диффов целиком. Эта глава фокусируется на механике и инструментарии.
Как сопоставляется идентичность
Движок диффа сопоставляет старое с новым в таком порядке:
- По стабильному идентификатору. Всё, что имеет
#id, сопоставляется по идентификатору. Модуль с#pay001в старом файле сопоставляется с модулем#pay001в новом, независимо от имени. Здесь определяются переименования. - По структурному пути внутри стабильного родителя. Фасеты и интерфейсы не несут идентификаторов (Глава 13). Их идентичность — точечный путь внутри родителя, который их несёт:
Payments.Authorizeсопоставляется сPaymentsService.Authorize, потому что родитель#pay001сопоставился первым. - По эвристикам формы контракта. Внутри сопоставленного родителя интерфейсы с разными именами проверяются на схожесть — вид, поля, описание, схожесть имён. Сильные совпадения сообщаются как переименования; слабые откатываются к удалению плюс добавлению.
Специальные проекции диффа
Режим диффа просмотрщика рендерит структурную дельту прямо на диаграмме. Интерфейс просмотрщика предлагает несколько сохранённых проекций диффа:
- Было / Стало — добавленные узлы окрашиваются в зелёный, удалённые в красный, изменённые в жёлтый.
- Что изменилось — только список, без диаграммы, просто маркированная форма структурной дельты.
- Новые зависимости — фокус на рёбрах, введённых диффом. Полезно, когда рефакторинг добавляет вызовы между командами и хочется убедиться, что они намеренные.
- Удалённые зависимости — обратное. Показывает то, что раньше общалось, а теперь нет.
Каждая — это просто настроенный рендер диффа; движок один и тот же.
Что сравнивается
У диффа две стороны. Это могут быть:
- Две git-ревизии в одной ветке — например, «что изменилось в последнем коммите?».
- Две ветки — что фича-ветка вводит поверх
main. - Рабочее дерево против HEAD — незакоммиченные изменения.
- Два произвольных каталога — пример из Главы 3.
Все четыре сводятся к двум разобранным снимкам; один и тот же движок их сравнивает. Разные поверхности инструментария (размещённый просмотрщик, плагины редакторов, скрипты, использующие основную библиотеку диффа) предоставляют разные удобные точки входа для выбора двух сторон.
Что заменяет TO-BE / AS-IS
Старые средства моделирования (RSM, Sparx) используют поля-маркеры — state: as-is, state: to-be, new, changed, existing — чтобы указать, что модель содержит и текущую, и предлагаемую структуру. Archlang в них не нуждается.
Эту роль играют ветки. Ваша основная ветка — это текущая архитектура. Фича-ветка — это предлагаемая архитектура. Дифф между ними — «что изменится, если мы внедрим это предложение». Ревьюеры видят структурную дельту и принимают решение.
Никаких флагов. Никакой второй копии того же модуля с другими аннотациями. Нет риска забыть удалить маркер state: to-be после внедрения.
Ограничения
- Переименования интерфейсов вместе со сменой вида могут победить эвристику.
command Authorize→query 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 и сдвиг мышления, после которого остальной язык обретает смысл.