27 November 2018

Приверженцы статической и динамической типизаций никогда не поймут друг друга. И TypeScript им не поможет

JavaScript.NETООPTypeScript


Когда мы с другом учились в школе и только мечтали стать разрабами, мы думали как сделаем вместе какую-нибудь штуковину — игру или супер-полезную программу.

Я начал учить плюсы и C#, он — JavaScript. Закончили школу, отучились в универах, отслужили в армии, устроились на работы. Нас потаскало промышленной разработкой там и тут, и когда мы оба от нее подустали, вспомнили с чего начинали.

Собравшись уже матерыми разрабами, мы решили, наконец, сделать свой проект — двумерную видеоигру. Так как друг был чистый фронт, а я фулстек, очевидной платформой для нас стал браузер. Раньше я разрабатывал фронт только на TypeScript, и мы подумали, никаких проблем, TS — всего-лишь надмножество JavaScript. Возьмем его и все пройдет гладко. Как бы не так. Когда мы начали обсуждать работу, столкнулись с невероятной бездной непонимания друг друга.

Вот я увидел задачу сделать игру. Я такой — ага, у нас есть тип «игра», тип «инвентарь», тип «вещь», тип «карта», тип «локация». Я примерно представляю, как они работают вместе, описываю их, собираю проект и все работает. Компилятор меня проверил, я все сделал правильно. Дальше я начинаю писать код, который использует эти типы. Из-за того, что они есть, мне намного проще. IDE мне подсказывает и проверяет на ошибки. Если проект собирается — скорее всего он и работает. Я потратил силы на описания типов, и это принесло пользу. Вот мой подход.

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

Серьезно, мы говорили часами, но при этом каждый говорил о своем, как будто на разных языках. И ведь не спишешь на то, что у нас мозги не гибкие. Буквально год назад я безболезненно переехал в функциональный мир из объектно-ориентированного, и обратно. Больше того, я потратил достаточно много времени на изучение JS, а он — на изучение нескольких статически типизированных языков.

Но технология, которая стала для разрабов первой «боевой», определяет их настолько сильно, что два взрослых опытных человека просто не готовы друг друга слушать. За годы изучения разработки наше видение сформировалось слишком по-разному и подходы к решению задач совершенно не работали вместе.

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

У статической и динамической типизации есть принципиальная непримиримая разница


Мой код отвечает на вопрос «Как с этим работать», а как код разрабов, привыкших к динамической типизации — «Как это работает». Оба подхода имеют право на жизнь, и есть инструменты для обоих, но только один может стоять во главе угла.

Статическая хороша для больших проектов, которые годами делают сотни разрабов, а динамическая — для маленьких команд, для задач, где часто нужен write-only код. С динамической ты экономишь время и силы в начале разработки, а со статической — в конце.

Идея ставить во главу типы серьезно повлияла на мое мышление разработчика.
Выбрав в начале пути C#, я прибил гвоздями статическую типизацию к своему мировоззрению, от чего теперь и страдаю. Увидев любую задачу, я пытаюсь представить ее решение как набор типов и принципов их взаимодействия. Когда я разрабатываю модуль, первым делом определяю, какими типами он оперирует и какими взаимодействует со своим окружением. Я даже не помню, как я подходил к решению задач раньше.

Все обучение программированию на Java — это обучение проектированию и использованию типов. .NET CLR — рантайм C# — построен на типах и для типов. Cтатическая типизация лежит в центре дизайна объектно-ориентированного программирования (привет, классы из JS, я решил, что вы не нужны). Канонические реализации большинства ООП паттернов пестрят кейвордом Interface, который совершенно бессмысленнен в динамически типизированном языке.

Сами паттерны — штука кросс-языковая, но кто-нибудь может мне сказать, зачем нужен паттерн «состояние» в динамически типизированном языке? А билдер? Эти паттерны не про разработку, они про типы. Типы и ООП связаны неразрывно.

Нельзя строить свою бизнес логику, основываясь на типах, но при этом ничего о них не знать в процессе разработки. Именно поэтому есть фронтендеры, которые покрывают свой код невообразимым количеством юнит тестов, которые как раз и проверяют кодовую базу на отсутствие type errors.
Мы все отлично знаем, что защищенность через покрытие — иллюзия. Тесты пишут руками, они по определению менее надежны, чем встроенная в язык система проверки типов.

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

Разрабы, с которыми я имел дело, не считают, что наличие статической типизации меняет подход к разработке, и пишут такой же код, как и в динамических языках, добавляя проверку типов. Я считаю — это в корне неверно. И наиболее сильно это заметно именно в кейсе с современным фронтендом.
Знаю, критиковать фронтов — табуированная тема. Однажды мы с друганом зафигачили ИИ, который троллит всех в твиттере, и на нее сагрился Брендан Айк. Серьезно, создатель джаваскрипта воевал с нашей нейросетью в комментах.



Эти ребята по неведомой причине просто не готовы жить в мире, где их видение содержит серьезные пробелы. Поэтому я критикую только тех из них, кто суется в мой definitely-typed проект, и наводит там свои any порядки.

Мы бы так и жили в своих мирках, но TypeScript нас столкнул


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

JavaScript-разработчики убеждены, что Тайпскрипт — это все тот же JS, но с возможностью иногда добавлять статическую проверку типов, если она им нужна. Это не так. Тайпскрипт в сотню раз мощнее, а их интересуют только три процента его возможностей.

Самый разрушительный аргумент — TypeScript лишь надмножество JS. Но на практике ты не можешь не рассматривать Тайпскрипт, как самостоятельный язык, даже если ты чертов король фронтендеров. Потому что тут нужен другой подход — статический, а не динамический.

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

Многим кажется, что TypeScript — это компромисс систем типов между JS и Java. Никакой он не компромисс, его система типов просто особенная.

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

JavaScript, как мне видится, очень хороший инструмент, чтобы написать код, который решает проблему, не выделяя при этом лишние абстракции. Самый вопиющий кейс в нем — паттерн Ice factory. Этой штуке можно скармливать свои инстансы, и она обмазывает их рантайм-иммутабельностью. Если я обработаю этой фабрикой свой объект, она отдаст мне такой же, но при попытке изменить значение одного из свойств, я получу исключение. Просто, WAT?!?

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

С другой стороны, и это не нужно, потому что есть чистое функциональное программирование. Бери F#, Haskell, OCaml, Кложу, ReasonML — там мутации запрещены из коробки. Но я боюсь, что если дать фронтендеру функциональный язык, он тут же начнет модернизировать его и сделает поведение похожим на JS.

Потому что выбор типизации — путь без возврата. Все компромиссные решения дают только иллюзию компромисса. Ты либо ставишь во главе угла типы, либо нет. Не знаю, как бы все сложилось, начни я учить C# и JavaScript одновременно и параллельно друг другу. Но сейчас я так крепко прибит к своему мышлению, что не вижу и не хочу видеть плюсов динамической типизации. Они есть, но вне поля моего зрения, и все что мне остается — закрывать на них глаза, как на любые чуждые мне проявления мира, с которыми приходится жить. Понимаю, что не прав, но работать мне надо здесь и сейчас, и метаться между полюсами бюджета нет.

Поэтому я не хочу искать компромиссов, а говорю прямо. Если только начинаете изучать разработку, начинайте со статической типизации.
Tags:типыtypescriptоопjavascript
Hubs: JavaScript .NET ООP TypeScript
+81
54.2k 113
Comments 1301
Popular right now
Top of the last 24 hours