Vanilla JavaScript не типизирован по своей натуре. Можно даже назвать его «умным», поскольку он способен вычислить, что является числом, а что строкой.
Это упрощает запуск JavaScript кода в браузере или при работе Node.js. Однако он уязвим для многочисленных ошибок во время исполнения (рантайм), которые могут испортить ваш пользовательский опыт использования.
Если с вами когда-либо случалось то, что будет описано далее, то вы только выиграете, если будете применять систему типов.
- После получения списка данных вы обнаруживаете, что определенное поле не существует в одной из записей. Это приводит к сбою в работе приложения, если этот случай не отлавливается и не обрабатывается явно.
- Экземпляр класса, который вы импортировали, не имеет метода, который вы пытаетесь вызвать.
- Ваша IDE не знает, какие методы и свойства доступны для модулей и библиотек, поэтому она не может эффективно предлагать автодополнение.
Flow, TypeScript или ReasonML
Скажем, у вас есть существующая база кода, которую вы хотите сделать более надежной и стабильной. Принимая во внимание существующие ошибки типизации, вы можете попробовать применить для этого Flow или TypeScript (они имеют достаточно сходный синтаксис).
С другой стороны, применение каждого из этих громоздких инструментов дело сложное. Вы загрузите себя работой, связанной с созданием типов и интерфейсов для кода, которые не были ранее разработаны.
И тем не менее Flow и TypeScript не дают 100% безопасности при добавлении типизации в коде.
По этой причине идеальная безопасность типизации достигается посредством вывода и делает аннотирующие переменные и сигнатуры функций более простыми.
Простые и явно надуманные примеры
Рассмотрим следующий код:
let add = (a, b) => a + b;
В обычном JavaScript эти аргументы могут быть числами или строками. В TypeScript или Flow эти аргументы могут быть аннотированы как:
let add = (a: number, b: number) => a + b
Теперь мы видимо задаем именно два int значения. Не два float или два string, для их операций сложения используются другие операторы.
А теперь давайте взглянем на чуть измененный пример в Reason:
let add = (a: string, b: number) => a + b
add('some string', 5) // outputs: "some string5"
Эта функция работает! И это может показаться удивительным. Каким образом Reason это понимает?
let add = (a, b) => a + b;
add("some string", 5);
/*
This has type:
string
but somewhere wanted:
int
*/
Эта функция имела недостатки на уровне реализации. У Reason есть разные операторы для сложения int, float и string.
Цель этого простого примера – показать, что вполне возможно иметь лишние «ошибки» типов, даже если это не роняет приложение.
В программе на Reason разработчикам не приходится разбираться с багами на продакшене, которые возникают из-за несовместимости типов или null значений.
Опыт разработчика
Одна из самых приятных особенностей в TypeScript – это то, что вы видите предложения по улучшению или автозаполнение в редакторе кода.
Это одна из областей, где TypeScript имеет преимущество над Reason, поскольку программа на TypeScript не должна быть скомпилирована идеально, чтобы предложить автозаполнение. Reason же заставит вас исправить все ошибки в синтаксисе и типах, перед тем, как предложить вам полезное исправление.
Так это работает в VSCode, но я знаю много Reason разработчиков, которые используют vim. Здесь мы не будем углубляться в сравнения.
Несмотря на то, что я большой фанат Reason, также я писал приложения на TypeScript или Flow. Волна хайпа вокруг TypeScript дает хороший стимул, чтобы выбрать именно его, поскольку из-за этого он имеет большую поддержку комьюнити.
С другой стороны, Reason сложнее использовать, поскольку для него доступно меньше статей и документации. Надеюсь, с его развитием это исправится.
Если Reason вас заинтересует, вы можете найти здесь его документацию. А еще подпишитесь на таких личностей, как @jordwalke, @jaredforsyth и @sgrove в Твиттере. Они многое могут рассказать об экосистеме Reason/OCaml.
Если же вы захотите узнать, как Reason работает с GraphQL, обратитесь к другой моей статье «Reason with GraphQL, the Future of Type-Safe Web Applications».
Ждем отзывы о материале и по устоявшейся традиции приглашаем всех читателей на день открытых дверей, который 25 марта проведет наш преподаватель — Александр Коржиков.
Комментарии (68)
VolCh
20.03.2019 18:00+5Теперь мы видимо задаем именно два int значения. Не два float или два string
number в javascript и typescript — именно, что float (если быть совсем точным, то double, если использовать сишные типы). В них нет int типа.
Akuma
20.03.2019 18:45+3Не так давно пытался внедрить в уже работающий проект Flow… ну так себе затея, в итоге отказался. Сторонние библиотеки не типизированы, твои типизированы, все это мешается друг с другом… уф, жесть какая-то была.
Возможно если писать так с нуля, то все будет шоколадно, но внедрять типизацию в большой и готовый проект — бе.worldxaker
21.03.2019 11:25мы потихоньку внедряем в большой проект ts и никаких особых проблем с этим не возникает
Akuma
21.03.2019 14:07В TS лично мне не нравится то, что нужно описывать каждую внешнюю библиотеку. И если для популярных файл с описанием обычно есть, то для мелких библиотек — только вручную.
По крайней мере так было около года назад, после этого я TS не трогал.
Сейчас так же?serf
21.03.2019 14:44Все более-менее популярные библиотеки описаны. Но все равно я предпочитаю использовать пускай менее функциональные но изначально написанные на TS библиотеки. Потому что часто возникают ситуации когда декларации расходятся с самой библиотекой или когда декларации не могут полностью выразить функционал библиотеки или делают это наоборот неполноценно (например без дженериков). Кроме этого я больше доверяю коду написанному на TS чем сырому JS. Уже заметен тренд переписывания популярных библиотек на TS и это замечатально, разумность приходит в массы.
worldxaker
23.03.2019 00:42да, иногда приходится страдать. но за последний год ситуация сильно улучшилась, и тайпинги есть на большую часть библиотек
ecmaeology
20.03.2019 19:02+3Однако он уязвим для многочисленных ошибок во время исполнения (рантайм)…
Если в рантайме пришли неверные данные, типизация их не починит. У нее есть свой ограниченный диапазон полезности.
После получения списка данных вы обнаруживаете, что определенное поле не существует в одной из записей. Это приводит к сбою в работе приложения, если этот случай не отлавливается
Ваша IDE не знает, какие методы и свойства доступны для модулей и библиотек
IDE плохо работает?morsic
22.03.2019 10:40>Если в рантайме пришли неверные данные, типизация их не починит. У нее есть свой ограниченный диапазон полезности.
Типизация поможет отделить валидированный инпут от невалидированного
stardust_kid
20.03.2019 19:19+1Статическая типизация немного облегчает разработку. Но идет вместе с большим оверхедом времени для написания кода и борьбы с багами системы типов.
Aquahawk
20.03.2019 19:50+3Тут не соглашусь, только пока программист не поймёт что это и как помогает. С опытом приходит помощь от системы типов, особенно в больших приложениях (только что померял своё 3671 файл, 45 мегабайт кода, typescript) И я вообще не представляю как такое приложение можно было бы писать без строгой типизации
serf
21.03.2019 00:01А можно раскладку по коду запуском этой утилиты github.com/cgag/loc?
Aquahawk
21.03.2019 11:19-------------------------------------------------------------------------------- Language Files Lines Blank Comment Code -------------------------------------------------------------------------------- JSON 1 615299 0 0 615299 TypeScript 3587 662204 21297 150768 490139 HTML 16 1868 222 7 1639 JavaScript 7 1583 401 186 996 C 1 703 95 28 580 Plain Text 24 740 228 0 512 CSS 8 332 8 22 302 C/C++ Header 1 39 9 3 27 Bourne Shell 1 7 1 1 5 -------------------------------------------------------------------------------- Total 3646 1282775 22261 151015 1109499 --------------------------------------------------------------------------------
Единственный Json кстати это набор данных для теста одной хреновины, так что да, объём кода завышен в мегабайтах.serf
21.03.2019 11:23490139 строк TS кода, прилично. Сколько народа в команде и начинался ли проект изначально на TS или переводили с JS?
Aquahawk
21.03.2019 11:25Проекту скоро 8 лет, изначально писан на actionscript 3.0. Там очень похоже всё на тайпскрипт. Мы написали транспилятор as3->ts и сейчас полируем всё для запуска. Да масштаб проекта позволяет окупить написание транспилятора и рантайма флеша только ради одного этого проекта. Это большая и старая flash игра, имеющая огромную армию фанатов. Это естественно чистые src, без node_modules и прочей шелухи. Из забавного, в проекте около 15 тыс игровых объектов, с артом, поведением и т.д. Даже typescript форкнуть пришлось :)
serf
21.03.2019 14:54Это все веб код или под ноду? Страшно предположить сколько весит итоговый собранный бандл. Может вам сразу на Rust переписать и в webassembly перегнать потом.
Aquahawk
21.03.2019 16:55Это клиент, с конвертером, там метров 15 js сбилженного, это без сжатия, для игры ок, вариантов нет, во флеше это было два мегабайта
VolCh
21.03.2019 11:17Где-то помогает, где-то мешает. Одно из основных рекламируемых достоинств — простой рефакторинг — на практике часто не работает, по крайней мере в IDE от JetBrains. А с почти всем остальным навскидку и тесты справиться могут и просто статанализ без явной типизации.
Aquahawk
21.03.2019 11:22По рефакторингам соглашусь, лучше, но не идеально. Статическая типизация позволяет сконцентрироваться в тестах на сложной логике а не на том что 2+2 сложилось и что поле записалось куда надо. За это любят бить ногами, но это так. Ну и писать гораздо удобнее когда у тебя есть нормальные подсказки по тоннам кода.
VolCh
21.03.2019 12:33Вот подсказки далеко не всегда нормальные. Особенно в случае каких-то JS библиотек (банальный jQuery) с декларациями, когда они активно используют «параметрический полиморфизм», приводящий к семиэтажным описаниям типов, а родные человекопонятные jsdoc игнорируются.
stardust_kid
21.03.2019 21:11Жаль вас разочаровывать, но многие большие кодовые базы на js написаны без типизации.
Aquahawk
22.03.2019 09:06Ну как бы египетские пирамиды тоже без современных средств механизации построены. Это же не повод сейчас так строить?
serf
21.03.2019 00:00Это не так. Начальный оферхед есть, но это даже не оферхед, а инвестиция в дизайн системы который как таковой отсутствует при использовании голого JS. Со временем, с ростом проекта и команды при использовании TS преимущества будут только увеличиваться в отличии если бы использовали сырой JS где нарастала бы боль.
SergeyEgorov
21.03.2019 09:57А почему дизайн системы отсутствует при использовании голого JS?
Неужели использование TS автоматически привносит в проект хороший дизайн?
serf
21.03.2019 10:30Очевидно потому что там нету типизации и нет интерфейсов, модель данных не опишешь и не зашаришь ее по всему приложению, между сигнатурами всех функций/методов/конструкторов. В сыром JS приоритет направлен на сговнякать что-то по быстрому и в продакшен. В сыром JS дизайн если он изначально в каком-то виде существовал, то очень быстро обесценивается тк он не подкреплен компилятором. Это как издать указы и потом просто считать и народ убеждать что они уже исполнены (реальный случай). Достаточно запустить например статический анализатор JS кода на любом более-менее крупном проекте и почти наверняка там вылезут грабли, как например самый частый случай — передача лишних аргументов в функции/методы. А если речь идет о разработке библиотек или взаимодействии между разными системами, то сырой JS использовать вообще вредно когда существует TS.
SergeyEgorov
21.03.2019 10:54А куда девалась типизация в JS? Почему вы считаете что в JS нет интерфейсов?
В сыром JS дизайн если он изначально в каком-то виде существовал, то очень быстро обесценивается тк он не подкреплен компилятором.
То есть вы считаете что в интерпретируемых языках с динамической и утиной типизацией дизайн как понятие в принципе отсутствует?
Ну то есть к примеру возьмем мы и напишем программу на языке C, запустим компилятор и он ее откомпилирует успешно. Означает ли это автоматически наличие хорошего дизайна в компилируемой программе?
Что вы вообще подразумеваете под понятием дизайн, если предполагаете что компилятор способен обеспечить его наличие?
serf
21.03.2019 10:58Очевидно имелся ввиду дизайн модели данных/типов, сигнатур методов.
Почему вы считаете что в JS нет интерфейсов?
Уже появились?
Ну то есть к примеру возьмем мы и напишем программу на языке C, запустим компилятор и он ее откомпилирует успешно. Означает ли это автоматически наличие хорошего дизайна в компилируемой программе?
Это означает что сигнатуры и типы не поломаны.
SergeyEgorov
21.03.2019 11:09Уже появились?
Никуда и не девались.
function FirstStringFactory() { this.make = function() { return "First"; }; } function SecondStringFactory() { this.make = function() { return "Second"; }; } function StringFactoryConsumer(stringFactory){ this.name = stringFactory.make(); }
Это означает что сигнатуры и типы не поломаны.
Вы считаете что это основа дизайна?
serf
21.03.2019 11:19Вы считаете что это основа дизайна?
Неотемлемая часть.
Никуда и не девались.
Это не интерфейсы но их реализация в виде абстрактных классов. TS позволяет описать StringFactoryConsumer и stringFactory без их реализации.
SergeyEgorov
21.03.2019 14:38Вы считаете что это основа дизайна?
Неотемлемая часть.
Странный ответ. А что еще в процессе разработки имеет отношение к дизайну, ну кроме компилятора?
Это не интерфейсы но их реализация в виде абстрактных классов. TS позволяет описать StringFactoryConsumer и stringFactory без их реализации.
Почему абстрактных?
serf
21.03.2019 14:50Не станут вас в чем-то убеждать. Если комфортно работать с сырым JS, то пожалуйста, каждому свое. Ваши выдуманные «интерфейсы в JS» таковыми не являютсят. В JS нет возможности гарантировать что в StringFactoryConsumer прилетит аргументом именно экземпляр FirstStringFactory или SecondStringFactory. Это просто бесполезный рантайм оверхед.
VolCh
21.03.2019 15:10В TypeScript таких гарантий тоже нет. Максимум он гарантирует, что прилетит что-то, у чего есть метод make, возвращающий строку. Что у класса, описывающего прототип прилетевшего объекта будет implements stringFactory он не гарантирует
serf
21.03.2019 15:32Не гарантирует потому что TS это структурная штука, которая не существует в рантайме, это by design. То есть да гарантируется только совпадение структуры. Этого как правило достаточно при работе со своим кодом. А при работе с внешними/сомнительными данными все равно применяется рантайм валидация.
VolCh
21.03.2019 11:12Интерфейс есть практически (перестраховка) у любой программной сущности. В некоторых языках он дополнен языковой конструкцией. Причём не только как статической, например в PHP проверка типов, в том числе работа с интерфейсами, происходит в рантайме.
serf
21.03.2019 11:21Интерфейс очевидно имелся ввиду как часть языка.
VolCh
21.03.2019 12:36В TS они неполноценные и польза от этого существенно снижается. Банальный this instance of ISomeDoable не работает.
serf
21.03.2019 13:41Не нужно притягивать слона за уши. Польза по сравнению с чем? В контексте обсуждения сравнивать есть смысл только JS с TS.
VolCh
21.03.2019 15:13Ели только с JS сравнивать, то могу переформулировать: цена за использование интерфейсов в TS может оказаться гораздо выше приносимой пользы.
qbz
20.03.2019 23:12Flow для меня ассоциируется с болью. Работал на нем какое-то время, но элементарные вещи эта штука не понимает. Или я тупой. Вообще не люблю продукты фейсбука, много хайпа, мало пользы.
Как можно так тупить?
https://flow.org/try/#0MYewdgzgLgBAHjAvDAFGAXDMBXAtgIwFMAnGAHxmmIEswBzASkxwJKQD4YBvAKAEhQkWNQgA5PEVLIoATwAOhEADMsSRMgDkLSRv7UVKEeNbEG3fn2KEo2YmFUAqGAEYADPwC+-KzbswATO5ePDxwKM4MQA
Или вот так?
https://flow.org/try/#0NoRgNAdgrgNjYGYC6A6AZgSxgFwKYCcAKCAAgF4A+E7ATwAdcB7NE0s9kgcmgFsAjApwCUKHgEM6xclVIAqEiAAMQoAserf
21.03.2019 00:40не люблю продукты фейсбука, много хайпа, мало пользы
Причины такого положения дел обсуждались на различных англоязчных сообществах. Основная как я понял состоит в политике повышений в должности и соответственно зарплате. Фейсбук поощряет запуск новых штуковин, это один из показателей проактивности влияющий на повышение. Но обнаружился лайфхак, можно просто запустить, развивать эти поделки и фиксить там баги не обязательно для получения повышения.qbz
21.03.2019 04:29Что-то подобное я читал от инсайдера гугла, какой-то экс-программист писал как у них там тоже так же вот устроено. Но продукты гугла на голову серьезнее и эффективнее фейсбука.
justboris
21.03.2019 12:29Если вам это как-то поможет, то второй пример нормально работает в typesctipt. Ссылка на демо.
Первый ломается и там, и там, но его можно пофиксить, перенеся проверку typeof напрямую в if. Ссылка на демо.
qbz
21.03.2019 16:38Спасибо, да, как раз хочу попробовать пересесть на TS и забыть про flow раз и навсегда. Насчет переноса условия прямо внутрь if я в курсе, но в этом и суть — бывают условия громоздкие или не совсем очевидные, в таком случае надо их выносить в отдельную переменную с ясным названием, которая позволит просто читать и понимать такой код.
justboris
21.03.2019 16:43Я так понимаю с условием все просто: если заставить компилятор разматывать переменные и смотреть на их суть, то это сильно замедлит скорость компиляции.
Еще посмотрите на type guards. Можно вынести код проверки типа в отдельную функцию, и typescript поймет, что там проверяются типы.
qbz
21.03.2019 16:50Интересно, гляну. Насчет переменных — так все равно же у них есть какой-то кеш или что-то вроде infer tree для такого. Почему бы переменные в ифе тоже не расчитывать..?
bugsfinder
21.03.2019 09:11Если Reason вас заинтересует, вы можете найти здесь его документацию.
Не найти там ничего — 404
There isn't a GitHub Pages site here.
gearbox
21.03.2019 10:40Не знаком с reason, но то что я увидел на сайте наводит на мысль что он тут в списке лишний. Flow и Typescript это система типов прикрученная к js, при этом это остается js, просто с типами. reason больше похож на ocaml компиляющий в js, ну так тут таких сотни. Правильнее его сравнивать с purescript и ему подобными.
dolovar
21.03.2019 11:25Краткое содержание:
Как система типов улучшает ваш код (обещание ответить на вопрос).
Не нашел ответ на вопрос в заголовке. Плохо искал, наверное.
Ванильный не типизирован (совсем?), что упрощает разработку, но добавляет уязвимости.
Варианты проблем: поле отсутствует в записи (объекте?), метода нет в экземпляре класса (объекте?), IDE не всё может автодополнять (никто не сможет).
Можно попробовать Flow или TypeScript, но они громоздкие, сложные, загружающие и не дают 100% гарантии безопасности (никто не даст).
Рассмотрим два с половиной примера, в которых внезапно появляется Reason, который позволяет сложить строку с числом.
Упомянут опыт разработчика-фаната, который не будет сравнивать VSCode и vim и надеется на появление документации.
Про Reason можно узнать там-то. И про GraphQL. И про день открытых дверей.
serf
21.03.2019 11:28Так это западная статья, они там любят пускать пыль в глаза и бросаться базвордами при этом часто не поработав плотно с описываемыми технологиями.
lightmaann
Разве «система типов улучшает ваш код на JavaScript» — не является само собой разумеющимся?)
VolCh
Нет. Она может его испортить и вообще не даст собрать хоть как-то работающее приложение :)
shai_hulud
В мире очень много плохо пахнувшего кода который работает, продается и даже кто то им гордится. У кода больше качеств, чем просто «работает». Кому то надо будет это поддерживать, расширять или просто пытаться понять.
serf
Скрипт кидди детектед.
PS При использовании лок файлов зависимостей сборка того же самого кода должна проходить успешно тк typescript компилятор остается тот же.
justboris
Имелось в виду, что если начать покрывать работающий код типами, то можно погрязнуть в куче разных несовпадений типов, которые пользователям никак не мешали, но компиляцию typescript ломают.
P.S. а что вы имели в виду под "скрипт кидди"? Я знаю только вот это определение и оно тут не при чем.
serf
Могу перефразировать — код манки.
justboris
А еще можно расставить any. Но это подменяет изначальную цель «покрыть код типами», на искусственную «внедрить typescript», который сам по себе безопасности кода не гарантирует.
serf
Речь ведь идет только о переходном периоде который неизбежен если происходит перевод JS кода на TS. Any расставить можно, но это муторно. Можно включить на время allow implicit any, но мне такой вариант не нравится.