Новости

24.08.2020

Книга «Эффективный TypeScript: 62 способа улучшить код»

Структура книги


Книга представляет собой сборник кратких эссе (правил). Правила объединены в тематические разделы (главы), к которым можно обращаться автономно в зависимости от интересующего вопроса.

Заголовок каждого правила содержит совет, поэтому ознакомьтесь с оглавлением. Если, например, вы пишете документацию и сомневаетесь, надо ли писать информацию типов, обратитесь к оглавлению и правилу 30 («Не повторяйте информацию типа в документации»).
Практически все выводы в книге продемонстрированы на примерах кода. Думаю, вы, как и я, склонны читать технические книги, глядя в примеры и лишь вскользь просматривая текстовую часть. Конечно, я надеюсь, что вы внимательно прочитаете объяснения, но основные моменты я отразил в примерах.

Прочитав каждый совет, вы сможете понять, как именно и почему он поможет вам использовать TypeScript более эффективно. Вы также поймете, если он окажется непригодным в каком-то случае. Мне запомнился пример, приведенный Скоттом Майерсом, автором книги «Эффективный C++»: разработчики ПО для ракет могли пренебречь советом о предупреждении утечки ресурсов, потому что их программы уничтожались при попадании ракеты в цель. Мне неизвестно о существовании ракет с системой управления, написанной на JavaScript, но такое ПО есть на телескопе James Webb. Поэтому будьте осторожны.

Каждое правило заканчивается блоком «Следует запомнить». Бегло просмотрев его, вы сможете составить общее представление о материале и выделить главное. Но я настоятельно рекомендую читать правило полностью.

Отрывок. ПРАВИЛО 4. Привыкайте к структурной типизации


JavaScript имеет неумышленную утиную типизацию: если вы передадите функции значение с верными свойствами, то ее не будет волновать, как вы получили это значение. Она просто его использует. TypeScript моделирует это поведение, что иногда приводит к неожиданным результатам, так как понимание типов модулем проверки может оказаться шире привычного вам. Развитие навыка структурной типизации позволит лучше чувствовать, где действительно есть ошибки, и писать более надежный код.

К примеру, вы работаете с библиотекой физических характеристик и у вас есть тип вектора 2D:

Вы пишете функцию для вычисления его длины:

и вводите определение вектора named:

Функция calculateLength будет работать с NamedVector, так как в нем присутствуют свойства x и y, являющиеся number. TypeScript это понимает:

Интересно то, что вы не объявляли связь между Vector2D и NamedVector. Вам также не пришлось прописывать альтернативное выполнение calculateLength для NamedVector. Система типов TypeScript моделирует поведение JavaScript при выполнении (правило 1), что позволило NamedVector вызвать calculateLength на основании того, что его структура сопоставима с Vector2D. Отсюда выражение «структурная типизация».

Но это также может привести и к проблемам. Допустим, вы добавите тип вектора 3D:

и напишете функцию, чтобы нормализовать векторы (сделать их length равной 1):

 

Если вы вызовете эту функцию, то, вероятнее всего, получите больше, чем единичную длину:

Что же пошло не так и почему TypeScript не сообщил об ошибке?

Баг заключается в том, что calculateLength работает с векторами 2D, а normalize — с 3D. Поэтому компонент z игнорируется при нормализации.

Может показаться странным, что модуль проверки типов не уловил этого. Почему допускается вызов calculateLength 3D-вектором, несмотря на то что ее тип работает с 2D-векторами?
То, что работало хорошо с named, здесь привело к обратному результату. Вызов calculateLength объектом {x, y, z} не выдает ошибку. Следовательно, модуль проверки типов не жалуется, что в итоге приводит к появлению бага. Если вы захотите, чтобы в подобном случае ошибка все же обнаруживалась, обратитесь к правилу 37.

Прописывая функции, легко представить, что они будут вызываться свойствами, которые вы объявили, и никакими другими. Это называется «запечатанный», или «точный», тип и не может быть применено в системе типов TypeScript. Нравится вам это или нет, но здесь типы открыты.

Иногда это приводит к сюрпризам:

Почему это ошибка? Поскольку axis является одним из ключей v из Vector3D, то он должен быть x, y или z. А согласно изначальному объявлению Vector3D, они все являются numbers. Следовательно, не должен ли тип coord также быть number?

Это не ложная ошибка. Мы знаем, что Vector3D строго определен и не имеет иных свойств. Хотя мог бы:

Поскольку v, вероятно, мог иметь любые свойства, то тип axis является string. У TypeScript нет причин считать v[axis] только числом. При итерации объектов может быть сложно добиться корректной типизации. Мы вернемся к этой теме в правиле 54, а сейчас обойдемся без циклов:

Структурная типизация может также служить причиной сюрпризов в классах, которые сравниваются на предмет возможного назначения свойств:

Почему d может быть назначен для C? У него есть свойство foo, являющееся string. Еще у него есть constructor (из Object.prototype), который может быть вызван аргументом (хотя обычно он вызывается без него). Итак, структуры совпадают. Это может привести к неожиданностям, если у вас присутствует логика в конструкторе C и вы напишете функцию, подразумевающую его запуск. В этом существенное отличие от языков вроде C++ или Java, где объявления параметра типа C гарантирует, что он будет принадлежать именно C либо его подклассу.

Структурная типизация хорошо помогает при написании тестов. Допустим у вас есть функция, которая выполняет запрос в базу данных и обрабатывает результат.

Чтобы ее протестировать, вы могли бы создать имитацию PostgresDB. Однако лучшим решением будет использование структурной типизации и определение более узкого интерфейса:

Вы по-прежнему можете передать postgresDB функции getAuthors в вывод, поскольку в ней есть метод runQuery. Структурная типизация не обязывает PostgresDB сообщать, что она выполняет DB. TypeScript сам определит это.

При написании тестов вы можете передавать и более простой объект:

TypeScript определит, что тестовый DB соответствует интерфейсу. В то же время ваши тесты совершенно не нуждаются в информации о базе данных вывода: не требуется никаких имитированных библиотек. Введя абстракцию (DB), мы освободили логику от деталей выполнения (PostgresDB).

Еще одним преимуществом структурной типизации является то, что она способна четко обрывать зависимости между библиотеками. Больше информации по этой теме вы найдете в правиле 51.

СЛЕДУЕТ ЗАПОМНИТЬ

JavaScript применяет утиную типизацию, а TypeScript ее моделирует при помощи структурной типизации. В связи с этим значения, присваиваемые вашим интерфейсам, могут иметь свойства, не указанные в объявленных типах. Типы в TypeScript не бывают запечатанными.

Имейте в виду, что классы также подчиняются правилам структурной типизации. Поэтому вы можете получить не тот образец класса, какой ожидали.

Используйте структурную типизацию для упрощения тестирования элементов.

С полным содержанием статьи можно ознакомиться на сайте "Хабрахабр":

https://habr.com/ru/company/piter/blog/513712/


Комментарии: 0

Пока нет комментариев


Оставить комментарий






Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.