Как система типов улучшает ваш код на JavaScript +7



Вот так не успеешь обернуться, а месяц уже стремится к своему завершению. Считанные дни остаются до запуска нового потока по курсу «Разработчик JavaScript», по традиции перед запуском курса делимся с вами переводом полезного материала.

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):

  1. lightmaann
    /#19916452 / +1

    Разве «система типов улучшает ваш код на JavaScript» — не является само собой разумеющимся?)

    • VolCh
      /#19916486 / +4

      Нет. Она может его испортить и вообще не даст собрать хоть как-то работающее приложение :)

      • shai_hulud
        /#19916540

        В мире очень много плохо пахнувшего кода который работает, продается и даже кто то им гордится. У кода больше качеств, чем просто «работает». Кому то надо будет это поддерживать, расширять или просто пытаться понять.

      • serf
        /#19917752

        Скрипт кидди детектед.

        PS При использовании лок файлов зависимостей сборка того же самого кода должна проходить успешно тк typescript компилятор остается тот же.

        • justboris
          /#19920498 / +1

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


          P.S. а что вы имели в виду под "скрипт кидди"? Я знаю только вот это определение и оно тут не при чем.

          • serf
            /#19920702

            Имелось в виду, что если начать покрывать работающий код типами, то можно погрязнуть в куче разных несовпадений типов, которые пользователям никак не мешали, но компиляцию typescript ломают.
            Можно на этап перевода кода разрешить работать с JS файлами (опция allowJs) и так постепенно по файлу переводить на TS. Или что-то такое испольовать github.com/evolution-gaming/tsc-silent

            P.S. а что вы имели в виду под «скрипт кидди»? Я знаю только вот это определение и оно тут не при чем.
            Могу перефразировать — код манки.

            • justboris
              /#19920802

              А еще можно расставить any. Но это подменяет изначальную цель «покрыть код типами», на искусственную «внедрить typescript», который сам по себе безопасности кода не гарантирует.

              • serf
                /#19920854

                Речь ведь идет только о переходном периоде который неизбежен если происходит перевод JS кода на TS. Any расставить можно, но это муторно. Можно включить на время allow implicit any, но мне такой вариант не нравится.

  2. VolCh
    /#19916484 / +5

    Теперь мы видимо задаем именно два int значения. Не два float или два string

    number в javascript и typescript — именно, что float (если быть совсем точным, то double, если использовать сишные типы). В них нет int типа.

  3. Akuma
    /#19916718 / +3

    Не так давно пытался внедрить в уже работающий проект Flow… ну так себе затея, в итоге отказался. Сторонние библиотеки не типизированы, твои типизированы, все это мешается друг с другом… уф, жесть какая-то была.

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

    • serf
      /#19917754 / +1

      Flow можно считать мертв, зачем почивших тревожить.

      • Akuma
        /#19917756

        1. Он упоминается в статье.
        2. Это было давно (достаточно давно для Flow и не так давно для проекта).

    • worldxaker
      /#19919262

      мы потихоньку внедряем в большой проект ts и никаких особых проблем с этим не возникает

      • Akuma
        /#19920116

        В TS лично мне не нравится то, что нужно описывать каждую внешнюю библиотеку. И если для популярных файл с описанием обычно есть, то для мелких библиотек — только вручную.
        По крайней мере так было около года назад, после этого я TS не трогал.

        Сейчас так же?

        • serf
          /#19920344

          Все более-менее популярные библиотеки описаны. Но все равно я предпочитаю использовать пускай менее функциональные но изначально написанные на TS библиотеки. Потому что часто возникают ситуации когда декларации расходятся с самой библиотекой или когда декларации не могут полностью выразить функционал библиотеки или делают это наоборот неполноценно (например без дженериков). Кроме этого я больше доверяю коду написанному на TS чем сырому JS. Уже заметен тренд переписывания популярных библиотек на TS и это замечатально, разумность приходит в массы.

        • worldxaker
          /#19928602

          да, иногда приходится страдать. но за последний год ситуация сильно улучшилась, и тайпинги есть на большую часть библиотек

  4. ecmaeology
    /#19916800 / +3

    Однако он уязвим для многочисленных ошибок во время исполнения (рантайм)…
    После получения списка данных вы обнаруживаете, что определенное поле не существует в одной из записей. Это приводит к сбою в работе приложения, если этот случай не отлавливается
    Если в рантайме пришли неверные данные, типизация их не починит. У нее есть свой ограниченный диапазон полезности.

    Ваша IDE не знает, какие методы и свойства доступны для модулей и библиотек
    IDE плохо работает?

    • morsic
      /#19924616

      >Если в рантайме пришли неверные данные, типизация их не починит. У нее есть свой ограниченный диапазон полезности.
      Типизация поможет отделить валидированный инпут от невалидированного

  5. stardust_kid
    /#19916872 / +1

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

    • Aquahawk
      /#19917008 / +3

      Тут не соглашусь, только пока программист не поймёт что это и как помогает. С опытом приходит помощь от системы типов, особенно в больших приложениях (только что померял своё 3671 файл, 45 мегабайт кода, typescript) И я вообще не представляю как такое приложение можно было бы писать без строгой типизации

      • serf
        /#19917768

        А можно раскладку по коду запуском этой утилиты github.com/cgag/loc?

        • Aquahawk
          /#19919208

          --------------------------------------------------------------------------------
           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
            /#19919244

            490139 строк TS кода, прилично. Сколько народа в команде и начинался ли проект изначально на TS или переводили с JS?

            • Aquahawk
              /#19919260

              Проекту скоро 8 лет, изначально писан на actionscript 3.0. Там очень похоже всё на тайпскрипт. Мы написали транспилятор as3->ts и сейчас полируем всё для запуска. Да масштаб проекта позволяет окупить написание транспилятора и рантайма флеша только ради одного этого проекта. Это большая и старая flash игра, имеющая огромную армию фанатов. Это естественно чистые src, без node_modules и прочей шелухи. Из забавного, в проекте около 15 тыс игровых объектов, с артом, поведением и т.д. Даже typescript форкнуть пришлось :)

              • serf
                /#19920402

                Это все веб код или под ноду? Страшно предположить сколько весит итоговый собранный бандл. Может вам сразу на Rust переписать и в webassembly перегнать потом.

                • Aquahawk
                  /#19921190

                  Это клиент, с конвертером, там метров 15 js сбилженного, это без сжатия, для игры ок, вариантов нет, во флеше это было два мегабайта

      • VolCh
        /#19919186

        Где-то помогает, где-то мешает. Одно из основных рекламируемых достоинств — простой рефакторинг — на практике часто не работает, по крайней мере в IDE от JetBrains. А с почти всем остальным навскидку и тесты справиться могут и просто статанализ без явной типизации.

        • Aquahawk
          /#19919222

          По рефакторингам соглашусь, лучше, но не идеально. Статическая типизация позволяет сконцентрироваться в тестах на сложной логике а не на том что 2+2 сложилось и что поле записалось куда надо. За это любят бить ногами, но это так. Ну и писать гораздо удобнее когда у тебя есть нормальные подсказки по тоннам кода.

          • VolCh
            /#19919668

            Вот подсказки далеко не всегда нормальные. Особенно в случае каких-то JS библиотек (банальный jQuery) с декларациями, когда они активно используют «параметрический полиморфизм», приводящий к семиэтажным описаниям типов, а родные человекопонятные jsdoc игнорируются.

      • stardust_kid
        /#19922354

        Жаль вас разочаровывать, но многие большие кодовые базы на js написаны без типизации.

        • justboris
          /#19922456

          Но они скорее стали большими не благодаря отсутствию типизации, а вопреки

        • Aquahawk
          /#19924064

          Ну как бы египетские пирамиды тоже без современных средств механизации построены. Это же не повод сейчас так строить?

    • serf
      /#19917764

      Это не так. Начальный оферхед есть, но это даже не оферхед, а инвестиция в дизайн системы который как таковой отсутствует при использовании голого JS. Со временем, с ростом проекта и команды при использовании TS преимущества будут только увеличиваться в отличии если бы использовали сырой JS где нарастала бы боль.

      • SergeyEgorov
        /#19918748

        А почему дизайн системы отсутствует при использовании голого JS?


        Неужели использование TS автоматически привносит в проект хороший дизайн?

        • serf
          /#19918942

          Очевидно потому что там нету типизации и нет интерфейсов, модель данных не опишешь и не зашаришь ее по всему приложению, между сигнатурами всех функций/методов/конструкторов. В сыром JS приоритет направлен на сговнякать что-то по быстрому и в продакшен. В сыром JS дизайн если он изначально в каком-то виде существовал, то очень быстро обесценивается тк он не подкреплен компилятором. Это как издать указы и потом просто считать и народ убеждать что они уже исполнены (реальный случай). Достаточно запустить например статический анализатор JS кода на любом более-менее крупном проекте и почти наверняка там вылезут грабли, как например самый частый случай — передача лишних аргументов в функции/методы. А если речь идет о разработке библиотек или взаимодействии между разными системами, то сырой JS использовать вообще вредно когда существует TS.

          • SergeyEgorov
            /#19919068

            А куда девалась типизация в JS? Почему вы считаете что в JS нет интерфейсов?


            В сыром JS дизайн если он изначально в каком-то виде существовал, то очень быстро обесценивается тк он не подкреплен компилятором.

            То есть вы считаете что в интерпретируемых языках с динамической и утиной типизацией дизайн как понятие в принципе отсутствует?


            Ну то есть к примеру возьмем мы и напишем программу на языке C, запустим компилятор и он ее откомпилирует успешно. Означает ли это автоматически наличие хорошего дизайна в компилируемой программе?


            Что вы вообще подразумеваете под понятием дизайн, если предполагаете что компилятор способен обеспечить его наличие?

            • serf
              /#19919102

              Очевидно имелся ввиду дизайн модели данных/типов, сигнатур методов.

              Почему вы считаете что в JS нет интерфейсов?
              Уже появились?
              Ну то есть к примеру возьмем мы и напишем программу на языке C, запустим компилятор и он ее откомпилирует успешно. Означает ли это автоматически наличие хорошего дизайна в компилируемой программе?
              Это означает что сигнатуры и типы не поломаны.

              • SergeyEgorov
                /#19919152

                Уже появились?

                Никуда и не девались.


                function FirstStringFactory() {
                    this.make = function() {
                        return "First";
                    };
                }
                
                function SecondStringFactory() {
                    this.make = function() {
                        return "Second";
                    };
                }
                
                function StringFactoryConsumer(stringFactory){
                    this.name = stringFactory.make();
                }

                Это означает что сигнатуры и типы не поломаны.

                Вы считаете что это основа дизайна?

                • serf
                  /#19919204

                  Вы считаете что это основа дизайна?
                  Неотемлемая часть.
                  Никуда и не девались.
                  Это не интерфейсы но их реализация в виде абстрактных классов. TS позволяет описать StringFactoryConsumer и stringFactory без их реализации.

                  • SergeyEgorov
                    /#19920296

                    Вы считаете что это основа дизайна?
                    Неотемлемая часть.

                    Странный ответ. А что еще в процессе разработки имеет отношение к дизайну, ну кроме компилятора?


                    Это не интерфейсы но их реализация в виде абстрактных классов. TS позволяет описать StringFactoryConsumer и stringFactory без их реализации.

                    Почему абстрактных?

                    • serf
                      /#19920376

                      Не станут вас в чем-то убеждать. Если комфортно работать с сырым JS, то пожалуйста, каждому свое. Ваши выдуманные «интерфейсы в JS» таковыми не являютсят. В JS нет возможности гарантировать что в StringFactoryConsumer прилетит аргументом именно экземпляр FirstStringFactory или SecondStringFactory. Это просто бесполезный рантайм оверхед.

                      • VolCh
                        /#19920476

                        В TypeScript таких гарантий тоже нет. Максимум он гарантирует, что прилетит что-то, у чего есть метод make, возвращающий строку. Что у класса, описывающего прототип прилетевшего объекта будет implements stringFactory он не гарантирует

                        • serf
                          /#19920660

                          Не гарантирует потому что TS это структурная штука, которая не существует в рантайме, это by design. То есть да гарантируется только совпадение структуры. Этого как правило достаточно при работе со своим кодом. А при работе с внешними/сомнительными данными все равно применяется рантайм валидация.

              • VolCh
                /#19919164

                Интерфейс есть практически (перестраховка) у любой программной сущности. В некоторых языках он дополнен языковой конструкцией. Причём не только как статической, например в PHP проверка типов, в том числе работа с интерфейсами, происходит в рантайме.

                • serf
                  /#19919218

                  Интерфейс очевидно имелся ввиду как часть языка.

                  • VolCh
                    /#19919696

                    В TS они неполноценные и польза от этого существенно снижается. Банальный this instance of ISomeDoable не работает.

                    • serf
                      /#19919976

                      Не нужно притягивать слона за уши. Польза по сравнению с чем? В контексте обсуждения сравнивать есть смысл только JS с TS.

                      • VolCh
                        /#19920508

                        Ели только с JS сравнивать, то могу переформулировать: цена за использование интерфейсов в TS может оказаться гораздо выше приносимой пользы.

                        • serf
                          /#19920666

                          На самом деле интерфейсы в TS не особенно и часто используются.

                          • VolCh
                            /#19924646

                            У нас на каждый чих, причём не просто используются, а куча объединений, пересечений и т. п.

                            • serf
                              /#19924656

                              Полагаю это все же TS типы а не интерфейсы.

  6. qbz
    /#19917622

    Flow для меня ассоциируется с болью. Работал на нем какое-то время, но элементарные вещи эта штука не понимает. Или я тупой. Вообще не люблю продукты фейсбука, много хайпа, мало пользы.


    Как можно так тупить?
    https://flow.org/try/#0MYewdgzgLgBAHjAvDAFGAXDMBXAtgIwFMAnGAHxmmIEswBzASkxwJKQD4YBvAKAEhQkWNQgA5PEVLIoATwAOhEADMsSRMgDkLSRv7UVKEeNbEG3fn2KEo2YmFUAqGAEYADPwC+-KzbswATO5ePDxwKM4MQA


    Или вот так?
    https://flow.org/try/#0NoRgNAdgrgNjYGYC6A6AZgSxgFwKYCcAKCAAgF4A+E7ATwAdcB7NE0s9kgcmgFsAjApwCUKHgEM6xclVIAqEiAAMQoA

    • serf
      /#19917876

      не люблю продукты фейсбука, много хайпа, мало пользы
      Причины такого положения дел обсуждались на различных англоязчных сообществах. Основная как я понял состоит в политике повышений в должности и соответственно зарплате. Фейсбук поощряет запуск новых штуковин, это один из показателей проактивности влияющий на повышение. Но обнаружился лайфхак, можно просто запустить, развивать эти поделки и фиксить там баги не обязательно для получения повышения.

      • qbz
        /#19918162

        Что-то подобное я читал от инсайдера гугла, какой-то экс-программист писал как у них там тоже так же вот устроено. Но продукты гугла на голову серьезнее и эффективнее фейсбука.

        • Cerberuser
          /#19918250

          Скажите это пользователям GMail...

        • serf
          /#19918316

          Допускаю что я перепутал фейсбука с гуглом, они для меня на одно лицо.

    • justboris
      /#19919648

      Если вам это как-то поможет, то второй пример нормально работает в typesctipt. Ссылка на демо.


      Первый ломается и там, и там, но его можно пофиксить, перенеся проверку typeof напрямую в if. Ссылка на демо.

      • qbz
        /#19921090

        Спасибо, да, как раз хочу попробовать пересесть на TS и забыть про flow раз и навсегда. Насчет переноса условия прямо внутрь if я в курсе, но в этом и суть — бывают условия громоздкие или не совсем очевидные, в таком случае надо их выносить в отдельную переменную с ясным названием, которая позволит просто читать и понимать такой код.

        • justboris
          /#19921120

          Я так понимаю с условием все просто: если заставить компилятор разматывать переменные и смотреть на их суть, то это сильно замедлит скорость компиляции.


          Еще посмотрите на type guards. Можно вынести код проверки типа в отдельную функцию, и typescript поймет, что там проверяются типы.

          • qbz
            /#19921160

            Интересно, гляну. Насчет переменных — так все равно же у них есть какой-то кеш или что-то вроде infer tree для такого. Почему бы переменные в ифе тоже не расчитывать..?

  7. bugsfinder
    /#19918522

    Если Reason вас заинтересует, вы можете найти здесь его документацию.
    Не найти там ничего — 404
    There isn't a GitHub Pages site here.

  8. gearbox
    /#19918990

    Не знаком с reason, но то что я увидел на сайте наводит на мысль что он тут в списке лишний. Flow и Typescript это система типов прикрученная к js, при этом это остается js, просто с типами. reason больше похож на ocaml компиляющий в js, ну так тут таких сотни. Правильнее его сравнивать с purescript и ему подобными.

    • serf
      /#19919114

      Здравая мысль. А то нас здесь пытаются сбить с толку.

  9. dolovar
    /#19919258

    Краткое содержание:

    Как система типов улучшает ваш код (обещание ответить на вопрос).
    Ванильный не типизирован (совсем?), что упрощает разработку, но добавляет уязвимости.
    Варианты проблем: поле отсутствует в записи (объекте?), метода нет в экземпляре класса (объекте?), IDE не всё может автодополнять (никто не сможет).
    Можно попробовать Flow или TypeScript, но они громоздкие, сложные, загружающие и не дают 100% гарантии безопасности (никто не даст).
    Рассмотрим два с половиной примера, в которых внезапно появляется Reason, который позволяет сложить строку с числом.
    Упомянут опыт разработчика-фаната, который не будет сравнивать VSCode и vim и надеется на появление документации.
    Про Reason можно узнать там-то. И про GraphQL. И про день открытых дверей.
    Не нашел ответ на вопрос в заголовке. Плохо искал, наверное.

    • serf
      /#19919284

      Так это западная статья, они там любят пускать пыль в глаза и бросаться базвордами при этом часто не поработав плотно с описываемыми технологиями.