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