Программирование с ИИ
ENRU
9.5

Типы как проверка работы ИИ

пять ошибок, пойманных до запуска, и одна, которая проходит

Что это

Главная статья главы. Вернись к главе 4: там десять статей о том, как руками ловить ошибки, которых не видно при запуске. Типы делают часть этой работы механически, при каждой проверке, бесплатно.

Ниже — пять настоящих ошибок ИИ и настоящие сообщения компилятора на них. А потом — честное ограничение: одна ошибка того же рода проходит мимо, и знать про неё важнее, чем про остальные пять.

Как выглядит в коде

1. Забыл проверить пустоту

Самая частая ошибка ИИ во всей книге:

function report(c: City): string {
  return `${c.name}: ${c.temp.toFixed(1)}°`;
}
измерено
error TS18047: 'c.temp' is possibly 'null'.

2. Перепутал имя поля

const c: City = { name: "Oslo", temperature: 4 };
измерено
error TS2353: Object literal may only specify known properties,
and 'temperature' does not exist in type 'City'.

В JavaScript без типов это тихо создало бы объект с лишним полем и без нужного. Ошибка всплыла бы в другом месте и выглядела бы как undefined.

3. Перепутал порядок аргументов

function subscribe(userId: number, city: string): void {}
subscribe("Oslo", 7);
измерено
error TS2345: Argument of type 'string' is not assignable
to parameter of type 'number'.

Классика при работе с ИИ: он помнит функцию с похожим именем и другим порядком аргументов.

4. Забыл вариант в union

type Status = "paid" | "pending" | "refunded";

function label(s: Status): string {
  switch (s) {
    case "paid": return "оплачен";
    case "pending": return "ожидает";
  }
}
измерено
error TS2366: Function lacks ending return statement
and return type does not include 'undefined'.

Забыт refunded. Без типов функция молча вернула бы undefined, и в интерфейсе появилась бы пустота вместо подписи.

Это важный случай: добавишь завтра четвёртый статус — компилятор найдёт все места, где его не обработали. Руками такое не ищут.

5. Обращение по индексу за границей

const cities = ["Oslo", "Bergen"];
const first: string = cities[5];
измерено
error TS2322: Type 'string | undefined' is not assignable to type 'string'.

Работает только с noUncheckedIndexedAccess (статья 9.3).

Где типы не помогают

Теперь честно. Вот код, который компилятор пропускает без единой ошибки:

interface WeatherResponse { main: { temp: number }; name: string }

async function fetchWeather(city: string): Promise<WeatherResponse> {
  const raw = '{"cod":"404","message":"city not found"}';
  return JSON.parse(raw) as WeatherResponse;
}

const data = await fetchWeather("Tonsberg");
console.log(data.main.temp);
измерено
tsc: ошибок нет
измерено
упало: Cannot read properties of undefined (reading 'temp')

as WeatherResponse — это обещание компилятору, а не проверка. Ты сказал «поверь, тут такая форма», он поверил. Пришло другое — и тот же самый Cannot read properties of undefined, с которого начиналась глава 8.

Схема из книги — будет перерисована для сайта
Типы проверяют твой код сам с собой. Данные, пришедшие снаружи, они только описывают — проверять их надо отдельно.

Граница — это всё, что пришло снаружи программы:

  • ответ API (fetch, JSON.parse);
  • данные формы;
  • localStorage;
  • параметры из адреса страницы.

Чем закрывать границу

Библиотеками проверки во время выполнения — zod, valibot и подобными. Они делают для фронтенда то же, что pydantic для бэкенда (статья 7.3): описываешь форму один раз, и она и проверяет данные, и даёт тип.

Запрос
Ответ API сейчас приводится через `as`. Замени на проверку
во время выполнения через zod: схема, разбор, понятная ошибка,
если форма не совпала. Тип выведи из схемы, не дублируй руками.

Последняя фраза важна: смысл в том, чтобы форма описывалась один раз и служила и проверкой, и типом. Два описания рядом разъедутся.

Как сказать ИИ

Типы — ещё и способ ставить задачу. Сигнатура точнее слов (статьи 3.4, 6.5):

Запрос
Напиши функцию под эту сигнатуру:

async function loadSubs(userId: number): Promise<Result<City[]>>

где Result<T> = { ok: true; data: T } | { ok: false; error: string }.
Исключения наружу не бросай, ошибки возвращай вторым вариантом.
tsc --noEmit со strict должен проходить.

Проверка готового кода:

Запрос
Прогони tsc --strict по своему коду мысленно: какие ошибки
он найдёт? Исправь их, не используя any, as и ts-ignore.

Запрет в последней строке обязателен — иначе «исправление» будет заглушкой (статья 9.6).

Шпаргалка

  • Типы ловят до запуска: забытую пустоту, неверное поле, порядок аргументов, пропущенный вариант union, выход за границу массива.

  • Добавил вариант в union — компилятор найдёт все необработанные места.

  • as — обещание компилятору, а не проверка. Данные извне он не смотрит.

  • Граница: ответ API, формы, localStorage, адрес страницы.

  • Границу закрывают проверкой во время выполнения, например zod.

  • Тип выводи из схемы, а не дублируй рядом.