Прежде чем начать, хочу упомянуть, что я фанат TypeScript. Это мой основной язык программирования для фронтенд проектов на React и для любой бекенд работы, которую я выполняю в Node. Я полностью за Typescript, но есть моменты, которые меня беспокоят и про которые я и хотел рассказать этой статьей.
Я писал исключительно на TypeScript в течение последних трех лет для множества разных компаний, так что на мой взгляд, TypeScript как минимум что-то делает правильно или закрывает определённые потребности.
Несмотря на своё несовершенство, TypeScript вошёл в мейнстрим фронтенд разработки и занимает седьмое место в списке наиболее востребованных языках программирования по версии HackerRank developer skills report.
Любой команде разработчиков, неважно, большая она или маленькая, пишет на TypeScript или нет, ради безопасности всегда стоит:
- Следить, чтобы хорошо написанные юнит тесты покрывали как можно больше кода в продакшене
- Использовать парное программирование: дополнительная пара глаз поможет поймать более серьезные вещи, чем просто синтаксические ошибки
- Качественно построить процесс code review и выявлять ошибки, которые не может найти машина
- Использовать линтер – такой как eslint
TypeScript хоть и добавляет дополнительный уровень безопасности поверх всего этого, но по моим ощущениям, он очень сильно отстает от других языков в этом плане. Объясню почему.
TypeScript не является надежной системой типов
Я думаю, что это, возможно, главная проблема с TypeScript, но прежде позвольте мне определить, что такое надежные и ненадежные системы типов.
Надежная система типов
Надежная система типов гарантирует, что ваша программа не попадет в недопустимые состояния. Например, если статическим типом выражения является string, при его вычислении во время выполнения вы гарантированно получите только string.
В надежной системе типов вы никогда не будете находиться в ситуации, когда выражение не соответствует ожидаемому типу, будь то во время компиляции или во время выполнения.
Существуют, конечно же, различные степени надежности, а также различные интерпретации надежности. TypeScript является в некоторой степени надежным и ловит ошибки типа:
// Type 'string' is not assignable to type 'number'
const increment = (i: number): number => { return i + "1"; }
// Argument of type '"98765432"' is not assignable to parameter of type 'number'.
const countdown: number = increment("98765432");
Ненадежная система типов
Typescript абсолютно открыто сообщает, что 100% -ая надежность не является его целью. Даже «не-цель» номер 3 в списке «не-целей TypeScript» четко гласит:
Иметь надежную или «доказуемо правильную» систему типов не является нашей целью. Вместо этого мы стремимся соблюсти баланс между правильностью и производительностью.
Это означает, что нет гарантии, что переменная имеет определенный тип во время выполнения. Я могу проиллюстрировать это на следующем несколько надуманном примере:
interface A {
x: number;
}
let a: A = {x: 3}
let b: {x: number | string} = a;
b.x = "unsound";
let x: number = a.x; // unsound
a.x.toFixed(0); // WTF is it?
Приведенный выше код не работает, поскольку известно, что a.x — это число из интерфейса A. К сожалению, после пары финтов с переназначением оно превращается в строку и данный код компилируется, но с ошибками во время выполнения.
К сожалению, данное выражение компилируется без ошибок:
a.x.toFixed(0);
То, что надежность не является целью языка, вероятно, одна из самых крупных проблем TypeScript. Я продолжаю получать много ошибок runtime error во время выполнения, которые не ловит tsc компилятор, но которые были бы замечены компилятором, если бы в TypeScript существовала надежная система типов. TypeScript сейчас одной ногой в лагере «надежных» языков, а другой в «ненадежных». Этот подход, состоящий из полумер основан на типе any, о котором я расскажу позже.
Меня фрустрирует факт, что количество тестов, которые я пишу, нисколько не уменьшилось с переходом на TypeScript. Когда я только начинал, то ошибочно решил, что смогу сократить тягомотную рутину написания большого количества юнит тестов.
TypeScript бросает вызов существующему порядку вещей, утверждая, что снижение когнитивных затрат при использовании типов важнее, чем их надежность.
Я понимаю, почему TypesScript выбрал такой путь и есть мнение, что TypeScript не был бы так популярен, если бы надежность системы типов была бы 100% гарантирована. Это мнение не выдержало проверки — язык Dart стремительно набирает популярность, одновременно с повсеместным использованием Flutter. А утверждается, что надежность типов является целью Dart.
Ненадежность и различные способы, которыми TypeScript предоставляет «аварийный выход» из строгой типизации, делают его менее эффективным и, к сожалению, делают его просто «лучше, чем ничего» в данный момент. Я был бы рад, если по мере роста популярности TypeScript стало доступно больше опций компилятора, позволяющих опытным пользователям стремиться к 100% надежности.
TypeScript не гарантирует никакой проверки типов во время выполнения
Проверка типов во время выполнения не является целью TypeScript, поэтому моё пожелание, вероятно, никогда не сбудется. Проверка типов во время выполнения полезна, например, при работе с данными JSON, возвращаемыми из вызовов API. Можно было бы избавится от целой категории ошибок и множества юнит тестов, если бы мы могли контролировать эти процессы на уровне системы типов.
Так как мы не можем ничего гарантировать во время выполнения, легко может случиться такое:
const getFullName = async (): string => {
const person: AxiosResponse = await api();
//response.name.fullName may result in undefined at runtime
return response.name.fullName
}
Есть несколько вспомогательных библиотек, таких как io-ts, что замечательно, но это может означать, что вам придется дублировать свои модели.
Страшный тип any и опция strict
Тип any означает «любой», и компилятор допускает любую операцию или присваивание переменной с таким типом.
TypeScript хорошо работает для небольших вещей, но люди склонны ставить тип any на все, что занимает больше одной минуты. Недавно я работал над Angular-проектом и видел много такого кода:
export class Person {
public _id: any;
public name: any;
public icon: any;
TypeScript позволяет вам забыть о системе типов.
Вы можете сломать тип чего угодно при помощи any:
("oh my goodness" as any).ToFixed(1); // remember what I said about soundness?
Опция strict включает следующие параметры компилятора, которые делают все более надежным:
- --strictNullChecks
- --noImplicitAny
- --noImplicitThis
- --alwaysStrict
Есть также правило eslint @typescript-eslint/no-explicit-any.
Распространение any может разрушить надежность вашего кода.
Заключение
Я должен повторить, что я фанат TypeScript и использую его в своей повседневной работе, но я чувствую, что он несовершенен и шумиха вокруг него не совсем оправдана. Airbnb утверждает, что TypeScript помог предотвратить 38% ошибок. Я очень скептически отношусь к настолько точно заявленному проценту. TypeScript не улучшает и не объединяет в себе все существующие практики хорошего кода. Мне все еще приходится писать уйму тестов. Вы могли бы поспорить, что я пишу больше кода, поэтому мне и приходится писать так много тестов. Я продолжаю получать много неожиданных runtime error.
TypeScript предлагает только базовую проверку типов, а то, что надежность и проверка типов во время выполнения не входит в его цели, оставляет TypeScript в мире полумер, на полпути между лучшим миром и тем, в котором мы кодим сейчас.
TypeScript прекрасен благодаря хорошей поддержке IDE, таких как vscode, где мы получаем визуальную обратную связь в процессе печати.
Ошибка TypeScript в vscode
TypeScript также улучшает рефакторинг и ломающие код изменения (такие как изменения в сигнатурах методов) мгновенно идентифицируются при запуске компилятора TypeScript.
TypeScript предоставляет хорошую проверку типов и определенно это лучше, чем никакой проверки или простой eslint, но я чувствую, что TypeScript может быть намного лучше и необходимые опции компилятора могли бы порадовать тех, кто хочет от языка большего.
Подписывайтесь на нашего разработчика в Instagram
Автор: owlnagi