Начнем с того, что я не пишу на JavaScript профессионально. Я много пишу на TypeScript, считаю что разбираюсь во frontend-разработке (например сейчас я занимаюсь интеграцией собственной реализации VDOM в свой датагрид-движок Lattice, о котором скоро будет отдельная статья. Ну как скоро… будет, в общем :), однако мой основной профиль — C# и делать всё, чтобы защитить бедных C# разработчиков от излишнего ныряния в JavaScript.
Так вот. Прошу обратить внимание, что я решительно не имею ничего против JavaScript как такового и его технической экосистемы. Это нормальный язык, с приемлемым тулсетом. Во всяком случае мне в нем комфортнее чем в каком-нибудь… LabView. Я не считаю его уж шибко плохо сделанным — в конце концов в нем нет несовместимых с жизнью недостатков, но однако и ничего революционного или магического я в нем так же не нахожу. Мой основной посыл — к сообществу. К людям, а не к технологии, ибо как с моей точки зрения, большинство проблем и холиваров вокруг JavaScript происходит не от самого языка, а от его, как мне иногда кажется, обезумевших последователей. Имея за плечами продолжительный опыт общения с разработчиками на JavaScript, я предположил что этот язык работает как мыслевирус, который со временем превращает пишущего на нем человека в агрессивного фанатика, чего мне не удалось отметить с другими технологиями. Собственно, эта статья является сатирой на штампы JavaScript-разработчиков, которых JavaScript поразил как мыслевирус. Отсюда и название.
И да. Я не считаю что все поголовно JavaScript-разработчики такие. Среди них много нормальных и адекватных людей, которым удалось (или не удалось :) остановиться на каком-либо квалификационном уровне и сохранить трезвость ума и здравость памяти. Честь им и хвала. Однако же, фанатики от JavaScript-а по своей природе — гораздо более «громкие» (так всегда: кто-то кричит, а кто-то работой занимается) и настолько фееричны, что заслуживают отдельного рассмотрения. Так же в статье будет упоминаться jQuery. Так вот — я ни в коем случае не агитирую писать всё на нём и не использовать более продвинутые решения. Используйте то, что нравится, но пожалуйста, оставайтесь при этом людьми. Итак…
Дорогой JavaScript-разработчик!
Если ты это читаешь, то ты должен знать: твой язык имеет характер мыслевируса. Он серьезно разъедает мозг по мере того, как ты продолжаешь заниматься этой гадостью. Сам ты этого, вероятно, не замечаешь, но мы, люди «извне», слышим тебя, видим и читаем. Мы разработали специальную карту с советами что делать, чтобы ты мог самостоятельно диагностировать тяжесть своего состояния, вылечиться и по-скорее вернуться к нормальной жизни.
0 уровень: «jQuery — хороший»
Симптомы:
— JavaScript тебя интересует только по работе;
— Ты можешь написать кусок кода в теге <script>;
— У тебя нет особого желания говорить о JavaScript, кроме как иногда жаловаться на него коллегам в курилке. Ты вообще его и за язык-то не считаешь;
— jQuery — удобный инструмент и искренне тебе помогает в делах;
— JavaScript написан в резюме одной строчкой наряду с Java, C/C++ и SQL.
Диагноз:
Здоров. У тебя все хорошо, мозг не задет, проблем не наблюдается. Скачай сериал и открой пиво — ты хорошо поработал на этой неделе.
1 уровень: «Я просто сижу там, где JavaScript-ят»
Симптомы:
— Тебя забавят замыкания в JavaScript, ты испытываешь легкую гордость от того, что понимаешь как они работают;
— Ты раскладываешь JavaScript-код по файлам, поскачивал кучу плагинов к jQuery, думаешь об изучении AngularJS и чувствуешь успокоение и расслабление;
— Читаешь мануалы по плагинам, в соседней вкладке браузера у тебя обычно порнушка;
— jQuery — рулит и педалит, AngularJS отдается в голове звуками таинственного Туманного Альбиона;
— Ты знаешь о том, что '3' — 2 == 1, но '3' + 2 == '32' и это вызывает у тебя негодование.
Диагноз:
Все по-прежнему в порядке, но хитрый вирус JavaScript летает повсюду в воздухе. Береги себя и носи защитную маску.
2 уровень: «Я могу бросить когда захочу»
Симптомы:
— Ты разбираешься в прототипном наследовании. Тебе немного мерзко, но в целом терпимо;
— AngularJS внедрен в домашний проект. Ты говришь начальнику что неплохо бы и в рабочем использовать;
— Читаешь мануалы по AngularJS, попутно изучаешь TypeScript/CoffeeScript. Изредка с интересом почитываешь рекламные статьи, рассказывающие о магии JavaScript и о том, как некая компания X с ним счастливо живет;
— jQuery тебя больше не заводит. Ты начинаешь использовать термины «MVW», «DSL», «DOM-дерево». JavaScript-объект называешь только «хэш»;
— Видео навроде JavaScript WAT больше не вызывают бурных эмоций. Ты понимаешь как это работает и почему.
Диагноз:
Началось! Ты этого не замечаешь, но твой мозг уже атакован! Ради всего святого, научись жить с этим. Научись контролировать это. Не давай ему тебя поработить — и тогда все будет нормально.
Здесь ты начал использовать npm
3 уровень: «Не вижу в этом ничего плохого»
Симптомы:
— AngularJS в продакшене твоего рабочего проекта. Не на всех страницах, но хоть так;
— Ты осознаешь что фронтенд — целая отдельная часть системы и невозможно же её поддерживать силами backend-разработчиков. Говоришь об этом с начальником, получаешь недоумевающий взгляд;
— Пишешь на TypeScript и нервно думаешь как бы автоматизировать сборку фронтеда. Читаешь статьи про grunt. Плотно знакомишься с опытом использования JavaScript в модных стартапах;
— Коллеги, в основном совмещающие backend с допиливанием скриптов на страницы проекта, перестают понимать что ты говоришь и назначают тебя ответственным за все, что касается клиентской части. Из-за этого у тебя появляется легкое негодование и небольшая обида на них;
— В резюме ты стираешь пункт JavaScript, и добавляешь новую секцию: Frontend.
Диагноз:
Твоя болезнь прогрессирует. Ты этого, скорее всего, не замечаешь, но просто поверь мне — вирус уже обосновался в твоей голове. Отложи JavaScript, попиши на C, освой какой-нибудь игровой движок. Тебе срочно необходимо отогнать от себя мысли о JavaScript, чтобы сдержать развитие болезни. На этой стадии еще можно взять себя в руки и быть просто хорошим fullstack-инженером.
4 уровень: «Вы просто не понимаете сути»
Симптомы:
— Сильное желание говорить о JavaScript. В курилке ты с горящими глазами рассказываешь недоумевающим коллегам о том, что JavaScript можно запускать вне браузера. Обижаешься, когда они не считают это серьезным достижением;
— Фронтенд рабочего проекта собирается отдельно, с использованием grunt и require.js. От этого тебе делается хорошо и приятно, появляется ощущение тепла и домашнего уюта. Коллеги не понимают что там происходит и почему именно так. На все вопросы отмахиваешься и отвечаешь нечто вроде «да там долго объяснять». Очень злишься, когда коллеги пишут что-либо в теге script;
— TypeScript становится тебе тесноват;
— Читаешь много концептуальных статей про будущее JavaScript. Узнаешь о существовании React, пробуешь его на домашнем проекте. Получаешь первый писательский опыт в комментариях к статье о React, находишь там поддержку и понимание;
— Подумываешь съездить на конференцию по JavaScript — встретиться с единомышленниками.
Диагноз:
Разжижение мозга в самом разгаре. Вирус самодовольно откусывает кусочки серого вещества и со смаком и хрустом пережевывает. Просто остановись, съезди в отпуск — желательно на Гоа, года на 2, научись медитации. Если ты не научишься контролировать свой JavaScript, то он убьет тебя и разрушит твою жизнь.
5 уровень: «JavaScript — няшка. А вы все — быдло»
Симптомы:
— Пробуешь React на домашнем проекте. Полоскаешь мозги начальнику, что весь ваш сайт надо срочно переделать на SPA и изоморфный рендеринг. Закономерно получаешь предупреждение о скором увольнении, но гордо хмыкнув увольняешься сам и уходишь в хипстерский стартап;
— TypeScript тебя сильно ограничивает в возможностях. Отказываешься от него не новом месте работы в пользу pure js;
— Любого, кто напишет тебе про '3' + 2 незамедлительно называешь безграмотным быдлом, которое просто не может осилить язык. Для тебя это уже очевидно. Начинаешь считать, что любой человек, у которого JavaScript не является основным языком работы — просто органически не способен понять прототипное наследование. Это — для избранных;
— Едешь на конференцию по JavaScript. Там тебе впервые показывают как за 20 секунд сделать сервер на Node.JS. Восторгаешься. Вирус, подпитавшись материалами с конференции откусывает ту часть мозга, которая еще помнила про многопоточность. Она помирает и не успевает задать свои вопросы;
— Считаешь что VDOM — неочевидное и гениальное решение, которое может прийти только в самые светлые головы этого мира.
Диагноз:
Болезнь подходит к необратимой стадии. Посмотри — вирус уже рушит твою жизнь. Немедленно достань учебник по C++ и выполни десяток-другой задач. С тобой становится невозможно общаться! Ты агрессивен и съехал с катушек. Я знаю, что ты в это не веришь, но окружающие всё видят.
Ты начал использовать Node.JS
6 уровень: «Это полноценный технический стек!»
Симптомы:
— Считаешь что весь web скоро перейдет на изоморфный рендеринг. Классические подходы устарели, а лично ты — стоишь на пороге технологической революции. Меняешь в резюме заголовок на «JavaScript-профессионал»;
— Раз за разом запускаешь сервер на Node.JS. Проверяешь его на самых разных задачах — блог, аналог твиттера, todo-лист, хостинг картинок. Поражаешься быстродействию. Считаешь что это и есть The Web Development;
— Любой эксперимент начинаешь с установки webpack-а, react-а и еще десятка пакетов. В процессе скачивания зачарованно смотришь на монитор;
— Забываешь зачем нужны SQL-базы. Искренне не понимаешь почему на mongodb нельзя делать вообще все. Авторов видео mongodb is web scale считаешь неграмотными толстыми троллями;
— Асинхронные операции в Node.JS для тебя — cutting edge. Ты полностью уверен, что ни в одном другом языке кроме как JavaScript это невозможно и никаким другим фреймворком кроме Node.JS это не поддерживается.
Диагноз:
Сильное повреждение головы. Картина мира капитально нарушена. Ну вот, это случилось. Мы тебя предупреждали. Сейчас с тобой уже невозможно дискутировать — просто поверь на слово, что надо остановиться. Перестань этим заниматься и поступи снова в университет. Просто поверь — так будет лучше.
7 уровень: «Мир должен знать!»
Симптомы:
— Пишешь свою первую статью об успешном внедрении Node.JS для решения, как тебе кажется, очень важных задач в вашей компании. В комментариях грубишь всем, кто посмеет усомниться в твоем успехе;
— Наотрез отказываешься слушать про другие языки и фреймворки. Предпочитаешь узнавать о них из разговоров с друзьями. Уверен, что C++ устарел и скоро его перестанут использовать. Java/C#/VB — это enterprise-языки и в них нет ничего кроме бессмысленного нагромождения паттернов, а работают они медленно потому что в них нет асинхронных операций. Вместо любых аргументов, что это не так — слышишь белый шум;
— JavaScript уже давно для тебя идеален. В нем нет недостатков, а если кто-то считает, что они есть — то только потому что этот кто-то не может открыть документацию и почитать, о чем ты ему незамедлительно сообщаешь;
— Социальная активность возвращается. Правда, ты не общаешься ни с кем, кто не входит в JavaScript-тусовку. Любого, кто задает тебе вопрос по JavaScript снисходительным тоном отправляешь «читать книжки» и упрекаешь в непонимании очевидного;
— Ты стал писать меньше кода. В основном ты проводишь время в чтении статей, расхваливающих JavaScript и пишешь одобрительные комментарии под ними. Называешь это «обмен экспертными мнениями» и «узкоспециальной профессиональной дискуссией».
Диагноз:
Критическое поражение мозга вирусом. Задеты важные мыслительные центры. Собери жесткие диски со всех компьютеров и выброси. Устройся работать грузчиком. Просто таскай коробки, пей, и ни с кем не разговаривай. Выброси свой Google Nexus и купи старенькую черно-белую нокию. Через несколько лет сможешь вернуться в программирование, начав изучение с С++. Не задавай вопросов — так надо.
8 уровень: «Вся индустрия только и была создана для JavaScript»
Симптомы:
— Для тебя в JavaScript-е лучшие пакетные менеджеры. Ты абсолютно уверен, что системы должны собираться из как можно более мелких пакетов по 10 строчек, а любая модульная декомпозиция строится по принципу «меньше модуль — значит лучше»;
— Считаешь что статическая сильная типизация — это тупиковая ветвь развития. Все более-менее типизированные языки скоро вымрут из-за своей негибкости и перегруженности. Ничего не должно ограничивать настоящего программиста — в том числе и система типов. Наследование, интерфейсы, абстрактные классы — пережиток прошлого, которые только все усложняют;
— … а JavaScript — вообще единственный язык, который избавляет человека от таких условностей. Он стоит на первых местах во всех рейтингах индустрии, развивается семимильными шагами и ничего кроме него не жизнеспособно. Ну может разве что TypeScript. И тот нужен только тем болезным, которые не осилили pure js;
— Ты забыл слово «многопоточность». Считаешь что это что-то уровня ассемблера и тебе это знать не нужно, ведь Node.JS полностью тебя от этого абстрагирует. Ты уверен что так и должно быть, а люди, которые в этом разбираются — или носители ненужной и бесполезной информации, или разрабатывают операционные системы;
— Ты чувствуешь, что вся IT-индустрия развивалась как-то неправильно. В ней много неуместных усложнений, которые никому не нужны. Ты искренне уверен, что все должно быть гораздо проще и что все люди, которые разрабатывали теоретическую базу для программирования — очень глупые, раз этого не понимали. И за что им только докторских степеней пораздавали? Ты бы справился гораздо лучше.
Диагноз:
Вирус целиком съел твой мозг и заменил его. Сожалеем, но тебя уже не спасти.
Комментарии (626)
Iqorek
05.08.2017 23:03-14jQuery — удобный инструмент и искренне тебе помогает в делах
jQuery это устаревший инструмент, имеющий ряд недостатков. Для своего времени это был прорыв, позволяющий писать кроссбраузерный код без уродливых конструкций типа
var ua = window.navigator.userAgent; var msie = ua.indexOf ( "MSIE " ); if ( msie > 0 ) // If Internet Explorer, return version number return parseInt (ua.substring (msie+5, ua.indexOf (".", msie ))); else // If another browser, return 0 return 0;
да, так писали в 2010 году
Это был один из его плюсов тогда, но сегодня этого всего уже не нужно. Теперь о минусах, с jQuery очень легко писать с нуля, но очень тяжело поддерживать, потому что очень тяжело выявлять связи между кодом js и html разметкой. Как только проект становится больше hello world, очень легко, что то сломать и даже об этом не знать, потому что ошибок в консоли скорей всего не будет.pnovikov Автор
05.08.2017 23:06+29Слушайте, ей-богу, ваше мнение о jQuery тут никого не интересует. В контексте статьи фраза про jQuery означала "тебе комфортно в jQuery и ты не чувствуешь потребности в чем-то большем". Пожалуйста, смогите в переносный смысл. Я в вас верю.
Zoolander
06.08.2017 04:18-4да нет, мнение интересует кого-то
как раз интересно получить краткое мнение по поводу тезисов, и почему они смешны в этой статье — ведь мне некогда учить полностью весь необходимый стек, я работаю на другом дереве технологий
так что пускай люди пишут свои мнение
и ради бога, не думайте за всех
не пишите «никого не интересует»Stalker_RED
06.08.2017 09:55+4Людей который слишком много кричат про «jQuery не нужен» можно довольно легко ловить на простую двуходовочку:
1. Просим их показать аналог jQuery.position()
Они говорят да вот же!!1, смотрите как просто!
2. А теперь нас интересуют координаты элемента, который обернут в элемент с position: absolute, а один из его родителей имеет position: fixed, и еще какой-то из родителей relative и там еще где-то margin'ов понапихано.
Это в общем-то можно всё разрулить, но будет не она строчка, а десятка полтора. И по смыслу не будет отличаться от того, что внутри у jQuery.patricksafarov
06.08.2017 11:38+8А мы вот возьмем и вынесем эту функцию в новую библиотеку — шах и мат.
dom1n1k
06.08.2017 13:32+6А потом вторую, третью, пятую, десятую…
Ну а там и до пакетного менеджера недалеко, чтобы управлять ими.RifleR
06.08.2017 17:24-1То есть, вы предлагаете, если нужно использовать одну функцию из jQuery, тянуть всю jQuery в проект, если нужна одна функция из lodash, тянуть весь lodash и т.д.?
DistortNeo
06.08.2017 18:55+1Ну вот смотрите: C/C++ компиляторы выбрасывают неиспользуемый код при компиляции, и для этого не нужно дробить библиотеки на элементарные. А вот в JS из-за особенностей языка так сделать нельзя, вот и приходится делать надстройки в виде пакетных менеджеров на каждый чих.
staticlab
06.08.2017 19:06+5Справедливости ради, в webpack 2 поддерживается dead code elimination на уровне модулей, то есть
import sortBy from 'lodash/sortBy'
лишнюю часть лодэша при оптимизации отсечёт, если его правильно использовать.
dom1n1k
06.08.2017 19:49+1Если реально одну — нет, но тогда и новая библиотека не нужна.
Однако практика говорит, что одной дело ограничивается очень редко.
Hardcoin
06.08.2017 13:49+2Тысячи людей используют, а вы говорите "не нужно". Нехорошо как-то, мы же не на дваче.
AnneSmith
06.08.2017 02:47+2нет никаких трудностей, если для обращений к элементам вы используете только id вместо html разметки и css классов
как только вы фокусируетесь на id элементов, то ваша работа сводится к работе с моделью приложения и javascript + jquery, консоль в таком варианте незаменима
проблемы большого проекта решаются разработкой грамотной модели приложения, а не отказом от jquery
Iqorek
05.08.2017 23:13-5Асинхронные операции в Node.JS для тебя — cutting edge. Ты полностью уверен, что ни в одном другом языке кроме как JavaScript это невозможно и никаким другим фреймворком кроме Node.JS это не поддерживается
Хотя бы в ноде он (async/await) работает так, как ты этого ожидаешь, в отличие например от .netpnovikov Автор
05.08.2017 23:15+20… и вот мы определили ваш уровень поражения вирусом. :)
Iqorek
05.08.2017 23:25-1Не ставьте диагнозы ;) Пишите больше на js или лучше на ts, который на мой взгляд взял лучшее от js и c#. И тогда вы поймете, что то, что вам казалось разжижением мозга, на самом деле просветление.
TheShock
06.08.2017 01:02-1ts, который на мой взгляд взял лучшее от js и c#
Чем ts лучше c#? =) Как по мне, ts хоть и улучшает js значительно, но до c# очень не дотягивает
mayorovp
05.08.2017 23:57+9Э… и в чем же отличие того как работает async/await в js и в .net? :-)
Tantrido
06.08.2017 04:08Тот же вопрос: в чём разница? Когда запустил NodeJS — понравилось, что async/await работает также как в C# (даже подумал: близнецы-братья), разве что дедлоков нет по Wait() или Result() — если синхронные операции вызываешь в асинхронном методе.
NeonXP
06.08.2017 05:16+30Ну как же ты не видишь, слепец! В js это работает с магией, а в этом твоём c# работает с энтерпрайзным унынием! Как вообще это можно спутать?!
Newbilius
06.08.2017 15:19-6Тот же вопрос: в чём разница?
разве что дедлоков нет
Хммм… действительно… в чём же разница… ;-)
DistortNeo
06.08.2017 16:16+2разве что дедлоков нет по Wait() или Result() — если синхронные операции вызываешь в асинхронном методе.
Эта проблема решается просто: не используем синхронные операции вообще и используем свой планировщик. Да-да, механизм async-await — это просто синтаксический сахар, что в C# (обёртка над Task<>), что в JS (обёртка над Promise). Вам ничто не мешает в C# здесь так, чтобы абсолютно всё выполнялось в одном потоке, и никакие Wait() и Result() не будут нужны.
PsyHaSTe
07.08.2017 18:36-2Синхронные операции не использовать вообще не получится. Типичный пример — обращение к асинхронной операции в конструкторе. Да, делать тяжелые операции в конструкторе не рекомендуется, но частично-инициализированный объект возвращать хуже. Да, можно написать фабрику, которая будет асинхронно создавать объект, но это не всегда применимо, в частности в случае наследования таких конструкторов.
Graff46
05.08.2017 23:26+3Я по причине обучения в ВУЗе столнулся с JS, до этого долго и упорно сидя на Lua. Ну первое время у меня матов не хватало, что бы описать JS, и если бы не jQuery, то возможно и сейчас бы не хватало.
Zenitchik
05.08.2017 23:58+6У меня была та же реакция, когда я после JS врубался в Lua.
ivan386
06.08.2017 10:27Слова вместо фигурных скобок не красиво соглашусь. То что функция может вернуть несколько результатов не привычно. Условия циклов и ветвлений можно обернуть в скобки чтобы выглядело привычно. То что всё можно использовать как ключь это круто. Правда пользовался этим только при сериалиации. То что конкатенация это отдельный оператор это плюс. С булевыми операциями всё просто. Всё true кроме false и nil.
ZyXI
06.08.2017 14:09«Всё как ключ» в общем удобно при работе с рекурсивными структурами. Не только сериализация, мне для конвертации в другие рекурсивные типы данных пригождалось (вроде из типов lua в C’шные структуры, используемые в другом языке программирования). Булевые операции в таком виде удобны, т.к. иногда позволяют обойтись без тернарного оператора. Но оператор был бы лучше.
Что мне категорически не нравится — это стандартная библиотека.
io.popen
не умеет даже такое малой частиsubprocess.Popen
из Python как «не использовать shell» и «открыть одновременно на запись и чтение».setenv
нет, хотяgetenv
есть. Конечно, в lua намеренно минимальная стандартная библиотека. Но я специально привёл примеры того, что я считаю присутствующим, но не полностью реализованным функционалом, а не тем, что вообще хотелось бы видеть в стандартной библиотеке.
Второе, что мне не нравится — совместимость. В lua её ломают, поэтому многие просто сидят на 5.1. Luajit до сих пор полностью совместим с 5.1, от 5.2 взято только то, что не нарушает совместимость, 5.3 в changelog вообще не упоминается. В Gentoo всё, кроме 5.1 замаскировано, maintainer’ы потихоньку монстрячат слотовый lua, чтобы ставить несколько версий lua одновременно — как я понял, множество пакетов в новой версии не работает и никто не хочет их чинить, поэтому выбирать не?5.1 в качестве основной версии просто нельзя.
Zenitchik
06.08.2017 21:33+1Нет, больше вымораживало отсутствие массивов.
ivan386
06.08.2017 23:24+1Таблицы в Lua выполняют так-же роль массивов. В отличии от JS манипуляции с "массивами" выполняются внешними функциями но если очень нужно их можно и прицепить к таблице.
t = {"a", "b", "c"} t.concat = table.concat print (t:concat("*")) --> a*b*c
В JS то тоже чистых массивов нет. Это просто тот-же объект с дополнительными фунциями работы с массивом.
Таблица в Lua аналог обьекта в JS.
ZyXI
07.08.2017 00:04+2В PHP вроде тоже. Но это как?то непривычно — я с Python и C на lua переходил. До 5.0, кстати, никаких оптимизаций для таких таблиц не было, всегда честный хэш (с соответствующей константой для вещей вроде
table.insert
), с 5.0 там гибрид массива и таблицы.table.insert
всё равно, правда, не содержит оптимизаций вроде “если можно, просто запустиmemmove()
”, но гибридность снижает затраты на lua_rawget()+lua_rawset(). Тем не менее, судя по коду в 5.1 оператор#
(длина) всё ещё О(log(N)). В 5.2—5.3 и luajit — не знаю.
egorcod
06.08.2017 19:37+1http://moonscript.org/. Как CoffeeScript, только для lua.
ZyXI
06.08.2017 21:25Как?то пытались использовать в Neovim, но отказались. Главная проблема: тесты на moonscript проваливались, никто не знал, почему. Другие проблемы:
- Если вы сделали ошибку в синтаксисе lua напишет, какая и где была ошибка. Если вы сделали ошибку в синтаксисе moonscript, вы получите «Failed to parse», номер строки и всё.
- Если вы сделали ошибку не в синтаксисе, то вы получите ошибку. А номер строки — нет, только номер строки в результирующем lua коде который в общем случае недоступен (вы можете сделать конвертацию локально, но вам нужна та же версия moonscript и, возможно, что?то из окружения).
- Moonscript отсутствует в репозиториях различных дистрибутивов. А у нас не lua пакет, чтобы ставиться из luarocks, где moonscript есть.
- Линтеров практически нет. Я знаю только встроенный и moonpick, при чём второй не существовал во время принятия решения, а первый весьма ограничен.
- Мажорная версия языка до сих пор 0, что означает, что язык будет считаться нестабильным. (Сам автор сказал, что он не использует semantic versioning и не считает язык нестабильным сам, но это скорее ещё один минус.)
- Moonscript добавляет ещё один протекающий слой абстракции.
- Людей, которые могут писать на moonscript неизбежно меньше тех, что могут писать на lua, потому что первые — подмножество вторых, потому что без знания lua отлаживать moonscript код нельзя.
Соответствующая дискуссия, окончившаяся тем, что текущий (@tarruda сейчас фактически не участвует в разработке) лидер проекта сказал, что будет использоваться moonscript, хотя больше людей было за lua. Пример неизвестных проблем с moonscript. Другой пример.
rail-ka
06.08.2017 00:33+7У вас ссылка на «JavaScript как праздник» введет туда же, куда и «JavaScript как явление».
однако мой основной профиль — C# и делать всё, чтобы защитить бедных C# разработчиков от излишнего ныряния в JavaScript.
«Бедные» C# разработчики не обязаны нырять с головой в JavaScript, если компания позволяет себе нанять отдельно разработчиков C# и JavaScript. Статья написана конечно же не для этого (по моему впечатлению). По вашей статье складывается впечатление, что все, кто пишет на более новых технологиях, чем JQuery — «больны».
Есть в уровнях (особенно последних двух) некоторые описанные симптомы, которые действительно кажутся маразмом, но не все. Например:
Едешь на конференцию по JavaScript. Там тебе впервые показывают как за 20 секунд сделать сервер на Node.JS. Восторгаешься. Вирус, подпитавшись материалами с конференции откусывает ту часть мозга, которая еще помнила про многопоточность. Она помирает и не успевает задать свои вопросы
Объясните, разве это плохо — ездить на конференции по своему языку программирования? Плохо, что в какой то технологии можно за 20 секунд сделать что-то?
Хочу обратить внимание, что первым появился пост, который обсирает JavaScript, и негодует его резкой популярности, второй же пост просто защищает JavaScript.
«Здоровый» человек не будет обсирать другую технологию, которую он не понимает, которая стала вдруг популярной. «Здоровый» человек напишет про свою любимую технологию. Не хочешь JavaScript на backend, не применяй (если ты решаешь чему быть), убеди тех. директора чем C# лучше JavaScript, не работай в компании с «больными» людьми, и все остальное в этом духе.
Мне кажется больше разводят холивар не JavaScript разработчики, а те, кто не являются JavaScript разработчиками, но кому приходится им пользоваться, ибо он сильно отличается от других.pnovikov Автор
06.08.2017 00:37+9У вас ссылка на «JavaScript как праздник» введет туда же, куда и «JavaScript как явление».
Fixed
По вашей статье складывается впечатление, что все, кто пишет на более новых технологиях
Нет, статья как раз о том, что тот, кто начинает считать свой любимый язык — звездой, а всех, кто не разделяет эту мысль — идиотами — тот больной. По стечению обстоятельств это происходит по мере усложнения используемых технологий.
Плохо, что в какой то технологии можно за 20 секунд сделать что-то?
Плохо, если технология скрывает от тебя очень важный аспект реализации чего-либо, а ты сам не желаешь разобраться как оно все-таки работает.
«Здоровый» человек не будет обсирать другую технологию
Пост как раз об этом. В шапке же написано, что я критикую фанатиков, а не технологию
maxpsyhos
06.08.2017 05:56+6Плохо, если технология скрывает от тебя очень важный аспект реализации чего-либо, а ты сам не желаешь разобраться как оно все-таки работает.
Простите, но разве не этим же самым заняты 99,99 любых библиотек на любых языках, да собственно и самих языков высокого уровня? Это же всё направлено на то, чтобы ты не воевал с инструментом за каждый шажок, а занимался решением своей прямой задачи.pnovikov Автор
06.08.2017 06:50+1Все верно. Однако — опять же — в хороших ВУЗах зачем-то изучают что такое стэк, как передаются параметры в функцию и вообще учат C. Если ты хотя бы ориентировочно понимаешь как и что работает — это помогает идентифицировать и обезвредить любой потенциальный факап, который может возникнуть с технологией. В случае многопоточного программирования — даже в node.js можно поймать race condition. И вот представьте что вы наткнулись на race condition, а вы не знаете что это :)
maxpsyhos
06.08.2017 07:03-3А что, в C нельзя устроить race condition, не понимая что это такое? Он настолько сложен, что без 5 лет учёбы в ВУЗе на нём нельзя сделать такую тривиальную вещь, как параллельный/асинхронный блок кода?
Да и тем более, race condition он что, спалит вам компьютер и аннигилирует вселенную, если вы за 5 секунд его не нейтрализуете? Для его устранения нужны какие-то сакральные знания, которые нельзя восполнить в пределах 15 минут?pnovikov Автор
06.08.2017 07:05+5Эаам…
Наоборот — на C очень легко сделать race condition с posix-тредами и натренироваться такие ситуации подмечать и бороться с ними.maxpsyhos
06.08.2017 07:08-5Тогда я вообще не понимаю. Вы ругаете язык за то, что в нём сложнее отстрелить себе ногу, потому что отстреливая себе ногу гораздо легче научиться не отстреливать себе ногу?
pnovikov Автор
06.08.2017 07:11+6Послушайте, я не ругаю языки. Я ругаю людей, которые не учатся основам перед тем, как начать делать что-то серьезное.
Hardcoin
06.08.2017 17:11+6Да, для устранения race condition нужны знания, которые нельзя восполнить за 15 минут. Очень интересно наблюдать, как такие проблемы решают разработчики с завышенным самомнением — обвешивают костылями, а потом отмазываются — php наверное сбоит. Любые абстракции текут, поэтому фундамент нужно знать.
Keyten
06.08.2017 12:00-3Я не очень понимаю.
C# / C++ / Haskell / Java / etc -разработчики активно нападают на JS.
Js-разработчики защищают свой язык.
Фанатики как раз первые, а вовсе не вторые. Почему вы написали пост про js-фанатиков, а не haskell-фанатиков или c#-фанатиков?
pnovikov Автор
06.08.2017 12:09+12Я вот фанатиков от этих языков особо как-то не встречал. Сообщества ведут себя абсолютно адекватно, не строят из своих технологий серебрянную пулю и не считают быдлом всех, кто не пишет на %язык_нейм%, не общаются рекламными штампами. Кроме того, относительно адекватно реагируют на вопросы. E.g. каждый раз, когда я задавал вопрос по C++/Java любому из опытных разработчиков на оных — я спокойно получал ответ и разъяснение как и что работает. Каждый раз, когда я задавал вопрос по JS-у, я получал в ответ упреки, что я ничего не понимаю, мне надо учить самые основы и вообще "не умеешь — не лезь". Так один из адептов рассказывал мне снисходительным тоном про прототипное наследование — будто Америку открывает. Делал он это в ответ на вопрос о виртуализации прокрутки. И такие ситуации возникли несколько раз подряд — вот я и подумал что что-то с разработчиками на JS не так. Когда они начали отрицать индустриально-базовые вещи, такие как строгая типизация и многопоточность — я понял что под видом сакрального знания чаще всего подают вопиющую неквалифицированность. Ну я не выдержал и все заверте… Я просто скомпилировал все штампы, которые слышал в одну отдельную статью
Antelle
06.08.2017 12:22+5Это фанатиков хаскеля-то вы не встречали? Видимо, вы мало с ними общались. Более яростных теоретиков, далёких от бизнеса, я никогда не видел. Фанатики C++, которые всё что выше C++11 называют хипстотой — тоже интересные личности.
C# и Java — встречал мало, да. С этим лучше.pnovikov Автор
06.08.2017 12:30+3Понимаете в чем дело, ни фанаты haskell, ни фанаты C++ не станут подвергать сомнению такие базовые дисциплины как операционные системы, сетевые технологии, базы данных или теория типов. В отличие от.
Они могут сказать что я пишу на не труъ-языке, но они никогда не скажут что многопоточность — это плохо.
funca
06.08.2017 15:48-9Многопоточность это сложно, а сложно это плохо. Согласитесь, без неё мир был бы проще. Просто есть задачи, которые без многопоточности решаются еще сложнее. И кстати многие среды выполнения JS внутри используют потоки. Взять тот же node.js, не говоря уже о браузерах.
pnovikov Автор
06.08.2017 15:49+4Промышленная химия — это сложно. А сложно это плохо. Согласитесь, без лекарств мир был бы проще.
Просто, как вы заметили, без лекарств людям жить еще хуже :)
funca
06.08.2017 16:57-4Просто, как вы заметили,
Ну что уж так уж?). Самые крутые истории, которые доходили из поддержки, связаны с эффектами, возникавшими в многопоточных приложениях. Не знаю какими таблетками кормят разработчиков, может такими когда приходится выбирать или печень, или почки.
Вы не поверите, но это реально круто, когда код можно тупо дебажить, покрывать юнит-тестами, а потом он реально так и работает.pnovikov Автор
06.08.2017 17:04+1Как вы покроете юнит-тестами конструкцию "показывать пользователю UI, давать работать с локальной копией БД, в то же время пытаться установить подключение к серверу с увеличивающимися интервалами в 10, 20, 30 сек и далее минута, но отвалиться после 5 попыток"?
funca
06.08.2017 17:18Мы сейчас точно говорим про юнит-тест (это которые должны тестировать SUT, абстрагировав от всех внешних зависимостей?) Мне почему-то кажется, что функцию которую вы описали, надо покрывать матом — слишком много на себя берет. :)
Во всех реализациях JS таймер является внешней штуковиной, которая благодаря этому тривиально заменяется на фейк. Например такой http://legacy.sinonjs.org/docs/#clock. Все предельно просто, как в детском конструкторе, и это правда работает.pnovikov Автор
06.08.2017 17:23+1При чем здесь таймер? Я вам не про функцию говорю, а про целую систему. Вы можете покрыть 2 потока отдельными тестами, но интеграционно вам придется тестировать это вручную.
0xd34df00d
06.08.2017 19:36+2Можно вспомнить, что многопоточность — это монада, и сделать аналог quickcheck-подобного тестирования для всех возможных последовательных представлений двух потоков (возможно, со снапшотами состояний для каждого из потоков, ибо race condition'ы же тоже надо уметь тестировать).
wheercool
08.08.2017 11:31+2Ну для этого как минимум операции должны быть атомарными, а скорость роста всех возможных перестановок явно отобьет охоту проверять все возможные ситуации :)
Я не говорю еще про инвалидацию кэша процессоров, оптимизации компиляторов и пр.низкоуровневые вещи, меняющие выполнение операций.
Да, жизнь боль :(0xd34df00d
08.08.2017 21:05Не обязательно проверять все возможные пути исполнения, их действительно многовато.
Ну а что до кэшей и оптимизаций компиляторов — представляется разумным (формально) проверять это отдельно и единожды, а затем рассчитывать на это как на работающий и доказанный примитив.
funca
06.08.2017 21:50+1Прикол в том, что 2 потока отдельными тестами это имитация бурной деятельности. 2 потока в тесте и 20К потоков при эксплуатации могут давать совершенно разную картину для одной и той же сборки. А это, в свою очередь, резонно порождает отдельную пачку вопросов относительно практической полезности и экономической оправданности таких тестов в контексте раннего обнаружения класса багов, специфичного лишь для многопточного кода.
Выпад про интеграционные тесты, если честно, не понял. Мой тезис был конкретно про юнит, ибо это база. Для end-to-end тестов в условиях приближенным к боевым, существуют свои решения как для ручного так и для автоматического тестирования.
Hardcoin
06.08.2017 17:16Вот вы и сами видите. Фанат haskell не скажет, что многопоточность, это плохо. Он хотя бы про видеокарты вспомнит, там многопоточность — это хорошо. А вы сказали, даже объяснение придумали. В этом разница.
funca
06.08.2017 17:33+1Фанат хаскель, если он конечно не разработчик ghc, скажет, что тождественный функтор для единичного морфизма это хорошо.) А многопоточность, ей богу — пофик — компилятор умный, вот пусть и думает как параллелить.
0xd34df00d
06.08.2017 19:39+1А многопоточность, ей богу — пофик — компилятор умный, вот пусть и думает как параллелить.
Я так на первом годужизниработы с хаскелем думал, но только вот понять, чтоmap fibonacci [0..1000]
для наивной рекурсивной реализации Фибоначчи — дорогая штука, и её лучше распараллелить, аmap (*2) [0..1000]
— нет, не очень тривиально.
Но вы правы, в подавляющем большинстве случаев параллелизм достигается заменойmap
наparMap rdeepseq
, а конкурентность — каким-нибудь forkIO в main.
Забавно, кстати, как раз сейчас нашёл баг в ghc, который выкидывает некоторые аннотации параллелизма.funca
06.08.2017 21:25Тогда можно еще задачу комивояжора припомнить (и вообще там целый класс задач из мира «очевидное-невероятное программирование», ни чем не хуже, чем «1 + '2' == '2' + 1» в JS). А про фанатов это конечно под впечатлением шутливого тона статьи. Правда, если воспринимать буквы совсем буквально, то меня можно и канделябром (запрещено законом РФ). Только вместе с автором, ведь не я первый здесь это начал.
0xd34df00d
06.08.2017 22:27+3Я не очень понимаю, как вы сравниваете задачу коммивояжера и типизацию в JS. Наверное, это всё от слабой типизации.
funca
06.08.2017 23:26Сожалею, что пример оказался путанным. Я имел ввиду задачи, где глядя на код, результат не совсем всегда очевиден. В случае map fibonacci и задачи нахождения оптимального пути в графе в хаскеле получается неожиданно плохая асимптотика. По-моему это ни чем не лучше, чем приколы с приведением типов в JS.
0xd34df00d
06.08.2017 23:45Я имел ввиду задачи, где глядя на код, результат не совсем всегда очевиден.
У любой оформленной в виде кода задачи есть intrinsic complexity (этакая неизбежно связанная с ней сложность), сложность, связанная с языком (ну трудно писать на брейнфаке, что поделать) и сложность, связанная с радиусом кривизны рук программиста. Когда результат не совсем очевиден только для задач с большой intrinsic complexity, это хороший, годный язык.
В случае map fibonacci и задачи нахождения оптимального пути в графе в хаскеле получается неожиданно плохая асимптотика.
Смотря как написано fibonacci. Если это каноничная рекурсивная реализация, то вести она себя будет действительно паршиво, но она в любом языке будет вести себя паршиво. Мемоизирующую реализацию написать довольно просто за счёт свойств языка:
fib = (fibs !!) where fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
А с оптимальным путём — что здесь не так?mayorovp
07.08.2017 07:35-2… и эта самая мемоизирующая реализация все еще в связке с map будет работать медленнее чем хотелось бы, хоть и не будет уже так тормозить.
0xd34df00d
07.08.2017 08:03-1Мы ж про асимптотику, а не про константу ;)
Если вы о том, что!!! — O(n), то возьмите Data.Array вместо обычного списка, и будет примерно то же самое, что и для императивного нечистого языка. Если хотите, чтобы результаты шарились между разными вызовами — ну, вам нужно будет ST или IO уже тогда, глобальный стейт же, и это правильно.
franzose
07.08.2017 02:01Если бы не было многопоточности, мы бы до сих пор сидели бы на древних компутерах.
SirEdvin
07.08.2017 18:31+2Многопоточность это сложно, а сложно это плохо
В программировании обычно наоборот. Просто это плохо, потому что на самом деле все сложно, но это просто скрывает от вас детали реализации и костыли, которые лежат внутри, а потом нужно это как-то решать, как только приходится делать что-то, о чем создатели этого просто не подумали. А так происходит почти всегда.
Вот и запускаем кучу процессов вместо одного и менеджим их вручную, вместо того, что бы нормально создавать пулы потоков.
S_A
06.08.2017 16:12+1Многопоточность — это не плохо. И не хорошо. Просто в рассуждениях о node.js, противопоставляя его многопроцессному подходу, смешивают в кучу «людей и коней».
Ведь все пользуются однопоточным nginx для статики, не так ли? Так вот. Всем же очевидны его преимущества? Так вот. Типовая модель использования node.js — та же: «принеси-подай» из базы. Накладных на преобразование http-запроса в sql/mongodb-запрос практически нет (ну не более чем nginx резолвит filename:)). Забрать из базы данные — почти та же статика, которая не содержит логики обработки по запросу (в отличие от типичного рендеринга в PHP). Чтение файла — такая же асинхронная операция, как и выборка.
Стандартный способ деплоя node.js — это pm2, который кластеризует процессы node по ядрам. По сути, мы перещаем бутылочное горлышко рендеринга из серверных процессов на клиента (в современных virtual dom-технологиях и серверах api на node.js по типу loopback). То есть нагрузку на сервер облегчаем, и это честно так.
И кстати, многопоточность — это плохо, когда накладные на переключение начинают влиять. Разница между процессами и потоками с точки зрения ОС небольшая (общая память => общий краш для потоков, для процессов нет), и вряд ли кто поспорит, что 4 процесса по 1000 коннектов лучше 4000 потоков (пусть даже не процессов) в системе. Банальный fork в линухах должен будет скопировать окружение процесса, проинициализировать процесс и прочие приседания на синхронизацию (где-то родительский поток ждет или поллит семафор заверешения чайлда).
Я вот не говорю, еще раз, что многопоточность плохо (или хорошо). Я говорю о том, что рассуждать о ней надо в контексте конкретной системы.TimTowdy
06.08.2017 17:03+3Ведь все пользуются однопоточным nginx для статики, не так ли?
Мир клином не сошелся на IO-bound задачах. Более того, IO-bound задач настолько мало, что их обычно изолируют и забывают (тот же nginx например).
Типовая модель использования node.js — та же: «принеси-подай» из базы
Да-да, в простонародье — формошлепство (а точнее, его бекенд). Почему же тогда фанаты JS начинают брызгать слюной, когда им говорят что их язык не годится для чего-то более серьезного? (ну, или, более толерантно, подходит хуже чем другие промышленные языки).
Я вот не говорю, еще раз, что многопоточность плохо (или хорошо). Я говорю о том, что рассуждать о ней надо в контексте конкретной системы.
Вообще-то в прошлых тредах (да и в этих тоже) фанаты утверждали, что многопоточность не нужна вообще, потому что слишком сложно. А это подразумевает как раз отсутствие контекста. За что их и высмеивали.pnovikov Автор
06.08.2017 17:06чего-то более серьезного
Пример вам под руку: написать GET-метод, который полезет в БД, достанет оттуда данные, соберет из них excel-файл и отправит клиенту. Вот тебе бабушка и
IO-bound задачиюрьев деньTimTowdy
06.08.2017 17:32+3Самое печальное, что так ведь и делают: крутят в одном потоке как генерацию xls, так и асинхронный чатик. И потом еще не верят что чатик зависает. У нас же NodeJS, это невозможно! Но если что, виноваты конечно же программисты, а не экосистема, которая их воспитала.
pnovikov Автор
06.08.2017 17:44-2Вот мы и нашли тонкую грань между хеллоуворлдом и серьезным проектом. Если для всей необходимой функциональности достаточно IO-bound операций (сериализация/десериализация не в счет), то вы пишете хеллоуворлд.
mayorovp
06.08.2017 19:11+2Ну, проблемы генерации больших excel-файлов на количестве потоков не заканчиваются. Так что тут node.js и C# для меня одинаковы — в обоих случаях я предпочту вынести генерацию файла в отдельный процесс :-)
grossws
07.08.2017 00:35+1Генерация-то ладно, она обычно не раздувается по памяти и CPU до бесконечности. А вот парсинг — может, как уже не раз показывал опыт с Apache POI, PDFBox и Tika.
mayorovp
07.08.2017 07:37-1Не скажите. Мне вот в этом году пришлось фиксить багу вида "после выгрузки в Excel справочника на 100000 строк сервер перестает отвечать". Правда, эта ошибка воспроизводилась только в тестовой среде, где веб-сервер и сервер СУБД за память воюют — но в другой процесс генерацию я все равно в итоге вынес.
S_A
06.08.2017 17:35Я повторюсь. Обработку картинок нейросетью на сервере я не буду писать на node.js. То что называют «формошлепством» — далековато от истины в случае того же loopback, который можно настроить на абсолютно всё.
Я противник лишней работы на сервере, и считаю, что для большинства современных задач (работаю в немаленькой веб-студии), скажем > 80%, node.js более чем достаточно.grossws
07.08.2017 00:36+1что для большинства современных задач (работаю в немаленькой веб-студии)
Наверное, вы хотели сказать "для большинства задач веб-студии".
wert_lex
07.08.2017 16:06Мы в каких-то видимо сильно разных мирах живём, но как мне видится пласт задач вида "сходи в базу, принеси A, потом прими решение на его основе и принеси Б" — довольно широкий и охватывает если не весь веб, то довольно серьезную его часть.
Разумеется, есть еще уйма CPU-bound задач, которые на ноду ложатся мягко говоря не очень. Но это ведь не делает ноду плохим инструментом, для решения io-bound задач.
Ну и многопоточность тоже момент очень хитрый. Безусловно многопоточность в природе нужна. Но в ряде случаев от этого можно отказаться, и поиметь определенный профит от этого — выше уже был пример с nginx.
pnovikov Автор
07.08.2017 16:48+2Вот статья, рассказывающая о том, как работает nginx. Суть её — вся магия работает покуда у вас только io-bound операции. А nginx как раз для таких и предназначен (раздача файлов, статический контент). А вот другая, не менее объемная статья о том, как сложно nginx-у жить с cpu-bound операциями и как он вынужден с ними справляться. Повторяю еще раз. Насущная CPU-bound операция для бизнеса, например — генерация документов (тот же самый excel). И покуда остаются проекты с cpu-bound операциями (а почти все серьезные проекты именно таковы) путь для nodejs в мир серьезного продакшена закрыт.
mayorovp
07.08.2017 17:01+1Ну с чего бы закрыт-то? Просто надо в отдельные потоки или процессы такие операции выносить.
pnovikov Автор
07.08.2017 17:05+1А это уже многопоточность. Это уже, как выше было JS-разработчиками сказано, сложно. А сложность, как они же нам объяснили — не нужна :)
mayorovp
07.08.2017 17:09+1Да, но многопоточность совершенно неклассическая: вместо атомарных операций, блокировок и разделения памяти — только обмен сообщениями.
pnovikov Автор
07.08.2017 17:14+1И через что будет строиться этот обмен сообщениями? Shared memory? Добро пожаловать в мир всего того, что вы сказали. Пайпы? Попрощайтесь с кроссплатформенностью. Сетевые сокеты? Ну вариант, но как по мне — серверное приложение, которое подключается само к себе выглядит как минимум странно.
Плюс к тому же — если я правильно врубаюсь, то nodejs как раз активно пытается уйти от излишних потоков в пользу архитектуры а-ля nginx. Однако во-первых тредпул она уже активно использует, во-вторых… будет еще один? :) Так или иначе разработчикам придется управлять потоками. А они, как сами дают понять — не умеют.
mayorovp
07.08.2017 17:22Не важно как это будет сделано, главное чтобы пользовательский код мог посылать и принимать сообщения. Внутренние детали реализации никому не интересны пока там нет ошибок.
pnovikov Автор
07.08.2017 17:32Ну фиииг знает. Я слабо представляю себе nodejs-разработчика, который без опыта классического мультитрединга будет строить IPC на сообщениях.
justboris
07.08.2017 18:47+1А зачем нужен сложный обмен сообщениями, чтобы построить xls в отдельном потоке? Запустили процесс и ждем ответа. Я бы сделал как-то так:
const exec = require('execa'); app.get('/get-xls', async (req, res) => { const params = getParamsFromRequest(req); const data = await db.requestData(params); const {stdout} = await exec('node', ['./generate-xls', data]); res.send(stdout); });
mayorovp
07.08.2017 20:50Как-то странно вы данные через командную строку передали. Они же структурированные и их много. Да и итоговую xls в памяти держать — тоже неправильно. И обработка ошибок нужна...
justboris
07.08.2017 22:13Данные предполагалось передавать чем-то типа json в виде аргумента командной строки. Все равно любые сообщения между процессами это строки, так что сериализовывать придется.
А если не нравится загружать весь xls в память, то можно взять stdout как поток и через pipe завернуть его в response.
Получилось бы больше кода, но не страшно.
wert_lex
07.08.2017 22:29В файл её и отдать через sendFile
justboris
07.08.2017 22:34-1Я думал об этом варианте, в памяти с данными работать быстрее, чем писать на диск. (Мы же не делаем XLS-таблицы на 8 Гб?)
В любом случае, варианты есть, а утверждение автора pnovikov, что в node.js все плохо с многоточностью/многопроцессностью — неверно
pnovikov Автор
07.08.2017 22:45Да варианты-то есть всегда. Просто, например, в C#/MVC генерация xls (cpu-bound) не является проблемой вообще и никаких "вариантов" для неё не надо — берешь и пишешь (с асинками разумеется)
mayorovp
07.08.2017 23:00+12 гигабайта в последнем поколении сборщик мусора не собирает пока его не пнуть. А после пинка — собирает довольно долго...
Я бы не говорил что в C#/MVC генерация xls не является проблемой вообще.
kekekeks
08.08.2017 16:39+1Пайпы? Попрощайтесь с кроссплатформенностью.
Эмгм. Что у QLocalSocket, что у System.IO.Pipes проблем с кроссплатформенностью не наблюдал. Причём у второго не наблюдал даже во времена Mono 2.6.
Просьба пояснить, что имелось ввиду.
DistortNeo
07.08.2017 17:09-2После прочтения статьи я немного удивился: ни в Windows, ни в Linux нет полноценной возможности асинхронно читать файлы.
wert_lex
07.08.2017 22:27+3Постойте, я и не говорю, что нода хороша для CPU-bound операций. Больших фишек у ноды ровно две:
- асинхронный io с приятным интерфейсом для простых смертных (нет, последователи Карри, для этого не нужна специальная монада, и да, парни из мира Стандартных Изданий, для этого не нужно упражняться с тредпуллами), который внезапно показывает весьма приятную производительности без лишнего тюнинга.
- javascript. Который, внезапно, не смотря на все фу-фу-фу, доступен практически любому разработчику на Java, C, C++, C#, Pascal, PHP итд. А если немного подтянуть матчать и разобраться в том как работает this и прототипное наследование (которое, вообще говоря надмножество Java-like OOP), то внезапно оказывается, что это вполне пристойный инструмент. А если копнуть еще чуть глубже, то внезапно оказывается, что JS можно декорировать типами (flow), проверять линтерами, собирать под платформы разной степени древности (babel) и использовать фичи из черновиков грядущих стандартов прямо вот сейчас (опять, привет babel). Чем, не смотря на некоторую костыльность и соберисамность, может похвастаться далеко не каждый ЯП.
Теперь про CPU-bound и тот-самый excel. Я конечно в офисных xml делах не очень рублю, но что-то мне подсказывает, что если мы собираемся массово генерировать excel- файлики, то я бы предпочёл это действо передавать системе и вызывать отдельным процессом. Потому что наверняка есть хорошая сторонняя/системная тулза, которую не стоит пытаться запихнуть ни в ноду, ни в похапэ, ни в питон.
Это не значит, что надо писать на ноде всё и вся. Но для типовых веб-серверных задач нода — прекрасный, доступный и достаточно мощный инструмент.
S_A
08.08.2017 04:38-2Я бы взял какой-нибудь js-xslx и генерил бы файлики на клиенте. Если нужен один файлик на много скачиваний… да, придется делать его на сервере (в таких случаях обычно это редкий запрос). Можно дернуть процесс (опять хоть с тем же js-xslx), хотя в доках по js-xlsx виден метод writeFileAsync (с коллбэком).
jetexe
08.08.2017 14:07+3Я бы взял какой-нибудь js-xslx и генерил бы файлики на клиенте
Хватит! Люди. Не везде и не у каждого суперкомпьютер. Тестируете своё приложение на своих машинах с 16 гб памяти и core i7. И думаете что всё в порядке. Любой чих перекладываете на клиент, что бы не дай бог сервер не загрустил… Вы там на адруино приложения хостите что ли?! Генерировать файл на клиенте…wert_lex
08.08.2017 15:48+1А в чем проблема? Eсли речь идет не о 8гб таблиц, то до определенного предела фронтенд вполне себе в состоянии из json получить xml.
Разумеется, если киллер-фича приложения это генерирование xlsx на любой кофеварке с браузером, то нужно крепко подумать, желательно дважды. Но если речь идёт о данных, которые уже есть в вебе и их не десятки мегабайт, то в чем проблема? Современные движки JS достаточно быстрые для этого.
Xandrmoro
09.08.2017 15:20+2> Я бы взял какой-нибудь js-xslx и генерил бы файлики на клиенте
Прям вот квинтэссенция того, за что я не люблю современных жсников.
DistortNeo
06.08.2017 19:23По сути, мы перещаем бутылочное горлышко рендеринга из серверных процессов на клиента
Мне кажется, этот момент наступил с повсеместным переходом на толстые фреймворки, требующие для работы много ресурсов. И рендеринг на стороне клиента — просто попытка оправдания использования новомодных технологий.
Если рендеринг является бутылочным горлышком — распараллельте его. Все равно процессорное время, затрачиваемое БД на обработку запросов, много больше времени рендеринга.
Почему-то Facebook, ВКонтакте до сих пор используют PHP. Можете это пояснить?
И кстати, многопоточность — это плохо, когда накладные на переключение начинают влиять.
На самом деле, асинхронность — та же самая разновидность многопоточности. Принципиальная разница здесь заключается только в том, что в одном случае планировщиком задач является сама операционная система, а в другом — пользовательское приложение. И в случае ручного переключения задач мы вполне можем добиться экономии на многих вещах: на переключении контекстов, на примитивах синхронизации.
Кстати, что-то подобное было в Windows 3.1, когда приложения (процессы?) работали в многозадачном режиме, но приложения в явном виде сообщали системе (DoEvents), что они готовы передать управление в другое приложение.
funca
06.08.2017 22:07-1Асинхронность говорит лишь о порядке выполнения, который не важен, в отличие от синхронного, и больше ни о чем (о разновидностях многозадачности упоминать еще рано). Но это, вроде бы простое, свойство открывает ряд интересных возможностей для применения различных стратегий выполнения такого кода, что будет принципиальным образом сказываться на производительности и отзывчивости.
pnovikov Автор
06.08.2017 22:08Вставлю 5 копеек внеапно, в защиту JS.
рендеринг на стороне клиента — просто попытка оправдания использования новомодных технологий
Да нет, вы знаете, зачастую бывает очень уместно переложить монотонную конкатенацию строк на клиентскую машину. Тут, конечно, важно не перегибать, но в общем и целом рендеринг HTML-я, если так, по-хорошему — это не очень релевантная для сервера задача. Чисто концептуально, если её можно перекинуть на клиента, который бьет баклуши — я лично, с позиции бекенда, был бы только за. Другое дело что встают вопросы с индексированием… Но это уже к самой концепции отношения не имеет.
Если рендеринг является бутылочным горлышком — распараллельте его
Рендеринг не параллелится в общем случае. Максимум что можно — сделать его поточным. Чтобы соединить пайплайн отрисовки и записи в сокет. Но и тут все сложно.
staticlab
06.08.2017 22:15А что думаете насчёт связки изоморфное фронт-приложение + API-бэкенд?
pnovikov Автор
06.08.2017 22:24Думаю что backend на JS — это очень плохая идея. Поэтому изоморфизм в общем случае не взлетит, покуда у нас нет технологии для изоморфного рендеринга не на JS.
staticlab
06.08.2017 22:26Ждать webassembly, чтобы изоморфный сервер можно было поднять на другом языке?
pnovikov Автор
06.08.2017 22:29Простите великодушно, у меня чуть другой взгляд на проблему — я уже почти собрал движок для изоморфного рендеринга на C#. :)
staticlab
06.08.2017 22:33А как изоморфируете на клиент? Транслируете C# в JS?
pnovikov Автор
06.08.2017 22:37Что-то вроде. .cshtml-шаблон с некоторыми специальными расширениями за счет хитрых перегрузок TextReader-а, при запросе выдает не HTML, а специальную рендер-функцию, которую можно через шаблонизатор вызвать на клиенте и она вернет строку с HTML-ем, которая пропускается через поточный HTML-парсер на state-машине и через атрибуты обратной привязки связывается с вью-моделью. Можно делать VDOM-перерисовку (сейчас вот запиливаю, о чем сказал в статье). Тот же .cshtml-шаблон технически можно выполнить и на сервере, получив тот же результат.
Я все хочу выдрать эту фиговину в отдельную библиотеку и присобачить к ней какой-нибудь транслятор из C# в JS, распарсив существующие ts-тайпинги в C#-интерфейсы. Просто чтобы посмотреть что получится.
staticlab
06.08.2017 22:44И это будет работать в общем случае, или потребуется какое-то допиливание/перепиливание существующих скриптов, шаблонов и компонентов?
(Я имею в виду некий иной абстрактный .NET MVC-проект)
pnovikov Автор
06.08.2017 22:47Ну вам же не нужно перепиливать существующие скрипты и компоненты, когда вы пишете на TypeScript. :)
Я эту идею вынашиваю уже давно и по задумке это получается очень похоже на React.js, только не на JS, а на C#. Помимо этого возможна прозрачная интеграция с ASP.NET MVC-бекендом. Т.е. вы можете вот те же интерфейсы REST-контроллеров использовать тут же, в клиентском коде, что сделает, например, swagger идейно бесполезным для таких проектов.
staticlab
06.08.2017 23:12Ну, как бы клиентские скрипты должны быть всё-таки рассчитаны принять от сервера не "базовый" HTML для расширения, а уже готовую вёрстку, подтянув её в свой VDOM, как это делает Реакт.
Я подразумеваю вот какой кейс: допустим, у нас есть классическое MVC-приложение. Сервер рендерит основную часть вёрстки, затем клиентский скрипт (например, на jQuery) расширяет его (превращает селекты в красивые дропдауны или добавляет интерактивности какому-нибудь девэкспрессовскому компоненту), затем пользователь переходит на другую страницу, браузер рендерит её полностью с нуля, и процесс повторяется.
Как я понял, в вашем случае при переходе на новую страницу будет сделана попытка обновить разметку на основе имеющегося VDOM, но что при этом будет с теми блоками, которые были изменены внешними клиентскими скриптами в обход VDOM, и что будет с уже запущенными скриптами первой страницы? Получается, что для корректной работы клиентские скрипты должны быть либо написаны с использованием некоей специфической библиотеки, либо быть сынтегрированы с C#-кодом для корректной инициализации и деинициализации (как это делается при использовании компонентов jQuery в компонентах React)?
pnovikov Автор
06.08.2017 23:25Ну там конструкция построена так, что кто рендерит основную часть верстки — не важно. Главное чтобы в ней были атрибуты обратной привязки. На любой HTML-элементы вы можете добавить специальный backbind-атрибут
@Callback("initPlugin")
, который вызовет абсолютно любую JS-функцию после отрисовки элемента — и этот элемент функция получит первым параметром. Так же на вашу совесть ложиться добавить@DestroyCallback("killPlugin")
, который вызовется при удалении элемента из DOM-дерева. Если ваш коллбек добавляет какие-либо дополнительные ноды — то лучше обернуть это место в div с атрибутом@VDomDontTouch()
, чтобы VDOM-перерисовка не корежила ваши jQuery-плагины (спасибо bano-notit за эту идею). Это как оно работает сейчас. В случае если использовать транслятор из C# в JS, то аргументы@Callback
и@DestroyCallback
скорее всего будут переписаны на строго типизированные (лямбда-выражения).
funca
06.08.2017 23:16А рассматривали опыт аналогичных предшественников (gwt, например или jsjinja)? Предыдущие попытки пока заканчивались не очень из-за тормозов при выполнении, проблем с безопасностью и с отладкой генерированного кода.
Вообще основные заморочки с JavaScript ведь не в языке, а в большом количестве разнообразных неконтролируемых сред исполнения. Это не сразу бывает очевидно при переходе с бекенда. Каждый браузерный движок имеет свои особенности, которые необходимо учитывать.
Например, первое, что приходит на ум. На бекенде, в обычной среде исполнения ASP.NET MVC, дернуть из chtml метод, выполняющий запрос в базу, можно счесть и за шалость. При передаче такой имплементации на клиента, даже если решены все технические проблемы, это может стать дырой в безопасности. Аналогично получается со всеми сторонними зависимостями, библиотеками и т.п.
Поэтому писать в такой системе все равно придется с постоянной оглядкой на клиент-сайд, т.е. еще на любимом языке, но «как бы на JavaScript». Все бы ни чего, но это на порядок усложняет (удорожает) как задачу самого кодинга, так и последующей поддержки.pnovikov Автор
06.08.2017 23:25Не волнуйтесь, я знаю что делаю :)
funca
06.08.2017 23:30А можете рассказать каким образом это решается в вашем фреймворке, и какие накладываются ограничения?
pnovikov Автор
06.08.2017 23:31В каком фреймворке, простите? Это пока еще только задумка — у меня на руках только изоморфный шаблонизатор.
Что касается "дернуть метод из cshtml" — в настоящий момент этот метод выполнится на сервере и его результат запишется в клиентский шаблон (да, параметризуемый шаблон для шаблона — так тоже бывает). Методы, которые влияют на генерируемую шаблон-функцию и нативные C#-конструкции строго друг от друга отделены. В случае если у кого-то чешутся руки пошалить — кидается ошибка компиляции.
kekekeks
07.08.2017 10:45+1я уже почти собрал движок для изоморфного рендеринга на C#
https://youtu.be/MiLAE6HMr10?t=1939 — к сведению-
DistortNeo
06.08.2017 23:00Да нет, вы знаете, зачастую бывает очень уместно переложить монотонную конкатенацию строк на клиентскую машину. Тут, конечно, важно не перегибать, но в общем и целом рендеринг HTML-я, если так, по-хорошему — это не очень релевантная для сервера задача.
Согласен, но из соображений экономии трафика, а не экономии производительности. Конкатенация строк — это быстрое действие.
Чисто концептуально, если её можно перекинуть на клиента, который бьет баклуши
Как бы я хотел, чтобы программисты отлаживали свои веб-приложения на ноутбуках с Atom-ами с искусственными секундными задержками сети и потерей пакетов и поняли, наконец, что клиент баклуши не бьёт, а трудится в поте лица, пытаясь не только сформировать страницу, но и отобразить её в окне браузера.
Рендеринг не параллелится в общем случае. Максимум что можно — сделать его поточным. Чтобы соединить пайплайн отрисовки и записи в сокет.
Как это? Почему клиенская машина может рендерить страницу независимо от сервера, но сервер этого делать в общем случае не может?
Но и тут все сложно.
Подумайте о старых технологиях, где это было совсем просто.
pnovikov Автор
06.08.2017 23:13Мы видимо разные вещи подразумеваем под словом "рендеринг". Я имею в виду формирование HTML для страницы. И в современных условиях, мне кажется, клиент вполне в состоянии сделать это сам, имея нужные данные. Серверу конечно не сложно, но идеологически это не его задача, хотя де-факто много лет он её выполнял. Под тем, что эта задача не параллелится я имею в виду, что невозможно ускорить формирование HTML для страницы с помощью нескольких потоков. Тем более, как показывает лично мой опыт, сформировать большую строку (и даже её распарсить) — это пустяки по сравнению с отрисовкой. И раз уж клиенту все равно её рисовать — то пущай и формирует. Нече сервер по пустякам дергать :) "Вас много, а я одна", "у меня таких как вы тысячи" и все такое.
DistortNeo
06.08.2017 23:45Мы видимо разные вещи подразумеваем под словом "рендеринг". Я имею в виду формирование HTML для страницы.
Я тоже.
И в современных условиях, мне кажется, клиент вполне в состоянии сделать это сам, имея нужные данные.
Конечно, в состоянии. Но у клиента только браузер с JS. А на сервере HTML может формироваться гораздо более эффективными способами.
Под тем, что эта задача не параллелится я имею в виду, что невозможно ускорить формирование HTML для страницы с помощью нескольких потоков.
Но это и не нужно ускорять. Тем более, что это пустяки по сравнению с отрисовкой.
И раз уж клиенту все равно её рисовать — то пущай и формирует. Нече сервер по пустякам дергать :) "Вас много, а я одна", "у меня таких как вы тысячи" и все такое.
Если клиент получает от сервера HTML с минимумом скриптов — отображение такой страницы для клиента — лёгкая задача.
Если клиент получает от сервера JS-бандл + данные, причём скрипт однократно генерирует HTML при каждой загрузке/обновлении страницы — это тоже нормально. Один раз протормозится, дальше будет хорошо. Огромный минус — тормозить будет при открытии каждой новой вкладки.
Но если скрипт работает на уровне DOM (типа, так удобнее программистам) — клиента ожидает лагалово, особенно, когда данных много.
pnovikov Автор
06.08.2017 23:54А на сервере HTML может формироваться гораздо более эффективными способами.
Если в двух словах, то я просто отдаю клиенту JS-функцию, которая принимает на вход модель и возвращает строку с HTML. И эта функция генерируется на сервере (разумеется, результат легко кэшируется), в неё переносятся все эффективные способы формирования HTML. Читайте — шаблон компилируется на сервере. Клиенту остается просто вызывать — и вуаля. HTML есть. Дальше — хоть через innerHTML вставляй.
причём скрипт однократно генерирует HTML
Таки я не понял — мы предположили что получить HTML — это сложно или это просто? :) Клиенту так и так нужно будет преобразовывать полученный HTML в DOM-дерево и если мы предполагаем что шаблон-функция все-таки простая и не завешивает его на 10 секунд (даже мне с моими кривыми руками не удалось добиться такой просадки производительности, хотя я пытался) — то разница в итоге получается небольшая. А бандл, кстати, можно и кэшировать.
DistortNeo
07.08.2017 00:31Если в двух словах, то я просто отдаю клиенту JS-функцию, которая принимает на вход модель и возвращает строку с HTML.
И это хорошо.
Клиенту так и так нужно будет преобразовывать полученный HTML в DOM-дерево
Разница в том, что браузер HTML в DOM преобразовывает быстро.
А вот формирование DOM напрямую, используя JS — это медленная задача.
А бандл, кстати, можно и кэшировать.
Но браузер все равно будет тратить сколько-то времени просто на его парсинг.
P.S. Я JS-программист уровня 0, и мой взгляд — чисто со стороны пользователя: просто не люблю, когда сайты тормозят.
pnovikov Автор
07.08.2017 00:35Аээ… DOM можно сформировать только в браузере. Нельзя в браузер "отдать DOM" — узлы привязаны к контексту и единственный способ их сериализации — HTML (да и то без привязанных событий). Таким образом единственный путь — сделать чтобы HTML появился в браузере любым способом, а дальше создать DOM-узлы или через innerHTML, или через самописный парсер.
mayorovp
07.08.2017 07:40+2Разница в том, что браузер HTML в DOM преобразовывает быстро.
А вот формирование DOM напрямую, используя JS — это медленная задача.А можно пруф?
pnovikov Автор
07.08.2017 09:30Ой, а можно я за него отвечу? :)
На самом деле короче нет. innerHTML чуть быстрее чем document.createElement и они оба даже работают чуть быстрее, чем изначальный парсеринг/рендеринг HTML-документа при загрузке страницы. Источник — личный опыт.
mayorovp
07.08.2017 09:32Мой личный опыт говорит обратное. Я даже писал бенчмарки (но не сохранил). Потому и прошу пруф.
pnovikov Автор
07.08.2017 09:39Там сильно зависит от браузера на самом деле. Через innerHTML можно отрисовать в общем случае не все, поэтому треба использовать только document.createElement (в общем случае). Например в IE7 вы не можете проставить innerHTML у tbody (внезапно). Поэтому только через парсер и createElement. И да — оно работает СУЩЕСТВЕННО медленнее чем в IE9 и Edge. В хроме вроде проблем с производительностью не возникает. Но с таблицами тоже не все работает.
franzose
07.08.2017 09:43Еще есть метод
insertAdjacentHTML
, он вроде как быстрее установки свойстваinnerHTML
работает.
DistortNeo
07.08.2017 11:23+1А можно пруф?
Ну вот, например. Правда, там используется jQuery. Если использовать VanillaJS, то результаты будут другие: в старых браузерах генерация HTML была более быстрым вариантом, в современных — всё наоборот. Видимо, я не был в курсе последних оптимизаций браузеров.
pnovikov Автор
07.08.2017 11:40Тут еще смотрите какой момент. Если вы выведете на страницу, скажем, 70 тысяч элементов (я выводил 10 тысяч строк в таблице по 7 колонок, без доп. форматирования), то у вас все будет тормозить вне зависимости от скорости генерации HTML и создания dom-узлов. Ну просто потому что браузеру сложно отрисовать такой объем данных. Ну и как правило, пользователям видеть столько данных одновременно — тоже не с руки. Там уже вступают в игру всякие виртуализации, пейджинги и прочее разделение интерфейса на части. Так что практической пользы в создании огромного числа элементов нет — они все равно не смогут отрисоваться как надо. А небольшое число элементов в любом случае инстанциируется довольно быстро. Так что мерить производительность document.createElement тут не совсем релевантно.
DistortNeo
07.08.2017 12:01Ну зачем сразу 10 тысяч? Сколько-то там лет назад при выводе совсем простой таблички я наблюдал подтормаживание уже при 50 строчках с использованием JQuery, без же него нормально было только до 300-400 строчек.
pnovikov Автор
07.08.2017 12:02Межпрочим, мой Lattice и был основан сначала на jQuery. Потом, когда я познал быстродействие на 100 строчках — мне пришлось сделать свой HTML-парсер
mayorovp
07.08.2017 14:21По вашей первой ссылке сравниваются разные способы использования innerHTML, а не работа со строковым HTML против DOM.
grossws
07.08.2017 00:26-2Ведь все пользуются однопоточным nginx для статики, не так ли? Так вот. Всем же очевидны его преимущества?
https://nginx.ru/en/docs/ngx_core_module.html#worker_processes:
worker_processes number | auto
https://nginx.ru/en/docs/ngx_core_module.html#thread_pool:
thread_pool name threads=number [max_queue=number];
Как минимум в linux процессы и потоки технически отличаются не столь сильно.
mayorovp
07.08.2017 07:42Так ведь и в ноде есть cluster. Слово "однопоточный" в данном случае указывает не на количество потоков, а на модель работы с сетью, когда сокеты распределяются по существующим потокам вместо создания нового потока на каждый сокет.
grossws
07.08.2017 07:47+1Т. е. если я использую thread pool, скажем, в jetty, то программа/среда выполнения внезапно стала однопоточной о_О?
0xd34df00d
06.08.2017 19:32+2Это фанатиков хаскеля-то вы не встречали? Видимо, вы мало с ними общались. Более яростных теоретиков, далёких от бизнеса, я никогда не видел.
Почему? Bus factor высокий, да, но кроме этого в чём проблема?
Фанатики C++, которые всё что выше C++11 называют хипстотой — тоже интересные личности.
Это какие-то неправильные фанатики, которые скорее держатся за свои знания 15-летней давности и не хотят учить новое.Antelle
06.08.2017 19:49Проблема в том, что человек может часами рассуждать и спорить о теоретической чистоте, вместо того чтобы решать задачу (это я, если что, о фанатиках, которые прямо-таки упарываются, а не вообще обо всех; такие личности есть не только в хаскеле, но почему-то среди них видел намного больше, наверное заумность языка способствует).
неправильные фанатики
А что, бывают фанатики правильные? :) Вот тот пример про C++ (тоже в холиваре про JS кстати). И ещё знаю много таких любителей старого брутального C++, которые даже auto не используют пока по голове не настучишь.
0xd34df00d
06.08.2017 19:58+4Проблема в том, что человек может часами рассуждать и спорить о теоретической чистоте, вместо того чтобы решать задачу
Я таких больше всего среди обчитавшихся паттернов видел :(
А что, бывают фанатики правильные? :)
Ну, те тоже фанатики, просто статуса-кво или своего ощущения крутости от разработки на худших технологиях 80-х, а не свежего языка.
Я вот правильный фанатик, мне вчера кусок С++20 понадобился.
Вот тот пример про C++ (тоже в холиваре про JS кстати). И ещё знаю много таких любителей старого брутального C++, которые даже auto не используют пока по голове не настучишь.
Знаю-знаю таких. В соседнем треде как раз вялотекущий срачик где-то с марта идёт.
Keyten
06.08.2017 16:54+1Первая статья из этого цикла "JavaScript как..." была написана как раз-таки функциональщиком, отрицающим такие индустриально-базовые вещи, как return и скобочки.
slonopotamus
06.08.2017 12:58+5А если я пишу на JS и при этом считаю JS говном, которое продолжает не дохнуть единственно по той причине что в браузерах других языков нет, вы куда меня классифицируете?
pnovikov Автор
06.08.2017 13:00+5"Так что же вы стоите, батенька?! Давайте к нам, на бrоневичок, на бrоневичок!"
Mycolaos
06.08.2017 12:13Пост как раз об этом. В шапке же написано, что я критикую фанатиков, а не технологию
Так нужно было и писать о фанатиках, а не о технологии.
dplsoft
07.08.2017 00:27+6Объясните, разве это плохо — ездить на конференции по своему языку программирования? Плохо, что в какой то технологии можно за 20 секунд сделать что-то?
Нет, ездить на конфернцию не плохо.
Плохо терять критическое мышление в результате этой конференции)))
именно об этом пишет автор.
в который раз раз повторюсь: я был свидетелем безумия рубистов и пхпшников, которые запихивали свои «супертехнологии» в промышленные проекты ( со сложной бизнес-логикой, высокими требованиями к надежности, и большим объемом обратываемых в одном запросе данных, с высоким темпом изменений, в условиях большого объема кода и сложной логики) — не понимая где лежит граница применимости их технологий.
и я видел, что происходило с этими проектами через полгода, через год, через 2. ничего хорошего.
ведь, имхо, именно о потере критического мышления пишет автор во фразе «вирус откусывает ту часть мозга, которая помнит о многопоточности — она умирает не успев задать свои вопросы».
событийно ориентированный стиль разработки прекрасен.
но строго до тех пор, пока им не пытаются замень что то сугубо многопоточное или требующее синхронности.
скажите, вы когда либо писали код взаимодействия с промышленым принтером через rs-232? а я писал.
и в гробу я видел, извините за выражение, тех, кто написал «событийно ориентированные» классы, которыми я был вынужден пользоваться. потому что когда ты работаешь с принтером — тебе надо поддерживать его состояние и контекст, а проверять это всё каждый раз когда ты просыпаешься в «onByteRecieved()» — настолько усложныет код, что мне пришлось отдельно сидеть и писать «синхронные обертки» над асинхронным интерфейсом работы с ком-портом. потому что только синхронный интерфейс позволял мне сохранить простоту структуры кода и прозрачность логики. а обертка работала, только благодаря многопоточности.
вот такая сказка об идеализации однопоточности и событийности в реалиях жизни.
к чему я? к тому что сейчас очень много школоты, потеряв критичность мышления пихают js куда можно и куда нельзя. и этт плохо. об этом весь холивар.
«Здоровый» человек не будет обсирать другую технологию, которую он не понимает, которая стала вдруг популярной. «Здоровый» человек напишет про свою любимую технологию.
Не хочешь JavaScript на backend, не применяй (если ты решаешь чему быть), убеди тех. директора чем C# лучше JavaScript, не работай в компании с «больными» людьми, и все остальное в этом духе.
несколько не правы, имхо. действительно здоровый человек знает ограничения и границы прменимости технологии. и использует js там где он пригоден, а java там, где java работает лучше всего, с# — там где ему место, а не убеждает тех директора что c#/java лучше или хуже js.
у каждой технологии — свои границы применимости. и, напрмер, имхо, пихать js-на сервер, это прмерно так же, как java-апплеты встраивать на страницы публичного сайта. в принципе, работает, но «здоровые» люди так как правило не делают, потому что слишком много минусов, и ограничений у получившегося решения.DistortNeo
07.08.2017 03:32+1событийно ориентированный стиль разработки прекрасен.
Ну, к слову, async/await в JS позволяют весьма неплохо причесать код, избавив от необходимость делать цепочку колбэков, замыканий и хранений состояний.
Касательно работы с ком-портами в C# — да, это недоразумение. Я предпочитал работать напрямую через WinAPI.
PsyHaSTe
07.08.2017 18:56Работал с COM-портами в шарпе, никаких проблем не видел. Ну, там конечно довольно старый АПИ на EAP, вместо тасок, но в целом довольно адекватно.
dplsoft
07.08.2017 22:14так, народ)))
во первых… не в шарпах я работал. дело было в году 2006-м 2007 и сишарп тогда был… далеко не самым интересным, чем можно было заниматься. от слова совсем.
во вторых — я делал на с++ кроссплатформенную обертку над работой с компортами, и в виндовой части проекта я работал с WinAPI и использовал неблокирующее чтение с коллбеком. Потому что реализовать блокирующее чтение с таймаутом — … ну вот как-то не вышло. В линуховой части все вышло на ура, а вот c WinAPI — матов было много. А таймаут надо было иметь.
а потом туда же (через этот интерфейс) был пристыкован доступ к «виртуальному компорту» ( serial профиль USB-устройства — некоторые принтеры делают именно так)… и матов было примерно столько же.
Да и не про это разговор. разговор о том, что не все задачи можно и удобно решать в событийно-ориентированной логике. А работа с ком-портом — это только пример такого противоречия, когда низкоуровневый интерфейс — событийно-ориентированный, а вам надо делать синхронное взаимодействие, потому что иначе логика инициализации ии проверки состояния принтера усложняется во много крат — когда ты прежде чем послать задачу, проверяешь у него с десяток флагов и состояний, и при необходимости — ещё и настраиваешь режимы и подрежимы.
PsyHaSTe
07.08.2017 18:58+2Правильнее было тогда бы наверное скорее обернуть через TaskCompletionSource в нормальный асинхронный интерфейс и работать не теряя производительности и не блокируясь, и при этом пользуясь хорошим инструментом. У майкрософта можно почитать о гайдлайнах по конвертации и оборачивании.
dplsoft
08.08.2017 11:57-3Правильнее было тогда бы наверное скорее обернуть через TaskCompletionSource в нормальный асинхронный интерфейс и работать не теряя производительности и не блокируясь, и при этом пользуясь хорошим инструментом.
нет))) не правильнее))
не будет а-синхронный интерфейс давать вам видеть нормально логику работы кода и отслеждивать диалог с принтером.
Мыслите не уровнем «stateless» механизма принял-отдел, а уровнем всей задачи, когда вам надо хранить состояние и (очень желательно) контекст исполнения.
Вот давайте по порядку на примере
Инициализация принтера — это порядка 20 команд, каждая и которых имеет свой ответ. и в зависимости от ответа вы ещё должны иногда задавать дополнительные команды в зависимости от того, о чем вам рассказал принтер. Например для этой прошивки надо ещё кодировку поменять, а тут — установить плотность другую.
Рассмотрим на примере псевдо c++ кода гипотетической функции инициализации принтеров определенной серии с использованием синхронных функций:
Функция waitForBytes() — синхронная. не отдает управление пока не получен минимум 2 байта, или не истек таймаут. Если получено больше чем ожидается — отдает весь полученный объем данных.
int initPrinter() { serial->send( {0x??, 0x??, 0x??}); //запросим модель принтера byte[] resp=serial->waitForBytes( 18, 5000); //ждем ответ принтера - минимум 8 байт, 5 секунд. if (resp==NULL) return -100; // timeout if (sizeof resp <8 ) return -101; // ответ не распознан String model = new String(resp); if ( ! model->startsFrom("Posiflex") ) return -102; // не поддерживаемая модель принтера if ( ! model->equals("Posiflex PP5200") ) { serial->send( {0x??, 0x??, 0x??}); //запросим прошивку принтера resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд. if (resp==NULL) return -100; // timeout ... if (resp[0]==0x23 and resp[0]==0x24) // упс... у нас тут модель с прошивкой с известным багом { serial->send( {0x??, 0x??, 0x??}); //уст. кодировку 866 - в этой прошивке исходно 1251 resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд. if (resp==NULL) return -100; // timeout .... } }; serial->send( {0x??, 0x??, 0x??}); //переведем принтер в страничный режим с абс. позиционированием resp=serial->waitForBytes( 2, 5000); //ждем ответ принтера - 2 байта, 5 секунд. if (resp==NULL) return -100; // timeout .... return 0; }
Вот теперь, перепишите этот маленький и простой кусочек кода «в асинхронной, неблокирующей логике». Вы сможете сделать его столько же простым, прозрачным и понятным, что бы он не вызывал проблем при отладке, и не приводил к желанию материться?
Поймите, тут как раз тот случай, когда асинхронная логика «ну не в вперласть» никаким боком.
От слова совсем.
Удобно и просто прозрачно когда один поток, который ведет диалог с принтером, и ждет ответ от него, с сохранением контекста исполнения и состояния. Потому что это позволяет просто и легко описывать сложную логику диалога с устройством.
Если вам сложно мыслить принтером и последовательным компортом, то представьте, что у вас сокет на удаленный хост — у вас кастомный протокол, и в процессе «рукопожатия» — вам надо запросить у него кучу подробнотей, и в зависимости от того, что он вам ответит — что то ему рассказать дополнительно, или не рассказать.INC_R
08.08.2017 12:37+3В языках, которые умеют async/await (тот же C#), в вашем примере все сведется к такой замене:
serial->waitForBytes( 18, 5000)
на
await serial->waitForBytesAsync(18, 5000)
В результате код ничуть не усложнился, а взаимодействие стало асинхронным. Так что асинхронные интерфейсы при наличии соответствующих механизмов ничуть не сложнее синхронных.
dplsoft
08.08.2017 14:42-3Скажите, а вы точно понимаете что такое асинхронный и синхронный вызов?
Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции, которая заявлена как «асинронная», и говорите что взаимодействие стало асинхронным? вы уверены что не запутались?
Синхронный вызов — это когда исполнение приостанавливается в точке вызова.
Именно это вы и делаете, когда предлагаете использовать await.
А блокируется или приостанавливается при этом поток, какие потоки при этом вызываются, что используется внутри или не вызываются, что происходит внутри — это уже технические детали рализации.
Например похожие технические эффекты, (похожие на await) можно добиться в Java 'без всяких await' с использвоанием yeld() — он отдает ресурсы потока на другие задачи, не не разрушая контекст исполнения.
А «асинхронный» вызов, не приостанавливает выполнение кода в точке вызова в ожидании ответа, а сразу преходит к следующей инструкции.INC_R
08.08.2017 14:55+5Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции.
Синхронный вызов — это когда исполнение приостанавливается в точке вызова.
Именно это вы и делаете, когда предлагаете использовать await.Синхронный — это если бы я написал waitForBytesAsync.Wait().
Здесь же, если говорить грубо, await приводит не к синхронному ожиданию, а просто код после него будет вызван, когда асинхронная операция завершится. В остальное же время поток будет свободен и ничего ждать не будет.vintage
09.08.2017 13:49-2https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)
await — оператор синхронизации места его использования в зависимой задаче с завершением асинхронно запущенной задачи. В сумме получается вполне себе синхронное исполнение. То, что блокируется не системная "нить", а легковесное "волокно", сути не меняет.
INC_R
09.08.2017 14:36+1То, что блокируется не системная "нить", а легковесное "волокно", сути не меняет
Вообще-то от await не блокируется вообще ничего. В тот момент, когда используется await, происходит выход из текущего метода с незавершенной задачей, и дальше будет испольнятьс ято, что там снаружи написано. А там будет либо очередной await, либо какие-то вычисления, которым не требуется ожидание завершения операции.
vintage
09.08.2017 14:51-1Системные нити точно так же "выходят", а потом "возвращаются". Единственное, что отличает explicit реализацию сопрограмм (которой нужны async/await) от implicit (которой они не нужны) — это то, что первые не имеют стека, а вторые — имеют.
INC_R
09.08.2017 15:00Куда выходят? Когда? Куда возвращаются? Причем здесь стек? Причем здесь вообще потоки ("нити"), если async/await не завязан на них и будет спокойно работать в среде, где понятия потоков может вообще не быть?
vintage
09.08.2017 15:17-2Ваши async functions — частный случай coroutines, которые также известны как lightweight threads. Как вы думаете почему?
DistortNeo
09.08.2017 15:24Нет. Coroutines — это кооперативная многозадачность с планировщиком, работающим в пользовательском коде. Нити имеют свой стек, и переключение между ними производится явно.
А асинхронные функции — просто функции, которые компилируются в state machine возвращают Task<>. Просто синтаксический сахар для событийно-ориентированного программирования, и ничего больше. Планировщик может задачи выполнять как кооперативно, так и параллельно, в зависимости от реализации. Если, например, нет ни IOCP, ни epoll/kqueue, то ему ничего не останется, как запускать задачи параллельно.
vintage
09.08.2017 15:41Асинхронные функции — не более чем stackless coroutines.
Что thread, что stackfull coroutine, что stackless coroutine имеют пачку кода, расположенную последовательно и указатель на текущий кусок из этой пачки. Логически это сущности одного порядка. stackfull имеет бонусом стек, stackless — вырожденный случай stackfull с фиксированным размером стека = 1. Вот и всё различие. Исполняться они все могут хоть на одном ядре, хоть на нескольких, хоть по очереди, хоть попеременно — это уже во власти планировщика и расставленных yield и await.
INC_R
09.08.2017 15:37А какое отношение это имеет:
- к вопросу о том, как связаны понятия потоков (thread, нить) и async/await?
- к вашему изначальному утверждению, что от использования await исполнение внезапно становится синхронным? Я так и не увидел аргументов к этому утверждению.
vintage
09.08.2017 15:54Код синхронный, когда исполнение идёт последовательно с ожиданием завершения подзадач. Используете ли вы для синхронизации sync call, await или thread.join — совершенно не важно.
Смотрите, совершенно синхронный код, который при этом исполняется на всех ядрах процессора:
enum n = 1_000_000; enum delta = 1.0 / n; alias getTerm = (int i) { immutable x = ( i - 0.5 ) * delta; return delta / ( 1.0 + x * x ) ; }; immutable pi = 4.0 * taskPool.reduce!q{ a + b }( n.iota.map!getTerm ); assert(pi.approxEqual(3.1415926));
INC_R
09.08.2017 16:09Код синхронный, когда исполнение идёт последовательно с ожиданием завершения подзадач.
Видимо, ваш "синхронный код" — это вообще не то, что синхронные/асинхронные операции в общепринятом понимании. Приведите пример "асинхронного кода" в вашем понимании.
И по-вашему, если я начал долгую асинхронную операцию IO, потом долго что-то считал, и потом "жду" результата начатой ранее операции через await — это типа все синхронно?
vintage
09.08.2017 16:27-3потом "жду" результата
Это и называется "синхронизация". Без неё ваш код становится асинхронным.
INC_R
09.08.2017 16:57+1Неужели? То есть, по вашему, первый код синхронный, а второй нет?
// 1 async Task Example1() { await DoSomethingAsync(); DoSomethingElse(); } // 2 Task Example2() { var t1 = DoSomethingAsync(); return t1.ContinueWith(t => DoSomethingElse()); }
И не уходите от вопроса. Что в вашем понимании асинхронный код?
Вообще асинхронность подразумевает, что что-то происходит независимо от основного потока выполнения. Использование await соответствует этому определению, потому что фактически обозначает:
То, что после await, выполнится тогда, когда задача, на которой сделан await, завершится (что является асинхронным событием). А сейчас возвращаем управление тому, кто нас вызвал.vintage
09.08.2017 17:18-1Да, во втором случае, когда мы выходим из функции колбэк ещё не отработал.
INC_R
09.08.2017 17:33+2Сюрприз, в первом тоже. При вызове await происходит выход с незавершенной задачей. Это один и тот же код, написанный по разному.
vintage
09.08.2017 17:44-8Классный сюрприз — забыл await и всё сломалось.
INC_R
09.08.2017 17:53+2Поэтому ниже вы предлагаете вообще отказаться от async/await. Тогда-то ошибки не будет, просто сломается и починить нормально нельзя будет.
Вы уходите от первоначального вопроса: о том, что это один и тот же код и что он может быть в обоих случаях синхронен или асинхронен (в вашем понимании), но точно не в одном случае так, а в другом иначе.
И "забыл await" — это совершенно другой вопрос. Для этого есть инструментарий, который показывает предупреждения, для этого есть суффикс Async, который намекает на необходимость await, и все такое. Никто не мешает во втором примере вернуть t1, что будет точно такой же ошибкой.
vintage
09.08.2017 18:01-1Ложная уверенность — хуже её отсутствия.
А предупреждения можно к любому варианту выдавать.
DistortNeo
09.08.2017 17:55+1Классный сюрприз — забыл await и всё сломалось.
Умная среда разработки и компилятор напомнят программисту о том, что он дурак — вызвал функцию и не использует её результат.
dplsoft
09.08.2017 17:50Вообще асинхронность подразумевает, что что-то происходит независимо от основного потока выполнения. Использование await соответствует этому определению, потому что фактически обозначает:
определение не полное, именно поэтому вы путаете многопоточность и асинхронность.
Асинхронность (в случае локального, выполняющегося на одной машине кода) как правило говорит о том, что вы умеете реагировать на события/сообщения/уведомления от «того, что происходит независимо от вас» — когда что-то стороннее начинает вас уведомлять — и у вас как правило есть «обработчики»/«подписки»/«хуки»/«коллбеки»… — которые будут вызваны когда событие произойдет).
А если вы останавливаетесь на текущей инструкции, до тех пор пока не выполнится что то независимое от вас — это синхронный код.
Вы сами синхронищируетесь с чем-то — вне зависимости от того, на что вы смотрите или за чем вы следите.
То, что после await, выполнится тогда, когда задача, на которой сделан await, завершится (что является асинхронным событием). А сейчас возвращаем управление тому, кто нас вызвал.
завершение процесса — не стоит называть асинхронным событием.
Вот если бы ваш таск присылал вам в случайные моменты времени сообщения, и вы на них реагировали — это было бы асинхронным событием.
А так — это поведение, сходное с обычным «семафором».
Сравните вашу фразу с фразой "то, что после «вызова mуSemaphore.release(), выполнится только тогда, когда сосдений поток освободит семафор».
Но семафоры — это обычная синхронизация потоков, а ни какая ни «асинхронность».PsyHaSTe
09.08.2017 17:53+1Хорошо, такой вопрос, майкрософт в своей документации говорят, что async-await механизм асинхронного программирования, вы же очевидно утверждаете обратное. Вопрос — как вы считаете, майкрософт нарочно вводит людей в заблуждение или они просто неграмотные?
dplsoft
11.08.2017 01:51-4документация майкрософт… это очень скользкая штука. двусмысленнее этого только рекламные объявления.
в указанной вами статье от асинхронности только название, самопровозглашение «про асинхронность», да схожая с асинхронностью идея «отложенного задания» которая фактически описывается под видом «асинхронности».
прочитайте внимательно содержимое, ищите признаки описывающие асинхронность, и судите не по заголовку а по содержимому…
например там нет ни слова про коллбеки, или событийность, нет ни слова про асинхронный запуск (коим можно конечно классифицировать запучк процедуры в параллеьном потоке, но в асинк… типа же нет потоков(?)...)
там есть некая «регистрация продолжения (»continuation") про которыую никто не знает что это именно аткое.
судя по _содержимому_ это не асинхронность. это нечто другое. это некий механизм отложенных заданий, с принудительным вызовом задания к исполнению в момент await, с отсутствием блокировок, который обладает некими плюсами которыми обычно обладает асинхронный код, но обладающий логикой и стилем синхронного.
и только ради хайпа и «повышения продаж» они назвали его «асинхронным программированием». хотя это ни то ни другое.
в части документации — я за ними лет 20 наблюдаю и поверьте мне — они легко пойдут на подмену понятий и назовут «теплое» «мягким», если это повысит продажи, или поможет сформировать в головах своих разрабов некую «узкокалейность майкрософтвей», наплевав на то, что во всем остальном мире это именно «теплое».
узкокалейность мышления — это примерно как с 1с — но последним хоть простительна узкокалейность, потому что у них свои уникальные механизмы (кстати, очень грамотно методически продуманные), и они не создают путаницу в терминах — «регистры учета» вот есть только в 1с, а общепринятые термины импользуют именно так, как это принято везде. вот1 или вот2. это понимание точно такое же как скажем в js.
на мисте, кстати, очень правильная фраза есть:
1с не реализовала неявное преобразование обычного кода в асинхронный, переложив тяжесть реализации асинхронности на программиста.
.
а вот майкрософт, похоже… реализовало. но вот что?)))
То, что вы называете "асинхронным программированием" с легкой' руки майкрософта, никакое не асинхронное програмирование, а скорее «неявное преобразование» обычного/синхронного кода во что то иное при компиляции.
теоретически, байткод может быть и асинронным, но на этот счет в указанной документации нет ни каких иных признаков, кроме «самопровозглашения что это мол асинхронное»: нет ни слова про обработчики событий, ни коллбеки…
есть, повторюсь, некая «регистрация продолжения»(continuatiin)" но такого технического термина нет, и что имеется в виду — никто не знает, и додумки ваших коллег про то что это выкидывается в коллбек-функцию — пока не подтверждены ничем.
сейчас, снова повторюсь, я бы классифицировал await/async как некий мехапизм «отложенных заданий».
Но это никак не асинхронный механизм.
Потому что там даже «асинхронного запуска» (типа запустили, оно начало работать, а мы отвалились) нету — потому что и параллельности то нет: согласно опять же этой документации, асинк / await не требуют многпоточностиок значит он не выполняется параллельно. последнее кстати, очень подозрительно и смахивает на ошибку.
я конечно не знаю деталей этой магии, но фразаАсинхронные методы не требуют многопоточности, поскольку асинхронный метод не выполняется в своем собственном потоке. Он выполняется в текущем контексте синхронизации и использует время в потоке, только когда метод активен
… wtf?! вы серьезно?)))
так в каком потоке выполняются асинк-методы, из какого потока они выбирает время, если многопоточности нет, а в своем потоке он не выполняется. есть тайный тайный поток, про который забыли рассказать в документации и который не входит в понятие многопоточности? (это как история с пользователем Администратором, который на самом деле не совсем администратор, и может не всё, а есть еще более «администраторный администратор»)
или мс научились выполнять машинный код вне потока / вне времени / вне процессора?))) предположим, они используют свободное время потока, что бы в нем в фоне выполнять таски, наподобии того, как процессор делит время между задачами для эмуляции мультизадачности? ок. но это все равно выполнение таска в каком-то потоке. не блокирующее, но в потоке, а они говорят что нет, ибо мультипоточности нет.
в общем я не знаю кто кого обманывает, но, как минимум, называть использование отложенных заданий, с хитрым «магическим» процессом исполнения — асинхронным программированием — это подмена понятий.PsyHaSTe
11.08.2017 03:49+5документация майкрософт… это очень скользкая штука. двусмысленнее этого только рекламные объявления.
Значит все-таки врут. Как и мозилла, и куча других языков с аналогичным функционалом. Просто заговор какой-то
например там нет ни слова про коллбеки, или событийность, нет ни слова про асинхронный запуск (коим можно конечно классифицировать запучк процедуры в параллеьном потоке, но в асинк… типа же нет потоков(?)...)
То есть асинхронность это что-то с коллбеками? :) Никогда не встречал такого определения. Наверное, все же бывает и по-другому.
там есть некая «регистрация продолжения (»continuation") про которыую никто не знает что это именно аткое.
Ну если вы не знаете, не надо выдавать свою неграмотность за проблему языка.
судя по содержимому это не асинхронность. это нечто другое. это некий механизм отложенных заданий, с принудительным вызовом задания к исполнению в момент await, с отсутствием блокировок, который обладает некими плюсами которыми обычно обладает асинхронный код, но обладающий логикой и стилем синхронного.
Получается вы все-таки поняли. Да, async/await это способ писать асинхронный код так, чтобы он выглядел и читался нормально, а не как лапша из коллбеков.
Потому что там даже «асинхронного запуска» (типа запустили, оно начало работать, а мы отвалились) нету — потому что и параллельности то нет: согласно опять же этой документации, асинк / await не требуют многпоточностиок значит он не выполняется параллельно. последнее кстати, очень подозрительно и смахивает на ошибку.
Наверное, потому что многопоточность и асинхронность это разные вещи, м?
сейчас, снова повторюсь, я бы классифицировал await/async как некий мехапизм «отложенных заданий».
Но это никак не асинхронный механизм.асинхронность это и есть про "я не могу дать результат сейчас, подожди, когда будет готово, и зарегистрируй действие, которое должно в этот момент произойти". await это как раз про это.
так в каком потоке выполняются асинк-методы, из какого потока они выбирает время, если многопоточности нет, а в своем потоке он не выполняется. есть тайный тайный поток, про который забыли рассказать в документации и который не входит в понятие многопоточности? (это как история с пользователем Администратором, который на самом деле не совсем администратор, и может не всё, а есть еще более «администраторный администратор»)
Там понятно написано для человека в теме, что такое контекст и какое отношение он имеет к потокам.
или мс научились выполнять машинный код вне потока / вне времени / вне процессора?))) предположим, они используют свободное время потока, что бы в нем в фоне выполнять таски, наподобии того, как процессор делит время между задачами для эмуляции мультизадачности? ок. но это все равно выполнение таска в каком-то потоке. не блокирующее, но в потоке, а они говорят что нет, ибо мультипоточности нет.
Этим тайным знанием обладают все. Если вы вдруг все же не читали ссылку, которую я давал, то пожалуйста, и там на пальцах объясняется, почему асинхронность возможна с одним-единственным потоком, как действия могут выполняться без потока вообще, ну и прочие веселые темы.
в общем я не знаю кто кого обманывает, но, как минимум, называть использование отложенных заданий, с хитрым «магическим» процессом исполнения — асинхронным программированием — это подмена понятий.
Никакой магии нет, просто компилятор сам переписывает код на коллбеки, чтобы программист не парил себе мозг этими простынями и что компилятор превращает такой код, который просто и понятно читается человеком
В такой
Где начинается чехарда с обработкой ошибок, непонятно что где происходит и непонятным дебаг экспириенсом. Вот презенташка целиком.
Подытоживая: оба варианта на слайде является двумя вариантами записи одного и того же. Если во втором случае вы такой "о, коллбек, значит асинхронность", а в первом "чет коллбеков нету, это какая-то фигня", значит вы просто не понимаете ни асинхронности, ни того, как современные ЯП облегчают жизнь и позволяют писать меньше бойлерплейта.
dplsoft
11.08.2017 09:17-6по порядку ))
Значит все-таки врут. Как и мозилла, и куча других языков с аналогичным функционалом. в документации мозилла и в документации майкрософт одинакового только слово async.
Просто заговор какой-то
мозилла, в отличии от сишарпа, возвращает промис, в который вы подставляете функцию-обработчик, которая должны быть вызвана.
это построение асинхронного кода. в отличии от «не-явных преобразований в c#».
То есть асинхронность это что-то с коллбеками? :) Никогда не встречал такого определения. Наверное, все же бывает и по-другому.
асинхронность, это event-driven стиль построения системы. коллбеки — это наиболее частый способ указания на функцию обработчик.
у вас нет в коде событий и функции обработчика событий? значит ваш код — ни разу ни асинхронный, потому что он построен не по асинхронной логике.
то что появляется в результате неявных преобразований в байткоде при компиляции — дело совершенно третье (там действительно может быть полный аналог мозиловского асинка, но может и нет), и это попадает под тему замены логики работы при компиляции.
а то, что вы считаете что есть как-то по другому, так это итог замены понятий и путаницы в терминологии авторов сишарпа, и несоответствия этих теринов, тем, которые используют все остальные.
асинхронность это и есть про «я не могу дать результат сейчас, подожди, когда будет готово, и зарегистрируй действие, которое должно в этот момент произойти».
верноawait это как раз про это.
не верно, потому что await не регистриурет никаких действий, и его поведение, на уровне исполнния последовательности строчек кода, ничем не отличается от работы семафора.
да, await волшебным образом не блокирует текущий поток, но это иные свойства, не связанные event-driven стилем разработки.
Этим тайным знанием обладают все. Если вы вдруг все же не читали ссылку, которую я давал, то пожалуйста, и там на пальцах объясняется, почему асинхронность возможна с одним-единственным потоком, как действия могут выполняться без потока вообще, ну и прочие веселые темы.
асинхронность возможна с одним потоком, да.
действия выполняющиеся вне какого-либо потока — нонсенс, противоречит основам работы операционной системы. это базис. код всегда выполняется в каком либо потоке. сисемном, фоновом, еще каком. но если процессор исполняет код, значит какойто поток есть. могут быть действия или вычисления без потоков, но тогда не на процессоре. у нас наши коллбеки выполняются
или у вас есть ссылка на теорию операционных систем или ещё что что? бложики «разных специалистов» — это конечно хорошо, но подмена понятий — это уже плохо.
еще раз повторюсь: то, что await (вероятно) приводит к асинхронному байткоду — это результат компиляции. я могу вам постороить компилятор, который будет преобразовывать код в набор runnable (скажем до await и после) и выполнять эти раннаблы в пуле потоков, итоговое поведение будет точно точно таким же — это не будет блокировать ваш гуи поток, все что после await будет выполняться строго после завершния того раннабла, в который я засуну сам await, но это ни разу не будет асинхронный или синхронный байткод. это нечто иное.
на остальную «лапшу» отвечу попозже.mayorovp
11.08.2017 11:03+5мозилла, в отличии от сишарпа, возвращает промис, в который вы подставляете функцию-обработчик, которая должны быть вызвана
И в чем же вы видите принципиальную разницу между методами then и ContinueWith?
grossws
11.08.2017 11:45+5мозилла, в отличии от сишарпа, возвращает промис, в который вы подставляете функцию-обработчик, которая должны быть вызвана.
Двойная буква ять.
await
в js — сахар надPromise#then
, работающий поверхPromise
(с автоматической конвертацией не-Promise в resolved promise. См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await.
То, что в шарпе не отличается ничем, кроме имён классов и методов.
INC_R
11.08.2017 11:55+6не верно, потому что await не регистриурет никаких действий, и его поведение, на уровне исполнния последовательности строчек кода, ничем не отличается от работы семафора.
да, await волшебным образом не блокирует текущий поток, но это иные свойства, не связанные event-driven стилем разработки.Во-первых, принципиальное отличие await от семафора — отсутствие блокировки. Во-вторых, "волшебным образом" он это делает потому, что это по сути и есть callback, о чем вам сто раз сказали. Но вы, видимо, знаете, как работает await, лучше, чем официальная документация и целая куча специалистов и экспертов.
действия выполняющиеся вне какого-либо потока — нонсенс, противоречит основам работы операционной системы
Вам бы Таненбаума почитать или Руссиновича. Некоторые действия выполняются ОС на таком уровне, где понятия потоков и процессов нет вообще. Это к слову. И есть, например, такая штука, как DMA, которой программно ставится задание что-то где-то переместить в памяти, и она потом это делает сама независимо от процессора, который в это время может делать что угодно другое. Каноничный пример асинхронной операции, для выполнения которой не требуется никакого потока, о чем и была речь в статье.
dplsoft
11.08.2017 13:08-4И есть, например, такая штука, как DMA, которой программно ставится задание что-то где-то переместить в памяти, и она потом это делает сама независимо от процессора, который в это время может делать что угодно другое.
DMA — это не выполнение вычислений описанных у вас в async функции.
Когда у вас вычисления внутри вызываемой через await функции — они выполняется внутри какого-то потока.
и выполняемое операционкой действия глубоко внутри — тоже формально прикреплены к какому-либо потоку. то не имеет отнощения к мифу о внепоточности кода внутри awaitINC_R
11.08.2017 13:44+5Когда у вас вычисления внутри вызываемой через await функции — они выполняется внутри какого-то потока.
Вычисления — это что? Вся полезная нагрузка или именно обычные вычисления на CPU? Если вся полезная нагрузка, то вы ошибаетесь, потому что полезная нагрузка может выполняться в том числе на DMA и подобных механизмах. Если вы только про вычисления на CPU вроде решения всякого матана — ну да, это в конечном итоге делает какой-то поток, и что? Основной профит от асинхронности идет за счет асинхронного IO, который процессор как раз вообще не занимает и никаких потоков там нет.
и выполняемое операционкой действия глубоко внутри — тоже формально прикреплены к какому-либо потоку
Потоки в ОС появляются на определенномуровне абстракции. На уровнях ниже есть драйверы, прерывания, DPC, которые работают "просто на процессоре" и никаких потоков там нет.
dplsoft
11.08.2017 13:34-3Во-вторых, «волшебным образом» он это делает потому, что это по сути и есть callback, о чем вам сто раз сказали. Но вы, видимо, знаете, как работает await, лучше, чем официальная документация и целая куча специалистов и экспертов.
вот как раз в документации то, ничего про callback не сказано.
Более того — там несется некая ахинея про код, выполняющийся вне основного потока, но без потока. Я согласен что возможно, async таск выполняется вне потока вашей программы, а в неком «сервисном потоке», который предоставляется .net-средой -исполнения. Но если у вас есть вычисления внутри async функции, они будут выполняться внутри потока.
А в документации которую вы показали рассказывается о некой «магии», и ни слова о том, как это действительно проиходит. Потому вы или покажите офф. документацию где явно сказано, что там действительно последующая часть кода выносится в call-back-функцию, или признайте что вы «не совсем правы». Потому что повторюсь — в документации рассказывается о неких «contonuation» — а что это — нигде не написано.
Более — того. По большому счету не важно, что там внутри. Считаете что там создается асинхронный байткод — ок, я допускаю, я не против. Я против того, что бы на основании того, _во_что_ превращается await в машинном коде, классифиировать исходный код.
Await позволяет вам создавать код в синхронном стиле, который при компиляции создает асинхронный. Да, он добавляет в эту синхронность особые точки в которых поток может заняться другими задачами пока выполняется код вызванный через await. Да, такое особое свойство. Но код последователен, синхронен, событий и обработчиков в нем не объявлено. Значит это синхронный код.
А то что вы считаете что при синхронности должен «блокироваться поток» — это следствие того, что вы привязываете определение к конкретной реализации.
бритва оккама, говорите? если код построен по логике последовательного выполнения операций, синхронного, выглядит как синхронный, выполняется как синхронный. подчинятся логике синхронного — значит код синхронный.
Исходный код. но не байт код, который получаем в итоге компиляции.
не верно, потому что await не регистриурет никаких действий, и его поведение, на уровне исполнния последовательности строчек кода, ничем не отличается от работы семафора.
да, await волшебным образом не блокирует текущий поток, но это иные свойства, не связанные event-driven стилем разработки.
Во-первых, принципиальное отличие await от семафора — отсутствие блокировки.
давайте ещё раз)))
то что await, приводит к тому, что перебор операций исходного кода в данной точке приостанавливается (но поток не блокируется) — это хорошее свойство но с точки зрения поведения, эта логики кода, к понятию «асинхронность» он имеет отношение косвенное, связанное только с тем как оно реализовано.
Повторюсь — как вы будете классифицировать этот код, если он будет собран компилятором, который разнесет ваш код на 3 Runnable (до await, сам await, после await ), сложит их в очередь, и поставит на выполнение для некого планировщика? плаировщик будет брать на выполнение следующий раннабл только тогда, когда выполнится предыдущий.
первый и последний runnable он выполнит в gui потоке, сам await — в сервисном/фоновом.
Поведение будет точно таким-же.
«GUI-Поток» не блокируется, операции выполняются строго в нужном порядке, ждут друг друга без блокировки gui потока.
Классификация исходного кода изменится или нет? можете ответить на этот вопрос?dplsoft
11.08.2017 13:40Поведение будет точно таким-же.
«GUI-Поток» не блокируется, операции выполняются строго в нужном порядке, ждут друг друга без блокировки gui потока.
Классификация исходного кода изменится или нет? можете ответить на этот вопрос?
при этом отмечу, что никаких асинхронных call-back в этом случае нет. нет ни обработчиков, ничего. Просто конвеер задач на входе планировщика.
от такой реализации — исходного ли «синхронность» исходного кода?
INC_R
11.08.2017 14:01+3вот как раз в документации то, ничего про callback не сказано.
А в документации которую вы показали рассказывается о некой «магии», и ни слова о том, как это действительно проиходит. Потому вы или покажите офф. документацию где явно сказано, что там действительно последующая часть кода выносится в call-back-функцию, или признайте что вы «не совсем правы». Потому что повторюсь — в документации рассказывается о неких «contonuation» — а что это — нигде не написано.Запросто: https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern
Under the covers, the await functionality installs a callback on the task by using a continuation. This callback resumes the asynchronous method at the point of suspension.
In asynchronous programming, it is very common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. Traditionally, this has been done by using callback methods. In the Task Parallel Library, the same functionality is provided by continuation tasks. A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent finishes.
Но код последователен, синхронен, событий и обработчиков в нем не объявлено. Значит это синхронный код.
Где вы вообще взяли термин "синхронный код" и что это значит? Какая разница, написано оно текстом через события и обработчики или последовательно, если результат одинаковый? Синхронность/асинхронность — свойство выполняемых операций, а не текста, их представляющего. Мне без разницы, как писать код: через await или на callback-ах, результат будет одинаковый. Разве что на await это читаемо, а на callbackах мозг можно сломать.
Классификация исходного кода изменится или нет? можете ответить на этот вопрос?
Опять же. Я не понимаю, где вы взяли термин "синхронный исходный код" в вашей интерпретации. Приведите, пожалуйста, авторитетный источник.
Но если говорить об этом в ваших терминах, то нет, если в результате поведение не отличается от исходного, то "синхонность" не изменится.
DistortNeo
09.08.2017 17:59"Синхронизация" и "синхронность" — разные термины, вообще-то, а вы их путаете.
Уместнее использовать термины "блокирующий" и "неблокирующий", тогда путаницы не будет.
А синхронизация — это механизм обеспечения порядка выполнения инструкций, и ей все равно, какой это код.
PsyHaSTe
09.08.2017 18:04-1Знаете, я тут ради интереса поискал по странице, и слово "синхронизация" потребляется исключительно вами, vintage и dplsoft. Сдается мне, что не я путаю эти термины потому что я все это время говорил про совершенно другой.
vintage
09.08.2017 18:07Вы сможете дать определение "синхронности" без использования слова "синхронизация"?
pnovikov Автор
09.08.2017 18:12+4"Синхронно" — это исполняется прямо здесь и сейчас.
"Синхронизация" — не имеет отношение к асинхронности. Вроде как общепринято означает использование семафорв/мьютексов/спинлоков/watever чтобы потоки не покусали общие данные.
DistortNeo
09.08.2017 18:21+1Мне, в принципе, нравится идея отсюда:
https://toster.ru/q/125725
Код называется «синхронным», если он использует только блокирующие операции операционной системы, такие как ввод-вывод, ожидание по таймеру, ожидание события, например, завершение потока.
Когда же код использует только неблокирующие операции, то такой код является «асинхронным».
Ну если же код использует одновременно и блокирующие, и неблокирующие операции, то такой код называется «говнокод».
И самое главное: языковая конструкция `await` не является вызовом к операционной системе, поэтому её появление в коде не позволяет судить о том, является ли код асинхронным или нет.
Nakosika
09.08.2017 18:26-1Я бы лучше поделил код на «последовательный» и «не последовательный».
Параллельно выполняемый код — это был бы частный случай «не последовательного» кода, другой пример не последовательного кода — это каллбэк. А синхронизация — это способ сделать не параллельный код последовательным.
К сожалению, у нас много книг написано про параллельное выполнение, но никакого внимания не уделяется «не последовательному». А между тем это гораздо более частый случай учложнения наших приложений.Nakosika
09.08.2017 18:32Вернее, «синхронизация — это способ сделать параллельный код последовательным»
pnovikov Автор
09.08.2017 18:40*в некоторых отдельных местах, но в целом код останется параллельным. (1000 потоков скачивают файлы и увеличивают счетчик на 1 — скачивают параллельно, счетчик увеличивают последовательно)
INC_R
09.08.2017 18:06+3А если вы останавливаетесь на текущей инструкции, до тех пор пока не выполнится что то независимое от вас — это синхронный код.
Сколько еще раз надо написать, что при вызове await не происходит остановки, а просто все после await фактически становится callback-ом? Это ровно настолько же синхронизация, насколько синхронизацией является подписка на событие завершения асинхронной операции и выполнение какой-то операции в качестве реакци на это событие. Если это в вашем понимании синхронизация, то пусть так. Но наличие такой "синхронизации" не делает код синхронным. От того, что две асинхронные операции "синхронизированы" так, что одна начинает работать после завершения другой, их выполнение синхронным не станет. Или вы будете с этим спорить?
завершение процесса — не стоит называть асинхронным событием.
Вот если бы ваш таск присылал вам в случайные моменты времени сообщения, и вы на них реагировали — это было бы асинхронным событием.Вообще я говорил про завершение задачи, а не процесса. Любой задачи. Ей как раз может быть возникновение случайного события, или ответ на запрос к удаленному сервису, или запись файла, или нажатие пользователем на клавиатуру. Окончание этих задач — это все асинхронные события. И потоки, семаформы и прочее здесь вообще не при чем.
dplsoft
09.08.2017 17:34И по-вашему, если я начал долгую асинхронную операцию IO, потом долго что-то считал, и потом «жду» результата начатой ранее операции через await — это типа все синхронно?
это запуск потока с последующей синхронизацией потоков.
И да, это, по логике/стилю работы работы — синхронный код.
Приведите пример «асинхронного кода» в вашем понимании.
а-синхронный код, в классическом, верном, понимании — это суть, событийно ориентированный код, который построен на колл-беках и/или обработчиках событий
примеры асинхронного апи: WinApi функции работы с последовательными портами, в которые вы передаете ссылку на функцию, которую надо вызывать, когда на вход поступят байтики. Qt-шные сигнал-слоты. Промисес в js (потому что вы передаете им функцию/ссылку на функцию)
int main() { ... serial->setOnByteIncomeCallBack(&onByteInome); serial->writeBytes(....); } int onByteInome(byte bytes[]) { ... }
INC_R
09.08.2017 17:42+2а-синхронный код, в классическом, верном, понимании — это суть, событийно ориентированный код, который построен на колл-беках и/или обработчиках событий
Ну и чем await не угодил? Это ровно то же самое. Визуально (то есть в исходнике) он выглядит как синхронный код с ожиданимями и всем таким, потому что так и задумано. Фактически же await обозначает, что все ниже него будет callback-ом. Довольно грубое описание, но концептуально это ровно так.
vintage
09.08.2017 17:47-4Кто выглядит как утка и крякает как утра — того и называют уткой. Даже если внутри у неё конечный автомат и колбэки.
PsyHaSTe
09.08.2017 17:50Такой вопрос задам просто, зачем нужна асинхронность? Как вы понимаете её задачу?
vintage
09.08.2017 18:06-4Да не нужна она. Даже в JS, на хайпе которого она выехала, её уже закапывают.
INC_R
09.08.2017 18:12+1Ничего себе. То есть, например, если надо опросить 1000 сервисов на предмет курса валют или прогноза погоды, то надо делать это строго синхронно, по-очереди, дожидаясь ответа от каждого, а не поставить 1000 асинхронных задач одновременно? Сильное утверждение
, поржал.pnovikov Автор
09.08.2017 18:29Я боюсь, в этом случае кстати надо использовать не асинхронность, а многопоточность, ибо как имеет смысл в одном потоке опрашивать по 10-20 серверов + барьерная синхронизация, а не делать 1000 тасок. Ну чисто так… Я бы так сделал по крайней мере.
INC_R
09.08.2017 18:36+1Это же частности. Я бы поставил 1000 тасок, а там планировщик сам разберется, как их раскидать по потокам, создать ли 10, 20, 50 потоков или вообще все дать в одном. Это зависит в том числе от железа. На 1-ядерном ПК нет смысла заводить 20 потоков.
И в конце-концов неважно, один поток будет делать 1000 запросов к сервисам или несколько, если время на инициализацию асинхронного запроса заведомо меньше, чем время ответа сервиса.
DistortNeo
09.08.2017 18:38+1> Я боюсь, в этом случае кстати надо использовать не асинхронность, а многопоточность, ибо как имеет смысл в одном потоке опрашивать по 10-20 серверов + барьерная синхронизация,
Одно другому не мешает. В чём проблема создать 1000 задач, которые планировщик сам распихает по потокам, как посчитает нужным?
DistortNeo
09.08.2017 18:23+4> Да не нужна она. Даже в JS, на хайпе которого она выехала, её уже закапывают.
Закапывают её только люди, которые эту асинхронность не понимают по причине недостатка образовательной базы. Например, не наигрались с потоками в C++, не пользовали функции-генераторы в C# и т.д.
DistortNeo
09.08.2017 18:00+3Ни фига себе — выглядит как утка. Синхронный метод вернёт
byte[]
, а асинхронный —Task<byte[]>
.vintage
09.08.2017 18:03-2getDataSync()
иawait getData
вернут одно и то же.DistortNeo
09.08.2017 18:28> getDataSync() и await getData вернут одно и то же.
За одной лишь разнице, что во втором случае сама функция, в которой вызывается getData, будет возвращать Task, тогда как в первом случае это недопустимо. И выход из функции во втором случае произойдёт на await, а не по завершении выполнения всей функции.
PsyHaSTe
09.08.2017 17:49+1Внезапно
async Task<int> ReadBytesAndGetWrittenByteCountAsync() { byte[] bytesRead = await serial.ReadBytes(); // читаем с устройства долго и асихронно return bytesRead.Length; // возвращаем количество записанных байт }
Эквивалентен:
async Task<int> ReadBytesAndGetWrittenByteCountAsync() { Task<byte[]> bytesReadTask = serial.ReadBytes(); // читаем с устройства долго и асихронно var bytesCountTask = bytesReadTask.ContinueWith(x => x.Result.Length);; // когда таска выполнится, будет вызван коллбек и мы посчитаем количество байт return bytesCountTask // возвращаем задачу, на которой пользователь будет ждать }
Что весьма похоже на ваш пример с
onByteInome
0xd34df00d
09.08.2017 21:26О, Qt, хорошо, что вы вспомнили.
У меня поверх кутешногоQFutureWatcher
с сигналами и по-вашему асинхронным кодом написана монадическая обёртка, которая позволяет писать что-то вроде
Sequence(QtConcurrent::run(func)) >> [] (auto result1) { return anotherFuture(result1); } >> [] (auto result2) { return smthElse(result2); };
и так далее.
Это асинхронный код?
Если я теперь возьму предложение Саттера по метаклассам, какое-нибудь ещё предложение по трансформации AST и наваяю что-то такое, что позволит переписать мне этот код как
await auto result1 = QtConcurrent::run(func); await auto result2 = anotherFuture(result1); return smthElse(result2);
будет ли этот код синхронным или асинхронным?mayorovp
10.08.2017 09:38Для await есть отдельное предложение, C++ Extensions for Coroutines (ранее называлось Resumable Functions)
0xd34df00d
10.08.2017 20:16Да, тоже хороший пример.
Просто когда это сделано в языке, а не либой, это не так интересно.
senia
09.08.2017 14:42+3Я бы хотел разобраться в вашей терминологии:
1. Вот это асинхронный код? Если нет, то что здесь блокируется?
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = { val person: Future[Person] = db.findPerson(email) val purchases: Future[List[Purchas]] = person.flatMap(p => storeRestServce.getPurchases(p.id)) val bonuses: Future[Seq[Bonus]] = purchases.flatMap(ps => Future.sequence( ps.map(p => bonusesSoapService.getBonus(p.promoCode)) )) val validBonuses: Future[Seq[Bonus]] = bonuses.map(_.filterNot(_.expired)) return validBonuses }
2. А этот? Если нет, то где здесь «блокировки»?
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = for { person <- db.findPerson(email) purchases <- storeRestServce.getPurchases(person.id) bonuses <- Future.traverse(purchases)(p => bonusesSoapService.getBonus(p.promoCode)) } yield bonuses.filterNot(_.expired)
3. А вот этот? (Тот же вопрос про «блокировки»)
def getBonusesByPersonEmail(email: Email): Future[List[Bonus]] = async { val person = await{ db.findPerson(email) } val purchases = await{ storeRestServce.getPurchases(person.id) } val bonuses = await{ Future.traverse(purchases)(p => bonusesSoapService.getBonus(p.promoCode)) } bonuses.filterNot(_.expired) }
vintage
09.08.2017 15:01-2К сожалению я не знаком с этим языком, так что ничего определённого сказать не могу.
Тут я подробней всё это расписывал: https://habrahabr.ru/post/307288/
senia
09.08.2017 15:10+3С чем знакомы? Java? Haskell? Знаете что такое монада?
Просто первый и последний примеры должны читаться и без знания scala. Достаточно понимать, что — Future — аналог CompletableFuture из Java или Task из C#. Методы map и flatMap работают так, как предполагается у монады.
Второй можно пропустить (аналог do-notation из haskell).vintage
09.08.2017 15:20-3Первый асинхронный, ибо колбэки вызываются чёрт знает когда.
Последний синхронный с явно обозначенными точками синхронизации.
DistortNeo
09.08.2017 15:27+2Вы удивитесь, но написано абсолютно одно и то же.
Если на пальцах, то:
async Task<...> A() { ... var x = await B(); ...; }
эквивалентно
Task<...> A() { ... return B().ContinueWith(() => { }); }
vintage
09.08.2017 15:58Значит я не правильно понял последний код и он тоже асинхронный. В принципе весь FP — об асинхронном исполнении.
senia
09.08.2017 16:07+2Но именно так работает async/await в Scala/C# и JS — вешает весь код дальше в колбек. На Future, Task и Promise соответственно.
Не находите, что все ваши рассуждения в этой ветке теряют смысл?dplsoft
09.08.2017 18:23-1Но именно так работает async/await в Scala/C# и JS — вешает весь код дальше в колбек. На Future, Task и Promise соответственно.
Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.
Не находите, что все ваши рассуждения в этой ветке теряют смысл?
Исходно, как я помню, мы говорили именно о логике исходного кода, и о том, что именно «синхронный» код позволяет описывать многие вещи проще и быстрее, чем событийно-ориентированный, асинхронный подход, потроенный на обработчиках и call-back.
и так:
Исходный код с использвоанием await — синхронный.
Байт-код/машинный код может быть каким угодно. И это не отменяет синхронности исходного кода.
Или вот для аналогии ещё пример: если я написал синхронные обертки над асинхронными функциями с call-back — то та часть исходного кода, которая использует обертки — синхронная. Вне зависимости от того, что внутри обертки — асинхронные функции.
или вот ещё загадка:
У меня есть абстрактный класс с одной функцией waitForBytes() которая не должна отдавать управлениедо момента своего окончания. Далее, я реализовал интерфейс в 2-х потомках — в одном я использвал синхронной функции, а в другом — на синхронных функциях и на асинхронных callback.
Скажите — код который использует интерфейс — он синхронный или асинхронный? если думать о том, что лежит под функцией, а не над тем, как оно должно себя вести, то у вас возникает ситуация, что классификация участка исходного кода будет динамически меняться. Что есть бред.INC_R
09.08.2017 18:32+2Тогда, согласно вашим определениям, любой код синхронный, потому что он выполняется так, как он написан, а не как ему вздумается. Только это называется не синхронность, а детерменированность.
Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.
Нет, они одинаковые. Трансформации, компиляция и все прочее не может менять логику.
dplsoft
09.08.2017 20:04+1Нет, они одинаковые. Трансформации, компиляция и все прочее не может менять логику.
Вы о какой логике говорите? Логика бывает разного уровня.
Если о логике и поведении описанном в коде высокого уровня — то оно сохраняется.
Но то как это поведение будет отражено в машинные инструкции — и по какой логике они будут работать — это может как сильно отличатьcя как от ваши представлений, так и сильно разниться от компилятора к компилятору.
Более того, логика работы исходного кода и логика работы машинного кода — они обязаны быть разными. Но обязаны реализовывать одинаковое поведение.
Например, на уровне кодов процессора нет никакого ООП и наследования.
О какой одинаковости логики вы вообще можете говорить, если логика работы вашего исходного кода построена на ООП-понятиях?
Сохраняется поведение, но даже не всегда детали логики исходного кода.
Посмотрите мой ответ https://habrahabr.ru/post/334964/#comment_10352244
и скажите — оптимизирущие компиляторы — они какую логику меняют? и что они вообще меняют?
ну на другой вопрос про разную реализацию await- тоже хотелось бы услышать ответ. спасибо )
PsyHaSTe
09.08.2017 18:50+1Вы зачем-то смешиваете логику работы откомпилированного байт-кода (после оптимизации, преобразований и трансформации) и логику исходного кода. Они могут быть разные.
Вообще-то компиляция это процесс отображения одного представления кода в другой. Логику выполнения компилятор не трогает, более того, если вдруг такое случайно происходит, то это баг и его очень быстро чинят.
dplsoft
09.08.2017 19:44+1Вообще-то компиляция это процесс отображения одного представления кода в другой. Логику выполнения компилятор не трогает, более того, если вдруг такое случайно происходит, то это баг и его очень быстро чинят.
Не совсем так))
Если быть более точными — компилятор реализует поведение, описанное на языке высокого уровня, с использованием языка более низкого уровня.
А вот какая логика будет использована на этом низком уровне — на уровне машинного кода или байткода — это уже его личное дело, покуда он остается в границах ожидаемого поведения, описанного языком высокого уровня.
Вот возьмем оптимизирующий компилятор — если поведение не изменится, он может вообще выкинуть всю логику расчета внутри вашей функции, и сразу выдавать на выход константу. Если, конечно, он обнаружит, что вне зависимости от входных параметров всегда получается одно и то же число.
Он легко может начать не умножать или суммировать, а дергать сдвиги, или, скажем, развернет циклы, если посчитает что так будет лучше. У вас в исходном коде — «визуально-однопоточный» циклический код, а на уровне машинного кода — 4 линейных параллельно исполняющихся цепочки.
Или, что скорее всего происходит и смущает вас — превратит синхронный код с await, в лапшу последовательно вызываемых асинхронных функций, связанных callback-ами.
А другой компилятор, может легко сделать не коллбеки, а 3 потомка и семафоры. Ведь согласитесь, await можно легко «сделать на семаформах».
Поведение программы, в обоих будет одинаковое, а логика и структура работы машинного кода (или байт кода) — разная.
И вот в такой ситуации — когда у нас реализация одного поведения, может быть построена на разных принципах, разной логике — скажите — как классифицировать сам исходный код? — по структуре его поведения, или по структуре того, что получается на выходе компилятора?
Вы, зачем-то, уцепились за выход компилятора. Вы уверены что это правильно?PsyHaSTe
09.08.2017 23:14Так это ВЫ уцепились за поведение компилятора. Я-то как раз говорю что с моей ТЗ это все асинхронно выполняющийся код, что там под капотом — стейт машины или гномики меня не волнует. Разной логики там быть не может, потому что одно является отображением другого.
А вот какая логика будет использована на этом низком уровне — на уровне машинного кода или байткода — это уже его личное дело, покуда он остается в границах ожидаемого поведения, описанного языком высокого уровня.
Если в терминах маш. команд или байт кода цикл является джампом на лейбл начала цикла, логика цикла от этого не меняется
Поведение программы, в обоих будет одинаковое, а логика и структура работы машинного кода (или байт кода) — разная.
Я работаю не с байткодом, а с ЯП. Что под капотом — не важно. Это ведь ваше собственное утверждение :)
dplsoft
11.08.2017 02:52-2Разной логики там быть не может, потому что одно является отображением другого
А вот какая логика будет использована на этом низком уровне — на уровне машинного кода или байткода — это уже его личное дело, покуда он остается в границах ожидаемого поведения, описанного языком высокого уровня.
Если в терминах маш. команд или байт кода цикл является джампом на лейбл начала цикла, логика цикла от этого не меняется
После «разворачивания циклов» у вас циклов как таковых не будет в байткоде, и не будет у вас ни джампов ни лейблов. Будет простой линейный код.
О какой «одинаковой логике» вы говорите? В сорсах у вас цикл и условия, а в байткоде — линейный поток вычислений без джампов и условий. это сохранение логики? О сохраннии какой именно логики вы говорите? не позорьтесь)))
или классифицируйте в терминах вашей вселенной, что именно делает с кодом оптимизирующий компилятор, и какую логику (или что именно?!) он меняет?
PsyHaSTe
11.08.2017 03:54+1О какой «одинаковой логике» вы говорите? В сорсах у вас цикл и условия, а в байткоде — линейный поток вычислений без джампов и условий. это сохранение логики? О сохраннии какой именно логики вы говорите? не позорьтесь)))
Сохранения логики означает что используя средства языка я не смогу увидеть разницы между результатом работы скомпилированной программы от той, которая выполняется на процессоре, где этот язык является нативным. Это очевидное определение.
dplsoft
11.08.2017 08:36-2Сохранения логики означает что используя средства языка я не смогу увидеть разницы между результатом работы скомпилированной программы от той, которая выполняется на процессоре, где этот язык является нативным. Это очевидное определение.
это сохранение поведения, а не логики работы.
да, поведение не будет противоресить той логике которую вы заложити в исходный код, но не факт что логика работы, структра кода будет такими же.
вот вам пример одинакового поведения, но разной логики: если насекомые используют магнитные поля что бы находить север, а птицы ориентируются по полярной звезде, и при этом в пасмурную погоду они не летают «потому что стремно» — разныцы вы в их поведении не увидите.
так и компилятор. он сделает такие реобразования, о которых вы и не помыслите, но поведение останется таким же.
разберитесь с разными уровнями логики.PsyHaSTe
11.08.2017 12:15+3это сохранение поведения, а не логики работы.
От жонглирования синонимами ничего не меняется. Если для меня нет способа найти разницу между двумя представлениями, значит этой разницы нет. Бритва Оккама во все поля.
dplsoft
11.08.2017 13:46-2От жонглирования синонимами ничего не меняется. Если для меня нет способа найти разницу между двумя представлениями, значит этой разницы нет. Бритва Оккама во все поля.
Получается, что «по вашей версии бритвы оккама» — оптимизирующий компилятор ничего не меняет? но по факту же он «что то» меняет — программа начинает работать быстрее, меньше ресурсов используется.
Так какую логику он меняет — можете описать? не уходите от ответа.PsyHaSTe
11.08.2017 15:10+2Конечно не меняет, по определению компилятора.
Compiler optimization is generally implemented using a sequence of optimizing transformations, algorithms which take a program and transform it to produce a semantically equivalent output program that uses fewer resources.
Если читали книгу дракона (ну или вообще что-то по теме), то "семантика" это и есть логика программы. И по определению он её менять не имеет права.
senia
09.08.2017 15:28+4Вот только второй и третий — синтаксический сахар над первым.
Второе — синтаксическая конструкция, преобразующаяся компилятором к первому, третье — макрос, преобразующийся к первому.
По сути здесь 3 записи абсолютно одного и того же. Какой смысл разделять их терминологически? Или вы считаете, что асинхронность — термин, описывающий способ записи действий в исходном коде?serg_p
09.08.2017 16:00Смесь предметной реализации и обобщающих метафор( п.1 Требующих раскрытия в общем. П.2 И отдельно с реализацией). Термин вообще не привязан к компьютеру.
DistortNeo
09.08.2017 15:02- Да, код асинхронный.
- Я не очень понимаю синтаксиса Scala, но если person имеет тип Future[Person], то мы не можем явно обращаться к person.id, будет просто ошибка.
- Вариация кода 1, переписанная с использованием async/await.
senia
09.08.2017 15:12Да все 3 примера делают абсолютно одно и то же (во втором считайте, что каждая стрелка — flatMap). Мне просто интересно как будет обосновываться не асинхронность первого примера или разный ответ для каких-либо 2 из них.
PsyHaSTe
09.08.2017 14:42+4await может вообще не создавать и не блокировать ни одного потока в принципе. Нужно просто осознать истину: никакого потока нет.
vintage
09.08.2017 14:54Он блокирует поток исполнения задачи, который flow, а не thread.
mayorovp
09.08.2017 14:58+2Вот только синхронность/асинхронность определяется в терминах thread, а не flow.
vintage
09.08.2017 15:06+1Раз вы не любите ходить по ссылкам, то процитирую:
Asynchrony, in computer programming, refers to the occurrence of events independently of the main program flow and ways to deal with such events.
DistortNeo
09.08.2017 15:19+2Да, await блокирует flow исполнения задачи, ну и что? Он блокирует flow задачи, а не вызвавшего задачу кода (main program).
P.S. Вот мне интересно, почему люди считают Википедию истиной в последней инстанции?
vintage
09.08.2017 15:28main program — такая же задача, как и все остальные.
Мне больше интересно, почему некоторые считают свои представления истинными, вместо того, чтобы хотя бы подумать над терминами "синхронное", "асинхронное", "синхронизация". Или попробовать найти хоть одно различие между "task", "fiber" и "thread".
DistortNeo
09.08.2017 15:48main program — такая же задача, как и все остальные.
Ну и, собственно, что? Процесс, запустивший другой процесс, продолжит работу, не дожидаясь завершения вызванного процесса.
Или попробовать найти хоть одно различие между "task", "fiber" и "thread".
Task вообще никакого отношения к fiber и thread не имеет. Это просто объект (языка программирования), позволяющий отслеживать выполнение события и задавать продолжение — код, который будет выполняться по завершении задачи.
Ну а между Thread и Fiber основное различие только в том, что в первом случае планировщиком является операционная система, а Fiber — пользовательский код.
Теперь давайте вашу версию.
vintage
09.08.2017 16:25-1Процесс, запустивший другой процесс, продолжит работу, не дожидаясь завершения вызванного процесса.
А может и подождать, через
awaitthread.join.
Task вообще никакого отношения к fiber и thread не имеет. Это просто объект (языка программирования), позволяющий отслеживать выполнение события и задавать продолжение — код, который будет выполняться по завершении задачи.
Я вам по секрету скажу fiber и thread — тоже не более, чем объекты.
INC_R
09.08.2017 15:09+1Ну очевидно, он блокирует flow, в котором подразумевается, что какие-то операции выполняются после завершения какой-то асинхронной операции. Только с каких пор операция от этого становится синхронной?
PsyHaSTe
09.08.2017 15:09+1Асинхронность, которая не блокирует flow — это fire and forget, частный случай "обычной" асинхронности, где мы после выполнения задачи все-таки хотим что-то сделать: узнать об этом, лампочкой помигать, что данные отправились и все такое.
DistortNeo
08.08.2017 15:31Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции, которая заявлена как «асинронная», и говорите что взаимодействие стало асинхронным?
Вот серьёзно, я не очень понял вашу мысль. Можете пояснить?
Отличие в том, что в случае асинхронного вызова исполнение не приостанавливается, а полностью прекращается. Конструкция await вызывает .GetAwaiter().OnCompleted(...), передавая делегат, который нужно вызывать при выполнении задачи, после чего происходит выход из функции.
То есть асинхронное программирование — это просто хорошо завуалированное событийное программирование.
А претензии к контексту выполнения мне вообще не понятны. Можно элементарно настроить планировщик так, чтобы выполнение продолжилось в том же контексте.
Например похожие технические эффекты, (похожие на await) можно добиться в Java 'без всяких await' с использвоанием yeld() — он отдает ресурсы потока на другие задачи, не не разрушая контекст исполнения.
То, что в Java для этого используют yield, означает только то, что в языке нет конструкции await, и поэтому приходится использовать не предназначенные для этого механизмы. Собственно, в C# до появления await тоже так делали.
mayorovp
08.08.2017 15:37-1То, что в Java для этого используют yield, означает только то, что в языке нет конструкции await, и поэтому приходится использовать не предназначенные для этого механизмы. Собственно, в C# до появления await тоже так делали.
Вы не поняли, там речь шла о операции yield для потока. Это почти то же самое что
Thread.Sleep(1)
в C#. То есть жуткий костыль, которому вообще не место в стандартной библиотеке...DistortNeo
08.08.2017 15:42Вы не поняли, там речь шла о операции yield для потока. Это почти то же самое что Thread.Sleep(1) в C#. То есть жуткий костыль, которому вообще не место в стандартной библиотеке...
Ой...
mayorovp
08.08.2017 15:34Асинхронная функция отличается от синхронной тем, что она возвращает управление почти сразу же. И async/await — это именно что способ написания асинхронных функций.
dplsoft
08.08.2017 23:31-3Асинхронная функция отличается от синхронной тем, что она возвращает управление почти сразу же.
верно! если быть точнее асинхронный вызов запускает выполнение потока инструкций в параллели, а сам, не дожидаясь его кончания, отдает управлепие в точку вызова, где после этого продолжается исполнение инструкций с места вызова.
«асинхронный» именно вызов, а не сама функция. и оператор await — занимается именно тем, что позволяет изменить синхронность вызова у функции, у которой стоит директива асинхронного вызова по умолчанию.
т.е. он позволяет приостановить выполнение потока инструкций в текущей функции, пока не закончится выполнение вызываемой функции.
сужу по официальным докам майкрософта.
И async/await — это именно что способ написания асинхронных функций
я бы рассматривал это как способ определения того, как вызывать функцию по умолчанию, и как изменить синхронность вызова.
идея таких директивы/оператора действительно интересная; выглядит удобно, позволяют не писать обертки. в джаве это желается несколько более громоздко.
но согласитесь, определение у функции директивы (?) «async», а потом вызов её с оператором await — это полностью соответсвует определению синхронного вызова: «выполнение инструкций в точке вызова, не продолжается пока не закончено выполнение инструкций в вызванной функции».
а если это соответвует синхронному вызову, то как же мы можем говорить про код, использующий await, что он асинхронный? он построен по логике синхронных вызовов. а то что где то там внутри есть асинхронные процедуры — это уже третье дело. исходник с await — синхронный.
INC_R
08.08.2017 23:37+1А ничего, что можно сначала вызвать асинхронную функцию, а только потом, после выполнения другой работы, сделать await? Например, так:
var receiveTask = client.ReceiveDataAsync(); // началось получение данных в фоне CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные var data = await receiveTask; // досчитали все, что хотели, теперь ждем получения данных
Это все еще синхронно, ведь await все делает синхронным, верно?
vintage
09.08.2017 13:57-7Я вам больше скажу, в нормальных языках вы можете написать так:
var receiveTask = client.ReceiveData(); // началось получение данных в фоне CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные var data = receiveTask; // досчитали все, что хотели, теперь ждем получения данных
await — лишь визуальный шум для синхронного кода.
mayorovp
09.08.2017 14:26+3Это что за "нормальный язык" такой? И как транслятор понимает где ждать, а где не ждать? Неужели смотрит на суффикс имени переменной? :-)
vintage
09.08.2017 16:36Транслятор и не знает. ReceiveData возвращает прокси, который при попытке доступа к содержимому останавливает текущую задачу в ожидании прихода данных. Данный код на NodeJS + node-fibers: https://github.com/nin-jin/node-jin#sod
mayorovp
09.08.2017 16:40+2Тогда вот в этой строчке комментарий не соответствует реальности:
var data = receiveTask; // досчитали все, что хотели, теперь ждем получения данных
vintage
09.08.2017 16:54-3Ага, ну я не стал добавлять дополнительную строку использования data по назначению.
0xd34df00d
09.08.2017 22:26+1У вас в D Хиндли-Милнера завезли, что ли, что он может вывести тип переменной по её дальнейшему использованию?
vintage
10.08.2017 01:40А зачем тут оно? Достаточно прокси-подтипа.
0xd34df00d
10.08.2017 01:53А каким будет тип, выведенный для data в вышеупомянутой строке?
vintage
10.08.2017 09:06Например
Proxy!Json
.PsyHaSTe
10.08.2017 12:09То есть вы пишете var a = b, при этом у a не тот тип, что у b? И как он это выводит, интересно?
vintage
10.08.2017 13:52Да нет, какой тип вернули, такой и записали.
receiveTask
вернётProxy!Json
, который является подтипомJson
, но с дополнительным поведением по синхронизации.PsyHaSTe
10.08.2017 18:43+1Так когда он поймет, когда надо "синхронизироваться" (кстати буду признателен, если вы свои термины будете кавычками обособлять, чтобы не путаться в терминологии с общепринятой)?
0xd34df00d
10.08.2017 20:16Тогда зачем что-то там переприсваивать?
vintage
10.08.2017 23:03Незачем.
0xd34df00d
11.08.2017 02:28Почитал чуть позже ваши другие комментарии, но было поздно. Я понял ваш поинт, спасибо!
INC_R
09.08.2017 14:30+2Приведите пример этого нормального языка, пожалуйста.
И, главное, что мне делать, если нужно проверить, завершилась ли операция, а не ждать результата, например. Или если я хочу дождаться результата и никак не использовать его (альтернатива инструкции await task;).
И каким образом я при взгляде на код вообще должен понимать, на каком этапе в каком состоянии асинхронные операции и есть ли они там вообще.vintage
09.08.2017 16:53-1Приведите пример этого нормального языка, пожалуйста.
Любой язык, поддерживающий stackfull coroutines: D, Go, JS (node-fibers) и другие.
что мне делать, если нужно проверить, завершилась ли операция, а не ждать результата
Используйте флаги.
если я хочу дождаться результата и никак не использовать его
А зачем вам дожидаться результата, если он вас не интересует? И наоборот, если это не "запрос", а "команда", то зачем возвращать прокси, вместо немедленной синхронизации?
И каким образом я при взгляде на код вообще должен понимать, на каком этапе в каком состоянии асинхронные операции и есть ли они там вообще.
А зачем понимать на высоком уровне детали внутренней реализации метода ReceiveData? Это его дело, делать ли запрос к серверу, или сразу взять данные из кеша. А у вас — простой интерфейс.
DistortNeo
09.08.2017 17:07+2Любой язык, поддерживающий stackfull coroutines: D, Go, JS (node-fibers) и другие.
А если я не хочу использовать stackfull coroutines? Например, есть >10000 кооперативных задач, и каждой выделять стек — расточительство. Чем вам функции-генераторы не угодили?
Используйте флаги.
Так и до busy wait недалеко.
vintage
09.08.2017 17:21Откуда у вас столько задач? Впрочем, ну потратите вы мегабайт 50 на все их стеки — это капля в море.
DistortNeo
09.08.2017 18:08+1> Откуда у вас столько задач?
А вы не слышали про серверы, которые держат по 10к соединений?
> Впрочем, ну потратите вы мегабайт 50 на все их стеки — это капля в море.
5 килобайт на стек — не маловато будет? Подумайте ещё о процессорном кэше: после переключения между фиберами будет новый стек, который, скорее всего, в кэше лежать уже не будет. А значит, увеличится нагрузка на не очень-то и быструю память.vintage
09.08.2017 18:22-2А вы слышали про сервера с десятками гигабайт оперативки?
В стеке всё-равно зачастую одни ссылки на объекты в куче, так что 5 кило — это даже много. Кроме того, Go, например, умеет стеки ресайзить.
Переключение стека стека происходит относительно редко. А вот переключение между асинхронными функциями гораздо чаще и они тоже далеко не всегда удачно лежат в куче.
INC_R
09.08.2017 17:10+3Используйте флаги.
Пример, пожалуйста. Асинхронная операция сама должна менять какой-то общедоступный флаг, чтобы вызывающий ее знал, когда она завершилась?
А зачем вам дожидаться результата, если он вас не интересует?
Потому что иногда меня интересует, что операция выполнилась, но не интересует непосредственно результат. Например, асинхронная операция меняет что-то в БД и возвращает количество измененных записей. А в данном случае меня это количество не интересует. В другом месте оно нужно, а здесь — нет.
А зачем понимать на высоком уровне детали внутренней реализации метода ReceiveData?
А меня не интересуют детали. Мне нужно понимать, что запрос завершится до выполнения какой-то другой логики.
Например, вот это получает текущее значение счетчика, увеличивает значение счетчика и возвращает то значение, которое было:
public async Task<int> GetCurrentCounterAndIncrement() { var counter = await client.GetCounterAsync(); client.IncrementCounter(); return counter; }
А теперь напишу по-вашему:
public int GetCurrentCounterAndIncrement() { var counter = client.GetCounter(); client.IncrementCounter(); return counter; }
Ничего, что в этом случае неочевидно, что client.GetCounter() асинхронен и теоретически операции могут выполниться в таком порядке, что я сначала обновлю счетчик, а потом получу уже новое значение?
vintage
09.08.2017 17:30Да нет, по моему будет так:
public int GetCurrentCounterAndIncrement() { var counter = client.GetCounter(); client.SetCounter( counter + 1 ) return counter; }
Ничего, что в этом случае неочевидно
В соответствующем окружение вполне себе очевидно, что любая операция может:
- быть асинхронной.
- кинуть исключение.
- записать что-то в лог.
...
INC_R
09.08.2017 17:38Да нет, по моему будет так:
Нет. Потому что если между этими двумя вызовами кто-то другой успел поинкрементить флаг еще 50 раз, вы потеряете информацию об этом. Это не тот же самый код.
В соответствующем окружение вполне себе очевидно, что любая операция может
Да пусть будет какой угодно внутри. Но для меня должно быть однозначно ясно, что и в каком порядке выполняется в написанном мной коде, и какие операции в какой момент гарантированно завершены.
vintage
09.08.2017 17:50Ну а в вашем случае, будет возвращено не актуальное значение счётчика. Если оно использовалось для генерации идентификаторов, но привет коллизии.
Пишите на ассемблере — будете точно всё знать.
INC_R
09.08.2017 18:19+1Ну а в вашем случае, будет возвращено не актуальное значение счётчика.
А я сказал, что мне нужно актуальное, чтобы генерировать идентификаторы? Мне атомарность не нужна, если я хочу, скажем, показать пользователю число просмотров поста и одновременно увеличить его на 1. Если он увидит, что просмотров было 10, а не 17, ничего страшного не случится. А вот увеличение значения счетчика должно работать атомарно.
Пишите на ассемблере — будете точно всё знать.
Спасибо, но нет. Я буду писать на нормальном высокоровневом языке, который позволяет мне понимать, что фактически происходит при работе моего кода.
vintage
09.08.2017 18:33Вот и возвращайте ему актуальное значение счётчика:
public int GetCurrentCounterAndIncrement() { return client.IncrementCounterAndGetIt(); }
Никакой язык высокого уровня не даст вам понимания что конкретно фактически происходит. По определению. Вы всегда работаете с той или иной абстракцией.
INC_R
09.08.2017 18:46+2Вот и возвращайте ему актуальное значение счётчика
А если у меня нет такого метода? Если у меня есть только два метода: "получить текущее" и "увеличить на 1"?
Слушайте, я не буду изобретать наглядный логичный согласованный пример, когда это может понадобиться, чтобы покрыть все ваши возможные "а что, если". Вот голые требования. Операция Б должна быть выполнена строго после того, как окончательно и успешно выполнилась Операция А.
Если Операция А не дает мне понять, когда она там завершилась и завершилась ли вообще (ведь, по-вашему, мне не должно быть важно, как там внутри работает и когда), то что делать?
Никакой язык высокого уровня не даст вам понимания что конкретно фактически происходит. По определению. Вы всегда работаете с той или иной абстракцией.
На текущем уровне абстракции мой язык дает мне полное понимание того, какие гарантии у меня есть. Ваш пример — нет.
pnovikov Автор
09.08.2017 18:57Слушайте, вы бы хотя бы бугуртили по поводу того, что встроенные коллекции в C# по умолчанию — не thread-safe, а так — слишком слабая иллюстрация протекающей абстракции. В таких случаях обычно про
IncrementCounterAndGetIt
пишут в документации thread-safe ли он или же нет. Если вдруг по какой-то причине в документации этого не сказано, то я бы подумал кто писал этот метод. Если его писали не клинические дебилы, то внутри наверняка Interlocked
PsyHaSTe
09.08.2017 19:07Погодите. Вот простой пример: мы что-то асихронно пишем на диск, и по окончании хотим залогировать, что запись произведена успешно. с await это будет
await drive.WriteBytes(bytes); Logger.Log("Write operation suceed");
А что будет в вашем варианте?
vintage
09.08.2017 19:20-1!drive.WriteBytes(bytes); Logger.Log("Write operation suceed");
INC_R
09.08.2017 19:21+5Предельно очевидно. "Отрицание drive.WriteBytes()" Спасибо, заверните мне await.
PsyHaSTe
09.08.2017 19:26+2То есть вся разница в том, какой терм будет обозначать ожидание —
!
илиawait
? И я правильно понимаю, что ваш вариант тоже асинхронный?vintage
09.08.2017 19:29-1Оба варианта синхронные неблокирующие.
DistortNeo
09.08.2017 20:13+3У вас что-то с терминологией. Для операций ввода-вывода «синхронный» — это синоним для «блокирующий», а «асинхронный» — синоним для «неблокирующий».
pnovikov Автор
10.08.2017 06:33var data = receiveTask;
А если я просто хочу положить
receiveTask
в другую переменную — как мне быть в нормальных языках?vintage
10.08.2017 09:11Извиняюсь, что поленился и ввёл в заблуждение. Правильно так:
var data = client.ReceiveData(); // началось получение данных в фоне CalculateSomething(); // долго считаем что-то, пока параллельно получаются данные var name = data["name"]; // досчитали все, что хотели, теперь ждем получения данных
pnovikov Автор
10.08.2017 12:27+1Ну т.е. вы предлагаете компилятору (интерпретатор тут написать ИМХО сложно) анализировать все последующие использования
data
на предмет "а когда оно разворачивается"? Попробуйте сами на досуге написать такой компилятор. Да еще и так, чтобы он не ждал на доступе кdata
, а честно ставил все, использует данные изdata
в continuation (уж простите за дотнетовскую терминологию). А если напишете — попробуйте использовать такой язык и не запутаться что в какой момент исполняется.vintage
10.08.2017 13:57Нет, я предлагаю компилятору не заниматься ерундой по созданию цепочек методов с потерей кол-стека. Волокна переключаются в рантайме по необходимости. Собственно я тут уже давал ссылку на свою реализацию этого дела на ноде: https://github.com/nin-jin/node-jin#sod
pnovikov Автор
10.08.2017 14:02Ну и что у вас происходит в момент, когда чтение файла еще не завершилось, а пользователь уже требует контента?
vintage
10.08.2017 14:11Fiber.yield()
https://github.com/nin-jin/node-jin/blob/1885e245d2e4c9d7548524425b100adb71804ac4/async2sync.js#L34
pnovikov Автор
10.08.2017 14:25А концептуально, чтобы не вчитываться в ваш код?
vintage
10.08.2017 15:50Запоминается ссылка на текущее волокно. Текущее волокно уступает исполнение другим волокнам. Обработчик события прихода данных берёт сохранённое волокно и возобновляет его работу. Таким образом внешнее API у ReceiveData получается полностью синхронным. А под капотом — асинхронщина и автоматический параллеллизм.
DistortNeo
10.08.2017 16:55Да, волокна синхронны, здесь я с вами согласен. Отличие волокон от потоков — в пользовательском планировщике и явной передаче управления другому волокну.
А вот когда мы переходим к колбэкам и синтаксическому сахару в виде async/await, получается уже асинхронщина.vintage
10.08.2017 18:45У волокон под капотом те же самые колбэки. Например: https://github.com/laverdet/node-fibers#sleep
pnovikov Автор
10.08.2017 17:03+1Нуу… сомнительная и неоптимальная схема. Из вашего описания очевидно, что волокно, которое вызвало ReceiveData может заблокироваться на неопределенный промежуток времени, если оно посмело обратиться к результату раньше времени и уступило место cpu-bound волокну, которое считает там… какой-нибудь 100500й знак числа пи. Прикиньте как это отлаживать на производительность. Попытка найти боттлнек сожжет все нервы даже бывалому ассемблероиду. Если это трейдофф чтобы не писать await — то я пас.
vintage
10.08.2017 18:51Вы не фантазируйте. await точно так же может заблокировать на неопределённое время. В любом случае профайлер покажет где чего ждала функция.
DistortNeo
10.08.2017 19:14+2Да не блокирует ничего await, поймите уже, наконец! Он просто задаёт продолжение — колбэк, который вызовется по завершении ожидаемой задачи.
pnovikov Автор
10.08.2017 20:13+1Ни разу. Коллбек от await-а вызывается сразу же после завершения асинхронной задачи, а не "после того как выполнится неопределенное количество других задач". (в общем случае конечно зависит от планировщика)
vintage
10.08.2017 23:22Ожидающее волокно тоже запускается сразу после завершения асинхронной задачи. Ребята, я устал вам объяснять простые абстракции. Высуньте голову из своего C# с костылями в виде транспиляции синхронного кода в конечный автомат. В нормальных языках есть более прямые и эффективные решения. Реально, почитайте про D, про Go.
Edison
11.08.2017 05:39Что такое "волокно"? Можете писать понятными и принятыми терминами? Та же корутина, etc
vintage
11.08.2017 09:11+2Edison
11.08.2017 10:40-1Я к тому, что я никогда не видел перевод термина fiber, волокно прям ухо режет. Лучше уже без перевода.
vintage
11.08.2017 13:22+1А вам ухо не режет, когда stream, thread и flow переводят одним словом "поток", а потом устраивают срачи из-за путаницы в терминах?
Нити и волокна — отличная метафора, показывающая как они друг с другом соотносятся.
DistortNeo
10.08.2017 19:13+3Да в принципе, такая схема имеет право на жизнь. Просто CPU-bound волокон быть не должно. Все CPU-bound задачи должны в пул потоков отправляться, а запустившее такую задачу волокно йелдиться.
Я сам работал с волокнами в С++, и даже в C#, и всё работало хорошо. Но один жирный минус перевешивал все плюсы, и минус этот — невозможность полноценной отладки волокон. Данные, лежащие в стэке другого волокна, попросту недоступны для отладчика, ну и всякие Step-ы по инструкциям не очень-то с переключениями между волокнами дружат.
А потом появился await, и волокна я забыл как страшный сон.
DistortNeo
08.08.2017 23:54+4если быть точнее асинхронный вызов запускает выполнение потока инструкций в параллели
Асинхронный вызов ничего не запускает. Конструкция
await task
вызываетtask.GetAwaiter().OnCompleted(continuation)
, гдеcontinuation
— колбэк — продолжение выполнения асинхронного метода, который вызовется при завершении задачиtask
.
Явно запустить в параллели что-то вы можете через
Task.Run(...)
, но к async/await это уже не имеет прямого отношения.
«асинхронный» именно вызов, а не сама функция. и оператор await — занимается именно тем, что позволяет изменить синхронность вызова у функции, у которой стоит директива асинхронного вызова по умолчанию.
Изменение синхронности вызова функции? Бредятина какая-то. Начните с азов: почитайте, как устроена асинхронная функция, которая разворачивается компиляторов в целый класс, что такое await и раз раньше обходились без async/await, используя всякие там
Task.ContinueWith
.
но согласитесь, определение у функции директивы (?) «async», а потом вызов её с оператором await — это полностью соответсвует определению синхронного вызова: «выполнение инструкций в точке вызова, не продолжается пока не закончено выполнение инструкций в вызванной функции».
Разница в том, что асинхронная функция возвращает не результат, а Task.
Вы вызвали асинхронную функцию и, дойдя до await, функция просто вернула вам результат в виде Task, дальше вы можете дождаться завершения выполнения задачи с помощью await.
а если это соответвует синхронному вызову, то как же мы можем говорить про код, использующий await, что он асинхронный? он построен по логике синхронных вызовов. а то что где то там внутри есть асинхронные процедуры — это уже третье дело. исходник с await — синхронный.
Я считаю, что нельзя допускать человека к асинхронному программированию, пока он не поиграется годик-другой с обычным многопотоком, затем не помается месяцок с промисами и тасками без использования async/await. Только после его человеку можно разрешать писать await.
Асинхронный код действительно выглядит как синхронный by design. Но от этого синхронным он не становится.
PsyHaSTe
08.08.2017 14:57+2Переписал (псевдокодовский микс C# и исходного кода):
async int initPrinter() { await serial->send( {0x??, 0x??, 0x??}); //запросим модель принтера var originalTask = serial->waitForBytes(18); var resp=await Task.WhenAny(originalTask , Task.Delay(5000)); //ждем ответ принтера - минимум 8 байт, 5 секунд. if (resp != originalTask) return -100; // timeout if (sizeof resp.Result <8 ) return -101; // ответ не распознан String model = new String(resp.Result); if ( ! model->startsFrom("Posiflex") ) return -102; // не поддерживаемая модель принтера if ( ! model->equals("Posiflex PP5200") ) { await serial->send( {0x??, 0x??, 0x??}); //запросим прошивку принтера resp= ... if (resp != originalTask) return -100; // timeout ... if (resp.Result[0]==0x23 and resp.Result[0]==0x24) // упс... у нас тут модель с прошивкой с известным багом { await serial->send( {0x??, 0x??, 0x??}); //уст. кодировку 866 - в этой прошивке исходно 1251 resp=... if (resp != originalTask) return -100; // timeout .... } }; await serial->send( {0x??, 0x??, 0x??}); //переведем принтер в страничный режим с абс. позиционированием resp=... if (resp != originalTask) return -100; // timeout .... return 0; }
Очевидно, что код полностью асинхронный. Говорить "а у тебя там await написан значит ты блокируешься!.. атата" значит просто плохо разбираться в теме. Если очень грубо, то компилятор переписывает так, что по завершении асинхронной операции выполнится коллбек, который представляет собой весь оставшийся ниже код.
DistortNeo
08.08.2017 15:41Посмотрите на следующий код и больше так не делайте:
var originalTask = serial->waitForBytes(18); var resp=await Task.WhenAny(originalTask , Task.Delay(5000)); //ждем ответ принтера - минимум 8 байт, 5 секунд. if (resp != originalTask) return -100; // timeout if (sizeof resp.Result <8 ) return -101; // ответ не распознан
А теперь обясняю, почему: в случае выхода из функции по таймауту задача originalTask продолжит своё выполнение. При повторном вызове функции будет либо ошибка, либо мусор.
Правильная реализация: с использованием CancellationToken, то есть:
byte[] res; try { using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5))) res = await serial->waitForBytes(18, cts.Token); } catch (OperationCanceledException) { res = null; }
Ну и лучше это в функцию с try-catch обернуть для удобства.
PsyHaSTe
08.08.2017 15:51Это в случае, если у таски есть параметр с токеном. Если его нет, то упс. А вот если есть, то так конечно лучше.
Мусора в первом варианте, как и ошибки, не будет, просто таска будет выполняться, я её результат никуда не уйдет. Не очень хорошо, но у нас нет способа оборвать таску если она явно не принимает CancellationToken.
INC_R
08.08.2017 15:58+2В изначальном примере таймаут передавался прямо в вызываемую функцию, для клиента это в общем-то то же самое, что и токен. Можно было не накручивать снаружи Task.WhenAny
PsyHaSTe
08.08.2017 16:07Ну кстати да, в рамках идеоматичности примера это больше подходит. Просто на моей практике почти все апишки не принимают "лишнего" параметра токена, из-за чего обработка таймаута происходит через такую обертку.
DistortNeo
08.08.2017 17:53Просто на моей практике почти все апишки не принимают "лишнего" параметра токена, из-за чего обработка таймаута происходит через такую обертку.
Мне кажется, это связано с "нечестной" реализацией асинхронных функций: вместо того, чтобы переписать код под асинхронщину (если лень переписывать — есть AsyncRewriter), просто в лучше случае обернули синхронный вызов в какой-нибудь Task.Run, а в худшем — сделали синхронный вызов с возвратом Task.FromResult().
Поэтому отсутствие CancellationToken в таком API для меня — плохой признак.
PsyHaSTe
08.08.2017 19:29Нет, просто например делают вызов через HttpClient, там вполне спокойно используется асинхронность. Сколько я видел использований этого класса? Миллион. Сколько из них принимали токен? ...
DistortNeo
08.08.2017 20:51Плохой пример. В HttpClient как раз таки используется CancellationToken.
PsyHaSTe
08.08.2017 22:29Пример как раз хороший. Потому что в клиенте-то он используется, а вот в коде, который его вызывает — нет. Почти весь код, который я видел, выглядит так:
async string GetFooAsync(int id) { var url = GenerateUrl(id); using (var client = new HttpClient()) return await client.GetAsync(url); }
DistortNeo
08.08.2017 22:37Потому что в клиенте-то он используется, а вот в коде, который его вызывает — нет.
Бить за такое по рукам надобно, если подобный вызвающий код представляет собой библиотеку.
PsyHaSTe
08.08.2017 22:42Ну, я согласен, но разработка — командная работа, так что имеем что имеем :) На предыдущей работе я так и не смог объяснить, что в async-методе вызывать
.Result
не стоит. Так что по сравнению с этим определенно прогресс.DistortNeo
08.08.2017 23:14-1Почему же не стоит? Если
Task.IsCompleted == true
, то вполне можно.
Вот здесь я, например, целых два раза использую.Result
.
Впрочем, мой код там таки содержит ошибку, и второй раз использование
.Result
недопустимо. Я забыл, чтоTask.WhenAny
не бросает исключение, поэтому строчка должна выглядеть как:
return cur.IsCompleted ? cur.Result : throw new TaskCanceledException();
PsyHaSTe
08.08.2017 23:28+2нет, я про то, что есть метод как в примере выше. И чуваку надо другую асинхронную операцию сделать. Ну например генерация урла стала асинхронной. И он недолго думая и не разбираясь, что там в остальном теле метода делает ровно то, что ему в таске написали, то есть так:
async string GetFooAsync(int id) { var url = GenerateUrlAsync(id).Result; using (var client = new HttpClient()) return await client.GetAsync(url); }
DistortNeo
08.08.2017 23:37+2Кажется, понял мысль. Асинхронное программирование имеет иллюзию простоты: типа, написал await, и всё хорошо.
Но это ложная простота. Только люди, познавшие дзен многопоточного программирования и съевшие на нём собаку, не будут совершать глупых ошибок при написании асинхронных методов.
PsyHaSTe
09.08.2017 00:23+2Дзен многопотока — не использовать многопоток кроме случаев, когда без него нельзя обойтись, но не реже. В крайнем случае использовать максимально высокоуровневые абстракции, и ни в коем случае не писать своим примитивы :)
pnovikov Автор
09.08.2017 00:34Вы знаете, зависит от задачи, которую вы решаете. Мне довелось столкнуться с лютой многопоточностью в основном в настольных приложениях. Там без явного резервирования тредов было не обойтись. Я где-то выше уже говорил про задачу "приложение пытается подключиться к серверу, пока ты работаешь с локальной копией" — а как только подключилось — дожидается окончания последней операции, которую ты произвел, блокирует тебе доступ к локальной копии, покуда оно синхронизируется. При всем уважении, асинки здесь — только часть решения задачи. В общем случае — надо продумывать дизайн тредов и блокировки. То же самое касается ситуации, когда ты пишешь свой собственный сервер (например игроделы знают).
Так что, in conclusion, не все решается асинками, не все решается нодой, не все пишется на Javascript-е :)
DistortNeo
09.08.2017 00:48Я придерживаюсь другого мнения: один раз примитивы написать можно и даже нужно. Велосипеды вообще писать полезно. Главное — не использовать их потом в продакшне.
pnovikov Автор
08.08.2017 23:44Вы еще про .ConfigureAwait человеку расскажите чтобы совсем крышу снесло :)
DistortNeo
09.08.2017 00:03+1Вы еще про .ConfigureAwait человеку расскажите чтобы совсем крышу снесло :)
Я его, кстати, считаю костылём. Не от хорошей жизни его создали.
В своих проектах я использую свой собственный планировщик, которому плевать на ConfigureAwait с высокой колокольни.
PsyHaSTe
09.08.2017 00:16Да я говорил, пообщался с командой. Итог обсуждения "пока у нас нет проблем с производительностью асинков и дедлоками — писать не будем". Не скажу, что я доволен таким ответом, но посмотрим, что из этого выйдет.
Кстати полностью согласен, что это костыль, предпочел бы атрибут на уровне сборки (Fody в это умеет, к слову).
mayorovp
09.08.2017 10:44+1Самый простой способ избежать дедлоков — запретить вызывать
Wait()
илиResult
:-)
Это и правда самое простое решение, и выглядит куда аккуратнее чем постоянные ConfigureAwait...
PsyHaSTe
09.08.2017 12:18Ну мы так и решили, потому что конфигурирование на "не нужен нам ваш контекст" выглядит как преждевременная оптимизация. Но тут возникает проблема с тем, что есть некоторые места, в которых можно написать только синхронно. А переписывание на асинхронное АПИ приводит к каскадным изменениям, кто конвертировал — тот знает. Но я согласен, что это нужно делать. Просто один коммит на 5000 файлов и который делается человекомесяц это весьма сурово, а постепенные изменения всегда приводят к тому, что где-то будет временная затычка в виде
Result
с потенциальным дедлоком.mayorovp
09.08.2017 14:23Для таких случаев надо разработать решение без дедлоков и использовать его. Например, на основе 4го решения из этого ответа: https://ru.stackoverflow.com/a/514533/178779
PsyHaSTe
09.08.2017 14:49Я пока пользуюсь советами из вот этого ответа, пока проблем нет. Я все же надеюсь за месяц-другой убрать все блокировки. Но спасибо за ссылку.
DistortNeo
08.08.2017 16:10Если его нет, то упс
Если нет, тогда нужно написать соответствующий враппер, либо не использовать внешний таймаут вообще. Как вариант — скофигурить таймаут через свойство ReadTimeout.
Ну или можно, например, тупо закрывать файловый дескритор при таймауте, чтобы таск принудительно завершился. Костыль, но работает.
Мусора в первом варианте, как и ошибки, не будет, просто таска будет выполняться, я её результат никуда не уйдет.
А что будет происходить, когда одновременно будет несколько запросов на чтение?
Вот вы послали байтики оборудованию и ждёте ответ. Оборудование ответ прислало, но упс, результат оказался в предыдущем таске, который остался висеть с прошлого раза.PsyHaSTe
08.08.2017 16:16Если нет, тогда нужно написать соответствующий враппер, либо не использовать внешний таймаут вообще. Как вариант — скофигурить таймаут через свойство ReadTimeout.
Как вы будете писать враппер, не зная внутренней структуры? Если знаете, то проблем никаких, но мы сейчас про функцию которая по сути с драйвером идет. И если там нет таймаута, то его никак не прикрутить, если только не дописывать этот драйвер самостоятельно. С другой стороны, что в примере он был, но почему я именно так написал я уже объяснил — практика.
А что будет происходить, когда одновременно будет несколько запросов на чтение?
Зависит от устройства.
Вот вы послали байтики оборудованию и ждёте ответ. Оборудование ответ прислало, но упс, результат оказался в предыдущем таске, который остался висеть с прошлого раза.
Такое тоже возможно, и вполне может быть валидным сценарием, что если мы по таймауту упали, это не значит, что мы захотим "дочитать" в следующий раз. Скорее наоборот, мы в следующий раз отправим новый запрос за новой процией данных, а при чтении считаем кусок предыдущего ответа.
DistortNeo
08.08.2017 16:34Как вы будете писать враппер, не зная внутренней структуры?
Если ни один байт не должен быть потерян, то, например, так:
interface IDeviceInterface { Task<byte> ReadByte(); } class Device { readonly IDeviceInterface device; Task<byte> cur; public Device(IDeviceInterface device) { this.device = device; } public async Task<byte> ReadByte(CancellationToken token) { try { if (cur == null) cur = device.ReadByte(); if (cur.IsCompleted) return cur.Result; var tcs = new TaskCompletionSource<bool>(); using (token.Register(tcs.SetCanceled)) await Task.WhenAny(tcs.Task, cur); return cur.Result; } finally { if (cur.IsCompleted) cur = null; } } }
Если же вместо одного байта читается несколько — без разницы, просто логика чуть сложнее будет.
PsyHaSTe
08.08.2017 16:41Хороший пример, но ведь таска в таком случае точно так же остается висеть. От того, что мы заканселили таску мы прекратили её "типа", но при этом device.ReadByte() как читал, так и продолжает читать, и ничего про отмену не знает.
DistortNeo
08.08.2017 16:52+1Хороший пример, но ведь таска в таком случае точно так же остается висеть. От того, что мы заканселили таску мы прекратили её "типа", но при этом device.ReadByte() как читал, так и продолжает читать, и ничего про отмену не знает.
Да, таска остаётся висеть, но так и задумано, потому что при следующем чтении мы просто подцепляем старую таску, а не запустим новую.
Ну а если же задачу хочется принудительно остановить с потерей состояния — пишите метод Dispose.
PsyHaSTe
08.08.2017 16:58-1В таком случае ничего не мешало это сделать в примере выше:
if (resp != originalTask) originalTask.Dispose();
И да, гайдлайны не рекомендуют этого делать.
DistortNeo
08.08.2017 17:14В таком случае ничего не мешало это сделать в примере выше:
Это действие (Task.Dispose) бесполезно, так как мы так не прервём выполнение задачи. К тому же, я имел в виду device.Dispose() — чтоб сразу гильотиной действовать. Есть шанс, что задача в этом случае завершится с ошибкой.
Моя же задумка в том, чтобы, наоборот, не останавливать задачу, но и не терять данные.
PsyHaSTe
08.08.2017 17:38В плане "не потерять таску и подобрать её, когда она упала" задумка классная, надо запомнить.
К тому же, я имел в виду device.Dispose() — чтоб сразу гильотиной действовать. Есть шанс, что задача в этом случае завершится с ошибкой.
Но ведь device не реализует IDisposable.
DistortNeo
08.08.2017 17:45Ну так реализуйтеinterface IDeviceInterface: IDisposable { Task<byte> ReadByte(); } sealed class Device : IDisposable { readonly IDeviceInterface device; Task<byte> cur; public Device(IDeviceInterface device) { this.device = device; } public async Task<byte> ReadByte(CancellationToken token) { try { if (cur == null) cur = device.ReadByte(); if (cur.IsCompleted) return cur.Result; var tcs = new TaskCompletionSource<bool>(); using (token.Register(tcs.SetCanceled)) await Task.WhenAny(tcs.Task, cur); return cur.Result; } finally { if (cur.IsCompleted) cur = null; } } public void Dispose() { device.Dispose(); } }
PsyHaSTe
08.08.2017 19:28Так речь шла о случае, когда он не реализует...
DistortNeo
08.08.2017 20:52Ну если не реализует, тогда остановить операцию не получится — очевидно же.
mayorovp
08.08.2017 15:56Я бы так не сказал. Потому что после отмены чтения данных входящий поток оказывается в неопределенном состоянии, и единственный способ продолжить работу с устройством — начать все заново.
Чтобы можно было продолжить работу с устройством после тайм-аута — надо где-то хранить текущее состояние — а в таком случае уже и асинхронные операции перестают выигрывать в понятности у событийной архитектуры.
PsyHaSTe
08.08.2017 16:17Событийной архитектуре трудно быть понятной, потому что там флоу нелинейный. Приведите пример что ли, в каком случае EAP будет понятнее, чем async/await флоу?
mayorovp
08.08.2017 16:26Когда кто-то уже разработал конечный автомат для решения задачи.
ПримерR:HDR @=======@ S:HDR R:HDR[!=S:HDR] +--------| START |-----+ +--------------------------------+ | @=======@ | | | \|/ \|/ | | @==========@ @==========@ S:OPEN | +----| HDR_RCVD | | HDR_SENT |------+ | | @==========@ @==========@ | R:HDR[!=S:HDR] | | S:HDR | | R:HDR | +-----------------+ | +--------+ +------+ | | | | \|/ \|/ \|/ | | | @==========@ +-----------+ S:CLOSE | | | HDR_EXCH | | OPEN_PIPE |----+ | | @==========@ +-----------+ | | | R:OPEN | | S:OPEN | R:HDR | | | +--------+ +------+ +-------+ | | | \|/ \|/ \|/ \|/ | | @===========@ @===========@ S:CLOSE +---------+ | | | OPEN_RCVD | | OPEN_SENT |-----+ | OC_PIPE |--+ | @===========@ @===========@ | +---------+ | | S:OPEN | | R:OPEN \|/ | R:HDR | | | @========@ | +------------+ | | | +------>| OPENED |<----+ | CLOSE_PIPE |<--+ | | @========@ +------------+ | | R:CLOSE | | S:CLOSE | R:OPEN | | +---------+ +-------+ | | | \|/ \|/ | | | @============@ @=============@ | | | | CLOSE_RCVD | | CLOSE_SENT* |<----+ | | @============@ @=============@ | | S:CLOSE | | R:CLOSE | | | @=====@ | | | +-------->| END |<-----+ | | @=====@ | | /|\ | | S:HDR[!=R:HDR] | R:HDR[!=S:HDR] | +----------------------+-----------------------------------------------+
PsyHaSTe
08.08.2017 16:38+1Учитывая, что async/await как раз-таки и разворачивается в КА, не вижу проблем. Как раз в этом случае можно просто воротить if else и всю эту логику, а с событиями придется попотеть. Каждый ведь с каждым нужно связать, и все эти взаимосвязи уследить в таком случае представляется не очень простой задачей.
mayorovp
08.08.2017 16:42Тот факт, что async/await разворачивается в КА никак не помогает писать код при заданном КА. Фактически, программисту нужно выполнить работу обратную той, которую делает компилятор.
А вот уследить за взаимосвязями — как раз нет проблем. Вот они все, на картинке.
PsyHaSTe
08.08.2017 16:51Не знаю, может я такой странный человек, но мне код в котором будет десяток ифов понять проще, чем систему из получается двух десятков событий, причем если вдруг эта документация куда-то потеряется, или что бывает чаще, устареет, то потом сам черт не разберет, что же куда передается. Нелинейный флоу это всегда боль, и если от него можно отказаться, то от него нужно отказываться. Это по факту goto. Триггеришь событие, оно перескакивает в совершенно другой контекст, что-то делает, опять дергает событие, разбросанные по десятку разных файлов и классов. По крайней мере мне это видится как-то так.
В чем проблема по такой схеме написать что-то вроде:
var data = await port.SendDataAsync(); if (foo) await OpenPipe(); else await OpenRcvd();
я, честно говоря, пока не понял.
mayorovp
08.08.2017 16:54Не вижу в вашем коде обработки ситуации, когда половина данных была успешно отправлена, а потом наступил тайм-аут...
DistortNeo
08.08.2017 17:13Посмотрите внимательно:
У вас в конечном автомате поток идёт сверху вниз без циклов. Вы никогда не возвращаетесь в предыдущее состояние. В этом случае плоский код с if-then-else может оказаться понятнее и проще в реализации.
- Управляющие команды всегда идут в строгом порядке — HDR, OPEN, CLOSE. Почему бы это не использовать в коде? Можно, например, уменьшить количество состояний, если полностью от них не отказаться.
DistortNeo
08.08.2017 16:18Я бы так не сказал. Потому что после отмены чтения данных входящий поток оказывается в неопределенном состоянии, и единственный способ продолжить работу с устройством — начать все заново.
А это уже зависит от реализации. В моих задачах бывает, что устройство не ответило — ну случается такое, оно было занято, нужно просто его ещё раз попросить, и оно ответит. А если всё рвать и начинать заново, то это может занять пару минут.
Ну и ещё иногда бывает просто полезно остановиться не по таймауту, а потому что захотелось.
P.S. В любом случае, оставлять за собой висящие таски — это, по меньшей мере, некрасиво.
dplsoft
08.08.2017 21:53-7Очевидно, что код полностью асинхронный
Я, конечно, подозревал, что полное, безоглядочное погружение в идеологию и технологии мягкотелых вызывает… определенного рода… профессиональную деградацию…
Но то, что вы тут уже 5й пост устраиваете параолимпиаду по синхронным методам запуска асинхронных функции… и при этом заявляете что это «полностью асинхронный код»…
Я потрясен. «Такое» я вижу впервые.
Вы… в некотором роде, теперь, мой личный рекордсмен.
В отдельной, особой номинации. Для «тяжеловесов».
То, что вы тут язощеряетесь и демонстрируете «удобные и красивые методы» работы с асинхронными функциями, как раз доказывает, что далеко не везде асинхронность и событийно ориентированность — к месту. Потому что все что вы показываете — это как раз вариации на тему, как бы нам асинхронный вызов превратить в синхронный, и приостановть выполнение текущего потока инструкций (и поверьте всем глубоко плевать, блокируется при этом поток или освобождается, или начинается другой поток — это детали реализации. не важные по большому счету)
Да, эти методы позволяет не писать особых оберток, но это не делает ваш код «асинхронным».
То, что вы вызубрили костыли сишарпа — это конечно похвально, но даже то что вы показали, не поможет вам красиво и удобно, без лишних оберток работать с WinApi в котором вы должны «передать вниз» ссылку на callback функцию, которую надо вызвать, когда данные поступают.
DistortNeo
08.08.2017 22:35+1и поверьте всем глубоко плевать, блокируется при этом поток или освобождается, или начинается другой поток — это детали реализации. не важные по большому счету
Ну и заявление! Именно детали реализации — это и есть самое важное в асинхронном программировании. Если конкретно вам на это плевать — это ваши проблемы.
То, что вы вызубрили костыли сишарпа — это конечно похвально, но даже то что вы показали, не поможет вам красиво и удобно, без лишних оберток работать с WinApi в котором вы должны «передать вниз» ссылку на callback функцию, которую надо вызвать, когда данные поступают.
Видимо, вы даже не догадываетесь, насколько просто это стало делать с появлением асинхронных функций. Достаточно просто вызвать в колбэке TaskCompletionSource.SetResult(...), не беспокоясь о том, как этот результат будет потом использоваться.
И представьте себе, при использовании C# обёртки придётся писать и для обычных синхронных функций.
vintage
09.08.2017 14:12-1это и есть самое важное в асинхронном программировании
В конкурентном исполнении, а не асинхронном программировании.
Краткие определения для упорядочивания:
Параллелизм — одновременное исполнение разными исполнителями.
Конкурентность — попеременное исполнение одним исполнителем.
Асинхронность — исполнение без ожидания завершения подзадач.DistortNeo
09.08.2017 14:48В конкурентном исполнении, а не асинхронном программировании.
Да, верно. Ну и ещё один момент: асинхронное программирование является синтаксическим сахаром для событийно-ориентированного программирования. Иногда это бывает очень удобно.
Параллелизм / конкурентность — это относится к исполнению, это, фактически, свойство планировщика задач
А вот асинхронность / событийность — это относится уже к программированию.
Да, действительно, асинхронные задачи можно запускать как параллельно, так и конкуретно. Но это всё лежит уже на совести планировщика.
Но нюанс в том, что существующие планировщики IO-bound операции выполняют конкурентно, а для запуска CPU-bound задачи нужно планировщик явно об этом попросить (Task.Run, ConfigureAwait и т.д.). И именно эта деталь реализации является принципиально важной.
P.S. Я, кстати, пробовал писать велосипед с Fibers — получался чисто синхронный код с конкуретным выполнением.
serg_p
09.08.2017 15:34Конкурентность никак не привязана к одному. Одним, многими. А бывает что и некому — по факту :)
PsyHaSTe
08.08.2017 22:39+1Потому что все что вы показываете — это как раз вариации на тему, как бы нам асинхронный вызов превратить в синхронный, и приостановть выполнение текущего потока инструкций (и поверьте всем глубоко плевать, блокируется при этом поток или освобождается, или начинается другой поток — это детали реализации. не важные по большому счету)
Интересно тогда послушать ваше определение "асихнронности", потому что в моем "блокируется или нет поток" является определяющим, а не "деталью реализации". И можно пример асинхронного вызова, который блокирует вызывающий поток? Раз это деталь реализации, значит бывают реализации, где и такое происходит.
dplsoft
09.08.2017 01:25-4сначала, про поток в определении: вы цепляетесь за реализацию, причем даже конкретный частный случай сишарпа.
Раз это деталь реализации, значит бывают реализации, где и такое происходит.
не совсем верный вывод… могут, и есть, реализации, к которым само понятие «блокировка вызывающего потока» не применимо, а понятие «асинхронный вызов» — применимо.
я пытаюсь вывести вас на определение асинхронного вызова, которое не зависит от деталей реализации. тогда вы будете понимать почему ваш код, который вы написали — «построен по логике синхронных вызовов».
вот скажем, о каких потоках и блокировках, мы можем говорить, если мы попытаемся определить, скажем что такое синхроные и асинхронные вебсервисы?
или вот: поток может быть вообще прекращен (как тут кто то говорил про то, как работает await), или под каждую инструкцию или группу инструкций будет свой поток создаваться (почему бы и не создать такую машину для байткода? проанализировать зависимости и распараллелить для оптимизации на многоядерном проце?) — вариантов масса, и понятие «блокировка потока» вообще будет неприемлемо — все зависит от реализации. особено, если мы говорим не про машинный код, а про машины исполняюшие байткод.
вы просили "пример асинхронного вызова, который блокирует вызывающий поток" — это конечно бред, но вот вам пример «асинхронного», для которого понятие вызывающий поток вообще не применимо: асинхронный вебсервис.
или вот еще «немного путарицы» пример: вызов синхронного вебсервиса может быть реализован на вызывающей стороне по синхронной локиге построения кода, так и по асинхронной.
Блокировка потока (который thread) «как признак асинхронности» — это частный случай.
теперь про определение асинхронности: вот немного скорректированное определние асинхронного вызова, которое я давал выше:
при асинхронном вызове запускается выполнение инструкций в параллели, а и не дожидаясь его кончания, управление возвращают в точку вызова (или сигнализирует о том, что выполнение инструкций запущено); в точке вызова, после этого, как правило, продолжается исполнение инструкций с места вызова.
при синхронном вызове, возврат управления в точку вызова не осуществляется (или сигнал об окончании исполнения не передается), пока не закончено выполнение инструкций в вызванной функции.
и совершенно не важно что у вас тут: сишарп, джава, или взаимодействие между двумя хостами при RPC или вызов вебсервиса описанного в wsdl.
отодвиньтесь от деталей. вы зарылись в частные учебные определения, и забыли изначальную суть. более того, вы используете смешанные механизмы, которые используют или допускают и асинхронный вызов, и последующую синхронизацию, но заявляете чтт это «полностью асинхронный код». жто не верно.
в вашем примере ( в конце которогоо гордо заявляете «вот полностью а-синхронный код») — вы строите именно синхронный код, вызываете функции синхронно,… а потом заявляете что код «полностью асинхронный»… извините, но это… очень мягко говоря… не верно.
вы не переходите на следующие инструкции пока не выполните операции чтения из компорта. иначе вся логика работы кода разрушится.
при вызове функции через await, вы не перейдете на следующую строчку кода, пока функция не будет завершена — и именно поэтому ваш вызов синхронный, и потому код ваш — синхронный: он построен по логике синхронных вызовов. вне зависимости от того что происхолит внизу с потоками, асинхронными и не очень функциями — ваш вариант псевдо-исходника и логика его работы — синхронные, или повторяют мой код, полностью построенный на синхронной логике.
если бы вы объявляли для своих асинхроных функций callback-и, и каждое обращение ловили именно в них, то ваш код был бы асинхронным. суть — событийно ориентированным. аки как его любят js-ники, ратующие за однопоточность и событийно-ориентированность.
я предлагаю прекратить говорить сугубо частными терминами сишарпа (таски, async/await ) когда рассуждаете об асинхронности. вы топик видели?))) вы понимате что такое опредение — это как минимум слишком узкое понимание? тем более, вы используете механизмы которые не столько относятся к асинхронному/синхронному вызову, сколько к потокам и синхронизации потоков.
DistortNeo
09.08.2017 01:54+4при вызове функции через await, вы не перейдете на следующую строчку кода, пока функция не будет завершена — и именно поэтому ваш вызов синхронный, и потому код ваш — синхронный: он построен по логике синхронных вызовов. вне зависимости от того что происхолит внизу с потоками, асинхронными и не очень функциями — ваш вариант псевдо-исходника и логика его работы — синхронные, или повторяют мой код, полностью построенный на синхронной логике.
В этом и заключается ваша ошибка. Вы никак не можете понять, что асинхронная функция вернёт результат (Task<>, Promise и т.д.) до того, как завершится её выполнение. Да, управление внутри функции не перейдёт на следующую после
await
строчку, ну и что с того? Вызвавший эту функцию код уже получит от функции результат.
если бы вы объявляли для своих асинхроных функций callback-и, и каждое обращение ловили именно в них, то ваш код был бы асинхронным. суть — событийно ориентированным. аки как его любят js-ники, ратующие за однопоточность и событийно-ориентированность.
Механизм async/await — это и есть синтаксический сахар для событийно-ориентированного программирования.
0xd34df00d
09.08.2017 01:59+6Такое чувство, будто у вас асинхронность не ради асинхронности, а ради боли, страданий и бойлерплейта.
PsyHaSTe
09.08.2017 02:01+4при асинхронном вызове запускается выполнение инструкций в параллели, а и не дожидаясь его кончания, управление возвращают в точку вызова (или сигнализирует о том, что выполнение инструкций запущено); в точке вызова, после этого, как правило, продолжается исполнение инструкций с места вызова.
Это ровно то, как работает await :)
jetexe
09.08.2017 10:41+4Тут похоже человек не может понять, что можно запустить выполнение функции, а await использовать тогда когда понадобится, а между этими двумя строчками делать любые другие действия в родительском потоке. И что асинхронная функция возвращает таск, а не результат.
PsyHaSTe
09.08.2017 12:26Кстати да, я думал, что неплохо понимаю await, но этот пример неплохо иллюстрирует такое непонимание, в котором сам до недавних пор пребывал:
async Task Main() { var sw = Stopwatch.StartNew(); var a = Task.Delay(2000).ContinueWith(t => 0); var b = Task.Delay(2000).ContinueWith(t => 0); var aa = await a; var bb = await b; Console.WriteLine($"Result = {aa + bb}, Elapsed = {sw.Elapsed.TotalSeconds}"); // 2 секунды }
Инлайним a и b:
async Task Main() { var sw = Stopwatch.StartNew(); var aa = await Task.Delay(2000).ContinueWith(t => 0); var bb = await Task.Delay(2000).ContinueWith(t => 0); Console.WriteLine($"Result = {aa + bb}, Elapsed = {sw.Elapsed.TotalSeconds}"); // 4 секунды }
Я раньше думал, что await в том числе запускает асинхронную операцию, а создание фактически эквивалентно
new Task(...)
, а на самом деле скорееTask.Run
.
vintage
09.08.2017 14:14-2setTimeout( ()=> console.log( 'one' ) , 10 ) console.log( 'two' )
Асинхронный блокирующий.
PsyHaSTe
09.08.2017 14:52+3И в чем же тут заключается блокировка, можно узнать? Пока что я вижу только таймер и операцию, которые вообще никак не связаны, и в общем случае могут произойти в произвольном порядке.
vintage
09.08.2017 17:16Оба кода на время своего исполнения блокируют поток, но исполняются асинхронно.
PsyHaSTe
09.08.2017 17:38+2Если в вашей терминологии "блокировать поток" == "выполнять в нем полезные операции", то будьте добры так заранее и говорить, потому что иначе возникает путаница. Потому что в общепринятой код
puts "hello world"
не является "блокирующей поток", потому что под блокировкой потока обычно имеют ввиду "усыпим тред, пока нас кто-то не разбудит". Condvar/LockFree/всякие циклы ожидания, это все детали реализации, но суть одна, блокировка потока — это когда мы ждем сигнала на продолжение работы, а на это время поток усыпляем, что и называют его "блокировкой".
В таком случае, "блокирующая асинхронная операция" в ваших определениях эквивалентна утверждению "асинхронные операции могут выполнять полезные вычисления" на обычном языке. Что ж, с этим я могу согласиться.
vintage
09.08.2017 17:55console.log — блокирующий io.
PsyHaSTe
09.08.2017 18:55console.log — это библиотечная функция, что она под капотом делает меня не касается. Функция является синхронной, если она возвращает объект, и асихнронной, если возвращает промиз. Что там под капотом — не моя зона ответственности. Асинхронность задается на уровне контракта — интерфейса — а не на уровне имплементации.
vintage
09.08.2017 19:24Вы тред-то почитайте. Речь шла об асинхронном блокирующем коде. но вы правильно заметили, что (а)синхронность — это тип интерфейса, а под капотом там может быть всё, что угодно.
DistortNeo
09.08.2017 20:25+1> Вы тред-то почитайте. Речь шла об асинхронном блокирующем коде. но вы правильно заметили, что (а)синхронность — это тип интерфейса, а под капотом там может быть всё, что угодно.
Неверно. Всё зависит именно от того, что находится под капотом, а не какой интерфейс предоставляет функция. Если результат синхронного (=блокирующего) вызова обернуть в Task, он сам вызов останется блокирующим, асинхронным он от этого не станет.
И наоборот, функции чтения типа stream.BeginRead, хотя и не возвращают Task<> и не позволяют использовать await, являются вполне себе асинхронными.vintage
09.08.2017 20:52-2Если результат синхронного (=блокирующего) вызова обернуть в Task, он сам вызов останется блокирующим, асинхронным он от этого не станет.
В правильной реализации вообще-то станет.
v = new Promise(resolve=>resolve(1)) v.then(console.log) console.log(2)
Выведет сначала 2, потом 1.
TimTowdy
10.08.2017 12:20+3Ребята, извините что влезаю со своким капитанством. Я вижу что под (а)синхронностью у вас подразумеваются сразу две вещи: стиль написания кода и модель его выполнения.
Есть синхронный (блокирующий) код — его легко читать, т.к. все операции выполняются последовательно, но в некоторых случаях (самый частый пример — ввод-вывод) он работает медленно, т.к. каждая операция блокирует поток выполнения.
Есть асинхронный код (неблокирующий) — он отлично работает операциями типа ввод-вывод, т.к. не блокирует поток при их выполнении и позволяет в этот момент выполняться чему-то другому, но из-за этого его сложно читать, т.к. написанный код выполняется непоследовательно (пример: callback hell)
async/await и их аналоги позволяют совместить лучшее из двух миров: писать код, который внешне выглядит как синхронный (т.е. его легко понять, т.к. читается последовательно), но выполняется — как асинхронный (т.е. не блокирует поток выполнения на каждой операции).
Flies away.
DistortNeo
09.08.2017 18:35> под блокировкой потока обычно имеют ввиду «усыпим тред, пока нас кто-то не разбудит».
Операционная система тоже может поток заблокировать, например, для выполнения операций ввода-вывода.
puts, console.log — это блокирующие операции. Как думаете, что будет, если вы перенаправите вывод, например, на дискету или в ком-порт с медленной скоростью?PsyHaSTe
09.08.2017 19:00Вызов console.log ничего не блокирует. Если блокируется внутри console.log — это, как я уже выше написал, не моя зона ответственности, и для меня этот метод полностью выглядит и ведет себя как обычный синхронный. Сколько там оно будет выполняться — beside the point, в определении синхронной операции нигде не сказано, какие временные рамки должны быть, чтобы она считалась таковой. Функция не может меняться от "синхронной" до "асинхронно" от того, посмотрели ли мы её реализацию или нет. Либо она синхронная — и тогда мы её просто вызываем и получаем результат, либо она асинхронная, и мы должны каким-либо образом подписаться на получение результата в будущем.
DistortNeo
09.08.2017 19:11+2> Вызов console.log ничего не блокирует. Если блокируется внутри console.log — это, как я уже выше написал, не моя зона ответственности, и для меня этот метод полностью выглядит и ведет себя как обычный синхронный.
Как это не ваша? Ещё как ваша. Если вы, например, загружаете веб-странцу, вам должно быть принципиально важно, как реализованы операции с сокетами. Просто об этом должно быть написано в документации.
Если после вызова функции она моментально возвращает управление, а в консоль данные пишутся в фоне, то она асинхронная (=неблокирующая). Ну а то, что не возвращается Task — считайте, что это fire-and-forget.
И наоборот, функция может возвращать Task, но вызывать внутри блокирующие операции и возвращать в самом конце Task.FromResult (программист просто поленился и поставил такую заглушку). От того, что она возвращает Task, асинхронной она не становится.PsyHaSTe
09.08.2017 19:32-1Как это не ваша? Ещё как ваша.
С чего это? Я вызываю функцию int foo = bar(). Я что-то блокирую? Или ответ зависит от того, как написана bar?
Если после вызова функции она моментально возвращает управление, а в консоль данные пишутся в фоне, то она асинхронная (=неблокирующая). Ну а то, что не возвращается Task — считайте, что это fire-and-forget. И наоборот, функция может возвращать Task, но вызывать внутри блокирующие операции и возвращать в самом конце Task.FromResult (программист просто поленился и поставил такую заглушку). От того, что она возвращает Task, асинхронной она не становится.
Так мы не про функцию работаем, а про работу с ней. await на Task.FromResult является асинхронной операцией, а вызов fire-and-forget в большинстве случаев говнокодом, потому что это как раз и есть "запустим асинхронно, но вызывающему коду не скажем". Но да, её вызов будет синхронным, потому что мы явно вызвали синхронную функцию, а что у нее внутри мы не знаем.
DistortNeo
09.08.2017 20:20> Или ответ зависит от того, как написана bar?
Да, зависит. Признак асинхронности — продолжение работы после возврата из функции.PsyHaSTe
09.08.2017 23:59Да, зависит. Признак асинхронности — продолжение работы после возврата из функции.
Ну а тут я уже не согласен. (А)синхронность функции закладывается в интерфейсе. И по интерфейсу всегда можно сказать, асинхронная функция или нет. В терминологии шарпа очень грубо говоря, если функция возвращает Task — её использование асинхронное, в противном случае — нет.
DistortNeo
10.08.2017 00:41> В терминологии шарпа очень грубо говоря, если функция возвращает Task — её использование асинхронное, в противном случае — нет.
А что насчёт Stream.BeginRead или Socket.BeginReceive?PsyHaSTe
10.08.2017 00:51Я же сказал "грубо говоря" :)
Что касается конкретно их, то оборачиваем в таск и тогда определение снова работает. И да, конечно же они асинхронные.
DistortNeo
10.08.2017 01:15Ну значит, всё верно. Если функция возвращает Task<>, то мы имеет право полагать, что функция действительно асинхронная, за исключением случаев, когда имеет место говнокод, например, функция объявлена как async, но внутри только синхронные операции или, о ужас, обращение к синхронному task.Result.
pnovikov Автор
10.08.2017 06:28-1return Task.FromResult(синхронная_операция)
mayorovp
10.08.2017 09:46-1Это и называется "говнокод". Если академичнее, то реализация функции не соответствует контракту.
pnovikov Автор
10.08.2017 11:56Я понима~ — я просто привел пример кода, который может лежать за якобы асинхронной функцией, ломая всю малину. Уберите минус :)
PsyHaSTe
10.08.2017 12:06Асинхронная функция — это не та, которая написана "асинхронно" (то есть нам не важно, что у неё в теле написано), а взаимодействие с которой происходит по асинхронному флоу — формирование задачи и ожидание её завершения. Что там под капотом — не важно, Task.FromResult, реальный запрос по сети/к устройству или еще что-то.
fromgate
06.08.2017 00:53А аналогичная градация для C#, Java и т.п. возможна? ;)
pnovikov Автор
06.08.2017 00:55Скорее да, чем нет. Но я рискну предположить что так как JavaScript — в целом экосистема довольно новая и разработчики на ней ещё юны и весьма максималистичны (о чем свидетельствует их поведение) — то само существование подобной градации наиболее характерно для JS. И это эмпирически согласуется с моим опытом.
igorch96
06.08.2017 03:21+4Ну JavaScript постарше C# будет…
pnovikov Автор
06.08.2017 07:08+1Во времена, когда AJAX был неизвестным нововведением, а на горизонте маячил Web 2.0 — на C# не было лямбда-выражений и LINQ, но уже во всю писали мелкий банкинг и CRM-системы, а все холивары Java vs. C# уже отгремели канонадой. Так что формально JavaScript старше, но активно использовать и развивать его начали значительно позже.
KasperGreen
06.08.2017 02:00+1У меня 8 стадия похоже. Спасибо за статью. Пожалуй для меня уже поздно что-то менять :)
Раньше крайне подозрительно относился к ноде на бэкенде, а теперь просто подозрительно к этому отношусь. Считаю сабж слишком молодым, чтобы не обрастал детскими инъекция и и другими граблями, но потенциал есть.
Про многопоточность кстати всё не так плохо. Плохо конечно, но уже и прямой доступ к памяти есть, и кривоватая, но многопоточность на видяхах.
Уверен, что если нужна числодробилка с миллионом операций в секунду лучше использовать хорошие низкоуровневые языки, а всякий скрипт подойдёт для быстрого написания тормозного прототипа, который можно переписать на труёвый язык. Ты же уже полюбил переписывать код по несколько раз, %Юзернейм%?
Дорогой автор, мне нравится твой стиль изложения пиши ещё на тему веб-программистов в принципе. Мне кажется вебдевелоперы отдельная каста и часть советов для них не совсем подходят. Но это может быть я глупенький и не понимаю простых вещей в силу своего программисткого несовершеннолетия. Авось после 18 лет придёт озарение в мою голову, а пока учу JavaScript и совсем php забросил.
KroTozeR
06.08.2017 02:07+16Пишу на C/C++. За плечами опыт ВЕБ-программиста (ещё не Frontend), когда jQuery был модным. Сбежал обратно на C/C++ от ВЕБ-а именно чтобы не иметь дела с людьми, которых описывает автор. Не считаю их идиотами или безумными фанатиками, но… Видимо мне с ними не по пути.
Конечно есть у меня и свои тараканы. Это, вообще-то, называется термином «профессиональная деформация». К примеру, привык считать программиста инженером, а инженер должен воспринимать программу не как проявление «машинного сознания» или, что ещё хуже, «магии», а как механизм. Т.е., программа — это механизм, продолжающий механизм самой электронно-вычислительной машины. Давно вы так «компьютер» называли? Очень давно? А, ведь, это — его суть.
В общем, согласен. В ВЕБ-е творится сущий фанатизм. Люди, наглухо оторванные от реальности, продолжают множить инструментарий, работающий поверх виртуальной машины. Они считают вопрос о производительности таких решений оскорбительным.
А глубинная причина лишь одна: аппаратная абстракция. Категорическое нежелание понимать машину. Плохо это? Хорошо? Да пусть пишут на этом языке, если он им так нравится) Только пусть не ведут «войну с неверными». Это как воевать за всеобщую автоматизацию, надеясь на вечную выработку электроэнергии.
JavaScript — красивый язык. Он очень демократичный и лёгкий. Почти идеальный скриптовый язык, о чём и говорит его название.
Я же предпочту знать, как работает ЭВМ, и насколько нелепыми кажутся со столь «низкого» уровня все эти танцы уверовавших в абсолютную многопоточность JS-программеров. Да-да, я знаю, как работает механизм её обеспечения, а потому знаю, что многопоточности нет. По крайней мере в рамках одной ЭВМ. Есть иллюзия.
К слову сказать, сколь бы ни были противоречивы языки go и Rast, но всё же это — попытка поиска «золотой середины». Это всё вытекает из интересов бизнеса. Заметьте! Бизнеса, а не науки. Наука преследует порядок, а бизнесу он не нужен. Ему нужен заработок. Так что, терпите. Или изобретайте альтернативу. Есть же проекты вроде LLVM.Graff46
06.08.2017 02:33+1Вот тут очень хорошо написал товарищ. Есть тенденция не понимать машину, понимать задачу и решать её, а машина все сделает сама. Кому напрягаться, так это разработчикам второго круга, более «инженерным», те кто пишет интерпретатор для JS, а не на самом JS.
Исходя из одной лекции в техникуме по микропроцессорам, я в глубине души понимал, что нет никакой многопоточности вообще, есть хорошо организованное прерывание.
Scf
06.08.2017 10:35+1Многопоточность в рамках одной ЭВМ очень даже есть. Многоядерные процессоры, устройства на общей шине, DMA...
KroTozeR
06.08.2017 10:49+9Так и полагал, что кто-то это напишет) Это не многопоточность, как ни странно. Ядра процессоров сидят на едином кэше третьего или второго уровня (у кого как), а доступ к этому кэшу — последователен для всех в порядке общей очереди. То же самое касается отдельных процессоров и ОЗУ. А то, что творится в рамках одного кристалла, это как исполнение макрокоманды. В общем, в этой ситуации многопоточность существует лишь на определённых уровнях приближения. Чтобы достичь настоящей многопоточности, нужно отказаться от архитектуры Фон-Неймана с её разделяемым доступом к памяти и устройствам.
А Вы, кстати, в курсе, что обращение к устройствам происходит через адресное пространство памяти? Если нет, то почитайте. Это — знатный костыль, который мы все воспринимаем как норму вещей) Последние массовые процессоры, лишённые данного костыля, были восьмибитными и имели отдельную адресную шину с сигналами переключения режимов доступа.Scf
06.08.2017 11:58+1Да, общая память как "выжившая" массовая архитектура. Что поделаешь — она наиболее удобна для программирования. Но у каждого ядра есть собственный кеш и в рамках него они работают независимо (почти). Алгоритм синхронизации кешей L1 весьма сложен, как и модель многопоточности процессора, см. memory fences и x86 fence instructions.
Про обращение к устройствам через память — да, но запись в память идет же мимо процессора и асинхронно? В отличие от старого подхода через порты ввода-вывода, потребляющие цпу.
KroTozeR
06.08.2017 12:13+1DMA да. Это пример многопоточности. Вообще, сейчас у меня состоялся дискут с коллегой, не менее глубоко разбирающимся в архитектуре процессоров. Сошлись на том, многопоточность зависит от того, к чему она применяется. И всё зависит от того, как относиться к разделению общих ресурсов. Но даже так количество потоков не может быть больше суммарного количества логических ядер ЦП внутри ЭВМ.
KostaArnorsky
07.08.2017 18:44+1Вот так и нужно было сразу написать, а не делать неверные даже в общем случае безапелляционные утверждения.
Strain
06.08.2017 02:34-2Мне понравилась статья, спасибо автору. Плюсанул бы, но кармы мало, так что только так. В первой статье текст был чересчур эмоционален потому что я находился под воздействием свежего погружения в React-Native проект от чего был нестройный слог плюс я забыл пару важных вещей ( в дополнение к тем что там упомянуты )
— кривая реализация integer в JS ( для многих задач крайне критично )
— в стрелочных функциях можно не писать return ( казалось бы ура ), но если там больше одной строки — без написания return спокойно возвращается undefined без всяких предупреждений
— неявные преобразования очень коварны — никогда на 100% нет уверенности какой же тип возвращает данный колбэк из DOM элемента / React компонента ( onChange, onSubmit etc ) и непонятно можно ли с ним безопасно проводить арифметические действия
В общем этот RN проект на ES6 был оставлен на совесть других разработчиков, никакого желания продолжать на этом писать нет. Похоже сработала естественная иммунная реакция организма на JS-мыслевирус, и болезнь не пошла дальше средних стадий. Так что теперь продолжаю писать на любимых языках и наслаждаюсь жизнью.farwayer
06.08.2017 09:21+6кривая реализация integer в JS
как может быть криво реализовано то, чего нету?)
без написания return спокойно возвращается undefined без всяких предупреждений
А почему функция без моего ведома должна что-то возвращать? Я понимаю, вы привыкли, что в функциональных языках так. Но JS — мультипарадигменный язык. Да, с однострочной лямбдой в 95% случаев мы хотим вернуть значение. С многострочной — уже не факт. А если принимающий код неправильно среагирует на это значение? Это не теория, а вполне конкретный случай факапа с coffeescript (в котором неявный return как раз).
неявные преобразования очень коварны — никогда на 100% нет уверенности какой же тип возвращает данный колбэк
Неявные преобразования не связаны в типом данных, который возвращает callback. И уверенности не будет в любом языке с динамической типизацией.
я находился под воздействием свежего погружения в React-Native
Т.е. я правильно понимаю, что вы взялись за коммерческий проект на незнакомой технологии в незнакомой экосистеме, облажались и написали разгромную статью про javascript?
Мне кажется, вы не представляете всю сложность «под капотом» технологии, которая нужна, чтобы запустить проект сложнее hello world на нескольких платформах.
React Native — это вообще не про голубое небо и пони какающие радугой. Это контракт с дьяволом: ты получаешь возможность быстрой кроссплатформенной разработки, а платишь своим опытом и готовностью решать проблемы.grossws
07.08.2017 01:02+1Неявные преобразования не связаны в типом данных, который возвращает callback. И уверенности не будет в любом языке с динамической типизацией.
Вы путаете динамическую и сильную/слабую (strong/weak typing) типизацию. Первая про то, что переменная обладает типом только в рантайме, а вторая касается автоматического приведения типов. Например, python — dynamic, но strong: тип переменной определяется в рантайме, но, скажем число не приводится к строке при попытке написать
"1" + 2
, оно просто даст по рукам exception'ом.TimTowdy
07.08.2017 01:26Все верно, хотя умножение у последовательностей (в том числе строк) все же переопределено:
>>> '3' * 5 "33333"
0xd34df00d
07.08.2017 08:07Ой, кажется, все отрицательные числа равны друг другу:
>>> '3' * (-1) == '3' * (-2) True
grossws
07.08.2017 08:12Да, несколько странное поведение, но оно мне на enumerable/iterable вообще не очень нравится. Как и корявое
a if b else c
.0xd34df00d
07.08.2017 08:19Как для этого
a if b else c
грамматика написана и зачем именно так, мне не очень понятно.grossws
07.08.2017 08:26Выражение
a if b else c
возвращаетa
при истинностиb
, иначе возвращаетc
. В общем вывернутый наизнанку тернарный оператор.
farwayer
07.08.2017 07:59Неа, не путаю. Разговор был про тип данных, которые вернет функция. В случае динамических языков он станет известен не раньше, чем функция отработает.
staticlab
06.08.2017 12:36в стрелочных функциях можно не писать return ( казалось бы ура ), но если там больше одной строки — без написания return спокойно возвращается undefined без всяких предупреждений
Смотрите, во-первых, там есть синтаксическое различие: выражение передаётся без фигурных скобок, а операторы — в фигурных скобках. Во-вторых, если вам так уж хочется автовозврата значения, то есть оператор do (но, stage 0)
Keyten
06.08.2017 17:00В любом туториале по стрелочным функциям (или туториале по особенностям ES6, где упоминаются стрелочные функции), в любом! — упоминается, что многострочные стрелочные функции требуют return.
Кто вам виноват, что вы не потрудились прочитать базовых вещей?Strain
06.08.2017 17:14+3Диагноз
4 уровень: «Вы просто не понимаете сути»
То что есть один тип функций который меняет this, второй тип функций который не меняет this, при этом второй тип функций делится ещё на два — в третьем писать return со скобочками не нужно, а в четвёртом нужно, определённо говорит об удобстве, высокой продуманности и однозначности понятия «функция» в лучшем в мире мультипарадигменном языке и венце технической мысли, JavaScript.staticlab
06.08.2017 22:20+2Кстати говоря, в Java точно так же есть стрелочные функции с фигурными скобками и без. Для первых нужен return. А вот варианта
() => do { }
там нет.
Keyten
06.08.2017 23:32+3Как удобно отослать к диагнозу вместо нормального ответа по теме.
… определённо говорит об удобстве, высокой продуманности...
Подобное можно написать о любой фиче в любом языке. А уж в функциональных-то — тем более.
А this в самом деле очень прост. Не говоря уже про return. Я даже описал всё это всего лишь двумя абзацами.
Немного негодования по темеЕсли я приду в ваш язык и буду негодовать, что return где-то не требуется, что вы сделаете? Согласитесь «ну да, вот язык такой ужасный, сам мучаюсь» или пошлёте читать мануал?
Если я приду в C++ и заявлю, что деление на h и cpp-файлы некошерно, и вообще никто в 2к17 не подключает файлы к проекту с помощью директивы препроцессора, а все делают это функцией?
Если я приду в Python и заявлю, что язык ужасный, потому что в нём нет {}, а вместо этого отступы?
Да, в конце концов, если я приду в любой сильно типизированный язык и скажу, «сильная типизация — это фигня, и поэтому язык плохой»?
Нет, я так не делаю ). Потому что понимаю, что языки разные, разным людям по-разному удобно, и вообще (как мне кажется) тут имеет место аналог гипотезы лингвистической относительности.
И мне не совсем понятно, почему вы приходите в JS и заявляете о неправильности this (к слову, я могу назвать ряд действительно слабых (по моему мнению) вещей в js — например, typeof… но уж никак не полностью логичный this).TimTowdy
07.08.2017 00:21+3Проблема с this не в том, что он сложен, а в том, что он нелогичен.
counter.increment() // сделает то что задумано call_later(counter.increment) // не сделает вообще ничего и даже не выдаст warning/exception
Попробуйте вспомнить, как часто вы реально использовали великолепную фичу «потеря контекста в колбеке» и как часто вы с ней боролись, прибивая гвоздями контекст чтоб не отваливался (либо костыляли c замыканиями var self = this).
Никто в здравом уме не пишет методы которые работают в двух режимах: с контекстом и без него. Так зачем JS вставляет палки в колеса своим разработчикам? Когда каждому разработчику приходится бороться.с языком, проблема явно не в разработчиках.ivan386
07.08.2017 00:37Можно так:
call_later(()=>counter.increment())
Но я даже не представляю в каком языке ваша конструкция отработает верно.
DistortNeo
07.08.2017 00:44+4Но я даже не представляю в каком языке ваша конструкция отработает верно.
Например, в C#.
TimTowdy
07.08.2017 00:58+1Да я в курсе что можно либо создать замыкание (слегка уродливо и менее производительно), либо прибить гвоздями через bind (более уродливо). Именно это я и имел ввиду под стандартным поведением, с которым всем разработчикам приходится бороться.
А отработает корректно например в Python: у instance методов контекст не отваливается, у static/class методов контекста нет.
При этом, если очень хочется, оторвать контекст от метода можно, но мне за последние годы это понадобилось лишь один раз, для настолько магической фичи, что я до сих пор сомневаюсь правильно ли я сделал.
ZyXI
07.08.2017 00:59В Python методы объектов связаны с объектами, поэтому
call_later(counter.increment)
вполне будет работать. До несвязанного можно достать черезcounter.__class__.increment
(правда, не всегда), вот этому придётся передаватьcounter
в качествеself
в любом случае.
TheShock
07.08.2017 04:49Но я даже не представляю в каком языке ваша конструкция отработает верно.
В очень многих. Например в C#, но это другие языки.
mayorovp
07.08.2017 07:51+1Но я даже не представляю в каком языке ваша конструкция отработает верно.
В Python. Там функция является одновременно дескриптором свойства, что переопределяет поведение при чтении свойства. Для функций при обращении вида
counter.increment
возвращается специальный служебный объект под названием BoundFunction.
Ну и про C# уже тут писали. Там в рантайме делегат хранит не только указатель на функцию, но и, опционально, нулевой аргумент. И синтаксис языка позволяет в случаях вроде
counter.increment
эту возможность использовать.grossws
07.08.2017 08:05+1И в java method reference вида
counter::increment
отработает так же в контексте объекта.staticlab
07.08.2017 08:10Это не совсем честно, поскольку всё же требуется двойное двоеточие. Опять же, в stage-0 есть bind operator, благодаря которому на JS можно записать
call_later(::counter.increment)
.grossws
07.08.2017 08:14+2В java method reference пишется только через
::
, других вариантов нет. Не знаю, что уж тут нечестного. В scala IIRC method reference будет выглядеть какcounter.increment
, если вам так захотелось прицепиться к синтаксису.
senia
07.08.2017 08:56Скорее всего везде, где подобная запись вообще имеет смысл.
Эта запись чаще является частным случаем частичного применения.
Рассмотрим общий случай частичного применения (scala):
arg0.method(arg1, _, arg3, _)
Здесь на основе метода, принимающего 5 параметров (this + 4 явных), определяем лямбду, принимающую 2 параметра.
И даже если в языке нет полного синтаксиса частичного применения, работает та же логика: если указан параметр, то он применяется. Java:
Runnable r = counter::increment // без параметр (this применен) Consumer с = Сounter::increment // 1 параметр
Проблема здесь в прототипном наследовании. В JS просто нет способа получить метод класса, не указывая конкретный экземпляр (так как нет классов в привычном понимании).
И в каком-то смысле поведение JS консистентно и логично — функция — это еще 1 поле объекта и, в данном случае, поведение такое же, как и со всеми другими полями. Но this в таком случае имеет смысл совершенно иной, нежели в большинстве других языков.
TheShock
07.08.2017 04:57-1Проблема с this не в том, что он сложен, а в том, что он нелогичен.
Проблема с this не в том, что он нелогичен, а в том, что его логика отличается от других языков.
как часто вы реально использовали великолепную фичу «потеря контекста в колбеке»
Вы не понимаете. Это не фича «а давайте терять контекст, вдруг кому-то удобно будет». Это следствие его реализации. this — просто переменная, которая указывает, в каком контексте вызвана функция. То есть контекст не теряется, его никогда и не было, он просто подставляется в момент вызова. И такое следствие вы используете значительно чаще, чем вы думаете (все прототипное наследование на этом построено).
Вот смотрите.
var a = { foo: function () { console.log(this) } } a.foo()
Тут вроде логично.
var a = { foo: function () { console.log(this) } } var b = { foo: a.foo } b.foo()
Вот тут могут быть сторонники каждого решения.
var Proto = { foo: function () { console.log(this) } } var B = function () {} B.prototype = new Proto(); var b = new B(); b.foo();
А вот тут контекст должен указывать на Proto, по вашему, потому что там объявлена функция?
Другое дело, что в 95% случаев работа с this в JS крайне неудобна и новички об нее постоянно спотыкаются. Это да. Но она по-своему логична.
Keyten
07.08.2017 13:19+1Define «логично».
Я считаю, что this логичен. This хранит контекст, в котором функция вызвана. Ну, TheShock уже всё отлично объяснил.
Смотрите, вот тут мне нужен родной контекст функции:
SomeClass.prototype.init = function () { this.element.addEventListener('click', this.onElementClick); }; SomeClass.prototype.onElementClick = function () { this.style.color = 'red'; }; // так делать не очень хорошо, но не суть: легко придумывается более адекватный и правильный, но многословный пример
А здесь нужен контекст класса, и я просто делаю bind:
SomeClass.prototype.init = function () { this.element.addEventListener('click', this.onElementClick.bind(this)); }; SomeClass.prototype.onElementClick = function () { this.element.style.color = 'red'; };
fukkit
07.08.2017 21:43Вы передаете в call_later один параметр — адрес метода, но не передаете адрес объекта контекста. Она угадать его должна?
ZyXI
07.08.2017 21:55+2Передаётся один параметр — метод. Не адрес метода, метод. Во многих языках (кстати, сейчас к ним присоединился даже дремучий VimL, в котором ни классов, ни прототипов нет, только словари) при таком обращении либо происходит связывание метода с объектом, либо возвращается уже связанный метод (а связывание происходит при создании). Это вполне логично, такое связывание позволяет реализации языка «не знать», как вызывается метод и что следующей операцией вообще будет вызов: просто происходит что?то вроде «оператор точка достаёт атрибут объекта (достаётся уже связанный метод) и кладёт его на вершину стёка, затем оператор скобочки вызывает то, что находится на вершине». Или не вызывает, если такого оператора нет.
fukkit
08.08.2017 11:27-1Чистой воды вкусовщина. Лично я не люблю, когда язык навязывает неявные действия. Любите, чтобы в одном параметре передавалось два объекта или некий новый костыльный метаобъект? Не вопрос, есть такие языки.
Вы ему: передай той функции метод этого объекта (функцию, которая умеет обрабатывать любой объект данного класса, у нас типа ООП),
Он вам: хорошо, но я неявно присобачу к нему и сам объект, чтобы ты не гневался, если вдруг имел в виду именно этот случай, а накладные расходы не были слишком низкие.TimTowdy
08.08.2017 14:01Вы о чем? this в JS как раз и является максимально неявным способом передачи дополнительного параметра в функцию. Вас почему-то не смущает неявный костыльный метаобъект, который передается при вызове counter.increment().
<expression1> == <expression2> // true <expression1>() // делает мяу-мяу <expression2>() // не делает ничего
В хороших языках, оператор «скобочки» — это просто вызов функции. А в JS — это анализ выражения перед скобочками, возможно подстановка контекста, и только потом уже вызов функции. Т.е. результат работы функции зависит не только от переданных параметров, а еще и то того, какое выражение было написано для того, чтоб эту функцию вызвать.
Вы ему: передай той функции метод этого объекта (функцию, которая умеет обрабатывать любой объект данного класса, у нас типа ООП),
Функция, которая умеет обрабатывать любой объект данного класса, это метод класса, а не объекта. В него объект должен передаться явно, через аргумент, а не через магический контекст.
неявно присобачу к нему и сам объект, чтобы ты не гневался, если вдруг имел в виду именно этот случай, а накладные расходы не были слишком низкие.
Вы же не думаете что вся эта магия с контекстом на самом деле сделана для оптимизации накладных расходов? JS вообще-то практически всегда неявно присобачивает какой-то объект. И делает это при каждом вызове метода. То, что он не используется, не значит что его нет.TheShock
08.08.2017 14:39-1Функция, которая умеет обрабатывать любой объект данного класса, это метод класса, а не объекта. В него объект должен передаться явно, через аргумент, а не через магический контекст.
Увы, нету в JS классов в таком виде. Есть функция первого класса, которая создает объект и которому присваивает прототип. И даже нету методов. Есть функции, которые лежат в прототипе.
У нас даже нету «это объект класса А». Нет. Скорее так: «у этого объекта сейчас прототип ссылается на инстанс А». А может потом вдруг начать ссылаться на что-то другое
Не мыслите о this в JS с точки зрения C#, это не имеет никакого смыслаTimTowdy
08.08.2017 17:59Классов нет, но абстракция «метод объекта» есть, именно поэтому контекст неявно и подставляется в counter.increment(). Проблема в том, что абстракция часто течет, например при работе с колбеками. Тот факт, что проблема объясняется устройством языка, не отменяет само существование проблемы.
Но веселье call_later(counter.increment) ведь не только только в контексте. Мало того, что язык легко позволяет допустить такую ошибку, так он еще и без проблем обратится к несуществующему свойству у window, извлечет undefined и увеличит его на 1. Никаких ошибок в процессе.
Я мыслю не с точки зрения C# (последний раз писал на нем более 10 лет назад), а с точки зрения того, каким должен быть промышленный язык. Особенно когда у него появляются ярые фанаты, которые считает что на нем можно (и нужно) писать абсолютно все.TheShock
08.08.2017 18:39-1Я говорю лишь о том, что это вполне логично с точки зрения внутреннего устройства языка.
О том, что это крайне неудобно — я совершенно не спорю. В том же C# лично мне этот момент нравится значительно больше — оно более интуитивно-понятно и значительно приятнее используется.
vintage
09.08.2017 14:41+3без проблем обратится к несуществующему свойству у window
Это давно уже починили в strict mode.
DistortNeo
07.08.2017 22:10+1Она угадать его должна?
Но ведь угадывает же при вызове counter.increment().
fukkit
08.08.2017 11:37+1В контексте вызова counter.increment() существует вычисляемое значение counter, потому угадывать ничего не нужно.
Когда вы вычисляете counter.increment и передаёте уже его значение в другую функцию, еще большой вопрос, должен ли кто-то услужливо тащить за вами оторванный counter или же делать точно то, что написано.
Выше справедливо отмечают, что модные языки так делают. Возможно, для удобства, чтобы не отвлекать девелопера в поездке до барбершопа на гироскутере. Если это такая востребованная фича, в принципе — нет проблем, простите брюзжание старого сишника.DistortNeo
08.08.2017 15:35+1Когда вы вычисляете counter.increment и передаёте уже его значение в другую функцию, еще большой вопрос, должен ли кто-то услужливо тащить за вами оторванный counter или же делать точно то, что написано.
Я считаю, что должен. А если я не хочу, чтобы контекст тащился вместе с методом, я просто создам статическую функцию increment с параметром counter и будут передавать "контекст" в виде counter в явном виде.
yhaskell
08.08.2017 11:43На самом деле она не указывает. Запись
a.foo()
обозначает "вызвать методa.foo
в контексте сa
". Просто контекст по умолчанию не bound, как в Python.
Bonart
06.08.2017 03:26+4Однако у автора чуткий инстинкт самосохранения. Ни единого слова про язык Go.
pnovikov Автор
06.08.2017 06:45+8А что про go? Я не общаюсь с его фанатами. Пока что мне нечего про них сказать, за исключением того, что они много говорят про микросервисы, а когда им намекаешь что при микросервисной архитектуре неизбежна встреча с координатором распределенных транзакций — сразу начинают говорить что-то невнятное и уходить куда-то в тень.
alexeyknyshev
06.08.2017 09:30Иногда eventual-consistency вполне достаточно, при необходимости можно сделать 2-phase commit.
pnovikov Автор
06.08.2017 09:39+5Тут проблема в другом. Любители go обычно топят за микросервисы со страшной силой. Так вот меня интересует — если у вас один микросервис А обращается к микросервису Б, а тот в свою очередь — к миросервису С, то
а) как передать контекст транзакции между ними?
б) как откатить результаты работы микросервиса А, если микросервис С по какой-то причине выдал ошибку?
Здесь нужна некая третья сторона, которая, как ни странно, координирует распределенную транзакцию. Логично? Ну и вот ответа на этот вопрос мне пока ни один адепт go не дал. И я уже молчу о сложных кроссистемных случаях вроде транзакционная запись в файл + в базу (если ошибка — откатывается и то и другое).
В остальном — гугл неплохо пишет на go всякие низкоуровневые инструменты вроде небольших
галактиксерверов баз данных. И это определенно хорошо, но ИМХО не дает повода писать на go бизнес-логику и бекенд.farwayer
06.08.2017 10:19+2Осмелюсь порекомендовать
вот этот докладfarwayer
06.08.2017 09:51+3В плане маркетинга у них все классно: хочется выучить язык хотя бы для того, чтобы с чистой совестью поставить на стол
вот такую штуку :)
SDSWanderer
06.08.2017 04:11+1Статья в двух словах:
Ей, вы там, js разработчики, да, я не спец в этом вашем js, но слушайте меня, я вам сейчас поставлю диагноз!
И не лень же людям столько текста писать!
mogaika
06.08.2017 11:06+2Комментарий в двух словах:
Ей, вы там, читатели комментариев, да, я не читал статью, либо не понял смысла, но здесь, похоже, затронуты мои чувства!
И не лень людям комментарии оставлять
ankh1989
06.08.2017 04:40+3После 9-го уровня возникает какое то безразличие ко всем этим джаваскриптам и на такие вопросы флегматично отвечаешь, что не видишь большой разницы между Java и JavaScript. К слову, на одном собеседовании в Амазон я также флегмантично ответил — интересно было реакцию посмотреть, а мне в ответ "значит вам всё равно на каком языке писать? прекрасно!"
pnovikov Автор
06.08.2017 06:46-1Это отходняк и деменция, друг мой. Последствия быстрого слезания с JS.
lxsmkv
06.08.2017 06:25+3Язык программирования это ведь в первую очередь всего лишь язык. Странно было бы если бы все взахлеб восхищались испанским или до хрипа хейтили корейский. А вот среди переводчиков есть хорошие и не очень. Мне кажется дело только в этом.
pnovikov Автор
06.08.2017 06:57-2Есть в этом доля правды. Но я же не могу написать что все программисты JS — плохие программисты. Во-первых в общем случае это не так, хотя среди них и много "нанятых по объявлению". Во-вторых тапками закидают. В-третьих это просто некрасиво.
А вот есть ли среди человеческих языков те, которые всерьез разъедают мозг переводчика — я бы с радостью послушал. :)
lxsmkv
06.08.2017 14:34+4Я неудачно выразился. Не очень не в том смысле. А в смысле опыта. Хорошо владеющие языком и не очень. Т.е. когда ты в совершенстве владеешь языком, он не кажется тебе странным. Трудности возникают при переходе, особенно при смене парадигмы.
Когда я только начал учить английский язык мне не нравилось что там есть определенный и неопределенный артикль который, не до конца понятно зачем нужен. Потом я по воле судьбы выучил немецкий язык который тоже имеет определенный и неопределенный артикль. Но немецким, по прошествии шестнацати лет, я владею как вторым родным языком и мне артикль кажется уже не искуственной вещью (надо ставить, будем ставить), а естественной. И в английском языке, поскольку они родственные, артикль стал для меня такой же естественной вещью.
Наверно правильно не язык ругать, а то что его трудно учить. Наверно это будет более объективной критикой.
AnutaU
06.08.2017 21:07Вышеупомянутый корейский — ещё какой мозговирус. У меня, кажется, уже терминальная стадия КГМ: я захожу на Хабр почитать про языки программирования, а комментарий пишу, кхм, всё про то же.
slonopotamus
06.08.2017 13:09Не могу согласиться. Естественные языки вполне себе можно сравнивать. И ругать китайский за упоротую письменность, а французский за проглатывание тонн букв в устной речи.
lxsmkv
06.08.2017 14:44+2Это вряд ли будет объективно. Пока носители языка считают его удобным, количество критики в адрес этого языка будет просто выражением углового коэффициента кривой обучения для изучающих его.
uvelichitel
06.08.2017 16:57Испанский и корейский это естественные языки. А вот эсперанто искусственный, его можно ругать.
Mycolaos
06.08.2017 06:43+2… со временем превращает пишущего на нем человека в агрессивного фанатика, чего мне не удалось отметить с другими технологиями.
Лукавите.
borisxm
06.08.2017 07:45В каждой шутке, есть доля шутки. У нас в компании C++/Java/Pascal разработчику никогда не поручат написать на JS что-то сложнее приветмира. А JS-программисты должны держать себя в рамках, чтобы не достигнуть четвертого уровня.
ju5tify
06.08.2017 09:14-18Единственной что я выношу из подобной статьи — у автора подогрело от того, что в 2017 году за JS платят как минимум в полтора раза больше чем за такие любимые и строжайше типизированные и трижды многопоточные плюсы или ещё там что-то.
Как же это так? Сраный джаваскрипт, на котором ещё вчера делали выпадающие менюшки, теперь является одним из самых популярных и высокооплачиваемых инструментов разработки! А как же Страуструп? Как же моя магистратура? Что же это делается такое, а? Беспредел! Пойду напишу разоблачающую статью о том какое JS говно. Ее все прочтут и сразу прекратят писать на нем и снова мой "правильный" язык будет править миром (и зарплатами на Stackoverflow careers).
М — многоходовочка.pnovikov Автор
06.08.2017 09:19+11Мне очень льстит что вы так беспокоитесь за мое финансовое положение, но все же уверяю вас — с ним все в порядке. Настолько в порядке, что мне иногда даже стыдно становится :)
Вообще апелляция к деньгам, конечно, самая хитрая. Скажешь что получаешь мало — де-факто согласишься с оппонентом. Скажешь что получаешь много — все подумают что хвастаешься. Не люблю такое. Поэтому намекаю что за свои знания строжайше типизированного языка, Страуструпа и магистратуру я получаю больше, чем квалифицированный JS-разработчик в Москве.
ju5tify
06.08.2017 10:42-11Ого, круто то как! Вот это я понимаю успешная карьера! И зарплата настолько большая, что аж стыдно называть, и хвастаться не хочется (на самом деле хочется :)), и работа в столице.
Все настолько хорошо, что аж появилось желание и уйма времени написать огромный трактат про недостатки совершенно чужого языка. Действительно, зачем? Вот я тоже получаю миллиарды заграницей (мы все тут богатые и успешные, да), но мне и в голову не приходило написать пьесу по полям о недостатках Rust. А все потому что он мне нахер не нужен. А что ж вас тогда этот убогий JS беспокоит?pnovikov Автор
06.08.2017 10:47+9Я вам секрет открою — у меня еще время есть написать парочку opensource-фреймворков, один из которых на 60% состоит из кода на TypeScript и, как вы понимаете, это означает что JavaScript меня беспокоит, в отличие от Rust и Go (см. шапку статьи). И работаю я далеко не в столице, а прямо из дома.
Зачем писать статью? Ну например чтобы посмотреть как у вас припекает :)
staticlab
06.08.2017 11:54Открыл Reinforced.Lattice/CoreInterfaces.ts, а там 26 any. Туда действительно можно засунуть что угодно, или просто описать типизированно слишком муторно?
pnovikov Автор
06.08.2017 11:56Действительно можно засунуть что угодно. В каждый any ставится пользовательский тип данных, который пользователь может сам задать из C#-части. Как вы понимаете, этой информацией невозможно обладать на момент написания Lattice
Бтв, доку с примерчиками тоже посмотрите чтобы понимать что оно делает. Там не дописано 80%, но даст примерное понимание что я делаю.
PsyHaSTe
07.08.2017 19:35+1Я правильно понимаю, что From может быть string, а to — DateTime? Если нет, то почему бы не сделать было
IPrecomputedRange<T>
, чтобы пользователь сам явно указывал необходимый тип? Потому как мне кажется, чтоIPrecomputedRange<string>
!=IPrecomputedRange<DateTime>
, а у вас по сути они одного типа..pnovikov Автор
07.08.2017 21:11-1Вспомнил.
Слушайте, вы точно контекстно понимаете как используется эта структура, чтобы комментировать? Просто если я тут буду объяснять — будет пара экранов. Но в двух словах — да, any там — это ок, пользователь эту структуру не видит. Она сделана export из-за того, что в typescript нет internal.PsyHaSTe
07.08.2017 21:43+1Ну я посмотрел пару использований, и не нашел, почему информация о типе должна быть спрятана. Почему вот сюда например:
module Reinforced.Lattice.Filters.Range { export interface IRangeFilterUiConfig { ColumnName: string; FromPlaceholder: string; ToPlaceholder: string; InputDelay: number; FromValue: string; ToValue: string; ClientFiltering: boolean; ClientFilteringFunction: (object: any, entry:Reinforced.Lattice.IPrecomputedRange, query: IQuery)=>boolean; CompareOnlyDates: boolean; Hidden: boolean; DefaultTemplateId: string; InclusiveLeft: boolean; InclusiveRight: boolean; } }
Нельзя сразу показать, что IPrecomputedRange тут от типа string? У нас есть FromValue — string, но ничто не мешает запихнуть любой object в качестве FromValue, причем ToValue может быть вообще третьим типом, несовместимый с этими двумя.
Я понял, что это "internal класс, у него полтора использования, и я всегда передаю только нужные и совместимые типы", возможно это придирка, но мне было интересно чем вызвано такое решение. Иногда типы явно передавать нельзя, согласен, но ведь это не тот случай? А если тот, то каков юзкейз который дженерик разрушит?
pnovikov Автор
07.08.2017 21:57Нет, нельзя. Потому что конкретный тип range-фильтра задается пользователем на сервере и выводится из C#-класса. Получается что клиентский typescript-интерфейс (которого не существует после сборки) должен параметризовываться серверным C#-типом. В связи с чем я повторяю свой вопрос — вы точно понимаете как работает этот код?
pnovikov Автор
07.08.2017 22:04Бтв, здесь FromValue/ToValue — сериализованное значение для начальной конфигурации range-фильтра. Циферки просто парсятся, дата — десериализуется из ISO, строки остаются как есть. Более того этот класс (Config) — автоматически сгенерирован из C#-кода. Вам точно хочется в этом разобраться или вы с какой целью интересуетесь?
К слову: в C# коде я тоже часто использую object, что не означает что я должен незамедлительно перевести этот код на generic-и.
PsyHaSTe
08.08.2017 13:49Бтв, здесь FromValue/ToValue — сериализованное значение для начальной конфигурации range-фильтра. Циферки просто парсятся, дата — десериализуется из ISO, строки остаются как есть. Более того этот класс (Config) — автоматически сгенерирован из C#-кода. Вам точно хочется в этом разобраться или вы с какой целью интересуетесь?
Да, я пытаюсь разобраться, о чем я сразу и сказал.
К слову: в C# коде я тоже часто использую object, что не означает что я должен незамедлительно перевести этот код на generic-и.
Зависит от использования. Если это условный params — то не вопрос, а если условный object sender — то увольте.
pnovikov Автор
08.08.2017 13:49Пишите в личку тогда — я вам отдельно расскажу. Там реально все сложно и информации очень много.
ju5tify
06.08.2017 12:45-13- Маам, смотри, я траллю!
Серьезно? Успешный высокооплачиваемый разработчик тратит своё время чтоб потроллить бичей вроде меня?
И не забудь ещё пару ссылочек кинуть, а то вдруг кто-то пропустит этот пиар и не поставит звёздочку на гитхабе :) :) :)
Hardcoin
06.08.2017 18:25+1А в чем проблема? Успешный разработчик должен тратить всё своё время, что бы зарабатывать бабки и становиться ещё более успешным? :)
- Маам, смотри, я траллю!
0xd34df00d
06.08.2017 09:43+8Готов поспорить на среднее арифметическое между вашей и моей зарплатой, что за JS таки платят не в полтора раза больше, чем мне за мои плюсы, например. И даже вряд ли вообще больше.
herr_kaizer
06.08.2017 09:36+8В статье упущены всякие PhoneGap, Electron и прочие. Вот уже где апофеоз бесполезности и фанатизма.
Скрытый текстpnovikov Автор
06.08.2017 09:44+2Ну кстати концептуально насчет бесполезности Electron я бы не согласился. Но только концептуально, то есть "ну наверное было бы неплохо писать десктопные приложения на html+js" (тем паче что многие так делают). Но в деталях реализации я ни черта не смыслю, поэтому не упоминаю.
Sad_Bro
06.08.2017 10:20+2Своя ниша есть, почему нет.
Пишутся же не плохие приложения на electron, тот же Sluck, Postman да еще и под все OS.
Alex_ME
07.08.2017 00:18Сам отношусь к js-вакханалии примерно как описано в статье (основной язык — cs). Но тут надо делать одно небольшое веб-нечто, да еще требуют мобильное приложение. Я подумал, и решил использовать React + Cordova, чтобы сэкономить время, силы и прочее.
Доктор, на какой я стадии?
pnovikov Автор
07.08.2017 00:21Ну… вы по крайней мере ещё умеете думать сами. Так что лично я вас благословляю.
Antelle
06.08.2017 11:39+2Нет уровня "да наплевать на чём писать, главное чтобы это решало проблему, если это для вас на сейчас javascript, то и юзайте".
TimTowdy
06.08.2017 13:10+13Жду ответной статьи «Джаваскрипт как религия». С описанием почему именно эта религия единственно правильная и всем нужен радикальный Джаваскрипт. С пошаговым руководством как взрывать проекты написанные на технологиях неверных, чтобы потом переписать их на единственно верном технологическом стеке. Каждый кто перепишет проект на JS — получит в награду 72 сервера с NodeJS (после банкротства).
А если серьезно, такие фанаты появляются в основном у хайповых языков, с явными общеизвестными недостатками. Ну там типизация в Джаваскрипт или дженерики в Go. У хипстеров возникает чувство неполноценности, и они таким образом борются с ней. «От слабой типизации мы только выиграли». «ДЖЕНЕРИКИ НЕ НУЖНЫ!»
Казалось бы, прими недостаток и живи себе дальше спокойно. Идеальных технологий не бывает. Изучи другую технологию, сравни плюсы и минусы… Но нет, хипстерам нужен максимализм.farwayer
06.08.2017 13:36+4Да фигня в том, что часто происходит как раз наоборот. Приходит такой чувак из Java (спокойно, это только пример) к go-разрабам и говорит: go — говно, потому что в нем нет дженериков. А те сидят, хлопают глазами и думают, как объяснить чуваку, что у них и так все хорошо.
TimTowdy
06.08.2017 14:29+5Вот об этом фанатизме я и говорю, отрицание вполне объективных проблем потому что «у нас все и так хорошо».
Роб Пайк вообще-то признает, что не все хорошо: «A weakness of Go is that any generic-type operations must be provided by the run-time. Some day that may change»
Жаль, что некоторые не хотят это понимать и бездумно защищают статус кво. Умение видеть собственные недостатки — бесценно.pnovikov Автор
06.08.2017 14:39+2Напомнило замечательную историю про Java, C# и лямбда-выражения. Когда их сделали в C#, джависты хором кричали — "нинужно!". А когда ввели и в Java — "как же мы раньше без этого жили-то божечки?"
S_A
06.08.2017 15:35+5Вы знаете, я писал за деньги на C/C++, C#/F#, Java, Swift, Python, PHP, Javascript/Coffeescript/Typescript. На гироскутере в барбершопы не езжу. И считаю стэк JS/node.js наилучшим что есть в настоящее время.
Знаете почему? Потому что в его экосистеме из п. А в п. В я быстрее всего доберусь. Единственное, что я не доверяю пока JS — это мобильные приложения (React Native очень сырой, ненативные гуи не люблю). Предпочитаю Xamarin (и нет повода для жалоб). Nuget — следующий за NPM репозиторий, позволяющий не писать, а конструировать приложения (получая за это деньги, а не ЧСВ).
Вобщем не язык решает. Хайпа хватает и в тусовке шарперов, и джавистов. Плюсисты вообще смотрят на всех как на детей :) А кто на ассемблере пишет, тот «в цирке не смеется».TimTowdy
06.08.2017 16:43+1Я в одном из прошлых тредов уже писал, что жизненный цикл проекта обычно несколько сложнее чем добраться из п. А в п. В.
Подход «максимально быстро из п. А в п. В» например приводит к максимально быстрому накоплению технического долга. Иногда стоит задуматься о том, что пункт В — не конечная остановка. У бизнеса конечеой остановки может вообще не быть.
Если вам нужно срубить 5 деревьев, можно взять тупой топор. А если деревья нужно рубить каждый день, возможно стоит инвестировать в бензопилу.
Хотя конечно бывают сферы где для разработчика это зачастую неважно (тот же фриланс).
Ну а с тем, что наколеночные прототипы на JS делаются быстрее, чем на каком-нибудь Java, вроде никто и не спорит.S_A
06.08.2017 17:37Тот же Angluar веьма SOLID. И те же админки на том же Zend >= 2 я забрасывал не одну (и не с одним проектом на них уже) за то что они долгие.
p.s. во фрилансе никогда не работал.TimTowdy
06.08.2017 18:26+1Тот же Angular почему-то решил перейти на TypeScript.
Но проблема JS не в отсутствии фреймворков или библиотек (как раз наоборот, google: drinking game for web developers).
Проблема в том, что он не задумывался как промышленный язык, но в какой-то момент стал им. И в отличие от других пром. языков он не может развиваться, ломая обратную совместимость. Его посыпают сахаром и подпирают костылями, но 20-летние детские проблемы от этого не уходят. Это — объективно плохо. С этим можно жить, но этому глупо радоваться.S_A
06.08.2017 18:44Я не вижу проблем с Typescript --> Javascript. Есть fullstack фреймворки — fireloop например — которые прекрасно работают только с Typescript.
Проблемы в том, что Javascript как язык не тот для меня нет. Я и начинал свое комментирование с того, что неважно на чем писать, важно как быстро экосистема тебе поможет достичь целей. У меня в работе сейчас один проект с первым ангуляром, не жалюсь, пишу… считаю что на PHP я то же самое писал бы значительно дольше.
Nakosika
06.08.2017 13:34+17Вся драма ява скрипта в том, что ВЫ ЕГО НЕ ВЫБИРАЛИ.
Вы выбирали веб, его выбрали преподаватели, статистика популярности, менеджеры, коллеги, экосистема, эйчары, ваша лень, его выбирали все кроме вас лично.
Я не знаю ни одного случая чтобы этот язык был выбран просто после вдумчивого изучения нескольких разных языков, как наиболее подходящий для конкретной задачи. Такие случаи, кончено, есть, но лично я их не знаю.
Его можно полюбить не смотря на его недостатки, что демонстрируют его поклонники. Но речь не про любовь.
И это беда всей нашей индустрии — обычно мы не выбираем лучшие технологии просто потому что они лучшие для нас. Другие делают этот выбор.
Но редкие компании все-таки отваживаются делать свой выбор в пользу не мейнстримных технологий. Поднимем бокалы за храбрецов, хотел бы и я у них работать. Изучить какой-нибудь эфшарп или кложур, а то и пилить на расте — выдавать идеальный код со скоростью мысли и постить статейки в стиле "Х за Х минут", на правах первопроходца.
Но удел большинства все-таки идти за трендом. Просто потому что это тренд, который никто не выбирал.
Andchir
06.08.2017 15:00+1Как же достали статьи из разряда «я пишу на правильном языке, а вы все мудаки». Остановите этот поток бесполезного самолюбования на Хабре.
ua_lost_in_the_dark
06.08.2017 15:09+5Отложи JavaScript, освой какой-нибудь игровой движок. Тебе срочно необходимо отогнать от себя мысли о JavaScript
И тут такой скачиваешь Unity 3D и начинаеш писать на UnityScript.
khorn_sv
06.08.2017 15:09+7Странная, ей Богу, статья. Да и профиль у вас чертовски странный:
делать всё, чтобы защитить бедных C# разработчиков от излишнего ныряния в JavaScript
Особенно в том, что касается «фанатизма» странности. В самой статье, да и в комментариях под ней вы упорно пишете, что вас раздражает не сама технология, но скорее ее через-чур ярые поклонники и высокомерие, с которым они смотрят на «не причастившихся».
Но, вот ведь парадокс, сама статья является воплощением подобного же высокомерия. Так как она буквально вопиет о важных умолчаниях: «те, кто выбирают .js добровольно — с вирусом в голове», ну и то, что ваш стек технологий и знаний по умолчанию дает вам моральное право смотреть на этих бедолаг с высока и рассуждать о том, что хорошо, а что плохо.
Но за юмором, за фразочками «я не имею ввиду технологию, а только фанатиков от нее» не скрыть негативных коннотаций. Все то же необоснованное высокомерие на пустом месте.
Не понимаю, честно, цели написания статьи. И так ведь понятно, что все «фанатики» — это в большинстве своем неофиты. Но ведь не только они выбирают этот язык в качестве инструмента. Есть и матерые зубры, которые с энтузиазмом относятся к нему.
Но умолчания… Серьезно, сначала вы называете какую-то особенность языка и потом говорите, что если человеку она нравится, то он «болен». Но ведь нет, вы же не имеете в виду всех, а лишь только «фанатиков». Мастерство риторики — запредельное. Сначала обгадить людей, хочу подчеркнуть, исключительно на основе того, что отношения к одному и тому же языку разные, а потом сделать вид, что этого как бы и не было.
И главное, чего добиться то хотели не понятно. Остроумием блеснуть за счет того, что потешаетесь над другими.
Очередной высокомерный повод для холивара на пустом месте.pnovikov Автор
06.08.2017 15:26+4Вы нашли в моем тексте какой-то странный, неведомый мне доселе подтекст. Если бы я действительно хотел полить JS фекалиями, а всех, кто на нем пишет назвать идиотами, то я сделал бы это в куда более жесткой форме, без юмора и аргументировано, с техническими выкладками. Но я признаю право тех, кто пишет на JS писать на чем им вздумается и как им вздумается, равно как и право любого человека любить все, что душе угодно — хоть тот самый '3' + 2.
Другое дело, когда эти люди выходят во внешний мир и начинают кричать что это — правильно, а кто не разделяет этой точки зрения — идиоты. Особенно не люблю, когда подобные персонажи начинают меня учить как мне жить и как разрабатывать ПО. И категорически не люблю, когда такие персонажи еще и индустрию критикуют, называя все сделанные до них наработки — фигней на постном масле, зачастую не понимая их назначения. Вот тут-то им и надо давать по ушам, что я и делаю в игровой форме.
Короче. Я признаю право людей писать на на чем угодно, как угодно и любить что угодно ровно до того момента, пока они не перестают признавать мое право писать на на чем угодно, как угодно и любить что угодно. Нарушение этого права происходит способами описанными выше. Как видите, технологии тут абсолютно ни при чем.
все «фанатики» — это в большинстве своем неофиты
Доводы про строгую типизацию и многопоточность я услышал от senior js developer-а, который занимается программированием то ли 10 то ли 15 лет. Dixi.
Мастерство риторики — запредельное
Нет никакого мастерства риторики. Если вас кусает собака — то это жизнь и с этим надо жить. Если вам нравится когда вас кусает собака — вы со странностями. Если на основании этого факта вы убеждаете других, что им тоже должно нравится когда их кусает собака, а всех, кто с этим не согласен поливаете дерьмом — то вы больны. Именно об этом я и пишу. Обратите внимание на 8 пункт.
Andchir
06.08.2017 16:02+1Обратите внимание на 8 пункт.
Если серьезно, вы встречали в своей жизни хоть одного человека, подходящего под описание в вашем 8-м пункте? По-моему это только ваше представление, а если разобраться, поговорить с человеком, то окажется, что он не такой. Вы живете в каком-то своем виртуальном мире.pnovikov Автор
06.08.2017 16:05Ознакомьтесь с картинкой по ссылочке. Это вот как раз часть диалога с разработчиком из комментария выше.
uvelichitel
07.08.2017 00:49Писать на JS не право а долг сегодняшнему web. Кто то должен это делать, а то упадут небеса. Выбора ведь нет. А без игрух на unity ещё пока можно обойтись. Пока C# экзистенциальный выбор, а не священный долг.
fukkit
06.08.2017 18:53Обычная сублимация средней тяжести невроза, индуцированного латентной привязанностью к объекту отрицания (в детстве понравившуюся девочку за косичку дергали).
Высокий интеллектуальный уровень позволил автору отрефлексировать своё болезненное состояние, но не осознавая этого, артикулировать ему пришлось лишь в форме проекций в статье медицинской тематики.
Таким образом, уже очень скоро дорогой автор отринет C# и прочих языческих идолов, и вольется в стройные ряды адептов несравненного JS, который, как известно, любит и принимает абсолютно всех.
zKey
06.08.2017 17:58Интересная статья. Видно проблема назревает повсеместно, не только у нас в компании. С отделением фроненда и выносом его на ноду проблем особых не было. "Так модно, так современно". Появился независимый деплой фронта и бека. Теперь фронт может релизиться в два раза чаще. Нам понравилось) но JS-совцы решили пойти дальше и начали затягивать бизнес логику на фронт. Почему-то начали переписывать полностью готовые сервисы, которые никому не мешают на JS. У нас начался треш и угар. У кого-нибудь есть такое?
Fen1kz
06.08.2017 18:22+4Я определил у себя 7 стадию, уже собирался действительно пойти работать грузчиком, но на полпути дошло, что пред-терминальную стадию у меня диагностировал сишарпист. No way, вы же проклятые микрософт-зомби =/
sbnur
06.08.2017 18:24-4Мысленно убираем браузеры — и где видится javascript?
Keyten
07.08.2017 17:16sbnur
07.08.2017 17:24Вы меня убили (фигурально конечно)
Никогда не говори никогда (это я себе)
PS — а мне казалось (по собственному опыту), что для микроконтроллеров достаночно c или luaTheShock
07.08.2017 18:19А какая разница — JS или Lua?
grossws
07.08.2017 18:25Lua куда проще и прямолинейней. Простая стековая машинка, легко встраивается. Но зачем оно на контроллере со, скажем, 4k RAM мне не очень понятно.
Xitsa
15.08.2017 09:34У lua — регистровая машина.
Lua is dynamically typed, runs by interpreting bytecode with a register-based virtual machine
grossws
07.08.2017 18:24Для игрушек — да. Для чего-то серьёзного отдавать 350k медленному интерпретатору, лишаться всяких прелестей типа DMA и т. п. Подобные вещи оставляют этот проект в нише ардуино, но на более мощном контроллере (хотя сейчас уже есть ардуины на cortex-m).
Вон в соседней теме автор радостно заявляет:
Вводит в заблуждение приставка Script и несерьёзный имидж языка, а на деле обнаруживается, что язык применяется от front-end и back-end до дескопных и мобильных приложений, программирования интегральных микросхем, обработки видео и в множестве других сфер.
Но пока видится только фронт, некоторый кусок бэка, некоторое количество десктопных приложений на электроне, react native и хватит.
devalone
06.08.2017 18:35А ещё есть уникумы, которые считают, что писать на js десктопные приложения и прошивки для микроконтроллеров — норма.
ZhetoN
06.08.2017 19:11+4Пишу на JavasSript /* современном :) */, PHP, Python, C, C# – обожаю их всех. Хотя, в подобных статьях, в комментариях всегда интересно.
pawlo16
06.08.2017 20:20+1Немедленно достань учебник по C++
даже не знаю как отреагировать. То ли Наполеон ставит диагноз Гитлеру. То ли больному клаустрофобией прописывают ударные дозы суицидального чтива. В любом случае помощь квалифицированных медиков требуется обеим сторонам
teux
06.08.2017 22:38+1Голосую за писательский талант автора )
C JS пятый год. Бывает позыв бросить, перейти на какой-нибудь C(#++). Но JS тут ни при чем.
Скорее тяготит безмерности спектра применений JS.
Мозг не справится постичь, осилить все прикладные области, все тулзы и либы. Пять лет в состоянии цейтнота. Верно подметил автор, чтение концептуальных статей отнимает уйму времени.
Есть еще момент. Если нужно что-то разработать, в первую очередь думаешь не об алгоритме, а о том, есть ли подходящий npm-пакет.
Но все это реально настолько увлекательно. Не знаю, как сейчас в других языках, но в мир JS можно уйти так глубоко, насколько хватит ментальных возможностей.
Вот и думаешь иногда, метнуться на какой-нибудь C(#++), может даже вспомнить Assembler, на котором писал когда-то драйверы. Сидеть себе копаться в однотипных задачах. Развлекать мозг алгоритмами, а не поиском npm-пакетов.
Но потом понимаешь, будет скучно без ощущения вечного ренессанса, без камасутры react/redux/apollo/immutable/draft/jest/eslint/node/npm/sass/webpack/babel/uglify/pm2. И не можешь никуда деться. Все симтомы болезни налицо )
archik
07.08.2017 00:03+1Странно, что страйки многопоточности до сих пор летят в сторону JavaScript. В чисто виде, конечно же, таких механизмов в языке нет, но в браузере вы всегда сможете найти Web Workers, а в Node.js многопроцессорность с IPC и child_process. И это все, если очень хочется. Однако не надо забывать, что каждый «молоток» для своих «гвоздей».
«Компьютер — это конечный автомат. Потоковое программирование нужно тем, кто не умеет программировать конечные автоматы»
Алана Кокс, один из ведущих разработчиков ядра Linux
uvelichitel
07.08.2017 01:35+2Видимо неточный перевод, программа это конечный автомат. Компьютер это машина Тьюринга способная воспроизвести любой конечный автомат, исполнить любую программу. О взаимодействии двух машин Тьюринга с пересекающимися алфавитом можно почитать в CommunicatingSequentialProcesses Tony Hoare. Такой вычислитель всегда теоретически возможно построить, но не всегда практически.
staticlab
07.08.2017 00:32+2Кстати, доктор, а какая стадия у авторов библиотек, позволяющих писать такое? https://github.com/artptr/typed-server-example/blob/master/src/controller/ArticleController.ts
pnovikov Автор
07.08.2017 00:37+2Это мета-стадия, находящаяся за пределами классификации. Люди явно хотят ASP.NET MVC, но почему-то повторяют его на TS
uvelichitel
07.08.2017 01:58Автор, почему при таком неприятии overengineering JavaScript вы беретесь пилить свой 100500s vdom. Задача алгоритмически давно решена-diff() и patch() на дереве DOM, не представляет вызова для квалифицированного разработчика, прекрасно и неоднократно решена практически открытым кодом. Ценность подхода спорна. Почему не оставить предмет тем, для кого это хлеб насущный? Вы не инфицированы? Почему не порешать, недорешенную задачу распределенного консенсуса актуальную для микросервисов или block chain?
pnovikov Автор
07.08.2017 04:52+2Ну во-первых это все же немного не ваше дело, чем мне заниматься и какие задачи решать. Вы меня, при всем уважении, на работу не нанимали и задачи не ставили. Во-вторых, это ни для кого кроме меня не есть хлеб насущный, ибо как шаблонизатор тесно интегрирован с C#, разбираться в котором для "настоящего
шотландцаJS-разработчика" недостойно внимания. В-третьих — вот дорешаю задачу с vdom-ом у себя, потом займусь распределенным консенсусом. Если захочу.
В-четвертых, я кажется сказал, что у меня нет претензий к JavaScript как к технологии. Нет? Я тихо сказал? Или у вас каждое 5 слово в тексте произвольно заменяется на "overengineering"?
uvelichitel
07.08.2017 23:08+1Просто я тоже написал vdom. Это оказалось не сложно, но в конце я не понял зачем. Консенсус, византийские генералы это ни разу не просто, даже алгоритмы не все на Wikipedia лежат и у Кнута мало.
iit
07.08.2017 07:27+6Может быть я глупый php-шник и совсем не в теме, но по моему нет ни одного языка который бы являлся серебряной пулей.
Видел я конторы которые пишут на c# методом копипаста не зная технологии как таковой (сам я тогда писал на visual basic в этой конторке).
Видел как мой тесть который всю жизнь делает системы на asm/c++ для кристаллов на сайте тещи который на php определил hex константы и определял mime загружаемых файлов читая в потоке несколько байтов файла и прочитанное побитово сравнивал с hex и определял mime по итоговой маске, хотя это решается встроенной функцией на раз-два.
В нашей конторе есть пара фронтов, что поддерживают проект на backbone.js и перепиливают его на vue.js, есть отдел из java маньяков которые пишут суровый javaEE копипасятя код из stack overflow и деплоят его на glassfish.
Есть и несколько php вроде меня которые пишут api пол 100500 сервисов и партнеров. Но вот бизнес и тим лид почему-то решили что внезапно лендос должен быть сделан на java+jsp+backbone/vue а вот CRM сделанную изначально на delphi и падающую 10 рад на дню нужно переписать внезапно на php.
И вот я блуждая по клубку процедур и функций в main.pas (благо паскаль в школе учил) вытаскиваю доменную логику и сую её в shedule комманды на laravel одним глазом наблюдая как чуваки с команды vue.js ругаются с java ситхами кто какую бизнес логику будет делать и на каком уровне за глаза называя друг друга нелестными словами.
В итоге я сделал вывод что все безумны и я в псих больнице я тупо дальше пилю скоринг на php, crm на php и еще кучу вещей на php которые не должны быть на php. В то время как эту чуваки уже 13 месяцев не могут ни допилить старый леднинг на backbone+javaEE ни переписать его на vue+javaEE.
Итог:
- используете нужный язык в нужной ситуации и будет вам счастье
- языки и стек технологии — это вопрос религии а мы должны быть атеистами
djw
07.08.2017 08:33Ну вот почему так-то? Ведь все же знают и про KISS, и про каждой задаче свой инструмент, но всегда начинается дикая полемика (а иногда и с переходом на личности, что страшно), стоит кому-то просто высказать свое мнение…
Естественно, каждому ленивому (в хорошем смысле слова) программисту хочется иметь универсальный инструмент на все случаи, но такого не бывает. Дайте людям найти свои грабли и набить свои шишки.
Ну хотят люди писать бэкенд на JS, так пусть пишут (хотя я противник этого действа, см. каждой задаче свой инструмент), а вдруг, что толковое получится, ведь выбился же JS из роли "часики на страничке" на роль полноценного и самодостаточного языка для фроненда.
Кстати, и статью и комменты было интересно почитать.
saintbyte
08.08.2017 11:28На днях смотрел исходники Radium-Keylogger думал что код так себе и надо писать такой же но круче на js. Это у меня какой уровень?
Juribiyan
08.08.2017 13:47+4Как пациент, обнаруживающий у себя многие из описанных симптомов, могу предположить следующее: фанатизм джаваскриптеров может быть своеобразным защитным механизмом. Годами нас считали людьми второго сорта, а сам язык странной, тормозной, несерьезной игрушкой, которую солидные люди вообще отключают в своих браузерах. Но из-за его монополии все равно кто-то должен был на нем писать, а чтобы продолжать этим заниматься и не страдать от чувства собственной неполноценности, приходилось как-то компенсировать, поддерживать друг друга душеспасательными статьями, сопровождаемыми их картинками с логотипом JS на фоне карты мира, конференциями и т. д. Думаете, зачем кому-то понадобилось программировать на JS микроконтроллеры? Все для этого же. «Они думают, мой язык несерьезный, ну тогда я назло им буду использовать его везде, где только возможно, в самых неочевидных местах». И я не буду скрывать, я действительно ощущаю при мыслях об этом тепло во всем теле…
jetexe
08.08.2017 14:19А что тогда делать PHP разработчикам? Нас и по сей день считают людьми третьего сорта. Но мы же не лезем на фронтэнд и МК.
wert_lex
08.08.2017 16:00+1Не кидайте помидорами, но мне кажется, что сегодняшний JavaScript ближе к Java чем когда бы то ни было. Смотрите сами:
- Кроссплатформенность. Java на кофеварке сегодня запустить не так уж и прямо (профили, вот это всё), но браузер там скорее всего есть. И с помощью нескучного тюнинга бабеля, думаю можно под этот браузер писать со всеми радостями ES7+.
- Write Once Run Everywhere. Опять же за счёт бабеля (по желанию можно взять что-то другое) веб-приложение может быть собрано в какой-нибудь ES3 и запуститься на старых платформах.
- Язык/Платформа. Сам по себе язык довольно простой с несколькими неочевидными нюансами, которые спустя пару недель проклятий становятся уже не настолько неочевидными. Платформа, конечно, так себе — реализации отличаются, стандартная библиотека очень маленькая, но тем не менее.
М? Чем не каноничная Java? Осталось портировать Spring, чтобы было для чего конфиги иксэмэльные писать, и готово :)
iCoderXXI
09.08.2017 00:53+3Первые несколько лет жабаскрипт (es3) меня дико кошмарил. После «нормальных» ЯП типа PHP4, Clipper 5.2, Borland Pascal 7 он казался каким-то уродским уродом.
Тем не менее жизнь сказала «НАДО!» и никуда не денешься.
Потом во фронтенд бодро ворвался jQuery и жить стало неимоверно легче, кошмары закончились.
Потом были достаточно сложные проекты с кучей логики на фронтенде и выяснилось, что jQuery не решает.
Потом JavaScript внезапно начал нравиться, особенно после курсов зоракса и JavaScript Weird Parts. В голове наступило просветление.
А сейчас мне уже становится сложновато писать на PHP, потому что остро не хватает удобств JS…
В общем я безнадёжен.
P.S.: И да, куда ж мы без ноды и реакта… :)
metalim
11.08.2017 00:01-2"Не читал, но осуждаю". За вброс на вентилятор.
В статья описаны какие-то стереотипные видения и на их базе строится остальное. В-общем троллинг, фальсификация фактов и провокация. И ничего более.
dcc0
12.08.2017 19:06-1Один знакомый не мог понять, почему я скептически отношусь к JS, на все мои доводы он находил какое-нибудь оправдание.
Но однажды я показал ему это:
534 other ways to reload the page with JavaScript
https://stackoverflow.com/questions/3715047/how-to-reload-a-page-using-javascript/43064234#43064234mayorovp
12.08.2017 20:07Там основных способов не так много — всего пять, большинство строк — просто игры с синтаксисом языка.
pnovikov Автор
13.08.2017 09:00+2От перестановки self, location, reload местами, результат не меняется :D
Aries_ua
Понимаю, что автор статьи подошел с юмором :)
А теперь по сути, из личного опыта так сказать. Был свидетелем, когда в JS приходили люди из мира Java. Первое, что я слышал — «да ваш JS га… о. Нет типов? Да это убожество! А это еще и работает?». Первое что приходило в голову тогда — «дааа… походу люди какие-то фанатики Java и ничего другого не видят и видеть не хотят».
Т.е люди приходят в JS, считают, что JS это что-то типа для формочек в браузере. Не разбираются, как что работает, не вникают в тонкости и бегут на хабр/стековерфлоу/другой блог и давай писать — JS га… о! Да как на этом можно писать!?! JS девелоперы слепые фанатики!
Читал первую статью, потом вторую и вот третья появилась. Ребят, да оставьте кесарю кесарево. Вам удобно на C#/Java писать? Пишите, вас же никто не заставляет принудительно сменить стек технологий, потому что стильно/модно/молодежно.
PS Пишу на JS уже лет 6-7 и прошел путь от «что-то подкрутить в jQuery» до сложных SPA для ERP/CRM систем, а так же архитектурных решений на NodeJS на серверной части.
PSS пожалуйста, давайте без холивара.
pnovikov Автор
Я просто пытаюсь намекнуть, что фанатизм не выражается в том, что кто-то с упоением пишет на JS. Большая часть статьи пестрит выражениями "ты думаешь" и "ты считаешь". Фанатизм — это больше психологическое. Он проявляется, когда человек начинает на каждом шагу проталкивать идею о том, что "мой язык — хороший, а все кто его не понимает — идиоты". Вот этот самый постфикс про идиотов и отличает фанатика от обычного разработчика. Я пытался вложить посыл "фуфуфу таким быть". :)
Aries_ua
Так дело в том, что я наблюдаю фанатиков с обоих сторон. Кто-то пишет на Java и пытается доказать, что все остальное полная хрень и недостойно внимания и времени. Те, кто пишет на С# тоже пытаются доказать, что лучшего нет. И так далее, и так далее. И каждый с пеной у рта доказывает свою правоту, при этом пытается втоптать в дерьмо опоннента.
И ломаются копья, и пишутся посты гнева. Зачем? Что кто кому пытается доказать?
pnovikov Автор
Честно вам скажу, я думал "хм, а ведь в статье JavaScript можно заменить на любой другой язык". Но вот сугубо ИМХО, сугубо из моего опыта — разработчики на JS ведут себя подобным образом чаще. Я знаю Java-разработчиков, я знаю C++-разработчиков. Я даже знаю Haskell-разработчиков. Но самые громкие из них — JS-разработчики. Вот просто так получается по моей, вероятно нерепрезентативной выборке.
Aries_ua
Мне кажется, что это из-за того, что JS язык молодой во второй реинкарнации. Я имею ввиду, то, что как нормальный язык он стал только последние 5-6 лет с приходом ES6. И в него влились молодые люди, котороым стоит поумерить самомнение и преобрести терпимости. И я таким был, но ничего, мы взрослеем, набираемся опыта.
Думаю, надо научится уважать все стороны. И если кто-то кичится, то стоит человека на место ставить. Язык программирования тут не причем.
pnovikov Автор
Тем не менее я убежден, что если человеку мягко не намекнуть что он ведет себя некрасиво — он сам не поймет.
Aries_ua
Абсолютно с вами согласен. Но еще раз уточню — язык программирования тут не причем.
pnovikov Автор
… о чем и написано в шапке статьи.
0xd34df00d
Почитал бы такую статью о хаскелистах.
pnovikov Автор
Да там ничего интересного. Просто атрофируется возможность писать на языках без монад и к 8 уровню хочется строжайшей типизации для всего подряд, e.g. хочется иметь тип "циферки от 5 до 10.5"
0xd34df00d
Чёрт, хочется ведь :(
pnovikov Автор
Ну включите саморефлексию и напишите :) Может мне еще и яичницу за вас приготовить? :)
0xd34df00d
Я не про статью, а про тип «циферки от 5 до 10.5».
Но это уже ближе ко всяким идрисам. Или ждём ghc 8.8, там обещали допилить зависимые типы.
oldbie
Рефлексия упрощенно — саомкопание. "Саморефлексия" это уже что-то принципильно новое ;D дедлок какой-то. [/зануда]
Простите не удержался.
0xd34df00d
Да не, по идее, это идемпотентная операция, так что саморефлексия == рефлексия.
oldbie
Без вики Вас не понял)) Да все верно, мое занудство ущемлено =)
slonopotamus
Пишу (или когда-то писал) на Perl, PHP, JS, Java, C#, C++, Bash, XSLT, Python, ActionScript, Groovy, Lua. Наверняка что-то забыл упомянуть.
Во-первых, все вышеперечисленное дрянь, по тем или иным причинам :) Во-вторых, неперечисленное тоже дрянь.
franzose
От каждого по его способностям, каждому — по его труду ©
Checkmatez
Пишу на 1С и не парюсь. Зачем все остальные языки?!
APaMazur
В случае 1С — это уже 8 уровень )))
franzose
А еще есть те, кто говорит «язык X — говно, но я на нём зарабатываю больше, чем на Y, поэтому на нём и пишу»...
AxeLWeaver
Согласен с Вами, коллега, от холиваров фанатиков нет никакой пользы окружающим…
P.S. Сам я веб разработчик asp .net (C#), недавно дома начал пробовать писать игры на JS, сначала использовал jQuery, потом отказался от него, чтобы лучше изучить JS. Но ни востограюсь никакими языками и принципами, самое главное в нашем деле — простота и изящество решений, имхо…
TheShock
Ваша агрессивность фанатична
Keyten
Заметьте, вовсе не js-разработчики всё это на Хабре начали.
NoMad42
Тоесть это адаптация этой статьи на тему JS?)
lpwaterhouse
Бедненький, такое не лечится.