Как стать автором
Обновить
206
-2.4
Андрей @impwx

Программист

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

Однако на практике к подобным случаям относишься по-другому. Даешь задание нарисовать айфон, а человек на голубом глазу его в экселе рисует. Просто он не знает ничего, кроме экселя, и настолько сильно его любит, что существование тридэмаксов, фотошопов и вообще любого другого специализированного софта для него — откровение.
Идея хорошая, но есть пара комментариев. Во-первых, а нужен ли вам для вашего кейса весь Zone.js? Из всего функционала вы использовали только одну функцию, которую можно было бы реализовать самому в ~10 строчек:

window.zone = {
    context: {},
    run: function(callback) {
        var backup = zone.context;
        zone.context = {};
        callback();
        zone.context = backup;
    }
}

Во-вторых, в примерах попадаются какие-то мелкие досадные вещи, которые должны вызывать вопросы на code review:

  • Ссылка на injector получается в обход DI
  • Из ZoneService возвращается нетипизированный объект, хотя zone.d.ts существует
  • «Пул запросов» — это не pull, а pool
Далее в программе «Удивительное и невероятное из документации по .NET»: структуры передаются по значению, приведение примитива к object вызывает боксинг, строки являются неизменяемыми…
Из этих соображений был сделан вывод, что отказаться от типов-хорошая идея.

А как теперь быть с перегруженными функциями, которые по-разному обрабатывают аргументы разных типов?

При полном удалении типов только выигрывает в читаемости:

Не выигрывает. Теперь, чтобы определить, что можно передать в функцию, придется попотеть. Из вашего же примера, что именно принимает qsort в качестве значения для аргумента compare? Какой объем кода придется проанализировать человеку \ среде разработки, чтобы это определить?
Посимвольное выравнивание с табами плохо совместимо.
Нужно было еще посчитать количество файлов, где используются и табы, и пробелы вместе.
Сложность понимания синтаксиса регулярок, имхо, преувеличена. На первый взгляд они выглядят пугающе, но по факту возможные элементы можно пересчитать по пальцам — группы, квантификаторы, альтернативы, штук пять спецсимволов и всякие редкие вещи типа опережающих проверок. Всё это можно запомнить за вечер, а с подсветкой синтаксиса становится еще проще. Так что всё упирается в сложность самого выражение — проверку email-адреса по RFC одинаково невозможно постичь в любой форме записи.

Остается вопрос того, стоит ли доверять человеку без подготовки править код, руководствуясь наивным пониманием синтаксиса. Зачастую это чревато возникновением сложноуловимых багов — например, неопытный программист на C или JS может написать if(a = true) и долго удивляться, почему сравнение не работает, как надо.
Синтаксис регулярных выражений — это уже мнемоническая надстройка над внутренним представлением, как ассемблер над машинными кодами. Предлагаемый в статье вариант не создает новый уровень абстракции, а просто заменяет одну строку на другую, более длинную — как если бы вместо mov ax, 0 пришлось писать set first register to value zero.
Проблема трудночитаемости регулярок решается так же, как проблема трудночитаемости обычного кода — выравниванием и комментариями. Во многих языках поддерживается, но, к сожалению — не во всех.
Что это за среда?
Википедия:

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

Это неправда. Не бывает «языка без типов» — все данные, которыми оперирует ваша программа, так или иначе имеют определенный тип. Если вы передаете в функцию 1 вместо [1], то функция скорее всего упадет с TypeError. А чтобы обезопасить себя от таких случаев, в JS принято использовать проверки с помощью typeof или instanceof. По факту, вы всё равно оперируете типами, но используете для этого другой синтаксис, отказываетесь от подсказок автодополнения и статических проверок.

вместо того, чтобы закинуть просто объект, нужно будет сделать инстанс типа

Это также неправда. В Typescript типизация работает структурно, на основе списка полей и их типов. Например, вы можете объявить интерфейс, указав в нем список полей и их типы. Любой объект, в котором эти поля присутствуют, будет удовлетворять интерфейсу:

inteface IOptions {
    path: string;             // поле всегда должно присутствовать
    version: string | number; // может быть строкой или числом
    plugins?: string[];       // опциональное поле
}

function doStuff(options: IOptions) { ... }

doStuff({ path: 'test', version: 1 });          // всё окей
doStuff({ path: 'test', plugins: ['a'] });      // всё тоже окей
doStuff({ });                                   // ошибка: нет поля path
doStuff({ path: 'test', plugins: [1] });        // ошибка: plugins[0] будет Number, а нужен String

компиляция это когда на выходе исполняемый машинный код

Компиляция — это трансляция языка более высокого уровня в язык уровнем чуть ниже. Typescript под это определение отлично подпадает, равно как и Babel, Coffeescript и многие другие.

вредны для понимая логики исполнения и качества кода

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

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

есть только один правильный путь, в то время как это не так.

Уже описал в предыдущем комментарии. Прототипное наследование требует либо жирной прослойки в рантайме, либо безумно сложной реализации VM. Как следствие, языков с прототипным наследованием гораздо меньше, чем с «классическим» — кроме EcmaScript, Lua и LISP я других толком не знаю.

Это только в JS так?

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

Кстати, вы почему-то в этом комментарии раз 5 упоминаете компиляцию. Какое она имеет отношение к JS?

JIT.

Почему вы считаете это грязными трюками?

Расширять прототипы встроенных объектов опасно по причине возможных конфликтов. Методы-расширения решают проблему, но в JS их невозможно реализовать по причине динамической типизации. Что же касается подмены прототипа, то тут сам Брендан Эйк признается, что идея была отстойная.

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

Это касается только символов в глобальном реестре.

Виноват. Действительно, это верно только для символов, полученных через Symbol.for. Но тогда становится еще страннее: если Symbol придуман как раз для того, чтобы к данным мог получить доступ только тот, для кого они предназначены, зачем сразу же закладывать дыру в абстракции, создавая глобальный реестр символов и Object.getOwnPropertySymbols?
Прототипное наследование намного мощнее вашего так называемого «классического».

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

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

Хорошая аналогия — рекурсия в ФП. Отличная математическая концепция, хорошо позволяющая описать любое циклическое вычисление. По факту же, в компиляторе без tail call optimization пользоваться ей опасно.

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

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

Что позволяет расширять объекты мета-информацией, доступной только тем, кто в ней заинтересован

Для собственных объектов в «классическом» наследовании то же самое можно сделать с помощью интерфейсов, и даже больше — явная реализация интерфейса позволяет объекту иметь даже несколько методов с одинаковым названием, и они не будут пересекаться. Символы же всё равно упираются в строки, и точно так же можно получить коллизии, потому что Symbol.for("a") === Symbol.for("a").

Для чужих же объектов такое вообще лучше не делать, потому что прототипы глобальны и изменение может иметь непредвиденные последствия в других модулях. Именно по этой причине prototype.js так быстро попал в ранг антипаттернов.
Ну вы в курсе, да, что языку уже двадцать лет, и он значительно изменился за это время?

Область применения языка изменилась гораздо сильнее, чем изменились возможности языка.

Вот например, одна из фундаментальных особенностей языка — нестрогая динамическая типизация. Для написания DHTML, особенно в середине девяностых, это была киллер-фича: пишем пару строчек и всё волшебным образом работает. Вполне возможно, что именно низкий порог вхождения помог JS приобрести свою популярность на ранних этапах.

Пока двадцать лет веб был относительно простым и дышал романтикой юношеского максимализма, противоположная сторона IT — суровый и серьезный энтерпрайз — также развивался и накапливал опыт. Умные дяди выяснили, что статическая типизация позволяет повысить качество приложений и сократить время разработки: ошибки ловятся компилятором на ранних этапах разработки, автокомплит работает лучше, появляется простор для оптимизаций. Более того, объем кода от этого не только не раздувается, а даже наоборот — современные компиляторы типа C# \ Scala \ Rust могут в большинстве случаев вывести тип самостоятельно, лишь изредка опираясь на подсказки со стороны программиста.

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

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

Нужны приватные поля? Вместо этого изобрели новый базовый тип Symbol, который сложно понять и всё равно можно обойти. Вместо потоков — веб-воркеры, без примитивов синхронизации и с урезанными возможностями взаимодействия. Вместо классического наследования — прототипное. Список можно продолжать довольно долго.

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

Сейчас на нём можно писать приложения любой степени сложности.

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

Особенно иронично, что некоторые типично динамические трюки в JS сделать сложнее, чем в языках, обычно считающихся статически типизированными. Например, как перехватить обращение к любому полю и получить название этого поля в виде строки? В ES6 для этого будет новый класс Proxy, который пока не везде поддерживается и с горем пополам полифиллится. В это же время аналогичный функционал в C# был доступен еще в 2010 году.

20 лет производителями браузеров были потрачены не зря, интерпретаторы заоптимизированы по самое «не могу»

Двадцать лет были потрачены как раз-таки зря: пришлось придумывать решения для проблем, которые в статически типизированных языках вообще не существуют. Результат, безусловно, похвальный — но у пользователя по-прежнему масса возможностей написать код так, чтобы сломать оптимизации.

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Зарегистрирован
Активность

Специализация

Fullstack Developer
Lead
От 10 000 €
C#
.NET
SQL
TypeScript
Vue.js
Angular