Code Review на TypeScript: 7 вещей, которые проверяю я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: Почему «просто компилируется» — это плохой критерий

Представьте ситуацию: вы открываете Pull Request на GitHub, видите зелёную галочку CI и слышите внутренний голос: «Типы сходятся — значит, код корректный». Знакомо? Если да, то вы попадаете в ловушку, которую мы называем «ложное чувство безопасности TypeScript».

TypeScript — это не панацея. Он не ловит логические ошибки, не проверяет бизнес-требования и уж точно не гарантирует, что ваш код не развалится в рантайме. Именно поэтому code review на TypeScript — это не про «пробежаться глазами», а про системный подход. В этой статье я расскажу, на что реально стоит смотреть, когда вы ревьюируете TypeScript-код. Я веду блог на PythonLib, и здесь мы привыкли копать вглубь — приготовьтесь.

1. Тип any: красная тряпка

Самое очевидное, но почему-то самое частое. any — это выключатель TypeScript. Если вы видите any в коде, ваша задача — понять: это лень автора или действительно оправданный случай?

Плохой пример:

function processData(data: any) {
    return data.name.toLowerCase();
}

Здесь мы теряем всю информацию о типе. Если data придет null или undefined — привет, ошибка в рантайме.

Хороший пример:

interface UserData {
    name: string;
    age?: number;
}

function processData(data: UserData) {
    return data.name.toLowerCase();
}

Что проверять на code review: Если any всё же используется (например, для работы с JSON с неизвестной структурой), требует ли автор валидации на входе? Часто any маскирует отсутствие нормальной типизации для внешних данных.

2. Strict mode: не отключайте его

Первое, что я проверяю в tsconfig.json — это флаг strict: true. Если его нет, вы лишаете себя целого пласта проверок: strictNullChecks, noImplicitAny, strictFunctionTypes и других.

Без strictNullChecks вы можете написать:

const user = getUser(); // может вернуть null
const name = user.name; // TS не ругнётся, но в рантайме упадёт

На code review: Если вы видите, что проект работает без strict mode — бейте тревогу. Это технический долг, который накапливается экспоненциально. Лучше потратить день на включение strict mode и исправление ошибок, чем месяц на отлов багов в проде.

3. Дженерики: сила и ответственность

Дженерики — это суперсила TypeScript. Но с большой силой приходит большая ответственность. На code review я всегда смотрю: не переусложнили ли автор с типами?

Пример излишней сложности:

function identity<T extends Record<string, unknown>>(arg: T): T {
    return arg;
}

Зачем ограничивать T до Record<string, unknown>, если мы просто возвращаем то же самое? Достаточно:

function identity<T>(arg: T): T {
    return arg;
}

Что проверять:

  • Используются ли дженерики там, где нужна гибкость, или можно обойтись конкретным типом?
  • Нет ли избыточных ограничений (extends), которые мешают переиспользованию?
  • Правильно ли выведены типы — не приходится ли пользователю вручную указывать <SomeType> там, где TS мог бы догадаться сам?

4. unknown vs any: выбирайте правильное оружие

Когда вы работаете с данными, тип которых вам неизвестен (API, localStorage, пользовательский ввод), используйте unknown, а не any. unknown заставляет вас явно проверять тип перед использованием.

Плохо:

function parseJSON(data: string): any {
    return JSON.parse(data);
}
const result = parseJSON('{"name":"John"}');
console.log(result.name); // Тип any — никакой защиты

Хорошо:

function parseJSON(data: string): unknown {
    return JSON.parse(data);
}
const result = parseJSON('{"name":"John"}');
if (typeof result === 'object' && result !== null && 'name' in result) {
    console.log((result as {name: string}).name);
}

На code review: Если вы видите any в месте, где тип действительно неизвестен — предложите заменить на unknown и добавить guard-функцию для проверки.

5. Тип never: ваш лучший друг в исчерпывающих проверках

Тип never — это «пустое множество». Он отлично подходит для того, чтобы гарантировать, что вы обработали все варианты в switch или условных конструкциях.

Пример с enum:

enum Status {
    Active = 'active',
    Inactive = 'inactive',
    Pending = 'pending'
}

function handleStatus(status: Status) {
    switch (status) {
        case Status.Active:
            // ...
            break;
        case Status.Inactive:
            // ...
            break;
        case Status.Pending:
            // ...
            break;
        default:
            const exhaustiveCheck: never = status; // Ошибка, если не все кейсы обработаны
            throw new Error(`Unhandled status: ${status}`);
    }
}

Если кто-то добавит новое значение в Status, но забудет обновить switch — TypeScript выдаст ошибку компиляции. Это то, что нужно проверять на code review: использует ли команда never для исчерпывающих проверок? Если нет — предложите добавить.

6. Readonly и immutability: защита от случайных мутаций

В больших проектах мутация объектов — одна из главных причин багов. TypeScript даёт нам инструменты для защиты: readonly, ReadonlyArray, Readonly и as const.

Что проверять:

  • Параметры функций, которые не должны меняться — помечены ли они readonly?
  • Массивы, которые возвращаются из функций — не забыли ли использовать ReadonlyArray?
  • Константы — используют ли as const для получения литеральных типов?

Пример:

// Плохо
function getConfig() {
    return { url: 'https://api.example.com', timeout: 5000 };
}
const config = getConfig();
config.timeout = 10000; // Ничего не阻止ляет

// Хорошо
function getConfig() {
    return { url: 'https://api.example.com', timeout: 5000 } as                

Блоги

Рекомендаций по книгам