Learning Book

Что такое система типов и зачем она нужна

Проблема: JavaScript не защищает от ошибок

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

function greet(user) {
  return `Привет, ${user.name}!`
}

greet({ name: 'Алекс' })  // "Привет, Алекс!"
greet('Алекс')             // "Привет, undefined!" — нет ошибки, но баг
greet(null)                // TypeError: Cannot read properties of null

Последняя строка упадёт в продакшене. Первые две даже не предупредят. Это норма для JavaScript — ошибки типов проявляются только когда код выполняется.

Что такое система типов

Система типов — это набор правил, которые классифицируют значения и проверяют, что операции над ними имеют смысл.

Простая ментальная модель: тип — это множество возможных значений.

  • number — множество всех чисел
  • string — множество всех строк
  • 'hello' — множество из одного элемента (литеральный тип)
  • never — пустое множество

TypeScript проверяет, принадлежит ли значение нужному множеству до запуска кода — на этапе компиляции.

function greet(user: { name: string }) {
  return `Привет, ${user.name}!`
}

greet({ name: 'Алекс' })  // ✅ OK
greet('Алекс')             // ❌ Ошибка компиляции: string не совместим с { name: string }
greet(null)                // ❌ Ошибка компиляции: null не совместим с { name: string }

Ошибки ловятся до запуска — в редакторе, при сборке, в CI.

Статическая vs динамическая типизация

Статическая (TypeScript)Динамическая (JavaScript)
Когда проверкаCompile timeRuntime
Где ловятся ошибкиВ редакторе / CIВ продакшене
Скорость обратной связиМгновеннаяПосле запуска
Стоимость ошибкиКрасное подчёркиваниеБаг у пользователя
ГибкостьНужно описывать типыПолная свобода

Динамическая типизация быстрее для прототипов. Статическая — безопаснее для продакшена. TypeScript предлагает компромисс: постепенная типизация (gradual typing). Можно добавлять типы постепенно, а any позволяет обойти проверку там, где нужно.

Type erasure — типы исчезают

Ключевой принцип TypeScript: типы существуют только на этапе компиляции. При транспиляции в JavaScript все аннотации типов полностью удаляются:

// TypeScript
const name: string = 'Алекс'
const age: number = 25

function greet(user: { name: string; age: number }): string {
  return `${user.name}, ${user.age}`
}
// Скомпилированный JavaScript — типов нет
const name = 'Алекс'
const age = 25

function greet(user) {
  return `${user.name}, ${user.age}`
}

Это называется type erasure — стирание типов. Интерфейсы, type alias, generic-типы — всё исчезает. В рантайме работает обычный JavaScript, и движок V8 не знает, что код был написан на TypeScript.

Внимание: TypeScript НЕ добавляет runtime-проверки. Если данные приходят из API, пользовательского ввода или JSON.parse — типы не гарантируют корректность. Для рантайм-валидации используйте Zod, io-ts или class-validator.

Зачем нужны типы — практические причины

1. Автодополнение и навигация

Типы — это документация, которую понимает редактор. Вместо угадывания user.??? — точный список полей с типами:

interface User {
  id: number
  name: string
  email: string
}

function sendEmail(user: User) {
  user. // ← IDE покажет: id, name, email
}

2. Рефакторинг без страха

Переименовал поле — компилятор покажет все места, где оно используется. Изменил сигнатуру функции — все вызовы подсветятся ошибками.

3. Документация в коде

Типы описывают контракт функции лучше, чем JSDoc-комментарии. И, в отличие от комментариев, типы не устаревают — компилятор проверяет их при каждой сборке.

4. Ловля багов до продакшена

Исследования (Microsoft, Google) показывают, что статическая типизация предотвращает 15-20% багов, которые доходят до продакшена. Это не серебряная пуля, но значимый фильтр.

Почему TypeScript не является «абсолютно правильным»

TypeScript сознательно unsound — то есть не гарантирует, что если код скомпилировался, в рантайме не будет ошибки типа.

Это не баг, а дизайн-решение. Из официальных Design Goals:

Цель #9: «Использовать согласованную, полностью стираемую, структурную систему типов» Не-цель #3: «Применять звуковую (sound) или доказуемо корректную систему типов»

Причина — прагматизм. Абсолютно корректная система типов была бы слишком ограничивающей для реального JavaScript-кода.

Копай глубже: Soundness vs Completeness — формальный взгляд

В теории типов есть два свойства:

  • Soundness (корректность): если программа прошла проверку типов, runtime-ошибок типа не будет
  • Completeness (полнота): если программа корректна, проверка типов её пропустит

Ни одна практичная система типов не обладает обоими свойствами (следствие теоремы Гёделя). TypeScript жертвует soundness ради completeness и удобства. Примеры «дыр»: any, type assertions (as), ковариантные массивы, отсутствие проверки границ при индексном доступе.

Итого

ФактОписание
Что этоНабор правил для классификации значений и проверки операций
Ментальная модельТип = множество возможных значений
Когда проверкаCompile time, до запуска кода
Type erasureТипы стираются при компиляции — в рантайме их нет
UnsoundnessTypeScript сознательно не гарантирует 100% безопасность типов
Главная ценностьАвтодополнение, рефакторинг, документация, ловля багов