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

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

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

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

Состояние «до»

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

module #pay001 Payments {
team: Payments
"Core payment processing."
labels {
domain: Payments
zone: PCI
}
interface Authorize { "Authorize a transaction" }
interface Capture { "Capture an authorized amount" }
interface Refund { "Issue a refund" }
}
module #led001 Ledger {
team: Finance
"Financial record keeping."
labels {
domain: Finance
zone: Internal
}
interface Record { "Record a financial event" }
}
module #leg001 LegacyBilling {
team: Payments
"Old billing module slated for removal."
interface Charge { "Legacy charge endpoint" }
}
module #act001 Customer {
"End user initiating payments."
}
process #flow01 BasicPayment {
Customer > Payments.Authorize
Payments > Ledger.Record
}

Обратите внимание на префиксы #pay001, #led001 и так далее. Это стабильные идентификаторы. Они были выпущены форматтером при первом сохранении файла (см. Главу 13). Именно по ним механизм диффа понимает, что «модуль, ранее известный как Payments» — это тот же модуль после переименования.

Состояние «после»

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

module #pay001 PaymentsService {
team: Payments
"Core payment processing."
labels {
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 #led001 Ledger {
team: Finance
"Financial record keeping."
labels {
domain: Finance
zone: Internal
}
interface Record { "Record a financial event" }
interface AuditLog { "Append immutable audit entry" }
}
module #frd001 FraudCheck {
team: Risk
"Real-time fraud screening."
labels {
domain: Risk
zone: Internal
}
interface Screen { "Score a transaction for fraud" }
}
module #act001 Customer {
"End user initiating payments."
}
process #flow01 BasicPayment {
Customer > PaymentsService.Authorize
PaymentsService > FraudCheck.Screen
PaymentsService > Ledger.Record
}

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

Структурный дифф

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

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

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

Как было обнаружено переименование

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

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

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

Почему это важно для ревью

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

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

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

Резюме

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

Что дальше

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