14. Диффы
Каждая модель ArchLang живёт в git, и это делает изменение полноправным: ветки — это будущие состояния, коммиты — архитектурные решения, рабочее дерево — настоящее. Стабильные идентификаторы (Глава 13) закрепляют идентичность сквозь всё это, так что рефакторинг читается как переименования и перемещения, а не «всё удалено и заново добавлено». Интересная часть — как ArchLang превращает построчные диффы git в структурные диффы — диффы, говорящие о модулях, интерфейсах и процессах, а не о добавленных и удалённых строках.
Эта глава о том, как это работает, что с этим можно делать и в чём ограничения.
Что такое структурный дифф
Заголовок раздела «Что такое структурный дифф»Дифф git двух файлов .arch показывает добавленные и удалённые строки. Это полезно для чтения файла, но бесполезно для чтения архитектуры. Переименуйте модуль из Payments в PaymentsService, и дифф git покажет:
service #k7m2qx Payments {service #k7m2qx PaymentsService {Ревьюер может это прочитать и понять, что это переименование. Но автоматизированному инструментарию нетривиально сказать «переименование Payments не вводит новых зависимостей» — для этого нужно знать, что это переименование, а не удаление плюс добавление.
Структурный дифф говорит примерно так:
- Переименовано:
Payments→PaymentsService(тот же#k7m2qx). - Добавлен аспект:
criticality: "High"наPaymentsService. - Добавлен интерфейс:
PaymentsService.void. - Добавлен модуль:
FraudCheck(#r3n8wt). - Удалён модуль:
LegacyBilling(#b9x4cd). - Изменён процесс:
BasicPayment— вставлен один шаг.
Глава 3 прошлась по одному из таких диффов целиком. Эта глава фокусируется на механике и инструментарии.
Как сопоставляется идентичность
Заголовок раздела «Как сопоставляется идентичность»Движок диффа сопоставляет старое с новым в таком порядке:
- По стабильному идентификатору. Всё, что имеет
#id, сопоставляется по идентификатору. Модуль с#k7m2qxв старом файле сопоставляется с модулем#k7m2qxв новом, независимо от имени. Здесь определяются переименования. - По структурному пути внутри стабильного родителя. Поверхности и интерфейсы не несут идентификаторов (Глава 13). Их идентичность — точечный путь внутри родителя, который их несёт:
Payments.authorizeсопоставляется сPaymentsService.authorize, потому что родитель#k7m2qxсопоставился первым. - По эвристикам формы контракта. Внутри сопоставленного родителя интерфейсы с разными именами проверяются на схожесть — тип, поля, описание, схожесть имён. Сильные совпадения сообщаются как переименования; слабые откатываются к удалению плюс добавлению.
Специальные проекции диффа
Заголовок раздела «Специальные проекции диффа»Режим диффа просмотрщика рендерит структурную дельту прямо на диаграмме. Интерфейс просмотрщика предлагает несколько сохранённых проекций диффа:
- Было / Стало — добавленные узлы окрашиваются в зелёный, удалённые в красный, изменённые в жёлтый.
- Что изменилось — только список, без диаграммы, просто маркированная форма структурной дельты.
- Новые зависимости — фокус на рёбрах, введённых диффом. Полезно, когда рефакторинг добавляет вызовы между командами и хочется убедиться, что они намеренные.
- Удалённые зависимости — обратное. Показывает то, что раньше общалось, а теперь нет.
Каждая — это просто настроенный рендер диффа; движок один и тот же.
Что сравнивается
Заголовок раздела «Что сравнивается»У диффа две стороны. Это могут быть:
- Две git-ревизии в одной ветке — например, «что изменилось в последнем коммите?».
- Две ветки — что фича-ветка вводит поверх
main. - Рабочее дерево против HEAD — незакоммиченные изменения.
- Два произвольных каталога — пример из Главы 3.
Все четыре сводятся к двум разобранным снимкам; один и тот же движок их сравнивает. Разные поверхности инструментария (размещённый просмотрщик, плагины редакторов, скрипты, использующие основную библиотеку диффа) предоставляют разные удобные точки входа для выбора двух сторон.
Что заменяет TO-BE / AS-IS
Заголовок раздела «Что заменяет TO-BE / AS-IS»Старые средства моделирования используют поля-маркеры — state: as-is, state: to-be, new, changed, existing — чтобы указать, что модель содержит и текущую, и предлагаемую структуру. ArchLang в них не нуждается.
Эту роль играют ветки. Ваша основная ветка — это текущая архитектура. Фича-ветка — это предлагаемая архитектура. Дифф между ними — «что изменится, если мы внедрим это предложение». Ревьюеры видят структурную дельту и принимают решение.
Никаких флагов. Никакой второй копии того же модуля с другими аннотациями. Нет риска забыть удалить маркер state: to-be после внедрения.
Ограничения
Заголовок раздела «Ограничения»- Переименования интерфейсов вместе со сменой типа могут победить эвристику.
rest_create authorize→rest_read validateв одном коммите выглядит как удаление плюс добавление. Смягчите, разделив изменение на два коммита (сначала переименование, потом смена типа). - Перемещения интерфейсов между модулями в настоящее время трактуются как удаление плюс добавление. Если вы переместите
OrderEventsизOrdersвEventBus, дифф скажет, что одно событие удалено, а другое добавлено. Будущая итерация может отслеживать такие случаи. - Переименования целого пакета требуют ручной заботы. Имя пакета в
package.archspaceне несёт идентификатора; если переименоватьacme.shopвacme.commerce, загрузчик трактует результат как новый пакет. На практике большинство переименований происходит в каталогах и путях зависимостей, где разрешение зависимостей по пути это допускает.
Стабильность при рефакторингах
Заголовок раздела «Стабильность при рефакторингах»Поскольку движок диффа уважает стабильные идентификаторы и точечные пути, крупные рефакторинги остаются ревьюабельными:
- Разделение модуля на два: переместите половину интерфейсов в новый модуль с новым идентификатором. Дифф покажет: один интерфейс удалён из
OldModule, добавлен один новый модуль, содержащий эти интерфейсы. Ревьюеры видят перемещение; эвристика по форме интерфейса связывает перемещённые интерфейсы с их прежними определениями. - Реорганизация вложенности: меняем
service Orders { ... }наin CommerceSystem service Orders { ... }. Идентификатор модуля остаётся прежним; дифф сообщает о смене родителя, а не об удалении. - Переименование аспектов: меняем
domain: "Payments"наdomain: "PaymentDomain"во многих файлах. Дифф сообщает об изменениях значений полей по каждому модулю; структурная форма не затронута.
За пределами просмотрщика
Заголовок раздела «За пределами просмотрщика»Движок диффа доступен как функция библиотеки в @archlang/engine. Конвейеры и инструменты ревью потребляют его структурированный вывод — список переименований, добавлений, удалений и модификаций — и подают в:
- Автоматизацию ревью кода (блокировать PR, вводящие межкомандные зависимости без согласования).
- Записи архитектурных решений (автогенерировать разделы «что изменилось» в ADR).
- Отчёты о соответствии требованиям (любая модификация, затрагивающая модуль с
security.zone: PCI, помечается для проверки).
Контрольная точка для повседневного читателя
Заголовок раздела «Контрольная точка для повседневного читателя»Если вы потребляете только типы, определённые вашей стандартной библиотекой или платформенной командой, у вас уже есть полная картина:
- Вы знаете, что такое модуль, интерфейс, поверхность, процесс и проекция.
- Вы знаете, как поля, аспекты и описания их описывают и классифицируют.
- Вы знаете, как пакеты, стабильные идентификаторы и диффы делают изменения управляемыми.
Здесь можно остановиться. Оставшиеся главы посвящены определению метамодели — созданию собственных типов, управлению тем, как распространяются их поля, и созданию пользовательских виджетов. Они существенны для платформенных команд и авторов расширений языка. Они необязательны для прикладных команд, потребляющих типы стандартной библиотеки.
- Дифф ArchLang структурный — он говорит о переименованных модулях и добавленных интерфейсах, а не о добавленных строках.
- Стабильные идентификаторы позволяют диффу распознавать переименования; интерфейсы используют эвристики формы контракта.
- Просмотрщик поставляет несколько проекций диффа: Было/Стало, Что изменилось, Новые/Удалённые зависимости.
- Ветки заменяют маркеры TO-BE / AS-IS; ревью PR — это ревью архитектурной дельты.
- Движок диффа доступен как функция библиотеки для конвейеров и инструментов ревью.
Что дальше
Заголовок раздела «Что дальше»Глава 15: Зачем типы? → — первая глава Части IV и сдвиг мышления, после которого остальной язык обретает смысл.