Бюджет, рядом с которым стоит измерение
Нефункциональные требования чаще всего декоративны. «Быстро», «легковесно», «отзывчиво» — написали однажды на планировании, ни разу не измерили, к третьему релизу пришли в противоречие и не заметили, потому что никто не смог бы сказать, что́ считалось бы противоречием. Это не ложь. Это неопровержимость, а она хуже: неопровержимое требование живёт бесконечно и при этом не делает ничего.
nfr.md в Ritmolux начинается с отказа от этого одним предложением:
Каждый раздел — это бюджет, рядом с которым стоит измерение, а не пожелание.
Этот пост — о том, чего стоит соблюсти такую фразу, и о втором проекте, чьи бюджеты растут совсем из другого места.
Единица измерения — внутри числа
У основного бинарника мягкий предел в 10 000 000 байт. Не «10 МБ», и документ объясняет почему — придаточным, которое я больше нигде не встречал:
Единица внутри числа потому, что «~10 МБ» читается двумя способами, отличающимися на 4,9 %, и здесь нигде не было сказано, каким именно.
Десять мегабайт — это либо 10 000 000, либо 10 485 760, смотря кто говорит, и разрыв между прочтениями больше, чем большинство изменений, которые вы стали бы с ними сопоставлять. Кто-то добавил фичу ценой 400 КБ; пробила она бюджет или нет — зависит от того, какой мегабайт имел в виду автор бюджета, а автора уже не спросить. Бюджет, с которым нельзя сопоставить измерение, — не бюджет, а тема для разговора.
Дальше та же запись делает нечто более редкое: признаёт, что число не заслужено.
значение унаследованное, и его ни разу не сверяли с тем, что бинарник на самом деле содержит.
Предел произволен. Он достался от более раннего решения, его ни разу не обосновали через реальное содержимое бинарника — и вместо того чтобы задним числом придумать обоснование, документ говорит это вслух.
Такое признание стоит больше, чем выдуманный вывод. С бюджетом, у которого честное происхождение, можно спорить: кто-то скажет «число унаследованное, а бинарнику теперь честно нужно 11 МБ», и разговор пойдёт про бинарник. С бюджетом, у которого происхождение выдумано, спорить нельзя: сначала придётся оборонять выдумку, и защищающий на середине обнаружит, что защищает решение, которого никто не принимал.
Как всё-таки вывести число, когда это возможно
У компонента для foobar2000 свой предел — 12 582 912 Б, двенадцать двоичных мегабайт, — и вот он уже выведен, потому что два артефакта несут разное. Компонент несёт всё ядро, встроенную библиотеку пресетов и прослойку к SDK — и не несёт ни оконной библиотеки, ни окна, ни стека захвата звука.
Сам вывод и стоит перенять:
9 789 952 Б плюс ещё один шаг размером с шаг
text, округлённое вверх до ближайшей двоичной границы — так что предел допускает ещё одну фичу самого крупного класса, который этот проект когда-либо выпускал, а за вторую придётся отдельно аргументировать.
Отправная точка — то, что артефакт измеряет сегодня. Прибавьте один прирост размером с самую большую фичу, которую вы реально когда-либо добавляли, — не догадку о будущей, а самую крупную из уже случившихся. Округлите вверх до границы. Получается число с конкретным смыслом: место есть ещё на одну крупную вещь, а дальше поговорим.
Сравните с обычным методом — взять круглое число и надеяться. Круглое число не несёт информации о том, сколько запаса оно в себе содержит, поэтому никто не знает, восемьдесят процентов от него — это комфортно или тревожно.
Очевидный более дешёвый ход — дать компоненту тот же предел, что и бинарнику, «примерно тот же порядок» — отвергнут на том основании, что содержимое у них разное. Это важнее, чем звучит. Бюджет, поделённый на два артефакта, не работает ни для одного: он либо слишком свободен для меньшего, и тогда меньший не ограничен ничем, либо слишком тесен для большего, и тогда его отменяют, — а отменённый бюджет учит всех, что бюджеты отменяют.
Мягкий — и знающий об этом
Скрипт сборки печатает длину компонента при каждой сборке и предупреждает выше 11 324 620 Б, то есть на девяноста процентах предела. Релиз из-за размера он не валит никогда:
эти пределы мягкие, тогда как семь фатальных проверок рядом с измерением — это свойства корректного артефакта.
В этом различении вся конструкция, и именно здесь большинство проектов ошибается в сторону строгости.
Слишком большой компонент — хуже, чем должен быть. Компонент, проваливший одну из семи проверок, — неправильный. Это разные категории, и уравнивать их — значит получить предсказуемый отказ: заблокируйте релиз по размеру, и в первый же по-настоящему срочный релиз, который вылезает на 40 КБ, кто-нибудь либо пробьёт дыру в настоящих проверках, чтобы освободить место, либо отключит проверку размера. А однажды отключённая под давлением проверка снова становится декорацией — только теперь декорацией, которую все научились обходить, и это хуже честного предупреждения, которое она заменила.
Поэтому предел размера предупреждает, громко, на девяноста процентах, при каждой сборке, а решает человек. Семь проверок, описывающих корректный артефакт, валят жёстко.
Бюджет размера дотягивается и выше самого бинарника — до того, что в него позволено добавлять. Графическая библиотека названа единственной принятой фиксированной ценой; почти ничто больше ею не является. Любой новый крейт, тянущий за собой больше примерно двадцати транзитивных зависимостей, требует явного обоснования — комментария рядом с ним в манифесте или записи о решении, если он затрагивает весь проект. Это тот же бюджет, выраженный правилом о входе, а не числом о выходе, и он ловит цену до того, как она уплачена, — то есть в единственный момент, когда отказаться дёшево.
Вся запись целиком
Два раздела этого поста выросли из одной записи в том документе. Вот она дословно — чтобы было видно соотношение комментария к источнику: шесть пунктов, один из которых намеренно не является пределом вообще.
4. Размер и зависимости
- Мягкий предел 10 000 000 Б для релизного exe отдельного приложения. Единица внутри числа потому, что «~10 МБ» читается двумя способами, отличающимися на 4,9 %, и здесь нигде не было сказано, каким именно; само значение унаследовано и ни разу не сверялось с тем, что exe реально содержит.
- Мягкий предел 12 582 912 Б (12 МиБ) для компонента foobar2000,
foo_ritmolux.dll, — собственная цифра, а не «тот же порядок», потому что два артефакта несут разное. Компонент несёт всё ядро, встроенную библиотеку пресетов и прослойку SDK и не несёт ниwinit, ни окна, ни стека захвата WASAPI. Выведен в ADR-0159 как 9 789 952 Б плюс ещё один шаг размером с шагtext, округлённые вверх до следующей двоичной границы, — то есть допускает ещё одну функцию самого крупного класса, который проект отгружал, а вторую придётся обосновывать.packaging/foobar/build-component.ps1печатает длину при каждой сборке и предупреждает выше 11 324 620 Б (90 % предела). Релиз из-за размера он не валит никогда: эти пределы мягкие, тогда как семь фатальных проверок рядом с измерением — свойства корректного артефакта.- Записано, но не ограничено: 118 073 278 Б для windows-архива студии,
ritmolux-studio-v0.113.0-windows-x64.zip, измерено на первом архиве, который собралpackaging/studio/build-studio.ps1(ADR-0178 просит цифру в байтах; универсальный macOS-архив несёт две архитектуры Electron и будет больше, его цифра встанет в эту строку, когда её измерит первый релиз). Из распакованных 291 321 974 Б это раскладывается так: 277 424 419 Б готовой среды Electron, 10 701 824 Б плеера и 3 195 731 Бapp.asar— всё, что написано в этом проекте. Из-за этого соотношения строка записывает, а не ограничивает: 95 % артефакта — зависимость, размер которой не двигает ни одна правка здесь, так что предел измерял бы график релизов Electron, а не нашу сдержанность, тогда как два мягких предела выше существуют как показания именно по ней. Сдвинуть эту цифру можно, отказавшись от бандла, а не подрезав его; студия никогда не поставляется внутри плеера, и ничто поставляемое от неё не зависит.- wgpu — принятая фиксированная стоимость; почти ничто больше ею не является.
- Релизный профиль: LTO включён, символы вырезаны, у прямых зависимостей точные версии.
- Проверка: любой новый крейт, тянущий больше примерно двадцати транзитивных зависимостей, требует явного обоснования (комментарий в
Cargo.tomlили ADR, если решение сквозное).
Третий пункт — тот, для которого выше не нашлось места, и он, возможно, лучший аргумент во всей записи. Архив студии весит 118 МБ и не несёт предела, потому что 95 % его объёма — готовая среда Electron, размер которой не двигает ни одна правка в этом репозитории. Предел здесь измерял бы график релизов Electron, а не чью-либо сдержанность: его пробивало бы обновление зависимости и снова выполняло другое обновление, и ни то ни другое ничего не сообщало бы о проекте. Поэтому строка просто записывает число, а два предела выше остаются показаниями по той части, которая действительно своя.
Это то же самое суждение, что и разделение на мягкое и фатальное, только уровнем выше. Не всё, что стоит измерять, стоит ограничивать, и число, на которое вы не влияете, лучше записать без порога — чтобы позже никто не принял его превышение за дефект.
Когда число выигрывает спор
Интересный момент для любого бюджета — его первое настоящее столкновение с тем, что вы хотите построить. До этого он — теория.
Ritmolux потребовалась студия: правка пресетов вживую под музыку, сборка пресетов в проект под одно шоу, рендер клипов из трека, управление диффузионным проходом из того же окна. Каждое из этого — экран с ползунками, редактором кода, списком файлов или полосой прогресса. Вкомпилированные в плеер, они увели бы его далеко за предел, а плеер, измеренный в 10 277 888 Б, и без того уже был за ним.
Победил бюджет. Студия стала отдельным приложением, которое не рисует ни одного кадра, а плеер остался одним небольшим исполняемым файлом, который открывается, захватывает звук и рисует, и рядом с ним ничего не установлено. Без предела исход очевиден: редактор въезжает внутрь, бинарник удваивается, и каждый пользователь, который хотел только смотреть на красивые картинки, платит за то, чтобы носить с собой текстовый редактор, который никогда не откроет.
Вот и проверка на то, настоящий ли бюджет. Не записан ли он. Не измеряется ли. А менял ли он хоть раз конструкцию, которую кто-то хотел. Этот породил целое второе приложение — сигнал примерно настолько громкий, насколько это бывает.
Есть и эффект второго порядка. Предел не просто сказал «нет» — он вынудил лучший ответ. Студия внутри плеера была бы простым решением и худшим: плеер оброс бы режимом, режим оброс бы состоянием, и то, что обязано оставаться маленьким и надёжным ради живого шоу, делило бы процесс с файловым браузером. Ограничение дало более чистое разделение, чем, вероятно, дало бы одно обсуждение.
Ограничить утверждение тем, что вы реально контролируете
Бюджет задержки — меньше 60 мс от слышимого удара до видимой реакции, примерно три кадра при 60 Гц, с рабочей раскладкой по стадиям:
flowchart LR subgraph inside["меньше 60 мс — за это документ отвечает"] direction LR cap["захват / доставка<br/>≤ 15 мс"] --> ring["отставание чтения из кольца<br/>≤ 20 мс"] --> fft["шаг FFT<br/>≤ ~11 мс"] --> pres["рендер + вывод<br/>1–2 кадра"] end pres --> send["отвод кадра → отправитель"] send --> app["другое приложение<br/>компонует и выводит<br/>по своему расписанию"] app --> seen["что видит его зритель"]
Три вещи в том, как это сформулировано, стоит перенять.
Оно ограничивает поведение, а не артефакт. Документ прямо говорит: «Кольцевой буфер может вмещать больше 60 мс — требование в том, чтобы DSP читал близко к голове записи, а не в том, чтобы буфер был маленьким.»
Наивная версия этого требования ограничивала бы размер буфера. Это легко проверить, легко закрепить — и это не то свойство: маленький буфер, читаемый не с того конца, даёт ту же задержку, что и большой, а большой, читаемый у головы, не создаёт проблемы, ради которой требование писалось. Разница между измерением самой вещи и измерением чего-то с ней коррелирующего, что просто легче увидеть.
Оно называет то, что намеренно исключает. Движок умеет отдавать кадры другому приложению, которое компонует и выводит их по собственному расписанию:
Этот бюджет ограничивает окно, а транслируемая картинка лежит вне его… всё после отправителя от нас не зависит и намеренно не забюджетировано. Утверждение о задержке транслируемого пути — это утверждение про два приложения, а этот документ говорит только за одно.
Требование, которое втихую выходит за границу вашего процесса, невозможно ни выполнить, ни опровергнуть. Рано или поздно кто-нибудь измерит составной путь, получит 140 мс и заведёт баг против бюджета, который никогда про этот путь не был, — и единственная защита здесь заранее проведённая граница. Она же удерживает в честности остальные числа, потому что заставляет по каждому спросить, где оно заканчивается.
Оно принимает физику вместо того, чтобы делать вид. Одно изменение добавило второе, длинное окно анализа для нижних полос, которые коротким окном не разрешить вовсе. Это стоит примерно 85 мс групповой задержки на отклике уровня в нижних полосах, и документ записывает это как «принято как физика, а не скомпенсировано».
А дальше делает работу, которая делает это принятие безопасным: точно называет, каких параметров задержка касается, и объясняет, почему заголовочный бюджет не затронут, — путь «удар → реакция» длинного окна не касается вовсе, потому что атака, доля и темп по-прежнему читают короткое. Утверждение про 60 мс выживает, будучи ограниченным, вместо того чтобы тихо стать ложным из-за изменения, которое никто с ним не связал.
Измеренная цена изменения — 31,5 мкс на хоп против 17,2 мкс раньше, при отведённых примерно 11 мс, то есть около 350-кратного запаса, записанного числом, а не словом «пренебрежимо». Холодный старт сдвинулся с ~43 мс до ~171 мс, один раз на поток, и это тоже записано, хотя никто бы и не заметил.
Бюджет, который подстраивается, и форма этой подстройки
Производительность разобрана иначе, потому что она не может быть одним числом на неизвестном железе.
Движок поставляет два именованных уровня качества, несущих набор ёмкостей — число частиц, бюджет сегментов, пределы внутренних сеток, — которые вычисляются при построении рендерера. После одного из более поздних решений одна из этих ёмкостей перестала быть даже числом: бюджет сэмплов аттрактора — это плотность относительно цели рендеринга, так что рисуемое количество масштабируется вместе с числом реально заполняемых пикселей, зажатое между якорем и потолком.
Ограничение, которое всем этим управляет, я бы повесил на стену:
Уровень меняет, сколько движок рисует, но никогда — что, поэтому один и тот же пресет читается одинаково на обоих, при разных бюджетах.
Именно это делает адаптивное качество безопасным. Если бы уровень мог менять что рисуется, то пресет, созданный на одной машине, был бы другим пресетом на другой, любой результат визуального теста стал бы условным относительно уровня, на котором он прогонялся, а «у меня на ноутбуке выглядит неправильно» стало бы неотвечаемым. Ограничение подстройки одним лишь количеством оставляет одному пресету одно значение.
Логика выбора столь же осознанна. По умолчанию берётся дорогой уровень; регулятор времени кадра понижает его до дешёвого при устойчивом непопадании в бюджет обновления экрана — один раз за сессию, в одну сторону, с сообщением в диагностическом оверлее и в stderr, никогда молча. Автоматического повышения обратно нет, и причина названа не через производительность, а через тестируемость:
понижение предсказуемо и проверяемо, а осциллирующая или непрерывно сбрасывающая возможности конструкция — нет.
Система, непрерывно переторговывающая собственное качество, — это система, поведение которой нельзя воспроизвести, а значит, нельзя и разобрать по отчёту о ней. Одно объявленное понижение за сессию — это факт, который можно вписать в баг-репорт.
И есть предохранитель с явно заданным приоритетом: закрепление уровня — флагом, переменной окружения или ключом конфига, именно в таком порядке — действует в обе стороны, и регулятор его никогда не трогает. Это случай мощной машины, которую разово подавившийся кадр несправедливо понизил.
Прибор, показавший ноль, потому что ничего не измерил
Лучшее в требованиях обоих проектов — вообще не бюджет. Это правило об измерении, и родилось оно из измерения, которое было неверным так, что сначала этого никто не заметил.
Задача была оценить цену второй экранной поверхности: пять прогонов, одно окно без вмешательства, средний фреймрейт с закрытой поверхностью против открытой. Первый прогон дал 165,0 → 165,0 fps, 40,0 → 40,4 и 29,4 → 28,7.
Прочитанное как измерение, это означает «функция бесплатна». Как измерение это не читается, и причина структурная:
AuxTarget::presentвозвращаетOk(())приTimeout | Occludedи ещё раз приOutdated | Lost, оба раза до всякой работы энкодера; ничто не считается. Консоль, выводившая каждый кадр, и консоль, чья поверхность была перекрыта весь прогон, дают тот жеOk, тот же лог и тот же фреймрейт. Прибор в обоих случаях сообщает нулевую цену, и ни одна поверхность в этом проекте их не различает.
Ноль, означающий это бесплатно, и ноль, означающий это ни разу не выполнилось, — один и тот же ноль. Без свидетеля того, что работа действительно происходила, нулевой результат нечитаем, — а нулевой результат как раз тот, который вы наиболее склонны принять: он хорошая новость и не требует продолжения.
Контраст, сделавший правило очевидным, лежит в той же кодовой базе: основной путь рендера вызывает счётчик пропущенных кадров на том же самом пропуске и доходит до счётчика кадров только после успешного вывода. Поэтому его фреймрейт считает выводы, а не итерации цикла. Один путь был построен так, чтобы отличать «ничего не сделал» от «сделал быстро», а другой — нет, и сравнение их двоих породило правило: измерение нулевой цены называет свидетеля того, что вещь выполнялась.
Соседнее решение закрывает более тонкую, статистическую версию того же. Четыре теста оценивают цену одной возможности отрисовки против того, что она заменяет: каждый рендерит короткий и длинный прогон и делит разницу на разницу кадров. Наклон — правильный инструмент: вычитание короткого прогона снимает компиляцию шейдеров, первичное выделение памяти и любую другую фиксированную настройку, оставляя работу на кадр.
Неверной была оценка вокруг него. Каждый тест повторял пару трижды и хранил минимум разности. Но минимум long − short выбирает тот повтор, где длинный прогон случайно оказался быстрым, а короткий случайно медленным, — он подбирает шум с обоих концов и смещает оценку вниз. Починка вынесена в заголовок: берите лучшее из каждой длительности, а не лучшую разность. Минимум длинных минус минимум коротких.
Небольшая правка с общим уроком: когда вы вычитаете два зашумлённых измерения, минимизировать разность и вычитать минимумы — не одно и то же, и первое вам льстит.
У второго проекта бюджеты растут из другого места
market-analyzer — рабочее место для анализа рынка, и его нефункциональные требования вообще не про железо.
Бюджеты Ritmolux идут от физики. Экран обновляется каждые 16,67 мс независимо от того, готовы вы или нет. Аудиоустройство отдаёт буферы в своём темпе. Загрузка — это столько-то байт, которых кто-то ждёт. На другом конце каждого из этих требований стоит машина, и она не торгуется.
У market-analyzer они идут от того, что предметная область опасна. Его README формулирует причину одним предложением:
бэктест, который подглядывает в будущее или плывёт от запуска к запуску, хуже, чем бесполезен, потому что выглядит уверенно.
Это совершенно другой режим отказа, и он о себе не сообщает. Ничего не падает. Ничего не тормозит. Вы получаете число, число неверное, и приходит оно ровно в той же одежде, что и верное: то же форматирование, те же знаки после запятой, та же форма кривой капитала. Ловить нечего и грепать нечего, а человек на приёме сейчас начнёт по этому действовать.
Поэтому бюджеты записаны как инварианты в спецификации, в форме MUST, и читаются они скорее как стандарт безопасности, чем как цели по производительности:
- Никакого заглядывания вперёд. Решение на баре
iисполняется на бареi + 1и заполняется по цене открытия этого бара. Движок не имеет права дать сигналу заполниться по любой цене с индексом≤ iи обязан отбросить сигнал, у которого исполняемого бара не существует. - Побайтовая воспроизводимость при перезапуске — и не только внутри процесса: идентичность между процессами и между машинами, чтобы те же входные данные на другом компьютере дали ту же кривую капитала.
- Никакого скрытого недетерминизма на финансово значимом пути: между чтением входов и возвратом результата нельзя ни обходить множество, ни читать часы.
- Выходы раньше входов, когда и то и другое приходится на один бар, через устойчивую сортировку, — чтобы детерминированная стратегия давала детерминированный список сделок.
- Идентичность данных записывается, а не предполагается: каждый результат несёт хеш баров, на которых он посчитан.
- Чистота: ядро не трогает ни диск, ни базу, ни сеть.
Происхождение разное, дисциплина та же: каждый пункт называет измерение или тест, который его удерживает, а не описывает качество, к которому следует стремиться. И спецификация формулирует собственную задачу строкой, которую я перенёс бы в любой поведенческий контракт: «читатель должен уметь восстановить контракт детерминизма по одному этому файлу».
Раздел о реалтайм-безопасности в Ritmolux делает тот же трюк с другой стороны. Он озаглавлен «проверяемая переформулировка», и вместо «аудиопоток должен быть быстрым» там сказано: ноль выделений в куче, ноль блокировок, ноль логирования, ноль файлового ввода-вывода и никаких паникующих вызовов на путях, выполняющихся на каждом кадре. Каждое из этого проверяется чтением кода или инструментированием потока. «Быстрый» — нет.
Честные нули
То, что делают оба проекта и что я бы взял в любую кодовую базу, — отводить место под то, чего требование не покрывает.
Ritmolux делает это по ходу текста и неоднократно: незабюджетированный транслируемый путь; групповая задержка, принятая как физика; порог покрытия, про который сказано, что мерили его на машине с аппаратным GPU, тогда как в CI программный рендеринг, — и что поэтому он должен быть перемерен; пометка, что ни один раннер CI не загружает foobar2000, поэтому установка компонента проверяется руками.
Самая яркая — матрица оборудования, озаглавленная «что есть у пользователя»: не что проект поддерживает, а что физически существует для проверок. И под ней:
Мака в этой матрице нет, и в этом всё дело. Более ранняя версия таблицы перечисляла «Mac, macOS 13+» как доступное железо; его нет… Поэтому путь macOS-сборки проверяется получателем, а не своими силами, и пока кто-то не отчитается, он ни разу не исполнялся на железе Apple вообще.
Поставляемый артефакт, собираемый в CI, приложенный к каждому релизу, ни разу не запускавшийся на платформе, для которой он предназначен, — и это записано в документе требований, в разделе о том, что чем проверяется. Более ранняя версия таблицы была неверна в льстящую сторону, и исправление сделало документ хуже на слух и намного полезнее.
В спецификации бэктеста у market-analyzer у той же идеи есть заголовок: «Известные пробелы и честные нули». Под ним, среди прочего, — ограничение области только что данной гарантии: детерминизм держится для финансово значимого пути и явно не держится для идентификатора запуска и временных меток.
Это тот же инстинкт, что и в посте про слепые пятна проверок, только применённый к требованиям, а не к тестам. Гарантию, границы которой не названы, прочтут как всеобщую — а потом обопрутся на неё там, куда она никогда не собиралась дотягиваться. Обычно это сделает тот, кого не было рядом, когда её писали, а через несколько месяцев это все.
Что не даёт числу устареть
Бюджет, рядом с которым стоит измерение, всё равно портится, если документ и артефакт расходятся. План двигает цифру, запись сохраняет старую, и через год кто-то сравнивает сборку этой недели с пределом прошлого квартала, не зная об этом. Каждое число, процитированное в этом посте, — это число, которое нужно пересмотреть, когда его что-то сдвинуло.
В Ritmolux это решено чек-листом, а не старательностью. Навык архитектора — роль, которая пишет планы и записи о решениях, — несёт церемонию закрытия, и один из её шагов — таблица свежести документов, где каждому документу сопоставлено событие, после которого он устарел. Одна из строк:
docs/nfr.md| сдвинулся количественный бюджет
Это весь механизм, и важна именно его форма. Не «поддерживайте NFR в актуальном состоянии» — указание, провалить которое заметным образом невозможно, — а названный файл рядом с конкретным триггером, обязывающим его пересмотреть, читаемый в момент закрытия плана, то есть тогда, когда сдвинувший число человек ещё держит его в руках. Тот же инстинкт, что и в проверках документации: вынести нагрузку из памяти в шаг, мимо которого приходится пройти.
Что заставляет число работать
Судя по этому опыту — пять вещей.
Единица измерения, чтобы измерение можно было с числом сопоставить, не угадывая, какое соглашение имелось в виду.
Вывод — или признание, что вывода нет. Унаследованное число нормально. Унаследованное число в поддельном обосновании — нет, потому что его никто не сможет пересмотреть.
Область действия с указанием того, что остаётся снаружи, — особенно там, где ваш процесс заканчивается и начинается чужой.
Измерение, которое реально выполняется, печатая текущее значение при каждой сборке, независимо от того, может ли оно её завалить. Вот это и есть несущий пункт. Всё остальное — документ; это — то, что замечает.
Свидетель того, что измерение что-то измерило. Ноль от прибора, который ни разу не сработал, и ноль от бесплатной операции неразличимы, а первый из двух вероятнее.
Чего всё это не требует — так это чтобы бюджет был строгим. Предел размера только предупреждает. У бюджета задержки по одному из слагаемых 350-кратный запас, и он же признаёт, что смежный путь не забюджетирован вовсе. Строгость — не то свойство, которое делает требование настоящим. Настоящим его делает проверяемость, а честность насчёт границ — то, что оставляет его проверяемым и через год, когда читать его будет уже не тот, кто писал.