Недавно мы опубликовали материал, в котором Эрик Эллиот критиковал TypeScript. Сегодня мы представляем вашему вниманию перевод статьи Кента Доддса. Тут он рассказывает о том, почему в PayPal перешли с Flow на TypeScript.

image

Предыстория


Я работаю в PayPal и занимаюсь библиотекой paypal-scripts, которая представляет собой набор инструментов, напоминающий react-scripts из create-react-app, или angular-cli, или ember-cli. Об этом я уже писал. В основе этой библиотеки лежит идея объединения всех инструментов, используемых в приложениях PayPal и в опубликованных модулях. Цель создания paypal-scripts заключается в том, чтобы взять все зависимости разработки, devDependencies, из package.json, все конфигурационные файлы, и свести всё это к одной записи в разделе devDependencies. И, так как все конфигурации находятся в единственном пакете, при создании которого придерживаются вполне определённой точки зрения на то, «что такое хорошо», для того, чтобы поддерживать инструменты в актуальном состоянии, достаточно обновить лишь одну зависимость (собственно — paypal-scripts), обновления которой обычно не содержат в себе чего-то такого, что способно нарушить работу кода, полагающегося на неё. В результате достаточно поддерживать в актуальном состоянии лишь одну зависимость и спокойно заниматься разработкой приложений.

За последний год программисты в PayPal привыкли работать с paypal-scripts. Здесь для создания нового приложения достаточно щёлкнуть по нескольким кнопкам в веб-интерфейсе, в результате чего будет создан корпоративный GitHub-репозиторий, будут настроены средства развёртывания проекта, система непрерывной интеграции, и так далее. Автоматически создаваемый репозиторий основан на репозитории sample-app.

Буквально на прошлой неделе в него было включено моё добавление, рассчитанное на использование в нём paypal-script. Это означает, что в основе каждого нового приложения в PayPal будет лежать каркас, построенный на базе современных технологий и инструментов, об обновлении которых не нужно заботиться разработчику этого приложения. Кроме прочего, подобное приложение будет статически типизировано с использованием TypeScript и протестировано средствами Jest.

Честно говоря, это стало Magnum Opus моей карьеры. Я не думал, что когда-нибудь мне удастся достигнуть подобного уровня в PayPal. Этот проект оказывает огромнейшее влияние, и я благодарен PayPal за то, что мне предоставлена возможность работать над чем-то столь масштабным.

Так, в курс дел я вас ввёл, теперь поговорим о TypeScript.

В середине декабря я работал над тем, чтобы интегрировать paypal-scripts в sample-app. Ещё я работал (и продолжаю работать) над проектом pp-react, который представляет собой библиотеку компонентов (кнопок, окон, стилей), подходящих для повторного использования. Так как paypal-scripts поддерживает модули, которые могут быть опубликованы, я, для сборки pp-react, использовал react-scripts. Месяц тому назад библиотека paypal-scripts включала в себя поддержку Flow. Такую поддержку было очень просто добавить в эту библиотеку благодаря Babel.

12 декабря, когда я работал над pp-react и новой версией sample-app в плане поддержки Flow, я почувствовал, что от Flow я уже очень устал (подробнее об этом я расскажу ниже) и принял неожиданное решение. Я написал коллеге письмо, спросив его о том, как он смотрит на то, что я попытаюсь сделать так, чтобы в sample-app использовался бы TypeScript. Он ответил: «Да, сделай». Тогда я устроил опрос на Slack-канале #paypal-scripts, по результатам которого оказалось, что мою идею поддерживают все его участники. Для меня всего этого было достаточно для того, чтобы приступить к работе. Примерно через неделю я полностью перевёл paypal-scripts с поддержки Flow на поддержку TypeScript. Большая часть этого времени ушла на то, чтобы научить все инструменты распознавать расширения файлов .ts и .tsx, и на то, чтобы позволить пакету paypal-scripts самому себя протестировать, что оказалось довольно-таки непростым делом. Потом я потратил несколько дней на работу над PR в репозиторий sample-app, которая была направлена на использование новой улучшенной библиотеки paypal-scripts, и на переход с .js-файлов на .ts и .tsx-файлы. Потом были праздники, а потом мой PR был одобрен. Как результат, теперь в каждом новом проекте в PayPal используется статическая TypeScript-типизация.

Конечно, после того, как некто создаёт новый проект, он может делать с ним всё, что ему захочется. Скажем, может удалить весь шаблонный код и писать его на Elm, или на чём угодно другом. Это совершенно нормально. Но авторы большинства проектов придерживаются тех технологий, которые были использованы при их создании благодаря так называемому «эффекту умолчания».

Почему я так долго шёл к TypeScript?


Вопрос, вынесенный в заголовок этого раздела, часто задавали мне фанаты TypeScript. Дело в том, что я уже давно знаком с TypeScript, но отношения у меня с этим языком до некоторых пор не складывались. Так, я помню, как примерно в 2013 году коллега предложил мне перевести код объёмом примерно в 500 тысяч строк на TypeScript. Тогда я это предложение отверг, но не особенно жалею об этом, так как в те времена TS был достаточно молодым языком. А однажды я даже брал интервью у Андерса Хейлсберга, создателя TypeScript.

Вот из-за чего я всё это время держался в стороне от TypeScript.

?Причина №1. Боязнь разрушить сложившуюся рабочую среду, основанную на Babel и ESLint


Для меня, очень долго, главнейшим плюсом Flow перед TypeScript было то, что Flow лучше сочетался с инструментами, к которым я привык. В частности, я уже многие годы с удовольствием пользуюсь Babel и ESLint, мне нравится писать собственные плагины и для того, и для другого (вы тоже, кстати, можете этому научиться). Мне нравилось то, что вокруг Babel и ESLint сложились огромные сообщества. В результате отказываться от них я категорически не хотел. Собственно говоря, продолжалось это вплоть до недавних событий, так как, если бы я собрался с головой уйти в TypeScript, мне пришлось бы оставить и то и другое. Конечно, в мире TypeScript есть такая штука как TSLint, но сообщество ESLint значительно больше.

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

  1. Надо подключить к Babel пресет с поддержкой соответствующего синтаксиса.
  2. Нужно добавить в начало каждого файла, проверку типов в котором требуется организовать, конструкцию // @flow (существует плагин для ESLint, который позволяет это проверить).
  3. Добавить в проект скрипт, позволяющий запустить Flow для проверки типов в кодовой базе.

Мне очень нравится то, что проверка типов (с помощью Flow) и сборка проектов (средствами Babel, Webpack или Rollup) разделены. Мне не хотелось связывать свою жизнь с TypeScript, в частности, из-за того, что его компилятор, в любом случае, не понимал бы плагинов для Babel моей собственной разработки. А ещё — из-за того, что у меня был Flow — вполне сносный инструмент.

Теперь же всё продолжает работать как обычно. Благодаря Babel 7 (в частности, речь идёт о @babel/preset-typescript) можно сохранить привычные инструменты и, кроме того, получить в своё распоряжение большинство возможностей TypeScript. Главная проблема — это сделать так, чтобы инструменты принимали бы файлы с расширениями .ts и .tsx, но, к счастью, эта проблема решаема.

?Причина №2. Контрибуторам придётся учить TypeScript для того, чтобы внести вклад в проект


Я говорю, в основном, об опенсорсе, но необходимость освоения TypeScript теми, кто хочет вносить вклад в проекты, относится и к тому, чем я занимаюсь на работе. При этом я всегда считал, что рабочие проекты должны быть типизированными, и достигалось это средствами Flow. Я старался не пользоваться Flow в моих опенсорсных проектах, потому что тем, кто решил бы к ним присоединиться, пришлось бы осваивать Flow. Я сам всегда об этом говорил, но, подсознательно, всегда приводил контраргумент, который заключается в том, что типизация — это, по своей сути, лишь ещё одна форма тестирования, а тем, кто хочет вносить вклад в опенсорс, всё равно, приходится разбираться с тестированием.

Честно говоря, отказ от использования некоей технологии в опенсорсе только из-за того, что потенциальный контрибутор может ей не владеть, кажется мне плохим оправданием отказа от этой технологии. И, по мере того, как всё больше и больше программистов осваивают TypeScript, я думаю, что, возможно, через некоторое время буду писать на TS и свои опенсорсные проекты.

?Причина №3. Мощная система вывода типов Flow


Я читал этот пост, и он мне очень понравился. В особенности его последняя строка, в соответствии с которой при использовании Flow типы добавляют для того, чтобы сделать сообщения об ошибках приятнее, а не для того, чтобы их выявлять.

Так оно и есть. В наши дни Flow имеет более мощную систему вывода типов, чем TypeScript, и это меня обнадёживало.

?Причина №4. Flow, как и React, родом из Facebook


Я погрешу против истины, если скажу, что я не поддавался весьма распространённому заблуждению, заключающемуся в том, чтобы полагать, что если некая компания сделала что-то грандиозное, то всё остальное, что она делает, автоматически оказывается на столь же высоком уровне. Подобное совсем не гарантировано. Больше мне тут добавить нечего.

?Причина №5. Фанатичные приверженцы TypeScript


Думаю, все знают, что если некто по-настоящему восхищён некоей технологией, то он, не замолкая, всем вокруг о ней рассказывает. Тут кто-нибудь пользуется vim? И приверженцы TypeScript — не исключение.

В сообществе TypeScript, кстати, полно замечательных людей. Добрых, готовых прийти на помощь, полных энтузиазма, дружелюбных. Но мне приходилось пересекаться и с такими любителями TS, которые назовут человека дураком только из-за того, что он не пользуется TypeScript, или не понимает его, или пользуется чем-то другим. Они демонстрируют отсутствие способности к пониманию собеседника, а их позиция отдаёт снобизмом. Это — не то сообщество, частью которого я хотел бы стать. Я имею в виду то, что воодушевление, вызываемое выбранной кем-то технологией — это замечательно, но если оно заходит настолько далеко, что фанат этой технологии начинает притеснять тех, кто выбрал что-то другое, это уже весьма печально.

У меня всё ещё остаются некоторые опасения по этому поводу. Но я надеюсь, что все вместе мы сделаем так, что сообщество TypeScript будет более позитивным.

Теперь, когда я рассказал о причинах, по которым не спешил переходить на TypeScript, расскажу о том, что меня не устраивает во Flow.

Проблемы Flow


Как я уже говорил, в определённый момент я очень устал от Flow. Вот один из твитов, в котором я делился одной из главных проблем, с которой столкнулся при работе с Flow. Заключалась она в том, что для того, чтобы Flow заработал, регулярно приходится, после его неудачного запуска, его останавливать, а потом запускать снова. Вот ещё один мой твит, где речь идёт о неправильной работе Flow.

Меня окончательно оттолкнули от Flow регулярно возникающие проблемы с его надёжностью. Плагины для редакторов работали, так сказать, с переменным успехом (должен признаться, что я не работал с Nuclide, и возможно, попробуй я его, моя жизнь сложилась бы иначе, но я пробовал работать с Flow в Atom и в VSCode), я постоянно сталкивался с какими-то странностями. Это было весьма досадно, так как подрывало мою веру в используемую мной систему контроля типов.

Когда я, в ноябре, увидел этот твит, он выразил то, о чём я уже размышлял; краткий рассказ о переходе с Flow на TypeScript совпал с моим видением ситуации. Я, честно говоря, не мог перестать думать о том, чтобы как следует взяться за TypeScript. В итоге я так и поступил и я очень этому рад.

Вопросы и ответы


?Почему вы не пользуетесь TSLint?


На самом деле, я реализовал поддержку TSLint в paypal-script. Это был один из первых скриптов, который у меня заработал. Я собирался принимать решение о том, использовать ли TSLint или ESLint, основываясь на том, есть ли в проекте файл tsconfig.json. Но потом я вспомнил, что у нас есть некоторые ESLint-плагины собственной разработки (например, для проверки интернационализации), на переписывание которых в виде плагинов для TSLint мне не хотелось тратить время. Кроме того, интерфейс командной строки TSLint обладает меньшими возможностями, чем у ESLint, и он не очень хорошо подходил для совместной работы с paypal-scripts. Возможно, через некоторое время я снова присмотрюсь к TSLint.

Да, хочется отметить, что сообщество ESLint всё ещё гораздо больше, чем сообщество TSLint. Кроме того, я постепенно понимаю, что хорошая система контроля типов делает плагины для линтинга бесполезными. Пока же я использую с TypeScript ESLint, и то, что получается, выглядит совсем неплохо. Вот моё видео на эту тему.

И, кстати, у меня такое ощущение, что команда TypeScript склоняется в сторону ESLint, поэтому я полагаю, что я сделал правильный выбор.

?Почему вы не выбрали Reason?


Я, в переписке под этим твитом, ответил на предложение попробовать TypeScript, сказав, что уж лучше перейду с Flow на ReasonML. На самом деле, я часто говорил о переходе на Reason до перехода на TypeScript. Одной из главных причин подобных утверждений было мое желание сохранить привычные инструменты, о котором я уже рассказывал. Но, так как ни от чего отказываться мне не пришлось, TypeScript оказался для меня более привлекательным. Мне и сейчас очень нравится Reason, но переход на Reason означал бы необходимость огромных изменений для многих сотрудников PayPal. И хотя я думаю, что они с этим справились бы, я полагаю, что им комфортнее будет пользоваться TypeScript, чем пытаться изучать новый язык.

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

Итоги


Сейчас мне хотелось бы поблагодарить всех пользователей Твиттера, под влиянием которых сформировалось моё видение TypeScript. Как я уже говорил, то, что библиотека paypal-scripts попала в репозиторий sample-app в PayPal, возможно, главное достижение моей карьеры. И я считаю, что то, что теперь шаблоны всех новых приложений в компании по умолчанию оснащаются поддержкой TypeScript — это огромный плюс для всех сотрудников PayPal. Я безмерно рад тому, что выбрал TypeScript.

Уважаемые читатели! Как вы думаете, стоит ли тем, кто пользуется Flow, смотреть в сторону TypeScript?

Комментарии (60)


  1. Aingis
    29.01.2019 15:13
    -1

    Недавно мы опубликовали материал, в котором Эрик Эллиот критиковал TypeScript. Сегодня мы представляем вашему вниманию перевод статьи Кента Доддса. Тут он рассказывает о том, почему в PayPal перешли с Flow на TypeScript.
    Непонятно, как эта публикация связана с тем материалом. Она вдаётся в какие-то детали, но никак не отвечает на поднятые вопросы. В частности, нет ответа на главный вопрос, а именно что оверхед от использования типов попросту на порядок больше всех преимуществ. Это только способ удорожить разработку. Как оценивали затраты от перехода? Подозреваю — никак. Внедрение модной технологии просто ради технологии.


    1. Vestild
      29.01.2019 15:24
      +5

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

      звучит как «оверхед от использования тестов попросту на порядок больше всех преимуществ»


      1. Aingis
        29.01.2019 16:20
        +4

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

        Не говоря уж о том, что там приводится статистика, что типизация ловит 15% ошибок, в то время когда тесты, как известно, ловят порядка 40%. Причём те 15% входят в эти 40%. Добавочной ценности типизация не добавляет, а вот время на разработку очень даже. Это знает каждый кто тратил часы на отладку типизации, которая должна была бы работать. В комментариях по ссылке опять же есть примеры.

        Не зря тут говорится про фанатиков ТайпСкрипта — они так топят за типизацию, но самого убедительного довода: цифр, что типизация действительно помогает и ускоряет разработку — не приводят. К сожалению, здесь их тоже нет.


        1. Free_ze
          29.01.2019 18:01
          +1

          типизация ловит 15% ошибок, в то время когда тесты, как известно, ловят порядка 40%

          Откуда берется цифра 15? Любую ошибку компиляции во время разработки можно трактовать как «ловит ошибку».

          Добавочной ценности типизация не добавляет

          Документация кода же, простота поддержки, простота отладки.


          1. Aingis
            29.01.2019 19:01

            Откуда берется цифра 15?
            Никто не ходит по ссылкам. Буквально перед цитатой:
            …там [по приведённой выше ссылке] приводится статистика…
            Такое ощущение, что разговор с глухими.
            Я: «цифр нет».
            Ответ: пара базвордов без цифр.

            Ну, давайте, расскажите что-ли, как Тайпскрипт упрощает отладку по сравнению с родным для браузера Яваскриптом, в который он компилируется.


            1. Free_ze
              29.01.2019 19:44

              Никто не ходит по ссылкам.

              По ссылкам сомнительнительная методика, а дальше цитируемого вами текста моего комментария — обоснование этому.

              как Тайпскрипт упрощает отладку по сравнению с родным для браузера Яваскриптом, в который он компилируется.

              Поиск ошибки типов не требует запуска браузера и протыкивания смоук-тестов.
              Тип любой переменной розыскивается легко и быстро.


              1. Aingis
                29.01.2019 20:28

                По ссылкам сомнительнительная методика, а дальше цитируемого вами текста моего комментария — обоснование этому.
                Вам разобраться или придраться, когда нечего сказать по существу? Если первое, то вы не по адресу, задавайте вопрос там. Если второе, то правильно я понимаю, что контр-цифр у вас нет и по сути добавить нечего?
                Поиск ошибки типов не требует запуска браузера и протыкивания смоук-тестов.
                Тип любой переменной розыскивается легко и быстро.
                Речь про отладку. Какое протыкивание тестов? Баг уже есть. И большинство багов к типам не относится, точно ли там 85% или нет. Как TS поможет в отладке с найденном в его коде багом?


                1. Free_ze
                  29.01.2019 21:44
                  +1

                  Если первое, то вы не по адресу, задавайте вопрос там.

                  Размахивали ссылкой, а теперь отказываетесь обсуждать ее содержимое.

                  Речь про отладку. Какое протыкивание тестов?

                  Смоук-тесты — один из методов отладки.


                1. extempl
                  30.01.2019 08:55

                  И большинство багов к типам не относится

                  Вполне себе относится. И в основном возникает когда над кодом работает больше одного человека.
                  Большая часть багов которые мне приходилось ловить, это когда ожидается массив, а на входе объект (такое периодически бывает при не очень качественном рефакторинге) и по имени можно только понять что это коллекция.
                  Из последнего это мешанина Map/List от immutable. А называть переменные типа someList или someMap хоть и частично (рефакторинг, вызов метода из метода, в одном передаётся someList, в другом забыли поменять на входе someMap и всё, привет) решает проблему (самодокументация, ага), но по факту это просто костыль для слаботипизированного языка.
                  Большая часть багов связана с ситуациями когда метод ожидает несколько типов под одной переменной — типы тут не упрощают отладку бага, они попросту не допустят здесь баг запрещая такие кейсы ещё на стадии обдумывания метода.
                  И да, это мнение изнутри, я пишу на JS, а не на TS.


                  1. rusbaron
                    30.01.2019 10:37
                    +1

                    И да, это мнение изнутри, я пишу на JS, а не на TS.

                    Ёжики кололись, плакали, но продолжали есть кактус.


                    1. extempl
                      30.01.2019 11:57
                      +1

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


                  1. Aingis
                    30.01.2019 10:37
                    -4

                    Вполне себе относится. И в основном возникает когда над кодом работает больше одного человека…
                    Покажите мне цифры! Я вот с таким вообще не встречался. Такое ощущение, что это говнокод написанный кучей джунов. Причём без код-ревью, тестов и вообще какого-либо контроля по принципу тяп-ляп и в продакшен. Так при таком подходе они вам такое нагородят, что никакой Тайпскрипт не поможет.

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


        1. VolCh
          29.01.2019 21:52
          +4

          Есть неясность в этой статистике. Пускай тесты и тесты с типами ловят 40%. Но ошибки типов ловятся раньше и, по идее, написать тип для аргумента функции гораздо дешевле, чем тесты, которые будут проверять вызов функции с разными типами аргументов. а, главное, гораздо дешевле чем мок этой функции, который будет проверять, что из других функций эта вызывается только с этим типом аргумента. То есть даже если типы полностью дублируют 60% тестов, то они могут оказаться гораздо дешевле и в начальной разработке, и в дальнейшей поддержке.


          1. Aingis
            29.01.2019 22:04
            -3

            На самом деле это «раньше» ничего по факту не меняет. Ошибка либо проявит себя сразу и будет тут же исправлена, либо проявит себя на тестах (код-ревью) и тоже будет исправлена. С точки зрения разработки ничего не меняется. На выходе одно и то же, даже время разработки было бы одно и то же, если не одно но… Написание и отладка типов отнимает приличное количество времени, особенно, в некоторых сложных случаях. Не говоря уж, о неизбежных дополнительных затратах на компиляцию при каждом изменении.

            А ещё типы вселяют ложное чувство безопасности. Как в странном примере ниже, когда у вас в типах одно, а извне получены другие данные. Комментатор ниже привёл это почему-то как хороший пример, хотя по-любому контракт будет где-то описан.


            1. khim
              30.01.2019 01:35
              +2

              Комментатор ниже привёл это почему-то как хороший пример, хотя по-любому контракт будет где-то описан.
              Где и как? В моей практике, скорее, если типов нет — то все будут делать «кто во что горазд». Кто-то будет возвращать true/false, кто-то 0/1, а кто-то вообще «Ok» (строку!) и null. Пока всё это будет проверяться только в if — всё будет работать, а как только кто-то сравнит на равенство/неравенство… рассыпется.


            1. VolCh
              30.01.2019 08:53
              +3

              1) сразу ошибка себя проявляет при компиляции. По крайней мере при обычном flow код->компиляция->тесты->код-ревью->qa->прод. Чем раньше выявляется ошибка, тем дешевле она обходится.

              2) если очень сложные типы, такие что оценочно дешевле написать тесты, то надо так и сделать, поставив any или object, например.

              3) компиляция используется в подавляющем большинстве проектов о которых даже слышал в последнее вермя, разница только, грубо говоря, компилировать из TS в JS или из JS в JS. Плюс TSX/JSX если используется, то точно не обойтись без компиляции


              1. dumistoklus
                30.01.2019 11:36

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


                1. VolCh
                  30.01.2019 11:39

                  Как по мне, то это просто горячая компиляция. Собственно стремлюсь настраивать среды разработки и развёртывания так, чтобы вручную ничего не надо было запускать, кроме нажатия кнопки «commit & push».


              1. Aingis
                30.01.2019 11:48

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

                1) Это справедливо, когда задача переходит по стадиям к другим людям. Завести баг тестировщику, отдать на доработку, протестировать исправление — дороже чем исправить ошибку на этапе разработки. Если баг нашёл клиент, то добавляется ещё поддержка и цепочка ещё больше удлиняется.

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

                2) Вот именно. Только при таком подходе будут найдены только тривиальные ошибки, которые найдутся и так. С этим справится даже линтер. Сложные потенциально находимые типизацией ошибки при этом ею не находятся. Зато тратится время на разбор кривых ошибок, которые или плохо описаны, или вообще не должны возникать по-хорошему. В итоге действительно ставится any во всех сколь-либо запутанных местах.

                Вместо того, чтобы роботам помогать людям, люди почему-то тратят время в угоду роботам. Сам факт распространения any жирно намекает на избыточность типизации.

                3) Речь не о компиляции как таковой, а о дополнительном времени компиляции, которое добавляется каждый раз. Сколько раз вы перекомпилируете в день? Десятки? Сотни? И каждый раз вы так теряете скажем ещё секунд 10 в лучшем случае. Каждый разработчик. Каждый день. Это прямые потери. Я понимаю, разработчикам удобно так волынить, все мы люди ленивые, но это плохой довод для бизнеса.

                image


                1. mayorovp
                  30.01.2019 12:04

                  Typescript даже близко не подошёл к ситуации на этой картинке. Напротив, я частенько замечал что он работает быстрее чем babel.

                  Кстати, запуск тестов тоже требует времени.


                  1. Aingis
                    30.01.2019 12:48

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

                    Мы уже видели пример компилируемого языка: CoffeeScript. И где он теперь? Сейчас от него избавляются в местах, где он был. Это судьба любых надстроек.


                    1. VolCh
                      30.01.2019 13:07
                      +1

                      На сильно типизированном языке объём тестов должен быть меньше. А *SX браузеры вряд ли когда-нибудь будут поддерживать.


                      1. faiwer
                        30.01.2019 14:43

                        На сильно типизированном языке объём тестов должен быть меньше

                        Извините что я лезу в ваш holywar. Но вот именно этот пункт в таких спорах больше всего смущает. Я всякий раз пытаюсь примерить такой аргумент к тем проектам, над которыми я работал, и я, честно говоря, не вижу ни одного теста, которого в случае TS я бы не написал. Мы ведь не тестируем сами типы. Мы тестируем логику, результаты, всякие алгоритмические тонкости. Мы же не пишем в самом деле что-то типа:


                        it('result is boolean', () =>
                        {
                          expect(someMethod(someArg)).is.a('bool');
                        });

                        Или кто-то и правда такое пишет? Какая-то неблагодарная работа.


                        P.S. к TS отношусь с позитивом, сильно не пинайте )


                        1. VolCh
                          30.01.2019 16:11

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


                1. VolCh
                  30.01.2019 13:05

                  1) даже если без перехода. Компиляция быстрее тестов, если сравнивать соответственно полны или инкрементные.

                  2) есть какие-то цифры? :)

                  3) Ещё раз: запуск и прогон тестов обычно требует больше времени чем компиляция, а их ещё написать надо.


        1. khim
          30.01.2019 02:05

          Не говоря уж о том, что там приводится статистика, что типизация ловит 15% ошибок, в то время когда тесты, как известно, ловят порядка 40%.
          Вообще-то та же статья объясняет как типизация могла бы ловить больше 50% ошибок. Сильно больше.

          Однако не ловит. И в комментариях к статье видно почему. Потому что в TypeScript пытаются встраивать куски кода, написанные не на TypeScript — и на типизацию не рассчитанных.

          В результате самое важное достоинство типизированных языков — не «выстреливает». Потому что самое главное что вы получаете от полноценной (то есть неотключаемой) типизации — это отлов ошибок в спеках. Пока вы их не приведёте к виду, где все типы «сходятся» — вы ничего не сможете написать вообще… а соглавно вами же приведённой статье в спеках содержится 80% ошибок.

          К сожалению вещь, которая считается самой полезной особенностью TypeScript оказывается и его же ахилессовой пятой: если вы можете протащить в язык любой бред написав просто as any… то какой смысл в такой типизации?


          1. VolCh
            30.01.2019 09:05
            +1

            Отлавливать относительно простые случаи ошибок типов. any нужно писать только когда непонятно как описать тип (хотя в большинстве сложных случаев можно хотя бы object, function или [] указать), или понятно что это будет очень сложно или язык не позволяет.


          1. justboris
            30.01.2019 11:49

            протащить в язык любой бред написав просто as any… то какой смысл в такой типизации

            польза от такой возможности в том, что можно перевести на Typescript любое JS-легаси, не особо его переписывая. С течением времени, можно будет распутать эти any-типы и написать нормально.


          1. faiwer
            30.01.2019 14:46

            в язык любой бред написав просто as any… то какой смысл в такой типизации?

            Мне кажется смысл прост:


            1. Legacy. Ну тут понятно, переписывания многих узких мест может затянуться на месяца, а то и годы. И небось ещё поломает что-нибудь. А надо быстро получить profit от TS.
            2. Особо хитрые JS-трюки, которые могут сильно упростить разработку. А дабы это было надёжно ? их тщательно протестировать. Не спроста же почти везде есть всякие dynamic cast-ы и их аналоги, а не только в TS.


        1. movl
          30.01.2019 13:23

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

          Автор той статьи тоже ссылается на одно исследование, и похоже все кто согласился с его выводами, в свою очередь тоже не переходили по ссылкам.


          Причём те 15% входят в эти 40%.

          Нет!


          http://earlbarr.com/publications/typestudy.pdf


          Evaluating static type systems against public bugs, which have survived testing and review, is conservative: it understates their effectiveness at detecting bugs during private development, not to mention their other benefits such as facilitating code search/completion and serving as documentation. Despite this uneven playing field, our central finding is that both static type systems find an important percentage of public bugs: both Flow 0.30 and TypeScript 2.0 successfully detect 15%!


      1. CheatEx
        30.01.2019 14:00

        Это вполне реальный сценарий, вот один из примеров: labs.ig.com/code-coverage-100-percent-tragedy



    1. dark_ruby
      29.01.2019 16:20
      +2

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

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


      1. Aingis
        29.01.2019 17:51
        +1

        Правильнее сказать: «если компилируется, то запустится». Как будет работать — неизвестно, типизация ничего не гарантирует в плане правильной работы.

        В том-то и беда, что проставить правильно типы — это чуть ли не 20% времени разработки. Стоит ли оно того, чтобы ускорить рефакторинг на несколько часов? (Плюс-минус, в зависимости от кодовой базы.) Типизация тут не сильно ускорит работу. Да и рефакторинг обычно не каждый год даже проводится.


        1. Free_ze
          29.01.2019 17:57

          Стоит ли оно того, чтобы ускорить рефакторинг на несколько часов?

          Цифры зависят от объема кодовой базы. Рефакторинг вполне может требовать недели работы команды.

          Который обычно не каждый год даже проводится.

          Он может проводиться каждую новую фичу.


          1. khim
            30.01.2019 01:30
            +3

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

            Конечно если вы порождаете Write Only код с подходом «индусов много» — то вам могут типы оказаться не нужны.


            1. Free_ze
              30.01.2019 11:17

              В общем случае я с вами согласен, но, скажем, архитектура с поддержкой плагинов вполне может не требовать сколько-либо объемного рефакторинга.


              1. VolCh
                30.01.2019 11:24

                До поры до времени, пока не окажется, что очередная фича требует рефакторинга способа взаимодействия ядра и плагинов, а так же плагинов друг с другом.


        1. Murmurianez
          29.01.2019 19:51

          Сравним один и тот же пример с разными подходами — JS и TS. Представьте себе объект с данными. Пришёл он, значит с API в виде JSON — допустим это будет объект описывающий поля User. Чтоб узнать какие поля могут прийти в принципе, а не только в результате конкретно этого запроса — лезем в документацию/тыркаем соседа. Потом эти данные обрабатываем в бизнес-логике — что-то добавляем в этот объект, что-то убираем — просто потому что можем. И через пару тройку слоёв объект может вообще себя не напоминать. И тут бага — залезаем в DevTools, смотрим что за данные пришли — и пытаемся осознать — что это вообще такое и откуда оно взялось и почему именно так. Бегаем радостно по коду в попытках осознания почему данные именно такие.

          C TS. Все поля, которые могут прийти в API описаны — всё чётко и конкретно. Есть даже тулзы которые могут генерить описание типа на TS из описания той же сущности на бэкэнде — чёткое соблюдение контракта. На протяжении работы, абы как вы уже User не поменяете — только в соответствии с заданными правилами. Это даёт то что при дебаге вы сразу понимаете что это такое пришло, так как у вас ограниченное вами количество сущностей в системе — User, Post, Entity — и единственная ошибка которая с ними может быть — неверные данные, но не может быть ошибки нарушения структуры этих данных. Также это даёт уверенность при желании передать User в качестве параметра какой-либо функции — написано что может принять User — значит гарантированно корректно его обработает.

          По поводу 20% — на практике — нет. С TypeScript вы можете добавлять типы постепенно по мере особой необходимости и ровно по мере своих знаний — в простейшем случае это просто interface {name: string, age: number}, и вы уже получите преимущество в большинстве случаев, а уж потом, по мере роста, в особых случаях, запариваться на более хитрые проверки


          1. VolCh
            30.01.2019 09:11

            На самом деле, гарантия что с бэкенда пришёл конкретный валидный тип будут только если активно и грамотно использовать проверку типа в рантайме (type guards и ко), а это можно и в JS делать. Простое приведение ответа к типу только ложную уверенность даёт.


            1. mayorovp
              30.01.2019 10:17
              +1

              Не обязательно. Если фротн и бек собираются и деплоятся одновременно — то внезапным ошибкам в структуре ответа от бека будет просто неоткуда взяться.


              1. VolCh
                30.01.2019 10:18

                Бэк может быть нетипизированным или типы могут быть не синхронизированы.


                1. mayorovp
                  30.01.2019 10:28
                  +1

                  Есть даже тулзы которые могут генерить описание типа на TS из описания той же сущности на бэкэнде — чёткое соблюдение контракта.


                  1. VolCh
                    30.01.2019 10:30

                    Есть наверное для некоторых типов бэкенда, но, во-первых, не для всех, во-вторых, их ещё внедрить надо и не факт, что внедрение окупится за разумное время.


          1. CheatEx
            30.01.2019 21:16
            -1

            Случай который Вы описываете это пример места где типы в смысле TS сильно мешают. Условный User с которым хочется работать это не коробочка с отсеками id, nickname, email — это запись у которой есть аттрибуты id, nickname, email и что угодно еще. В TS и его собратьях если вы хотите положить рядом с почтой что то еще — вы обязаны заводить новый класс. Мало того они еще предлагают отвратительный выбор как это делать — текстовое копирование / наследование / композиция.


            Хочешь иконку? Новая коробочка — UserProfile. У вас появился компонент смены пароля? Будь добр напиши нувою коробочку… UserAccount, с полем под хэш. У тебя только UserProfile а Account делается из сырых User, давай-ка дружок писать адаптер или делать заплыв в API за правильной коробочкой. Это нарушает основной посыл типизированных языков — решать тривиальные проблемы силами машины.


            Классы в TS равно как Java и даже популярные в Haskell data — неадекватные способы моделировать внешние данные. Особенно в UI приложениях. Особенно построенных вокруг реляционных БД с их JOINами. Можно посмотреть вот этот доклад с более развернутым списком претензий: https://www.youtube.com/watch?v=YR5WdGrpoug


            Эта проблема отчати осознаётся сообществом типизированных ЯП, но внимания ей уделяется на фоне всяких зависимых типов и гетерогенных списков непропорционально мало. Я знаю только эту работу, в основном рализованную в Elm: https://www.microsoft.com/en-us/research/publication/extensible-records-with-scoped-labels/


            Это не какое-то глобальное оправдание динамической типизации, это одна из причин почему статическая малоприменима в ряде проектов.


            1. khim
              30.01.2019 22:03
              +1

              Случай который Вы описываете это пример места где типы в смысле TS сильно мешают.
              На самом деле, конечно же, всё зависит от того, что вы хотите получить.

              Хочешь иконку? Новая коробочка — UserProfile.
              Отличный пример! Вот о нём и поговорим. Что будет, если вы не заведёте новую коробочку? Где-то иконка появится, где-то нет, где-то вообще исключения полетят и кнопки перестанут работать.

              Дальше вы заводите таски и их чините. Как я уже писал — в результате прогресс — прогрессирует, аджайл — аджалирует, все довольны… даже заказчик, который деньги платит.

              А при статической типизации — да, вам придётся завести новую сущность и научить все компоненты с ней работать. Закроете одну таску, да ещё и нагоняй получите за то, что возились три дня с плёвой задачей

              Это нарушает основной посыл типизированных языков — решать тривиальные проблемы силами машины.
              Основной посыл типизированных языков — это не «решать тривиальные проблемы силами машины». Это «не давать программисту создавать проблемы».

              То есть статическая типизация — она вообще не про скорость. Она про качество. Если у вас есть сущности User и UserProfile, то компонент для редактирования иконки никак без неё не сможет остаться — просто вам компилятор не даст такое написать без явного насилия над собой.

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

              К сожалению в большинстве случаев люди, внедряющие статическую типизацию явно не говоря — и в результате получаются завышенные ожидания с последующим разочарованием: как же так — обещали, что получится конфекта, а получилось только замедление работы… ну так а чего вы хотели? Разруха — она не в клозетах, она в головах… и если в голове порядка нет — то и статическая типизация не поможет, при желании — её всегда можно обойти…


              1. CheatEx
                30.01.2019 22:24

                > Где-то иконка появится, где-то нет, где-то вообще исключения полетят и кнопки перестанут работать.

                У вас в профиле тоже нет иконки и еще куча полей не заполнена. Кнопка «Отправить» явно не должна работать.


            1. mayorovp
              31.01.2019 08:47

              В TS, вообще-то, давным-давно появился оператор & для типов.


              type UserProfile = User & { icon: string }

              Это текстовое копирование, наследование или композиция? :-)


              1. VolCh
                31.01.2019 09:28
                -1

                Ближе к множественному наследованию :)


              1. CheatEx
                31.01.2019 11:32

                Похоже на структурные типы. Попробовал по мануалу воспользоваться — не получилось. gist.github.com/CheatEx/4f771744bd393bd305660846c10df016
                Есть вариант сделать :23-24 рабочими?


                1. mayorovp
                  31.01.2019 11:48
                  +1

                  Не "похоже", а они и есть.


                  Изменить тип существующего объекта m вы не сможете: ведь другие части кода могут от этого поломаться. Вам нужно создать новый объект:


                  let g = { 
                      ...m,
                      name: "Vasya",
                      anotherFieldNeededElsewhere: true,
                  };

                  Если же вам нужно именно что изменить старый объект — можно воспользоваться вот таким трюком:


                  // declare module "foo" {
                  interface Message {
                      name?: string;
                      anotherFieldNeededElsewhere?: boolean;
                  }
                  // }
                  m.name = "Vasya";
                  m.anotherFieldNeededElsewhere = true;

                  Но без серьёзных причин так лучше не делать.


                  1. CheatEx
                    31.01.2019 12:20

                    Так оно даже юзабельно: gist.github.com/CheatEx/75e9192ee7bc8fbd289b52c9caa960d1

                    А почему в мане по языку примеры с дебильными конструкторами и придумыванием класса на каждый чих?


                    1. mayorovp
                      31.01.2019 13:09

                      Потому что это ман для тех, кто уже знаком с современным Javascript. Его задача — показать новые конструкции языка, а не все возможные.


                1. justboris
                  31.01.2019 23:43

                  По вот этому коду тоже есть улучшение:


                  class Message {
                      greeting: string;
                      body: string;
                      data: number;
                  
                      constructor(greeting: string, body: string, data: number) {
                          this.greeting = greeting;
                          this.body = body;
                          this.data = data;
                      }
                      //already sucks - triple repetition
                  }

                  Можно написать так


                  class Message {
                      constructor(
                          private greeting: string,
                          private body: string,
                          private data: number
                      ) { }
                  }

                  Если добавить модификатор видимости к аргументам конструктора, то они автоматически превратятся в свойства.


                  1. CheatEx
                    31.01.2019 23:54

                    let m: Message = {greeting: "hey", body: "im a message", data: 42};

                    Тоже катит. Я так понимаю можно вообще интерфейсом объявлять Message.


                    1. justboris
                      01.02.2019 01:09
                      +1

                      Если у вас только данные без методов, то покатит, да.


  1. Riim
    29.01.2019 16:55

    Прикручивал типы к текущему проекту, начитавшись статей о крутом выводе типов в flow выбрал его. Типизировав примерно четверть отказался от flow в пользу typescript. Причина ровно та же, что и в статье, мне просто не удалось заставить его стабильно работать, постоянно какое-то неадекватное поведение исправляемое только перезапуском. С инструментом приходится буквально нянчиться как с ребёнком, бесконечно уделяя ему время. Это, конечно, интересно когда только осваиваешь инструмент, но в какой-то момент хочется уже получать с него профит, а не продолжать нянькаться. С typescript тоже бывают подобные проблемы, но редко. Например, можно создать в проекте большое кол-во ошибок, скажем удалением node_modules, и после их исправления в редакторе всё станет нормально, а вот сборка продолжит показывать ошибки, помогает только перезапуск. Но это уже скорее проблема пакета awesome-typescript-loader или webpack-а, а не самого typescript.


  1. VolCh
    30.01.2019 10:31

    После перечитывания статьи сложилось впечатление, что автор компилирует TS через бабель, а не штатным компилятором. Или как-то в бабель прикрутил штатный компилятор?


    1. justboris
      30.01.2019 11:55
      +2

      Автор пользуется babel-preset-typescript, который просто очищает код от типов, а Typescript скорее всего проверяется в отдельном процессе, через fork-ts-checker


  1. ganqqwerty
    30.01.2019 12:55

    Я был уверен, что пейпал последние лет десять вообще разработчиков не держит.