Ревью TypeScript: замечания, которые нужно писать
Что реально стоит писать в комментариях к пулл-реквесту на TypeScript: не про табы и точки с запятой, а про any, приведения типов и утечки типовой безопасности через границы модулей.

Линтер уже отловил отступы, а Prettier — кавычки. На ревью TypeScript тратить время на форматирование бессмысленно: это делает CI. Ревьюер ищет другое — места, где типы врут. Где компилятор пропускает undefined, где any протекает через полкода, где приведение as прячет реальную ошибку до продакшена. Ниже — замечания, которые стоит писать, с формулировками, которые автор поймёт и исправит, а не проигнорирует как вкусовщину. Ориентир — TypeScript 5.x со включённым strict.
Замечания, которые действительно меняют код
1. any вместо unknown на границе
Самое частое и самое дорогое. any отключает проверку типов не только в точке объявления, но и всюду, куда значение утечёт дальше. Типичное место — разбор ответа от API или парсинг JSON.
// было — ревью-замечание
function parse(raw: string): any {
return JSON.parse(raw);
}
// стало
function parse(raw: string): unknown {
return JSON.parse(raw);
}
С unknown компилятор заставит автора сузить тип перед использованием — через проверку или валидатор. Формулировка замечания: «Здесь any отключает проверку для всех потребителей parse. Замени на unknown и добавь сужение — иначе поле user.email дальше по коду не проверяется вообще».
2. as как затычка вместо сужения
Приведение value as SomeType — это обещание компилятору «поверь мне». Компилятор верит и молчит. Если обещание ложное, ошибка вылезет в рантайме.
// подозрительно
const user = response.data as User;
// честно: type guard
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof (value as { id: unknown }).id === "string"
);
}
if (!isUser(response.data)) {
throw new Error("Ответ не соответствует форме User");
}
const user = response.data; // здесь уже User
Замечание: «as User ничего не проверяет в рантайме. Если бэкенд вернёт другую форму, user.id будет undefined, а тип скажет, что это string. Нужен type guard или валидатор схемы».
3. Двойное приведение as unknown as
Конструкция x as unknown as T — прямой обход системы типов. Иногда она оправдана (мосты к нетипизированным библиотекам), но чаще это флаг того, что типы спроектированы неверно. На ревью такое место требует комментария автора: «Почему нельзя без двойного приведения?» Если ответа нет — типы надо переделать, а не пробивать.
4. Необязательная цепочка, скрывающая логическую ошибку
Оператор ?. удобен, но он маскирует ситуации, которых по бизнес-логике быть не должно.
// order.customer по контракту всегда есть
const name = order?.customer?.name ?? "Гость";
Если order здесь не может быть null, то ?. — это ложное спокойствие: реальный undefined у customer проглотится и превратится в «Гость», а баг уедет в прод. Замечание: «Если по контракту order всегда определён, убери ?. — пусть компилятор гарантирует это по типу. Иначе непонятно, где настоящая ошибка, а где ожидаемое отсутствие».
Хорошее замечание на ревью TypeScript отвечает на вопрос «что сломается в рантайме и когда», а не «мне не нравится, как написано». Первое автор исправит. Второе — проигнорирует.
Замечания о структуре типов
5. Строка там, где должно быть перечисление
Поле status: string принимает что угодно, включая опечатку "activ". Union литералов ловит это на компиляции.
// слабо
interface Task {
status: string;
}
// точнее
type TaskStatus = "todo" | "in_progress" | "done";
interface Task {
status: TaskStatus;
}
Теперь switch по статусу можно сделать исчерпывающим, и компилятор укажет, если добавили новый статус, а ветку забыли.
6. Дублирование формы вместо переиспользования
Если в коде вручную повторяется набор полей другого типа — это место для утилитных типов. Ревьюер должен предложить Pick, Omit, Partial или ReturnType вместо ручной копии, которая разъедется при первом же изменении исходного типа.
// вместо ручного повтора полей User
type UserPreview = Pick<User, "id" | "name" | "avatarUrl">;
7. Отключённые проверки через комментарии
// @ts-ignore глушит ошибку на следующей строке молча — включая ошибки, которые появятся там позже. // @ts-expect-error лучше: он сам станет ошибкой, если проблема исчезнет. На ревью любой @ts-ignore заслуживает вопроса: «Что именно здесь не сходится по типам? Если это временно — поставь @ts-expect-error с комментарием, чтобы строку убрали, когда проблема уйдёт».
На что смотреть в зависимости от масштаба замечания
Не все замечания равны. Разделяйте блокирующие от косметических — иначе автор утонет в потоке и не поймёт, что критично.
| Замечание | Риск в рантайме | Блокирует мёрдж |
|---|---|---|
any на публичной границе | Высокий: проверка отключена для потребителей | Да |
as без валидации внешних данных | Высокий: рантайм-исключение при неожиданной форме | Да |
as unknown as | Средний: зависит от обоснования | Обсудить |
| Строка вместо union | Средний: опечатки не ловятся | Обсудить |
| Ручное дублирование формы типа | Низкий: расходится при изменениях | Нет, но пометить |
| Именование, порядок импортов | Нулевой | Нет — отдать линтеру |
Замечания, которые лучше не писать
Ревью портится не столько пропущенными багами, сколько шумом. Не стоит писать про то, что автоматизируется:
- Отступы, кавычки, точки с запятой — это Prettier. Если он не настроен, замечание одно: «Добавьте Prettier в pre-commit».
- Порядок импортов и неиспользованные переменные — ESLint с
@typescript-eslint. - Явные аннотации типов там, где вывод очевиден:
const count: number = 0избыточно,const count = 0достаточно. Но на публичных функциях явный возвращаемый тип полезен — он фиксирует контракт. - Стилистические предпочтения без обоснования:
interfaceпротивtypeдля объектов — обсуждается один раз на уровне команды, а не в каждом PR.
Как формулировать, чтобы исправляли
Три приёма, которые повышают шанс, что замечание примут:
- Назовите последствие, а не правило. Не «здесь any», а «через этот any поле
priceниже по коду не проверяется, иNaNуедет в расчёт суммы». - Дайте вариант. Замечание с примером исправления закрывается в разы быстрее, чем «переделай».
- Отделяйте обязательное от предложения. Пометка вроде
nit:перед косметикой снимает у автора ощущение, что всё блокирует мёрдж.
Что настроить, чтобы половина замечаний исчезла
Большая часть ручных замечаний по типам не нужна, если ужесточить конфигурацию. Проверьте в tsconfig.json, что включён strict: true — он подтягивает strictNullChecks, noImplicitAny и другие флаги. Отдельно стоит рассмотреть noUncheckedIndexedAccess: он добавляет undefined к результату доступа по индексу, и тогда обращение к arr[i] потребует проверки — это ловит целый класс багов, которые иначе всплывают в рантайме. Точный набор флагов и их поведение сверяйте с официальной документацией TSConfig для вашей версии компилятора: между мажорными релизами дефолты и доступные опции меняются.
Для валидации внешних данных вместо ручных type guard посмотрите в сторону библиотек, где схема и тип выводятся из одного источника — так граница между рантаймом и типами перестаёт расходиться. Выбор конкретной библиотеки — отдельная тема; здесь важно, что валидация внешних данных не должна держаться на as.
Инфографика для маркетплейса: Три главных преимущества товара короткими фразами, выноски в безопасной зоне превью, точный размер площадки.
Спросить за 60 ₽Источники: TSConfig Reference — TypeScript, TypeScript Handbook: Narrowing
Частые вопросы
any и unknown — в чём практическая разница на ревью?
У меня TypeScript другой версии, часть примеров не компилируется?
Стоит ли требовать явные возвращаемые типы у всех функций?
Что делать, если as действительно нужен и убрать его нельзя?
Как не превратить ревью в поток мелких придирок?
Помогает ли строгая конфигурация вместо ручных замечаний?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.