Как стать автором
Обновить

Комментарии 50

Да почитайте уже про TypeScript, ну чего вы мучаетесь в 2021 году…

Про TypeScript читать нужно было в 2012-м году. А сейчас те фишки, что он даёт в большинстве своём есть в самом JS. А основной минус - необходимость транспиляции, не перекрывает оставшихся плюсов. И это... я не мучаюсь :)

Вы все-таки почитайте, попробуйте. А потом уже решите, мучаетесь или нет. Будет хоть информированное решение.

ответил в тексте публикации

О. Ну тогда и про source maps можно… тоже… освежить…

Кстати, TypeScript является строгим надмножеством JavaScript. Т.е. любой валидный код на JS автоматически является валидным же кодом на TS. В этом большая разница с примером про Java.

Т.е. любой валидный код на JS автоматически является валидным же кодом на TS. 

Ну и тогда зачем мне использовать производное, если я могу использовать оригинал? Где "плюшки"?

Плюшки — в возможностях типизации.


Одно только написание


class ConfigEmailAuth {
    pass? : string;
    user? : string;
}

уже выглядит аккуратнее чем JsDoc-альтернатива.

:D И из-за этого вы мне предлагаете использовать транспиляцию и source maps при дебаге?! Я не эстет, меня вот этим не смутить:

class ConfigEmailAuth {
    /** @type {string} */
    pass;
    /** @type {string} */
    user;
}

Есть что-то более весомое? Как программист программисту, а не как художник художнику. Какие возможности типизации предлагает TypeScript, которые недоступны в современном ES2015+ & JSDoc? Я не перейду из-за этого на TS, но с удовольствием покопаюсь в JS/JSDoc в поисках альтернативы. Давайте, выкладывайте.

Дженерики, keyof, арифметика типов, вывод типов ("обратные дженерики")

Насколько я понял, все перечисленные выше "плюшки" TS'а связаны с преодолением ограничений статической типизации, которую TS и вводит. Логично, что в JS их нет.

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

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

А зачем вы тогда JsDoc-и всюду расставляете? Уж не для подсказок ли со стороны IDE? А ведь эти подсказки ломаются стоит лишь чуть-чуть усложнить код.


Вот взять хотя бы этот код:


const ConfigEmail = Rec({
    from: Str
})

Какой такой JsDoc надо повесить на функцию Rec, чтобы IDE поняла, что у new ConfigEmail() есть поле from?

Не знаю, этот вопрос - к коллеге @nin-jin

С точки зрения TS, именно JS будет производным, как подмножество. Что по определению делает JS более бедным.

... и более соответствующим "бритве Оккама" - не плоди сущностей сверх нужного. Что такого может TS, чего не может ES2015+?

Неподходящий аргумент, но пусть.

Что умеет ваш JS, что не умеет C?

Самостоятельно работать с памятью.

"Фишки" typescript не в новых языковых конструкциях, которые переходят в стандарт, а в статической типизации.

И как жить в 2021 без транспиляции, сложно представить, независимо от наличия typescriot. Руками на es3 писать?

Есть такой хороший язык Closure. Транспилируется и в Java, и в JavaScript (с небольшими отличиями). Пишите на нём.

Отличный язык, но нет, спасибо. ReasonML или Elm также супер удобны, да ещё и для веба. Но массовой славы не снискали, ибо концепции продвинутой системы типов ещё не прижились.

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

воспользоваться его советом перейти на TypeScript я не смогу - мешает старая психологическая травма, полученная лет 10-15 назад

Ядерную энергетику тоже не любите после 1986 года?

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

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

Ядерную энергетику тоже не любите после 1986 года?

Нет, что вы. С удовольствием плавал во внешнем контуре охлаждения Курской АЭС. И до, и после 1986 года. А почему вас это интересует?

А чем именно мешает статическая типизация вариативным типам? В моём понимании, вариативные типы — аргумент против JsDoc.

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

JSDoc позволяет разработчику указать, какой тип (интерфейс) он ожидает увидеть в данном месте кода, вне зависимости от того, что он получает на самом деле:

Валидатор кода говорит, что "Initializer type Type1 is not assignable to variable type Type2", но IDE руководствуется подсказками JSDoc при автодополнении. Т.е., я, как программист, фиксирую сам для себя свои ожидания в данной точке кода и кодирую исходя из них. Мне не нужно продумывать совместимость типов от места зарождения объекта и до всех мест, где этот объект может использоваться. Это примитивный пример, но бывают гораздо более кучерявые случаи, когда место использования объекта находится в паре десятков "пересадок" от места его создания, а каждая "пересадка" - это передача объекта в качестве входного/выходного параметра с необходимость совмещения типов. Если же типы не совмещать и везде использовать "any", то и вопросов нет. Но нет и типизации.

Я долгое время работал с Magento, так там базовые модели (client, sale order, cart, ...) могли очень сильно модифицироваться сторонними плагинами (разработчики которых иногда не подозревали о существовании друг друга), причём некоторые конфликты мне, как интегратору, приходилось разрешать вручную, в явном виде указывая порядок наследования.

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

То, о чём вы говорите, называется type cast. Он есть в любом статически типизированном языке. Да ещё и в более лаконичном виде:

let obj: Type2 = create()

Мне - моя ограниченность. Вам - наверное ничего. Попробуйте.

Тем, что вариативность нельзя типизировать полностью

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


JSDoc позволяет разработчику указать, какой тип (интерфейс) он ожидает увидеть в данном месте кода, вне зависимости от того, что он получает на самом деле

TS тоже так умеет: const foo = (create() as unknown) as Type2;


Но такой код попахивает, так что это явный кандидат на рефакторинг.


а каждая "пересадка" — это передача объекта в качестве входного/выходного параметра с необходимость совмещения типов

Так надо не заметать это под ковёр, а отрефакторить. Ведь это потенциально место для выстрела в ногу. Зачем осознанно плодить говнокод?


Я долгое время работал с Magento, так там базовые модели (client, sale order, cart, ...) могли очень сильно модифицироваться сторонними плагинами (разработчики которых иногда не подозревали о существовании друг друга), причём некоторые конфликты мне, как интегратору, приходилось разрешать вручную, в явном виде указывая порядок наследования.

Ну что сказать, видимо в magento плохая архитектура. Зачем её повторять? Или у вас уже сформировалось мышление и менять вы его просто не хотите?

На вопрос вы так и не ответили: что в JS за последние годы появилось такого для безопасности кода, что было в TS?


А почему вас это интересует?

Потому что единичный случай косяка в компиляторе вызвал у вас психологическую травму. А вы доверяете после того случая jit-компилятору в v8? А babel тоже не пользуетесь?
Вообще, с тех пор много времени прошло. Например, нынешний TS может генерировать всё меньше кода, если ставить цель компиляции esnext. Так что в 2012, когда по вашему мнению надо было ковырять TS, в нём было потенциально больше ошибок именно в части компилирования кода.

Я не знаю TS, поэтому не смогу ответить на ваш вопрос. Я просто вижу его влияние на JS (на JS я первый раз начал писать фрагменты лет 20 назад).

Это не единичный косяк, а типовая проблема - дебажить совсем другой код, чем ты писал. Babel я тоже не использую. Если нынешний TS генерирует всё меньше кода, значит скоро можно будет от него отказаться совсем, разве не так? Нет, мне не интересны старые браузеры.

Да, в magento архитектура не айс. Да, у меня сформировалось мышление. Да, я столько раз его менял, что уже лишний раз и не хочется - basic, fortran, pascal, c, prolog, clarion, lotusscript, visualbasic, c++, java, php, python, javascript. Я выкладываю свой код в открытый доступ, если вы видите там говнокод - ткните пальцем, исправлю. Или удалю, если он deprecated. Вот код по теме статьи (DTO) - TeqFw_Core_Back_Api_Dto_Plugin_Desc. У него, разумеется, есть "фатальный недостаток" - это не TypeScript. А в остальном - это результат того самого мышления, которое сформировалось. Если есть, что сказать - милости прошу.

Кстати, как оказалось, я плавал не во внешнем контуре охлаждения АЭС, а в пруду-охладителе. Зато сразу на выходе со станции. Внешний контур - внутри АЭС.

Если нынешний TS генерирует всё меньше кода, значит скоро можно будет от него отказаться совсем, разве не так

Нет, его люди используют не для генерации кода, а для типизации поверх JS.

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

Для этого есть sourcemaps, в итоге вы будете дебажить тот код, который написан.

Посмотрите вывод на консоль для этой страницы (F12 или Ctrl + Shift + i). Там, кстати, и ошибка есть. А теперь попробуйте её отдебажить. Вот, что вы имеете "в итоге" по факту.

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


Если они понадобятся (не вам, а разработчикам) — положить их на место нетрудно.

Это если они у них есть. Я как-то интегрировал в свои magento-проекты не просто обфусцированные модули, а зашифрованные. Вот, где засада была. Люди специально заморочились, чтобы зашифровать свой код. А "не положить" map-файлы в дистрибутив - проще простого.

Да, map-файлы удалили, а вызовы к ним всё равно остались. Их бы тоже удалить, по-хорошему. С чистым JS такой проблемы нет в принципе.

Так ведь и тут проблемы нет.

Я может не очень понял посыл автора и тему статьи.

Но есть же уже давно https://github.com/OAI/OpenAPI-Specification которая описывает взаимодействия и есть куча генераторов кода под разные языки для dto разной степени паршивости которые выдают код описанный в статье.

какой генератор кода под ECMAScript 2015+ из этой кучи вы мне посоветуете?

Вот здесь можно выбрать что подходит вам больше

https://openapi-generator.tech/

Мы пользуемся typescript-fetch, нас устраивает

Делать все поля опциональными - сомнительное решение. Ещё более сомнительно - не проверять типы полей перед их сохранением. Я бы рекомендовал создавать DTO через валидатор-нормализатор типа $mol_data:

import {
  	$mol_data_record as Rec,
  	$mol_data_string as Str,
  	$mol_data_integer as Int,
  	$mol_data_boolean as Bool,
  	$mol_data_optional as Resque,
} from 'mol_data_all'

const ConfigEmailAuth = Rec({
    pass: Str,
    user: Str,
})

const ConfigEmail = Rec({
    auth: ConfigEmailAuth,
    from: Str,
    host: Str,
    port: Resque( Int, ()=> 465 ),
    secure: Resque( Bool, ()=> true ),
})

const email = ConfigEmail({
		auth: {
				pass: 'a',
				user: 'b',
		},
		from: 'c',
		host: 'd',
})

DTO, только режима runtime, а не уровня кода

Ну, вообще-то библиотека написана на TS и предоставляет тайпинги, которые подхватывает IDE даже если её использовать из JS. Так что как либо "метить места использования соотв. DTO" нет никакой необходимости.

Это не валидатор, поэтому - норм. Валидатор уже потом может брать готовую структуру, пробегать по ней и принимать решения, что делать, если чего-то для чего-то не хватает.

Ну вот вы преобразовали своим "фильтром" объект в строку. Что по этой строке теперь валидатор сможет понять? Ну или пришёл вместо объекта "Хозяин" объект "Питомец". Как по "отфильтрованному" объекту понять, что это вообще не тот объект?

Библиотека - да, а объекты, которые создаются с её помощью, тайпингов не предоставляет:

const ConfigEmailAuth = Rec({
    pass: Str,
    user: Str,
});
const auth = ConfigEmailAuth({
		pass: 'a',
		user: 'b',
});

Каким образом IDE сможет обнаружить, где используется auth.pass, если, допустим, мы передадим весь объект auth в виде параметра в какую-нибудь функцию или ещё дальше? Для этого мы должны хотя бы определить интерфейс, как предлагает коллега @astec, и в описании функции указать, что параметр его имплементирует. А это и есть первая часть DTO - структура.

Как по "отфильтрованному" объекту понять, что это вообще не тот объект?

Никак. Это не задача фильтра и даже не задача валидатора. Это входные данные для валидатора - объект валидации и правила валидации. Фильтр вытягивает из объекта ожидаемую структуру данных, к ней добавляются правила валидации, соответствующие текущему контексту, и всё это передаётся в валидатор, который применяет правила на объект и выносит свой вердикт. Одна и та же структура данных может по-разному валидироваться в зависимости от контекста (правил). Можно, конечно, структуру и правила валидации совместить в одном объекте, но это уже какая-никакая, а логика, а не просто "объект для передачи данных".

ИМХО использовать интерфейсы/объекты вместо классов для DTO гораздо правильнее и удобнее. И работает быстрее.

Для кода в IDE - согласен. Но под отладчиком у экземпляра класса есть один плюс по отношению к простому объекту - видно имя конструктора, что позволяет от объекта перейти к описанию его структуры. Зачастую объекты используются не в том месте, где инициализируются, и тут имя конструктора помогает привязаться к коду.

SaleOrder.ID = 'id';

От чего вы таким образом защищаетесь? Чем запись {[SaleOrder.ID]: … } лучше {id: …}?


Полагаю, что вынос инициализирующего кода из конструктора в статический метод минимизирует потребление памяти в runtime — статические методы share'ятся между всеми экземплярами класса, а не "навешиваются" на каждый экземпляр.

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


На самом деле статический инициализатор оставляется кодогенератором в качестве возможной точки расширения.

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

const id = cfg[ConfigEmailAuth.ID];
const id = cfg['id'];

Подобные вещи очень хорошо работают в качестве алиасов в SQL выражениях при выборке данных.

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

class MyClass {
    constructor() {
        this.printName = function () {
            console.log('name: MyClass');
        }
    }

}

const one = new MyClass();
const two = new MyClass();

one.printName = function () { console.log('Op!');}

two.printName();
one.printName();

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

Что конструктор один на класс - с этим я согласен, а что "конструктор в JS и есть класс" - этого я не понял.

Сравните

Мне из ваших двух вариантов больше всего нравится cfg.id.


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

Уточняю: между экземплярами класса шарятся любые методы, которые объявлены как методы, независимо от статичности.


Что конструктор один на класс — с этим я согласен, а что "конструктор в JS и есть класс" — этого я не понял.

Очень просто же:


class Foo {
    constructor() { }
}

console.log(new Foo().constructor === Foo); // true

Очень просто же

Вот теперь понял. Спасибо.

Касательно большинства тредов выше про TS - TS полезен и нужен там, где код будет переиспользоваться и там, где нельзя допустить ошибку. DTO - очень хорошее место для TS (особенно когда серверную и клиентскую часть пишут разные люди и в прод они попадают асинхронно). А так же любые внешние библиотеки - не менее хорошее место для TS. При этом свой собственный end-проект можно писать на чём угодно, не заботясь об используемых библиотеках. JS с JSDoc + строгий JSLint (хотя не уверен, что он может в типизацию по JSDoc ам) - это очень хорошо. Ровно до тех пор, когда над кодом начинают работать 10 человек, с не каждым из которых можно легко договориться, и кроме как жёсткими правилами и ограничениями дело дальше не пойдёт. Но для себя - да, тут бесспорно, TS плодит больше кода, который не хочется (и для проекта на одного не нужно) писать.

Но для себя — да, тут бесспорно

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


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


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

Если вы не заметили, уточню - мой комментарий был скорее в пользу TS, чем против ;)
Скажу только что использование TS в библиотеках скорее необходимость, когда использование в своём пет проекте (который разрабатывается здесь и сейчас и меняться скорее всего не будет) - дело вкуса. Пет можно хоть на брейнфаке писать, а если предполагается что это фреймворк/библиотека, код, который уйдёт в прод, код над которым будут работать другие люди - тут лучше всё таки строже, но однозначнее.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации