Какие есть частые антипаттерны в TypeScript?
Вопрос
Какие практики в TypeScript считаются антипаттернами и почему они опасны?
Короткий ответ
Самые частые антипаттерны — злоупотребление any (отключает проверку типов полностью), небезопасные приведения через as, злоупотребление non-null assertion (!), подавление ошибок через @ts-ignore вместо их исправления, и неочевидные ловушки числовых enum. Все они меняют компромисс "поймать ошибку сейчас" на "поймать ошибку в проде".
Подробный ответ
1. Злоупотребление any. any полностью отключает проверку типов для значения и всего, что из него выведено дальше — по сути, откат к обычному JavaScript, но с ложным ощущением безопасности:
function process(data: any) {
return data.user.profile.name.toUpperCase() // ни одна ошибка не будет поймана
}
Правильная альтернатива — unknown (требует явного сужения перед использованием) или точный тип/дженерик.
2. Небезопасные as-приведения. as не проверяет совместимость типов на рантайм-уровне — это просто "заявление" компилятору поверить программисту. as от unknown/непроверенных данных может замаскировать неправильную форму объекта:
const response = await fetch('/api/user')
const user = (await response.json()) as User // TS верит, но структура не проверена
console.log(user.email.toLowerCase()) // упадёт в рантайме, если email отсутствует
Безопаснее — валидировать данные (вручную через type guard или библиотекой вроде zod) вместо приведения "вслепую".
3. Non-null assertion (!) "на всякий случай". Оператор ! говорит компилятору "здесь точно не null/undefined", отключая проверку — если разработчик ошибся, будет рантайм-ошибка Cannot read properties of undefined:
function getUser(id: number): User | undefined { /* ... */ return undefined }
const user = getUser(1)! // "я уверен, что не undefined" — а если нет?
console.log(user.name) // упадёт в рантайме
Лучше — реальная проверка (if (!user) return) или явная обработка отсутствующего значения.
4. @ts-ignore / @ts-expect-error вместо исправления типов. @ts-ignore подавляет ошибку компилятора на следующей строке без объяснения причины и без проверки, что ошибка вообще ещё существует — со временем код вокруг может измениться, а игнорируемая ошибка — превратиться в другую, незамеченную:
// @ts-ignore
const result = riskyFunction(wrongArgType) // почему это нормально? неизвестно
@ts-expect-error чуть безопаснее — вызовет собственную ошибку, если код внизу перестанет быть ошибочным (то есть проблема исчезла), но обе директивы стоит использовать точечно и с комментарием, а не как способ "заставить билд пройти".
5. Ловушки числовых enum. Числовой enum компилируется в двусторонний маппинг (значение → имя и имя → значение) и допускает присвоение любого числа переменной этого типа без ошибки:
enum Status { Pending, Success, Error }
let s: Status = 99 // OK для числового enum! Никакой проверки диапазона
Строковые enum безопаснее в этом плане, но чаще предпочитают union строковых литералов (type Status = 'pending' | 'success' | 'error') — они не генерируют дополнительный JS-код в рантайме и лучше сочетаются со структурной типизацией.
Пример
// Плохо: any, as и ! вместе
function renderBad(input: any) {
const user = input as { name: string }
return user!.name.toUpperCase()
}
// Хорошо: unknown + type guard + явная обработка отсутствия данных
interface User { name: string }
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'name' in value
&& typeof (value as User).name === 'string'
}
function renderGood(input: unknown): string {
if (!isUser(input)) {
throw new Error('Invalid user payload')
}
return input.name.toUpperCase() // input сужен до User, без as и !
}
// Плохо: числовой enum без защиты от произвольных чисел
enum RoleBad { Admin, User, Guest }
// Хорошо: union литералов — та же выразительность, без рантайм-кода и без дыр
type Role = 'admin' | 'user' | 'guest'
function checkAccess(role: Role) {
if (role === 'admin') return true
return false
}
Дополнительные вопросы
- Чем
unknownпринципиально отличается отanyс точки зрения безопасности? - В каких редких случаях
asдействительно оправдан и не считается антипаттерном? - Почему строковые
enumтоже не идеальны по сравнению с union строковых литералов, и когдаenumвсё же уместен?