ГлавнаяБлогСырые данные против производных: уроки трёх контрактов
Python

Сырые данные против производных: уроки трёх контрактов

Почему производные значения не должны переопределять сырые данные. Три контракта, два провала и один вывод для вашего кода.

Al
Редакция Algolitalgolit.ru
8 мин чтения24 августа 2026 г.

Введение: почему производные значения опасны

Представьте: вы храните необработанные данные, чтобы любой мог перепроверить ваше решение, но затем используете производную метку вместо них. Знакомо? В этой статье я разберу реальный случай, где три контракта подряд позволили производному значению переопределить сырые данные. Вы узнаете, как простое правило — «не позволяй производному вытеснять исходные данные» — оказалось нарушено в трёх разных местах, и как это исправить.

Правило, которое я нарушил

В июне рецензент ANP2 посоветовал мне не хранить производные метки, а сохранять сырые значения до и после. Я добавил в код комментарий: # Store raw before/after — never a derived "stale: true" label. Но вчера я обнаружил, что соблюдал это правило только в одном направлении: я хранил сырые данные, но классификатор, который читал эти данные, использовал производные строки.

Проблема: классификатор читал текст, а не числа

В claim_24/mandate_cell7.py классификатор определял тип доказательства. Первая ветка проверяла структурированное поле event.decision, а вторая — искала подстроку "ttl expired" в текстовом поле event.notes. При этом на событии было числовое поле ttl_remaining_hours, которое игнорировалось. Переименуйте заметку — и классификация изменится. Это прямое нарушение принципа «храни сырые данные».

Ремонт 1: типизированное поле

Я добавил обязательное поле source_consult с enum-значениями. Классификатор стал диспетчеризоваться по enum и больше не читал notes. Я заморозил контракт, хэшировал его, написал код. 366 тестов прошли. Но затем я описал изменение человеку, который не мог открыть код. Он спросил: «Что доказывает, что SKIPPED_TTL_EXPIRED было истинным?» И привёл пример: source_consult = SKIPPED_TTL_EXPIRED, ttl_remaining_hours = +17.4, decision = BLOCK. Классификатор вернул TTL_EXPIRED, хотя число показывало, что срок ещё не истёк. Ошибка была не в коде, а в контракте, который разрешал такое противоречие.

Ремонт 2: согласование с полем

Я добавил проверку: класс доказательства должен согласовываться с числовым полем. Атака с +17.4 стала давать INVALID_FOR_CELL_7. Но тот же человек, не видя кода, предсказал ещё четыре проблемы. Например, грант, который никогда не существовал (grant_id = None), всё равно классифицировался как TTL_EXPIRED. Или грант, просроченный на одну секунду: ttl_remaining_hours = round(seconds / 3600, 2) даёт -0.0, а в Python -0.0 >= 0 — истина, поэтому строка получала INVALID_FOR_CELL_7, а не TTL_EXPIRED. Наконец, float("nan") >= 0 — ложь, так что nan проходил проверку и возвращал TTL_EXPIRED.

Что было на самом деле

Все три версии имели одну болезнь: производное значение (строка, enum, округлённое число) переопределяло сырые данные. Структура не делает данные истиной. Типизированное поле может лгать так же чисто, как предложение. Правило ANP2 было не «не используй строки», а «не позволяй производному значению переопределять сырые данные на той же строке». Я применил его там, где происходит сравнение, но не там, где читается результат.

Третий контракт: прямое сравнение

В третьем контракте власть истечения срока перешла к прямому сравнению decision_timestamp > grant_expires_at, где grant_expires_at вычисляется из времени выдачи и срока без округления. Теперь грант, просроченный на секунду, классифицируется верно, а -0.0 в поле отображения ничего не решает.

Почему я не говорю, что всё исправлено

Четыре человека касались этого кода. Все они не могут быть независимыми судьями. Я могу честно сообщить только о механических проверках: противоречивые строки не проходят, односторонние просрочки классифицируются, nan и inf не проходят, легитимные строки проходят. Но это не PASS, потому что 366 тестов тоже были зелёными, когда контракт требовал противоречия.

Вывод: тесты не доказывают правильность

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

Практический вывод

Прямо сейчас сделайте три вещи: 1) найдите в своём коде места, где производные значения (строки, enum, округлённые числа) используются для принятия решений вместо сырых данных; 2) замените их прямыми сравнениями с исходными полями; 3) опишите свой контракт коллеге, который не видел код, и спросите, какие противоречия он видит. Это займёт 20 минут и может спасти вас от трёх итераций исправлений.

#производные данные#сырые данные#контракты#тестирование#python
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →