TypeScript: стоит ли усложнять типы? |
||
|
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ Атаки на ИИ Внедрение ИИИИ теория Компьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Промпты. Генеративные запросы Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2025-05-25 11:17 Что такое TypeScript? Официальная документация отвечает так: “TypeScript — это JavaScript с синтаксисом типов”. Однако некоторые считают TypeScript своеобразным слиянием двух языков: языка для манипулирования значениями JavaScript и языка для манипулирования типами. Cистема типов TypeScript Тьюринг-полная. Это означает, говоря по-простому, что система может решить любую вычислительную задачу при наличии некоторого представления входных и выходных данных. Можно ли использовать это знание на практике? Как избежать крайностей от примитивного аннотирования типов до избыточного усложнения? Эта статья для разработчиков, которые уже используют TypeScript, но хотят выйти за рамки базовых практик. Мы не будем обсуждать синтаксис или настройку конфигураций. Вместо этого сосредоточимся на стратегиях, которые помогут:
![]() Система типов, будучи Тьюринг-полной, обладает вычислительной мощью, сравнимой с полноценным языком программирования. На практике это позволяет реализовать, например, систему типов TypeScript, используя только систему типов TypeScript. Некоторые умельцы уже сделали это, выглядит это так: Кроме HypeScript существуют и другие интересные проекты на типах, например, SQL-движок и интерпретатор lisp-подобного языка. Система типов предоставляет мощные возможности для реализации таких проектов, но практической ценности здесь немного. Это явление известно как гимнастика типов (type gymnastics). Делают это чаще ради развлечения, а не из необходимости. Можно подумать, что такая универсальность, когда система достаточно умна, чтобы вычислять любую программу, может быть труднодостижима, но оказывается наоборот: труднее написать полезную систему, которая не стала бы Тьюринг-полной. Подробнее об этом можно почитать здесь. Ключевой момент — это уровень выразительности системы. На этом этапе становятся очевидны её ограничения. В качестве примера можно изучить код из zustand. Так что же такое TypeScript? Два языка в одном? Или просто JavaScript с типами? Существует ли здесь баланс? Следует ли всегда стремиться к максимально простым типам, или иногда их усложнение оправдано? Попытаемся ответить на эти вопросы. Типы, тесты, проверки в рантайме В этой части рассмотрим, как статическая типизация повышает надёжность и корректность кода. Сравним преимущества и недостатки с другими методами: тестированием, проверками в рантайме. Разберём такую задачу: написать функцию, которая принимает массив чисел и числовое значение, а затем возвращает индекс заданного значения. Важно сигнализировать, если значение отсутствует в массиве. Несмотря на то что задача кажется простой, для корректной работы пользователям необходимо понимание того, как пользоваться функцией, и уверенность в том, что она будет использоваться правильно. В идеале пользователи должны получать надежные, всегда актуальные гарантии, которые проверяются компьютером. Рассмотрим такой код: Такой пример не даёт никакой полезной информации кроме того факта, что функция принимает два параметра, и не обеспечивает её корректное использование. Напишем тесты, чтобы исправить это: Теперь у нас больше информации о назначении функции и есть уверенность в том, что функция корректно отрабатывает в заданных кейсах. Однако тесты не могут охватить все возможные сценарии. Например, мы не можем утверждать, что некорректные данные на входе всегда будут правильно обрабатываться. Попробуем это исправить, добавив проверку аргументов в runtime: Такая проверка отловит все случаи, когда функция получает некорректные аргументы. Но у такой проверки есть существенный недостаток – она зависит от удачи разработчика. Существует разница между введением ошибки и выявлением ошибки. Если разработчик не проверит все сценарии использования функции, он не заметит, что в некоторых случаях функция может получить некорректные данные. Проблема всплывёт лишь тогда, когда с ней столкнется пользователь или в лучшем случае тестировщик. И всё ещё нет гарантий, что вызывающий код корректно обрабатывает результат функции. Например, он может не ожидать возврата null. Теперь добавим типы: Типизация помогает отлавливать целый класс ошибок. Правильно написанные типы служат документацией, проверяются автоматически во время компиляции и всегда актуальны, так как отражают то, с чем работает код. Этот пример простой, но основные принципы, которые он иллюстрирует, применимы и к более сложным ситуациям. Типы, тесты и проверки в рантайме не взаимозаменяемы. Тестами можно протестировать что угодно, но они проверяют конкретные кейсы, охватывают лишь одну проблему за раз. Полностью покрыть код тестами для всех вариантов использования невозможно. Типы помогают выявлять целые классы ошибок на этапе компиляции, но эти классы ограничены. Однако, используя более продвинутые возможности TypeScript, можно расширить класс отлавливаемых ошибок. Понятно, что TypeScript способен выявлять простые ошибки, например, передали Бизнес и типы Группируем связанные поля. Рассмотрим тип, содержащий информацию о контакте: Здесь нас интересуют два свойства: В такой конфигурации можно легко упустить обновление флага Совет здесь простой: группируйте данные, которые должны быть связаны, и не группируйте то, что не подлежит объединению. Это позволит поддерживать целостность и логику ваших данных. Делаем невозможные состояния невозможными Смоделируем дверь. Дверь может находиться в различных состояниях: заперта или отперта, открыта или закрыта. В данной конфигурации возможна ситуация, когда дверь открыта, но одновременно и заперта. Это не имеет смысла. Как можно решить эту проблему? Решение простое: дверь может находиться в конечном числе состояний, поэтому просто перечислим возможные состояния с использованием union. Больше никаких бесполезных дверей, TS подсветит нам ошибку. Код становится самодокументируемым: достаточно взглянуть на типы, чтобы понять, как устроена бизнес-логика. Благодаря чёткому определению состояний мы можем избежать логических ошибок и сделать наш код более предсказуемым. Типы сохраняют свою актуальность и проверяются машиной, что делает наш код более надежным. Делаем state-machines на типах Определим все возможные состояния двери: Как и в предыдущем примере все состояния двери задокументированы. Теперь определим переходы между состояниями. Мы получили ещё больше информации о нашей двери. Каждый из этих типов функций отвечает за чёткое определение поведения системы при смене состояний. Например, функция Но есть и свои недостатки. Этот подход усложняет структуру типов, перед его использованием нужно убедиться, что плюсы перевешивают минусы. Учтите следующие моменты.
Использование машин состояний для моделирования бизнес-логики в TypeScript делает документацию ещё подробнее. Такой подход проектирования заставит разработчика думать о каждой возможности, которая может возникнуть. Но здесь особенно важно помнить о балансе выгод и затрат. Branded types Рассмотрим типы, представляющие идентификаторы поста и автора: Если кто-то случайно перепутает порядок аргументов, мы можем получить комментарии не от нужного автора или для другого поста. Эта ошибка может привести к неожиданным багам. Чтобы избежать таких проблем, был придуман паттерн Как это работает.
Брендированные типы гарантируют, что операции требующие особых примитивных типов, не примут неподходящие значения. url-parser Вернемся к теме полноты по Тьюрингу и рассмотрим более сложный пример. Допустим, в команде договорились об определенных правилах написания URL. Наша задача — из строки, представленной в виде: Перед тем, как приступить к реализации, необходимо освоить несколько концепций. Условные типы и infer Ключевое слово Шаблонные литералы в TypeScript позволяют реализовывать строковые шаблоны на уровне типов. С помощью такого шаблона можно, например, вести поиск определенной подстроки. Например, попробуем извлечь имя из литерального типа: "name: John, surname: Doe". Реализуем такой шаблон: Как работает данный тип?
Типы можно использовать рекурсивно. Например, создадим тип, описывающий массив чисел любой вложенности. Теперь объединим все это в единый тип. Создадим строковый шаблон для сопоставления параметра и сохранения остатка URL. Применив его к строке И используем рекурсию, чтобы извлечь остальные параметры, которые сохранены в Rest. Объединим всё: Затем создадим такую функцию: Хотя данный код может показаться не самым выразительным, думаю, он весьма полезен. Он гарантирует, что пользователи не забудут передать все необходимые параметры, а также упрощает рефакторинг, если мы решим изменить имена параметров, TypeScript поможет нам в этом. При наличии понятных названий и необходимых пояснений такой подход упростит работу с кодом и принесёт ощутимую пользу. Вывод TypeScript идет по интересному пути. Система типов становится языком программирования, и где-то типы уже тестируют. Например, zod, zustand, tanstack query. Понятно, что разработка библиотеки – не то же самое, что продуктовая разработка, но тесты типов живут рядом с тестами функционала – это уже реальность. Некоторые пытаются доработать систему типов, чтобы упростить с ней работу, например, hotscript. Я не утверждаю, что всё это нужно тащить в проект, но, думаю, важно понимать, в каком направлении это движется и как это может быть полезно. Я считаю, что понимание и осознанное использование возможностей системы типов TypeScript может повысить надёжность и документированность кода. Здесь важно найти баланс. Чем проще типы, тем больше допущений делаем. С другой стороны, чем сложнее и полнее типы, тем больше информации и гарантий мы получаем для кода. Однако есть риск создания сложночитаемого трудноподдерживаемого типа, что приведёт к обратному эффекту. Надеюсь, что техники, представленные в статье, помогут читателям взглянуть на TypeScript под новым углом и найти этот баланс. *Статья написана в рамках ХабраЧелленджа 3.0, который прошел в ЛАНИТ осенью 2024 года. О том, что такое ХабраЧеллендж, читайте здесь. Источник: habr.com Комментарии: |
|