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

3. Чтение диффа

В большинстве инструментов вопрос «что изменилось в архитектуре на прошлой неделе?» — сложный. Диаграммы несравнимы; у слайдов PowerPoint нет диффа. Текстовый исходник ArchLang означает, что git уже знает ответ — но обычный git diff показывает удалённые и добавленные строки, а не архитектурные изменения. Переименование модуля без других изменений в текстовом диффе выглядит как удаление всего, что упоминает старое имя, и добавление всего, что упоминает новое.

Дифф в ArchLang — структурный. Он понимает, что модуль с тем же стабильным идентификатором и другим именем — это переименование, а не удаление и добавление. Он сообщает об этом явно. Эта глава разбирает один такой дифф, по-прежнему используя только базовый язык.

Платёжная система несколько недель назад:

module #m4k29p Payments {
aspect team: "Payments"
"Core payment processing."
aspect {
domain: "Payments"
zone: "PCI"
}
interface authorize { "Authorize a transaction" }
interface capture { "Capture an authorized amount" }
interface refund { "Issue a refund" }
}
module #x7t1qd Ledger {
aspect team: "Finance"
"Financial record keeping."
aspect {
domain: "Finance"
zone: "Internal"
}
interface record { "Record a financial event" }
}
module #b0w83r LegacyBilling {
aspect team: "Payments"
"Old billing module slated for removal."
interface charge { "Legacy charge endpoint" }
}
module #u5e7af Customer {
"End user initiating payments."
}
process #z2j6vm BasicPayment {
Customer > Payments.authorize
Payments > Ledger.record
}

Обратите внимание на префиксы #m4k29p, #x7t1qd и так далее. Это стабильные идентификаторы, выпущенные форматтером при первом сохранении файла (см. Главу 13). Они намеренно непрозрачны — случайные символы, не кодирующие ни имени, ни типа, ни смысла — и они никогда не меняются. В этом весь смысл: идентификатор остаётся неизменным, даже когда модуль переименовывают, переносят в другую область видимости или перемещают, так что механизм диффа может распознать, что «модуль, ранее известный как Payments» — это тот же модуль после переименования. (Не вычитывайте ничего из символов; осмысленный идентификатор — это баг, потому что всё осмысленное со временем устаревает и провоцирует вас его править.)

Несколько недель спустя те же файлы выглядят так:

module #m4k29p PaymentsService {
aspect team: "Payments"
"Core payment processing."
aspect {
domain: "Payments"
zone: "PCI"
criticality: "High"
}
interface authorize { "Authorize a transaction" }
interface capture { "Capture an authorized amount" }
interface refund { "Issue a refund" }
interface void { "Void an unsettled authorization" }
}
module #x7t1qd Ledger {
aspect team: "Finance"
"Financial record keeping."
aspect {
domain: "Finance"
zone: "Internal"
}
interface record { "Record a financial event" }
interface auditLog { "Append immutable audit entry" }
}
module #h9n42c FraudCheck {
aspect team: "Risk"
"Real-time fraud screening."
aspect {
domain: "Risk"
zone: "Internal"
}
interface screen { "Score a transaction for fraud" }
}
module #u5e7af Customer {
"End user initiating payments."
}
process #z2j6vm BasicPayment {
Customer > PaymentsService.authorize
PaymentsService > FraudCheck.screen
PaymentsService > Ledger.record
}

Текстовый дифф сообщил бы, что Payments пропал, появился PaymentsService, какая-то часть строк сдвинулась, а несколько аспектов были изменены. Полезно для ревью самого файла, бесполезно как описание того, как изменилась архитектура.

Откройте обе версии в режиме диффа Viewer (размещённый Viewer принимает две директории рядом). Вы увидите следующее:

  • Переименовано: PaymentsPaymentsService (тот же #m4k29p).
  • Добавлен аспект: criticality: High у PaymentsService.
  • Добавлен интерфейс: PaymentsService.void.
  • Добавлен интерфейс: Ledger.auditLog.
  • Добавлен модуль: FraudCheck (#h9n42c).
  • Удалён модуль: LegacyBilling (#b0w83r).
  • Изменён процесс: BasicPayment — вставлен один шаг (PaymentsService > FraudCheck.screen).

Каждый пункт привязан к узлу на диаграмме. Переименованные узлы появляются на новой позиции и помечены как переименованные; добавленные узлы светятся зелёным; удалённые узлы светятся красным и затухают; изменённые процессы подсвечивают вставленные или удалённые шаги.

Payments и PaymentsService имеют общий стабильный идентификатор #m4k29p. Именно поэтому дифф это понимает. Стабильные идентификаторы привязаны к модулям и типам, а не к именам. Модуль можно свободно переименовывать, и дифф остаётся полезным.

А что насчёт нового интерфейса PaymentsService.void? У интерфейсов нет стабильных идентификаторов (см. Главу 13). Их идентичность — это путь через точку внутри объемлющего модуля. Механизм диффа использует эвристики по форме контракта (тип, поля, описание) и схожести имён для обнаружения переименований интерфейсов; всё остальное сводится к паре «добавить + удалить». В этом примере void действительно новый, поэтому дифф показывает его как добавленный.

Почему бы не дать всему стабильный идентификатор? Потому что цена — постоянный визуальный шум в исходных файлах. Идентификаторы у модулей дают что-то конкретное (межфайловые ссылки переживают переименование). Идентификаторы у каждого интерфейса внутри каждого модуля захламили бы исходник, мало что добавив — интерфейсы редко переименовывают независимо от их модуля, а когда такое случается, эвристика покрывает типичные случаи.

Автор pull request открывает режим диффа. Ревьюер видит:

  • FraudCheck — новый. Вопрос: кто им владеет, где он работает, с какими данными он взаимодействует?
  • BasicPayment теперь проходит через FraudCheck.screen. Вопрос: блокирующий ли это шаг? Какой у него SLA?
  • LegacyBilling исчез. Вопрос: всё ещё ли какие-то внешние системы вызывают его?

Эти вопросы — про архитектуру, а не про файл. Режим диффа выводит их напрямую. То же ревью по обычному git diff похоронило бы их под шумом.

  • Стабильные идентификаторы (#m4k29p) непрозрачны и постоянны; они закрепляют идентичность модуля сквозь переименования.
  • Дифф — структурный: переименование, добавление, удаление, изменение — а не построчный.
  • Интерфейсы используют эвристики (форма контракта + схожесть имён), а не идентификаторы.
  • Режим диффа Viewer отображает изменения прямо на диаграмме.
  • Глава 14 возвращается к диффам подробно после того, как мы покроем всё, что они могут показать.

Глава 4: Модули → — первая примитивная сущность, в подробностях.