TypeScript / архитектура
TypeScript не проверяет JSON: как защитить границы приложения
Тип ответа в редакторе не превращает внешний JSON в достоверную доменную модель. Надёжное приложение проверяет данные в точке входа и только после этого передаёт их типизированному коду.
Где заканчивается гарантия компилятора
Аннотация const user: User = await response.json() выглядит убедительно, но во время выполнения TypeScript уже удалил большинство аннотаций. Сервер может вернуть null, старую схему или строку вместо числа. Компилятор проверяет согласованность вашего кода с заявленными типами, а не честность сети, файла, localStorage или чужого пакета.
Поэтому входящие значения лучше сначала считать unknown. Дальше узкая функция проверяет форму объекта, обязательные поля и допустимые значения. После успешной проверки возвращается доменный тип. Остальная часть приложения не обязана повторять одни и те же защитные условия в каждом компоненте. Граница становится явной, а ошибки — локальными и объяснимыми.
Контракт должен жить рядом с входом
Если API возвращает профиль, полезно разделить сетевой DTO и модель интерфейса. У DTO могут быть необязательные поля или дата строкой; интерфейсу нужна нормализованная структура с понятными значениями по умолчанию. Слой преобразования делает это один раз: проверяет ответ, переводит дату, отбрасывает лишнее и возвращает либо валидную модель, либо структурированную ошибку.
Важно не подменить проверку принудительным приведением через as User. Assertion сообщает компилятору, что вы берёте ответственность на себя, но не меняет значение. Библиотека схем может уменьшить объём кода, однако ключевое решение остаётся архитектурным: какая именно граница считается недоверенной, кто владеет её схемой и как приложение ведёт себя при несовпадении.
Не все ошибки одинаковы
Сбой сети, HTTP-ошибка, невалидный JSON и валидный JSON неправильной формы требуют разного ответа. Первый можно повторить, второй зависит от статуса, третий указывает на транспорт или сервер, четвёртый — на нарушенный контракт. Если всё превратить в «что-то пошло не так», команда потеряет диагностическую информацию, а пользователь не поймёт, помогает ли повторная попытка.
Ошибку валидации полезно снабдить путём поля и кодом причины, но не надо выводить в лог весь ответ, если там персональные данные. В UI покажите безопасное сообщение и путь восстановления. Для обязательных данных экран может перейти в состояние ошибки; для необязательного поля иногда достаточно дефолта, если это явно допускает продуктовый контракт.
Как проверять такой слой
Минимальные тесты включают корректный ответ, отсутствие обязательного поля, неверный тип, неизвестное enum-значение и null вместо объекта. Особенно ценны реальные примеры ответов из staging или зафиксированных контрактов: они обнаруживают расхождение между описанием API и фактическими данными. Тесты на негативные случаи должны проверять именно поведение приложения, а не только факт исключения.
После добавления валидатора убедитесь, что типы выводятся из проверенной схемы либо поддерживаются согласованно. Дублирование независимого интерфейса и схемы создаёт две версии истины. Цель подхода не в максимальном числе проверок, а в том, чтобы недоверенные данные перестали незаметно превращаться в обещание, которое TypeScript технически не способен выполнить.
Рамка для принятия решения
В центре решения — проверки данных на границе TypeScript-приложения: его стоит оценивать в границах «один внешний JSON-ответ и одну точку преобразования в доменную модель», а не на абстрактном демо.
Тема проверки данных на границе TypeScript-приложения легко превращается в спор о вкусах. Чтобы принять инженерное решение, нужно сначала зафиксировать исходное состояние, ожидаемый результат и стоимость ошибки. Полезно сузить пилот до области «один внешний JSON-ответ и одну точку преобразования в доменную модель»: так легче сравнить поведение до и после, а также откатить изменение без потери данных.
- Ограничить пилот контуром: один внешний JSON-ответ и одну точку преобразования в доменную модель.
- Заранее описать доказательство готовности: валидатор, ошибки с путями полей и тесты на некорректные ответы.
- Выбрать один репрезентативный сценарий: получение профиля пользователя из внешнего API с неполными полями.
- Зафиксировать версии, настройки, права и бюджет, чтобы опыт можно было повторить.
- Назначить человека, который может остановить пилот и оценить качество без авторитета инструмента.
Критерии нужно согласовать до начала: если формулировать их после результата, любой исход можно объявить успехом.
Практический сценарий внедрения
Репрезентативный сценарий для пилота — получение профиля пользователя из внешнего API с неполными полями; он достаточно реален, но остаётся управляемым.
Хороший пилот начинается не с миграции всего проекта, а с одного повторяемого сценария. Для этой темы подойдёт «получение профиля пользователя из внешнего API с неполными полями». Важно записать не только итог, но и путь: какой контекст потребовался, где возникли неясности, сколько было повторов и какие ручные решения остались за разработчиком.
- Снять базовые замеры и сохранить чистую точку отката до начала работы.
- Разбить сценарий на исследование, минимальную реализацию, проверку и независимое ревью.
- Запустить проверки, соответствующие риску: негативные тесты, контракты API и реальные ответы в staging.
- Собрать артефакты: валидатор, ошибки с путями полей и тесты на некорректные ответы, не ограничиваясь текстом финального отчёта.
- Попросить ревьюера оценить результат по критериям, не зная, какой инструмент или подход использовался.
На каждом шаге должна сохраняться возможность остановиться, понять текущее состояние и продолжить работу вручную. Это делает процесс устойчивым к сбоям и не привязывает команду к одному инструменту.
Риски и защитные механизмы
Особого внимания требует ложная уверенность от аннотации типа поверх непроверенного JSON: эту зону нужно проверять отдельно и не скрывать за общим успехом.
Главный риск здесь — ложная уверенность от аннотации типа поверх непроверенного JSON. Его нельзя снять одним промптом, настройкой или проходящим unit-тестом. Нужна сетка защиты: минимальные права, маленький diff, изолированная среда, автоматические проверки и ручная оценка там, где ошибка влияет на пользователя, данные или производство.
- Отдельно проверить главную зону риска: ложная уверенность от аннотации типа поверх непроверенного JSON.
- Не расширять permissions и доступ к данным только ради более гладкого демо-сценария.
- Не смешивать миграцию, рефакторинг и новую функцию в одном diff: такой патч сложно проверить и откатить.
- Считать непроходящую или неопределённую проверку блокером, а не поводом изменить ожидание.
- Для данных, авторизации, платежей и публичных API оставлять обязательную ручную проверку и явное одобрение.
Защита не должна зависеть от того, что исполнитель «помнит» все инструкции. Критические ограничения лучше выразить правами, CI, валидаторами и средой, которую нельзя обойти обычным текстом.
План внедрения и метрики
Для итогового решения нужны артефакты «валидатор, ошибки с путями полей и тесты на некорректные ответы» и динамика метрик, а не субъективное впечатление от первого запуска.
Пилот стоит признавать успешным только тогда, когда он улучшает измеримую часть работы. Для проверки данных на границе TypeScript-приложения полезно наблюдать число ошибок парсинга, неожиданных значений и падений интерфейса. Сначала нужна базовая линия без нового подхода, затем две-три недели наблюдения и только после этого — решение о расширении. Единичный удачный пример не заменяет серию повторяемых задач.
- Измерять число ошибок парсинга, неожиданных значений и падений интерфейса на серии задач, а не на лучшем единичном примере.
- Сравнивать медиану и неудачные выбросы: среднее значение часто скрывает дорогие провалы.
- Записывать время человека на постановку, перехваты, ревью и исправления, а не только машинное время.
- Отдельно считать регрессии, откаты, инциденты и доработки, появившиеся уже после слияния.
- По итогам пилота обновить командные инструкции, проверки и ограничения, даже если сам инструмент не будет внедрён.
Хорошее внедрение улучшает не демо, а повторяемый инженерный процесс и качество результата.
После первого цикла стоит пересмотреть не только выбор инструмента, но и сам процесс: какие инструкции оказались неясными, каких проверок не хватило и где ревью началось слишком поздно.
Контрольные вопросы перед внедрением
Перед внедрением нового подхода команде стоит письменно ответить на несколько вопросов. Какую конкретную проблему мы решаем? Как выглядит тот же процесс сегодня? Какие данные, ключи, команды и внешние системы затронуты? Кто имеет право одобрить результат и кто будет разбирать инцидент? Ответы превращают новую технологию из личного эксперимента в управляемое изменение.
- Какой минимальный контур «один внешний JSON-ответ и одну точку преобразования в доменную модель» даст полезный ответ без большой миграции?
- Как будет собрано доказательство: валидатор, ошибки с путями полей и тесты на некорректные ответы?
- Нужно ли включить в обязательный набор негативные тесты, контракты API и реальные ответы в staging?
- По какому сигналу пилот будет остановлен, даже если первые результаты выглядят многообещающе?
Если на эти вопросы нет коротких и проверяемых ответов, то разворачивать новый подход ещё рано. Лучше потратить час на ясные границы, чем неделю на разбор неуправляемого эксперимента.