TypeScript / 37 минут

unknown и runtime-проверки

Проверяйте неизвестные данные до использования.

В процессе Теория: впереди Практика: впереди Тест: впереди
10 / 12
После этой главы
  • Проверяйте неизвестные данные до использования.
  • Применить тему в задаче «unknown и runtime-проверки» и объяснить граничные случаи.

Около 7 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Честный тип внешнего ввода

Данные из JSON, хранилища или пользовательского сообщения неизвестны до проверки. unknown позволяет принять такое значение, не разрешая опасно обращаться к его свойствам. Это полезнее any на границе: компилятор напоминает, что форма ещё не доказана. При этом unknown не является механизмом очистки или изоляции — это статическое описание отсутствия знания.

Функция разбора должна превратить неизвестное значение в проверенную модель либо вернуть понятный отказ. Не нужно распространять unknown через всё приложение, если одна ответственная граница может проверить схему и выдать конкретный тип. Тогда бизнес-код работает с устойчивым контрактом, а диагностическая логика остаётся в одном месте.

Поэтапная проверка объекта

Прежде чем читать name, нужно установить, что значение является подходящим объектом, не равно null, содержит поле и что поле имеет нужный тип. typeof value === 'object' недостаточно: null тоже даёт 'object'. Наличие свойства через in не доказывает его строковый тип. Каждая проверка отвечает только за свою часть обещания.

TypeScript
function readName(value: unknown): string | null {
  if (typeof value !== 'object' || value === null) return null;
  if (!('name' in value)) return null;
  return typeof value.name === 'string' ? value.name : null;
}

В зависимости от контракта может понадобиться исключить массивы и ограничить собственные поля. Для объекта профиля это нередко разумно, а для интерфейса «любое значение со строковым name» правила могут быть шире. Проверка должна следовать условию, а не случайно отвергать допустимые структуры или принимать недостаточные.

Type predicates и ответственность реализации

Предикат value is User сообщает компилятору, что при true вход можно считать User. TypeScript доверяет этому обещанию в значительной мере, поэтому неверное тело предиката способно сделать проверку типов обманчивой. Нужно действительно проверить все свойства, на которые затем будет опираться код.

TypeScript
type Named = { name: string };
function isNamed(value: unknown): value is Named {
  return typeof value === 'object' && value !== null
    && 'name' in value && typeof value.name === 'string';
}

Не используйте return true как временную заглушку предиката в готовом приложении. Ошибка может не проявиться на демонстрационном ответе сервера, но появится при повреждённом или намеренно некорректном вводе. Набор отрицательных тестов для validator часто важнее нескольких одинаковых успешных примеров.

Assertion functions и результаты разбора

Assertion function сообщает, что после успешного завершения условие считается доказанным; при нарушении она обычно бросает ошибку. Это удобно внутри сценария, где некорректность должна прервать выполнение. Для пользовательских форм иногда лучше вернуть структурированный список ошибок, чтобы показать все проблемы, а не остановиться на первой.

TypeScript
function assertNamed(value: unknown): asserts value is Named {
  if (!isNamed(value)) throw new Error('Ожидался объект со строковым name');
}
function useInput(value: unknown): string {
  assertNamed(value);
  return value.name.toUpperCase();
}

Выбор throw или Result зависит от контракта и владельца ошибки. В обоих случаях проверка должна быть runtime-действием. Утверждение as Named, интерфейс DTO без декораторов или generic-параметр request<Named> такой проверкой не являются, даже если после них редактор показывает удобное автодополнение.

Схемы, границы и ограничения ресурсов

Для больших вложенных DTO специализированные библиотеки схем помогают описывать и проверять правила последовательно. Но установка библиотеки не отменяет необходимость определить ограничения длины, количества элементов и разрешённых полей. Внешний ввод может быть не только неправильным по типу, но и слишком большим для допустимой стоимости обработки.

Не используйте проверенный объект шире доказанной схемы. Если схема подтвердила только name, нельзя затем доверять role из соседнего поля. Авторизация не следует из успешной валидации формы: корректно записанное 'admin' в клиентском JSON не даёт административные права. Данные о подтверждённом пользователе должны иметь собственный доверенный источник.

Практика неизвестного значения

Функция получает unknown и возвращает name только для подходящего объекта со строковым полем. Пустая строка допустима по условию и должна сохраниться; число в name недопустимо. null, массив без нужного поля и примитив не должны приводить к исключению. Сначала проведите проверки, затем обращайтесь к свойству.

Проверьте все перечисленные отрицательные случаи и обычный объект. После решения напишите предикат, а затем тест, который намеренно передаёт {name:4}. Объясните, какая именно проверка предотвращает ошибку вызова строкового метода. Для итогового TypeScript-проекта используйте тот же принцип в API-клиенте: ответ сначала unknown, потом validator, и лишь затем типизированные данные интерфейса.

Загружаем редактор…

Музыка: Reverie — Scott Buckley · CC BY 4.0