Почему дрейф не умирает 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, — поверхность, по которой может дрейфовать, сокращается радикально. Оставшуюся поверхность — поведенческие инварианты генератора — закрывает гейт. И всё же на самом верху остаётся последний слой, который некому делегировать, — суждение человека. Там мы каждый раз заново верифицируем и вбиваем обещание заново.

Это и есть рэтчет. Он вращается только в одну сторону. Однажды поднятый зубец не соскальзывает обратно. Энтропия тянет решения вниз, а рэтчет каждый раз поднимает их на одну зарубку. Равновесия нет. Остановишься — задрейфуешь.

Дрейф не умирает. Поэтому мы не останавливаемся. Строить обещания вопреки энтропии — это не однократная победа, а вечный рэтчет.

Связанные статьи

Дополнительное чтение (внешнее)

  • 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 — технический долг и «проценты на плохо написанный код».