Вступление: Почему «просто компилируется» — это плохой критерий
Представьте ситуацию: вы открываете 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