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 принимает две директории рядом). Вы увидите следующее:
- Переименовано:
Payments→PaymentsService(тот же#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: Модули → — первая примитивная сущность, в подробностях.