Image: AI generated
Возвращается на том же месте
Я построил инструмент, который замыкает дрейф.
Тезис yongol прост. Решения, не живущие в единственном авторитетном источнике (SSOT), дрейфуют. Поэтому решения помещаются в SSOT, а код превращается в одноразовую проекцию (projection), перерисовываемую при каждой генерации. Дрейф бизнес-логики, при котором колонка, определённая как BIGINT, после одного рефакторинга тихо откатывается к INT, — таким образом был закрыт.
Но недавно, анализируя пачку дефектов в коде, сгенерированном yongol, я увидел нечто странное. Дефекты признавались в одной и той же синтаксической структуре. «Сбор import отделён от проверки “действительно ли хэндлер использует time".» «Вывод requiredness отделён от проверки “действительно ли целевой API требует этот параметр как required”.» Параметры пути — всегда required, import — только при фактическом использовании токена. Одни и те же структурные решения, не записанные ни в одном SSOT, были вбиты в код генератора как удобные локальные прокси.
Дрейф не исчез. Он поднялся на слой выше. Бизнес-логика была замкнута в SSOT, но у структурных решений самого генератора — того, кто читает SSOT и выдаёт код, — своего SSOT не было. Тезис yongol вернулся к самому yongol. Именно в том месте, которое предсказывала теория.
Поэтому вопрос меняется. Почему дрейф возникает — знает каждый. Настоящий вопрос вот в чём: почему он возвращается, даже когда его исправили.
Корень: решение и деталь — разные вещи
Начнём заново от физики.
Решение — это информация. Причём информация с низкой энтропией. «Эта колонка должна быть 64-битной» — это намеренный выбор одного состояния из множества возможных. Природа не любит низкую энтропию. Если оставить как есть, информация расплывается в окружающий шум и исчезает. Второй закон термодинамики действует и на решения.
Программная инженерия давно наблюдает этот распад. Законы эволюции программного обеспечения Лемана утверждают, что сложность систем E-type растёт, если нет явных усилий по её снижению (1980). Информационная физика идёт ещё глубже. Ландауэр показал в 1961 году, что стирание даже одного бита требует минимальных термодинамических затрат (kT ln 2). Изменение и удержание информации принципиально не бесплатно. Чтобы решение оставалось на месте, нужно постоянно тратить энергию.
Чтобы информация выжила, нужны две вещи: авторитетное хранилище (authoritative store) и непрерывная активная репроекция из него (error correction). Так устроена ДНК нашего тела. Так работают биты чётности в цифровых хранилищах. Оригинал хранится отдельно, и каждый раз восстановление идёт по нему.
Дрейф происходит, когда это восстановление ломается. Механизм один. Я называю его прокси-связывание. Когда носитель не может отделить и сохранить решение от детали, следующий человек (или следующий агент) не может прочитать решение из авторитетного источника и переизводит его из ближайшего удобного коррелирующего сигнала. «Эта колонка — timestamptz, значит нужно сделать import time» — вот такая догадка. Обычно она верна. Поэтому и опасна. Иногда ошибается, и тогда решение беззвучно исчезает.
raw-код — именно такой носитель. Код не различает «это — решение» и «это случайно истинно в данном контексте». Поэтому более крупная модель проблему не решает. Сам носитель не способен хранить решение — неважно, насколько умён читатель, ему нечего прочитать.
У этого явления были имена. В программной архитектуре Перри и Вулф назвали нарушение принципов эрозией (erosion), а потерю чувствительности к архитектуре — дрейфом (drift) (1992). Каннингем назвал процент, накапливающийся на плохо написанном коде, техническим долгом (1992). Каждая дисциплина точно именовала симптомы. Я добавляю единый механизм под ними (прокси-связывание) и структуру, при которой этот механизм рекурсивно поднимается выше с каждым замыканием слоя. Вопрос не об именах, а о причинности.
Почему поднимается выше
До этого момента — известная история. Новое начинается дальше.
Чтобы замкнуть дрейф, нужны две вещи: хранилище, авторитетно содержащее решения (SSOT), и замыкающий агент, который читает его и выдаёт артефакты (генератор). Но сам замыкающий агент тоже принимает решения. «Параметры пути трактуются как required» — структурное решение такого рода. Носитель, в котором живёт это решение — код генератора, — тоже не может отделить решение от детали.
Тот же механизм повторяется слоем выше. Само замыкание создаёт незамкнутый носитель слоем выше. Дрейф не уничтожен — он переехал. На слой без авторитета.
Если продолжить до конца, вывод неудобен. Дать генератору SSOT? Тогда то, что создаёт этот SSOT, снова помещает свои решения в незамкнутый носитель. С каждым подъёмом поверхность уменьшается, но на самом верху всегда остаётся неавторитетный слой. Человек или генератор генератора. Дрейф асимптотически неустраним. (Это скорее сильная гипотеза, чем доказательство. Однако каждый слой, который я замыкал, в момент замыкания открывал слой выше.)
Вот ответ на вопрос «почему возвращается, даже когда исправили». Он не возвращается. Мы замыкаем один слой — и инструмент замыкания открывает следующий. Та же река просачивается через более высокую дамбу.
Асимметрия лечения: то, что можно декларировать, и то, что можно только верифицировать
Тогда как замыкать верхний слой? Здесь обнажается решающая асимметрия.
Решения бизнес-логики — это, как правило, значения. Колонка — 64 бита, доступ — только для владельца, пагинация — курсорная. Значения можно декларировать. Записать в DDL, в OpenAPI, в файл спецификации — и это становится SSOT. Замыкается декларацией.
Структурные решения генератора — другое. «Параметры пути — required», «import привязан к фактической ссылке на токен», «required (ключ существует) и непустое значение — разные вещи». Это не значения, а свойства поведения функции на всём пространстве входов. Поведенческие свойства нельзя перечислить декларативно. Пространство входов бесконечно. Нет способа записать в одной ячейке YAML: «это преобразование должно вести себя так для всех случаев».
Поэтому решения этого слоя замыкаются не декларацией, а только верификацией. Средства проверки типов, тесты свойств, шлюзы компиляции. Не вбивать решение как данные, а ставить заслон, где машина каждый раз ловит нарушение.
То, что я написал в другой статье — «кодифицируйте человеческую проверку», — относится именно сюда. Одни обещания можно декларировать, и тогда их хранит SSOT. Другие нельзя, и тогда их хранит гейт. Компилируется ли код, выданный генератором, — это не запишешь ни в каком SSOT. Это подтверждается только запуском компиляции каждый раз. Без этого заслона обещание «generate прошёл = можно собрать» висит за пределами архитектуры, validate проходит 0/0, а артефакт сломан.
Дрейф, который можно декларировать, замыкается SSOT. Дрейф, который можно только верифицировать, замыкается гейтом. Перепутаешь — будешь вечно гоняться за кротами, пытаясь закрыть декларацией то, что ей не поддаётся.
Та же река, другая дамба
Эта структура повторяется за пределами кода.
В знании дрейф — это утрата источника. Когда утеряно, кто, когда и на каком основании сделал утверждение, оно расплывается в шум под названием «факт». Следующий человек не может прочитать из авторитета (первоисточника) и переизводит из окружающего контекста. Именно поэтому я спроектировал GEUL как язык, принудительно прикрепляющий к любой информации источник, момент времени и степень достоверности. Эпистемология «фактов нет — есть только утверждения» — это предохранитель против прокси-связывания в слое знания.
В праве дрейф — это отклонение прецедентов от первоначального решения. Цивилизация закрыла это не через совесть судьи в каждом деле, а кодификацией правил, определением нарушений и присоединением механизмов принуждения. Хороший судья — не SSOT, а прокси. Писаный закон — вот SSOT.
Та же река. Если решение не живёт в авторитетном месте, если носитель не может отделить решение от детали — оно дрейфует. Будь то код, знание или закон.
Заключение: не уничтожение, а выталкивание вверх
Борьба с дрейфом не может ставить целью уничтожение. Уничтожение невозможно. Инструмент замыкания всегда открывает следующий слой.
Цель — другое. Вытолкнуть дрейф на более высокий слой с меньшей поверхностью и вооружить этот слой механической верификацией. Когда решения, рассеянные по десяткам тысяч строк raw-кода, собираются в одно место — SSOT, — поверхность, по которой может дрейфовать, сокращается радикально. Оставшуюся поверхность — поведенческие инварианты генератора — закрывает гейт. И всё же на самом верху остаётся последний слой, который некому делегировать, — суждение человека. Там мы каждый раз заново верифицируем и вбиваем обещание заново.
Это и есть рэтчет. Он вращается только в одну сторону. Однажды поднятый зубец не соскальзывает обратно. Энтропия тянет решения вниз, а рэтчет каждый раз поднимает их на одну зарубку. Равновесия нет. Остановишься — задрейфуешь.
Дрейф не умирает. Поэтому мы не останавливаемся. Строить обещания вопреки энтропии — это не однократная победа, а вечный рэтчет.
Связанные статьи
- Ratchet Pattern — как заставить агента дойти до конца
- Почему ваш агентный цикл расходится
- Reins Engineering — ИИ под управлением
Дополнительное чтение (внешнее)
- Lehman’s laws of software evolution — обзор эмпирических законов о том, что программное обеспечение без вмешательства усложняется.
- Landauer’s principle — термодинамическая стоимость стирания информации.
Источники
- Perry, D. E. & Wolf, A. L. (1992). Foundations for the Study of Software Architecture. ACM SIGSOFT Software Engineering Notes, 17(4), 40-52. ACM — разграничение эрозии (erosion) и дрейфа (drift).
- De Silva, L. & Balasubramaniam, D. (2012). Controlling software architecture erosion: A survey. Journal of Systems and Software, 85(1), 132-151. ScienceDirect
- Lehman, M. M. (1980). Programs, Life Cycles, and Laws of Software Evolution. Proceedings of the IEEE, 68(9), 1060-1076. IEEE — закон возрастания сложности, закон непрерывного изменения.
- Landauer, R. (1961). Irreversibility and Heat Generation in the Computing Process. IBM Journal of Research and Development, 5(3), 183-191. IBM — минимальная термодинамическая стоимость стирания информации.
- Shannon, C. E. (1948). A Mathematical Theory of Communication. Bell System Technical Journal, 27, 379-423. DOI — основа информации, энтропии и error correction.
- Cunningham, W. (1992). The WyCash Portfolio Management System. OOPSLA ‘92 Experience Report. c2.com — технический долг и «проценты на плохо написанный код».