если достаточно долго писать на javascript то вообще перестаёшь доверять людям и начинаешь обрабатывать всё подряд, будто каждый хочет тебе подсунуть undefined
Предлагаю сделать видео, где Соер просто 15 минут будет быстро говорить умные слова, за Макконнела, белые/черные списки и стратегии "мусор на входе - мусор на выходе" и т.д. больше спасибо
Касательно ключевой идеи "не усложнять код снаружи" -- жЫрный лайк! Спасибо! Касательно проверки -- упущен тот момент, что в коде без проверки условия о том, что с отрицательными числами, оказывается, не работаем, будет произведено несколько тысяч рекурсивных вызовов данной функции. Что вовсе не небольшой оверхед Дальше оффтоп и лирика. Один из вариантов решения -- разбить функцию на 2. Первая -- фронт-энд -- принимает данные и выполняет проверки, в случае успеха возвращает результат рекурсивной функции, а сама рекурсивная функция максимально простая. Таким образом мы получим максимальную производительность при минимальном оверхеде Не была рассмотрена хвостовая рекурсия. Ввиду отсутствия в JS оптимизации хвостовой рекурсии, использование хвостовой рекурсии в JS не делает код хорошим или плохим -- оно погоды не делает. Однако алгоритм может быть реализован на языке, поддерживающим оптимизацию хвостовой рекурсии -- и там этот момент будет очень принципиальным Читаю комментарии: строгая типизация решает одну проблему, порождая другую. Оказывается, в JS помимо Number есть ещё и BigInt -- в случае строгой типизации ваш код просто перестанет работать с этим типом. В других языках числовых типов бывает ещё больше. На примере python это Decimal, numpy.int* -- и это вершина айсберга. Если заниматься мacтypбaциeй с типами -- куча клиентского кода перестанет работать без объективных на то причин. Д -- достижение.
Мне кажется, тут уже недостаток джаваскрипта. Слева более понятная с точки зрения алгоритма функция (без специфики языка), но её поведение получается более сложным из-за отсутствия строгих типов. Наверное, хорошим тут был бы третий вариант, с проверкой типа, если мы берем общий случай, а не конкретно джаваскрипт.
Ну, в левой функции можно ексепшн обработать и отдавать тоже NaN. Например, сделать влежнную функцию реального рассчета факториала, которую вызывать с трай кетчем, где в кетч блоке результат делать NaN. Ну и тип параметра проверять, чтобы было подходящее число.
Как только услышал пример с рекурсией, рука сама потянулась выключать видосик. Но я решил всё таки посмотреть, включил заново и я не пожалел. Пример реально хороший, объяснения хорошие, того говна что я ожидал услышать НЕ_БЫЛО. Я доволен.
Всё понятно, спасибо. ECMAScript - сплошная головная боль. Когда его начал изучать и практиковаться, тогда полдня потратил, чтобы найти ошибку при неявном преобразовании типов - проверял переменную (взятую с тега input ) на равенство нулю, а в самой переменной то-ли пустая строка строка была, то ли "0"... С тех неявное преобразование не люблю. Ученики с трудом въезжают в эту тему. Наоборот, пусть лучше целое 2 и вещественное 2.0 не будут равняться друг другу. Пусть лучше 2 и 2.0 вообще не складываются. Беру на вооружение термины "Белый список" и "Чёрный список" в операторе ветвления. Буду ученикам также объяснять составные логические выражения.
Автор, спасибо. Действительно очень интересный пример. Но после просмотра меня не покидает чувство, что оба примера плохие. Просто один (правый) получилось вписать в одну из стратегий программирования, а второй пример - нет. Ни один из примеров не выполняет свою функцию с точки зрения математики. Факториа́л - функция, определённая на множестве неотрицательных целых чисел. Ключевой, на мой взгляд, момент - "целые неотрицательные числа". Т.е. пример слева "пытался" выполнить эти условия, но выполнил только половину, а пример справа даже не пытался. Возможно, что это и хорошо, по крайней мере именно эту мысль, как я понял, Вы и пытались донести, но тут скорее применима Теорема Эскобара.
А что насчёт них? Они молодцы. Они делают то, что соер предлагает прописывать руками, а именно ограничивать функцию от данных, с которыми она не работает, добавляя полотна проверок. Так что с ними все ок. Т.е. если параметр функции был условно integer n, то при мусоре на входе было бы исключение. И все норм. 3 состояния результата работы функции: число, бесконечность (это вроде как тоже число в жсе, я хз), и экзепшен.
@@0imax ну это для данного примера подходит, а что если в агрументы передаётся класс, части которого могут быть некорректными и из-за этого не отработает дальнейшая логика?
Здравствуйте! Меня волнует один вопрос: вы говорите, что, обработав одну ошибку в правой функции(пример с факториалом), мы обработаем одновременно и остальные. А как же правило, что ошибки мы обрабатываем каждую отдельно? Try{} catch ( SomeError1 e){} catch (SomeError2 e) ….
Насколько я понял основная идея этого видео в том, что поведение функции должно быть детерминированным. И я с этим полностью согласен. Но примеры на JS лично для меня только добавляют путаницы. Я, признаюсь, плохо знаю JS. Видимо поэтому у меня осталось только больше вопросов, чем ясности: - я правильно понимаю, что в правой функции выбрасываются одинаковые ексепшены для разных инпутов? - Если ексепшены разные - то это мало отличается от левой функции, так как надо их все обработать. - Если они все одинаковые и сообщают о переполнении буфера - то, вроде бы лучше, но не совсем понятно, как это поможет вызывающему коду понять, что он передал неправильный аргумент? - И вообще, если, я увижу, что при передаче неверного аргумента в функцию она выбросила ексепшен, который говорит о том, что она пыталась выполнить свою логику с этим заведомо неверным типом аргумента, мне будет не по себе. Просто представьте, что функция не считает факториал, а обновляет состояние в базе данных, например.
чтобы избежать переполнения стека: function sum(n, s = 0) { return (n) ? () => sum(n - 1, s + n) : s } function trampoline(fn) { return function (...args) { let res = fn.apply(null, args) while (typeof res === 'function') { res = res() } return res } } const num = 1000000 const trampSum = trampoline(sum) const res = trampSum(num) console.log(res)
Затея интересная, яп не известный. Это же все самое простое и примитивное, что дается на курсе по алгоритмам. И из квадратичного делается линейный алгоритм исполнения. Но это же правда просто, неделя - две над одним псевдокодом и решено... Запомнил... А вот решение задачи с 4 вложенными циклами и создание линейного алгоритма, это на Яндекс стажировке выдали, второй из 6 задачей, вот тут только мастер поможет) Спасибо.
может так еще лучше function factorial(n) { return fact(n); } function fact(n) { if (n === 0) return 1; return n * fact(n-1)); } таким образом мы уберем из рекурсии проверку (многократную) хоть белого хоть черного списка
Такой подход требует, чтобы во время рекурсивных вызовов входные данные оставались корректными, гарантии чего наш код не даёт. Например, при проверке "входное значение должно быть числом большим или равным единице" указанный код крашнется при n=1.2
Чтобы не создавать 2 глобальные функции лучше поступить так: function factorial(n) { if (!Number.isInteger(n)) { throw new TypeError('Variable n must be and integer type.'); } if (n < 0) { throw new RangeError('Variable n must be positive integer.'); } return (subf = (i) => { if (i === 0) return 1; return i * subf(i - 1); })(n); }
По хорошему факториал вычислять лучше через reduce в функциональном стиле. А если делать нечего и хочешь использовать рекурсий то пожалуйста, используй хвостовую рекурсию, чтобы не вызвать исключение переполнения стека.
Да, про мусор и не только писал Макконнел, у него же про защитное программирование, про контракты лучше всего у Мейера написано, так же можно кое что у Лармана прочитать по безопасному программированию.
Хм, хоть я и питонист, тем не менее - интересная концепция, особенно при переполнении стека (хотя это будет сделать проблематично - скорее комп положишь...) Ну и отсюда понимание, что питонячий обработчик исключений - просто топчик
А почему в правой функции не перехватить Exception и вернуть Nan ? И вообще если надо вернуть хорошо или плохо, то надо функции немногие варианты плохо и превратить в одно возвращаемое плохо!
Хм, здесь что-то очень специфичное относительно языка, что требует объяснения. Я не понял, как проверка на отрицательное число в итоге приводит к разным результатам в еще двух случаях?
Имхо для аргумента правой функции нужно ввести проверку на положительный инт из заранее определённого диапазона, иначе все эксепшионы про переполнение стэка, что не отражает реальную проблему. Естественно, это нужно делать вне factorial.
На самом деле все довольно сложно. Если более комплексный кейс рассмотреть, допустим с парсингом Http - все будет не так уж и очевидно. А люди из команды еще тебе и скажут, что им лень обрабатывать исключения и потому твой код не должен их бросать. Ну и в итоге - все придет к тому, что у тебя все публичные функции будут выглядеть как-то так: IResult, а в контексте веба - всегда 200 и пусть тот кто получает результат - смотрит, все ли нормально.
По поводу лени обработки: пускай тогда просто делают пустой catch раз уж так лень. По мне, подход: "появилась проблема, выдай исключение и умри" в разы лучше мусора на выходе, потому что мусор может потом уйти очень далеко от источника, и источник уже можно и не найти.
@@liravesnovaya242 Да я сам так же считаю(относительно принципа fail-fast), но что поделать, я же не один в команде. Потому и сказал что в конечном итоге все сведется к IResult. Как по мне - тупое решение, потому что не дает до конца понять - что не так, но что поделать.
чем мне джава нравится int factorial(int n) throws NegativeValueException,IntegerOverFlowException не валидные типы отсечет компилятор , также функция показывает какую ошибку можно ожидать в golang принято возвращать значение и error func factorin(number int) (int,e) в случае ошибки e будет != null , но тут неявно в чем причина ошибки и мне как пользователю метода не понятно что делать с цепочкой вызова
На любом вменяемом языке тип int убрал бы возможность передать передать 1.2, {}, [] остался бы только вариант < 0, uint снимает и её. Поэтому если нужен JS хотя бы используйте TS
Тут ещё нужно смотреть на ЯП. В golang, на котором в основном я пишу код, левый вариант лучше. При < 0 возвращается ошибка, которую необходимо обработать. При невалидных входных данных (< 0) функция уйдёт в бесконечную рекурсию.
А почему просто не выбрасить исключения вместо оператора return? Очевидно же, что программист хотел избежать ненужных операций, которые приводили бы к ошибке "Maximum call stack size exceeded", но выбрал неудобный путь.
Оба примера - плохой код. Должна быть валидация входящих данный и должно выбрасывать ошибку, либо в крайнем случае 1 (в зависимости от задачи). Также должно быть преобразование типов - это же JS *!!! Левый код более правильный. Валидация слабая, но она есть! Надо просто сделать это лучше. Потому что потом , когда во время разработки будет пойман результат Exception, хотя по факту будет ожидаться выполнение, а не переполнение, потому что якобы должно поступать небольшое число на вход - вот тогда разработчик "хорошего кода" будет рвать последние волосы на мягкой точке, ища что же не так. Так для примера на 8:00 идёт рассуждение "либо результат, либо Exception". А на каком результате он будет наступать? На 20000, на 10486? А если комп и движок JS из будущего и потянет и даже 100 000 на расчёт? * данная функция из-за особенностей JS автоматически будет приводить всё в числа. Но "0" ноль, она не переварит! Что выдаст factorial("0")? А если математическая функция использует сложение?
Очень наивный подход, на практике так не делается, уже в следующем видео на примере redux показал, что боятся исключений не стоит, и умение проектировать как раз в том и заключается - не делать лишних проверок, пытаясь подстелить соломинку.
@@S0ERDEVS ага, а потом разгребать багрепорты от пользователей. Вот так джуны почитают вас, ещё и формы проверять не будут, потому что наивный подход на практике. У вас вероятно свой опыт умения проектирования, а на практике - чем больше вариантов учтёшь, чем больше проверок и тестов предусмотришь, тем проще будет поддерживать. Не придётся искать ошибки. И хорошо, если эта функция не жизненноважные. А то может со счёта уйти пару миллиончиков долларов и будешь всю жизнь отрабатывать.
@PostoiParovoz причем тут мой опыт? Открой любую публичную популярную библиотеку и посмотри как они написаны. Было бы желание, а информации на счет проектирования более чем достаточно. Если в коде куча проверок и явных приведений - это первый признак, что никто его не проектировал, а код написан по интуиции.
еще раз убедился, что яваскрипт гавно как можно было додуматься до нан в качестве результата некорректной операции? а если функция и не должна выдавать числа, а строки там или массив или даже объект?
Расскажи, пожалуйста, как бы ты построил SPA веб приложение, в котором много связей между отдельными компонентами (например чекбокс на одной странице должен менять состояние элементов на другой). Некоторые данные используются сразу несколькими компонентами. Или хотя бы подскажи, где можно почитать про решение конкретных архитектурных проблем.
Если некоторые данные используются разными компонентыми, то их можно хранить в виде стейта в общем компоненте прородителе, это если без всяких state management. По хорошему конечно же завести глобальное хранилище состояния приложения, например вышеупомянутый редакс, и из него получать конкретные курски стейта в разных компонентах. "например чекбокс на одной странице должен менять состояние элементов на другой". Это штуку тоже можно редаксом решить, просто изменения состояния чекбокса должно запускать определенный actionCreator, который будет менять глобальное состояние, от которого и будет зависеть тот самый компонент, который вы хотите менять чек боксом, и не важно в каком месте, потому что как только тот кусок состояния в редаксе, от которого он зависит, изменится, он автоматически перерисуется. Так же черед редаксовские actionCreator's можно отправлять ассинхронные запросы на сервер, если подключить redux-thunk, что тоже добавляет удобства. Чтобы понять что к чему в редаксе потребуется определенное время и пытливость ума, но если есть время и желание, то это решение должно вам помочь.
он очень быстро кончается в жабаскрипте. можно в браузере проверить, падает почти мгновенно. там просто ограничение на глубину вызова стоит, оно не ждёт фактического окончания памяти
Не поможет, если сам разработчик дундук и поставил условный any как тип. Тут надо понимать что и зачем, язык лишь даёт (или не даёт) инструменты для этого.
Интересно конечно, но по-моему этот конкретный пример чито js заморочка. Например, я начинал с Паскаля, как учебного языка и потом сразу пошли C и С++. Там такое даже принципиально не возможно. Если тип возврата специфицирован, то и будешь сидеть и думать, что же вернуть при ошибке или вообще результат возварщать через указатель или ссылку в аргументах функции. А в return возвращать только код результата или ошибки.
Мне кажется, если уж дорабатывать левый код, то правильнее было бы бросить исключение в случае не валидных входных данных. это так же позволит разделять исключения "общие" и относящиеся к бизнес логике задачи.
NaN есть во многих языках. Это результат вычисления к примеру 0/0 Это число, но не число при этом. Inf/-Inf тоже в любом языке есть, это число в типах с плавающей точкой. undefined фишка js. По сути аналог null, но не null так как у null есть своё отдельное значение в js. А исключение Exeption тоже есть во многих языках, к примеру Java, C# Откуда взялся NaN? Сначала функция вернула просто return; -> undefined. Потом undefined попытались математически умножить с числом и получили число "нечисло", то есть NaN.
а че бы не проверить тип параметра? явное лучше, чем неявное. в правой функции отсутствие побочных эффектов достигается за счёт того, что нет условия ‘n < 1’. всем программистам в команде нужно знать и держать в голове, что доп проверка приводит к сайд-эффектам.
@@Poloskun4ik ну это классика же, когда начинают рассказывать про программирование в быдловузе и вместо конкретных кусков кода показывают непонятный факториал если быть точнее, то факториал это классика примера рекурсии типа показать, что функция может вызвать сама себя но за столько десятилетий никто не додумался придумать боле менее наглядный пример рекурсии нежели вычисление бесполезного ряда чисел а я таки один раз использовал рекурсию, что упросило и сократило мой код аж в 3 раза по визуальному объему я аж сам с себя нереально проперся
Здесь имелся в виду популярный среди JS-ов подход, когда выход из функции делается через пустой return и по факту эта функция возвращает не осмысленное значение, а undefined. Т.е. не конкретно выбор между NaN и undefined, а в любом непонятном случае. В результате в программе постоянно возникают ситуации, когда неопределенные переменные просачиваются в функции и появляется привычка проверять на undefined на всякий случай.
@@Maiq-The_Liar Тем что если ты ожидаешь число, то NaN - это число (по типу), и тебе не надо делать проверки на NaN чтобы оперировать с ним как с числом, т.е. например, (undefied).toFixed() - выдаст ошибку, NaN.toFixed() - нет. Про null, там я сказал null-object, т.е. объект-пустышка.
Еще один интересный метод реализации - через построение бесконечного списка - обращение к нему производится только по индексу поэтому получается "мусор на входе - исключение на выходе". Пример на Haskell: factorial = scanl (*) 1 [1..] :t factorial -- Тип списка: factorial :: [Integer] factorial !! (-1) -- Обращение к списку: *** Exception: Prelude.!!: negative index factorial !! 3 -- 6 factorial !! 300 -- Большое число не буду сюда копировать. factorial !! 30000 -- Очень большое число.
Не думаю что я - джун. Я никто вообще... Но что, зачем, как и почему - мне понятно. Думаю тут в принципе ничего такого нет. Ну разве что гуманитарий запутается...
правая функция вместо моментального выхода при отрицательном целом будет крутиться до переполнения стека. это что угодно, но не хороший код. можно было бы проверить тип, но динамикодебилы не догадаются. поэтому весь фронтенд в такой жопе, ждём переполнения стека
@@S0ERDEVS А на медленном компьютере? А если это происходит тысячу раз подряд? Тоже будет "так же моментально"? Что это вообще за больная логика: "компьютер быстрый, давайте делать лишние (!) действия, не писать же нам более аккуратный код в конце концов"? Поверить не могу, что приходится это объяснять серьёзному специалисту. Но, как я уже сказал, это объясняет, почему веб-страницы долго грузятся и медленно работают, а браузеру потреблять 2гб ОЗУ для отображения картинок и текста (!!) считается нормой. Печально, очень печально.
@Solitary Subscriber Поверить не могу, что для тебя нормальная ситуация - тысяча исключений подряд, в моей практике вероятность исключительной ситуации в 0.01% - это уже очень много, но даже в таком случае, если посчитать вероятность того что такие события произойдут 1000 раз подряд, то окажется, что это практически нереально (очень уж маленькая вероятность такой ситуации). Т.е. у тебя получается, что ради того чтобы сэкономить процессорное время 1 раз на миллиард вызовов (на самом деле даже больше, но считать точно не хочу), ты в каждом из этих вызовов готов делать доп проверку (которая не бесплатно выполняется). Вот поэтому и получаются медленные приложения, что пихаете в каждую функцию проверки, которых можно избежать, если перестать руководствоваться интуицией, а подучить тервер и посчитать реально насколько вероятно наступление события, ради которого вы вносите неопределенность и сложность в программу, убивая производительность в нормальном потоке выполнения. Еще раз подумай - речь идет не о нормальном потоке исполнения, а о исключительной ситуации, т.е. в нормальном потоке вообще не будут использоваться доп. ресурсы. В твоем же случае ты хочешь экономить ради редкого исключения, повышая нагрузку на нормальный поток.
Вот это контент. Вот это можно пощупать. Больше бы таких практических наглядных ситуаций, больше разборов!
Из этого ролика я узнал в каком стиле я проектирую и пишу все свои функции - мусор на входе, мусор на выходе :peepo_cry:
да и сам код тоже мусор XD
@@nazarii.lazarchuk мусор внутри 😉
😂
Никогда не рассматривал такие примеры с факториалом с такой стороны... Вроде пример банальный, а тонкостей оказывается очень много. Спасибо!
Хвостовые рекурсии наши друзья
А исключения переполнения стека - признаки плохого программиста
Интересная стратегия: мусор на входе, мусор на выходе. Хмм, надо подумать о внедрении
Я уже пакеты с мусором поставил как делает автор) А если серьезно - очень полезное видео) Заставляет задуматься и понять, что мой код - го*но...
Паттерн стратегия - поменяйте алгоритм в процессе работы на мусорный...
Вообще-то мусор на входе, ошибка на выходе, хотя я тебя понял)
Мусор ломится в квартиру
Спасибо за ролик! Подобной информации надо больше, таких разборов не хватает.
вывод - языки без строгой типизации позволяют ломать себе ногу передавая обьект туда куда надо передавать uint
В таких языках надо делать проверку и типизацию (валидацию) внутри функции.
а что скажешь насчет ассемблера?
@@anotherone3641 ассемблер это лучший способ сломать себе все что возможно))
Блин, я не программист, но автор канала так четко и понятно рассказывает, что даже мне абсолютно все понятно. Респект!
скоро тебя осенит, что ты оказывается и программировать можеш
бросиш свою унылую работу, возьмеш нотыбук и поедеш в тайланд фрилансить под пальмами
Благодарность. На редкость много полезного, на одну квадратную минуту видео.
Никто не пишет рекурсии вызывающие переполнение стека. Это ужасный код.
Юзаем хвостовые рекурсии вместо этого
Обожаю такие выпуски! Всегда есть о чем погуглить и чему поучится после!
Отличный ролик! Я ещё больше полюбил TypeScript)
Было очень полезно. Больше таких обучающих роликов. Спасибо🙃
Жаль, что в жс не придумали тайп хинтинг.
Хорошо, что для жс придумали тайпскрипт.
Сначала был скептически натроен, но сейчас ставлю лайкос и подписка. Не знаю на сколько ты профи по факту, но темы очень правильные.
Название видео не соответствует содержанию. Надо было назвать "Страдания JS разработчиков")
Истинно
Чтобы "докрутить" "мусор на входе - ничего на выходе", осталось добавить:
if (n > 170) return Infinity;
если достаточно долго писать на javascript то вообще перестаёшь доверять людям и начинаешь обрабатывать всё подряд, будто каждый хочет тебе подсунуть undefined
😂😂
Предлагаю сделать видео, где Соер просто 15 минут будет быстро говорить умные слова, за Макконнела, белые/черные списки и стратегии "мусор на входе - мусор на выходе" и т.д. больше спасибо
Отличный пример того, что !(n > 0) и (n
а в языках с нормальной типизацией такого не произойдет, стоял бы тип uint во входных данных, и сразу отлетели бы некорректные данные на входе
TS в помощь
@@yaroslav8609 ts всё равно транслирует код в js и выполняться код будет в js.
Максимум чем ts может помочь - это дать подсказки до запуска кода.
Касательно ключевой идеи "не усложнять код снаружи" -- жЫрный лайк! Спасибо!
Касательно проверки -- упущен тот момент, что в коде без проверки условия о том, что с отрицательными числами, оказывается, не работаем, будет произведено несколько тысяч рекурсивных вызовов данной функции. Что вовсе не небольшой оверхед
Дальше оффтоп и лирика. Один из вариантов решения -- разбить функцию на 2. Первая -- фронт-энд -- принимает данные и выполняет проверки, в случае успеха возвращает результат рекурсивной функции, а сама рекурсивная функция максимально простая. Таким образом мы получим максимальную производительность при минимальном оверхеде
Не была рассмотрена хвостовая рекурсия. Ввиду отсутствия в JS оптимизации хвостовой рекурсии, использование хвостовой рекурсии в JS не делает код хорошим или плохим -- оно погоды не делает. Однако алгоритм может быть реализован на языке, поддерживающим оптимизацию хвостовой рекурсии -- и там этот момент будет очень принципиальным
Читаю комментарии: строгая типизация решает одну проблему, порождая другую. Оказывается, в JS помимо Number есть ещё и BigInt -- в случае строгой типизации ваш код просто перестанет работать с этим типом. В других языках числовых типов бывает ещё больше. На примере python это Decimal, numpy.int* -- и это вершина айсберга. Если заниматься мacтypбaциeй с типами -- куча клиентского кода перестанет работать без объективных на то причин. Д -- достижение.
Мне кажется, тут уже недостаток джаваскрипта. Слева более понятная с точки зрения алгоритма функция (без специфики языка), но её поведение получается более сложным из-за отсутствия строгих типов. Наверное, хорошим тут был бы третий вариант, с проверкой типа, если мы берем общий случай, а не конкретно джаваскрипт.
"мусор на входе - мусор на выходе"
мое кредо
Ну, в левой функции можно ексепшн обработать и отдавать тоже NaN.
Например, сделать влежнную функцию реального рассчета факториала, которую вызывать с трай кетчем, где в кетч блоке результат делать NaN.
Ну и тип параметра проверять, чтобы было подходящее число.
Спасибо, за разбор. Очень познавательно
Однозначно спасибо, всегда очень крутой контент на канале!!!
Как только услышал пример с рекурсией, рука сама потянулась выключать видосик. Но я решил всё таки посмотреть, включил заново и я не пожалел. Пример реально хороший, объяснения хорошие, того говна что я ожидал услышать НЕ_БЫЛО. Я доволен.
Всё понятно, спасибо. ECMAScript - сплошная головная боль. Когда его начал изучать и практиковаться, тогда полдня потратил, чтобы найти ошибку при неявном преобразовании типов - проверял переменную (взятую с тега input ) на равенство нулю, а в самой переменной то-ли пустая строка строка была, то ли "0"...
С тех неявное преобразование не люблю. Ученики с трудом въезжают в эту тему. Наоборот, пусть лучше целое 2 и вещественное 2.0 не будут равняться друг другу. Пусть лучше 2 и 2.0 вообще не складываются.
Беру на вооружение термины "Белый список" и "Чёрный список" в операторе ветвления. Буду ученикам также объяснять составные логические выражения.
Отличное видео, побольше бы таких.
Автор, спасибо. Действительно очень интересный пример.
Но после просмотра меня не покидает чувство, что оба примера плохие. Просто один (правый) получилось вписать в одну из стратегий программирования, а второй пример - нет.
Ни один из примеров не выполняет свою функцию с точки зрения математики. Факториа́л - функция, определённая на множестве неотрицательных целых чисел. Ключевой, на мой взгляд, момент - "целые неотрицательные числа". Т.е. пример слева "пытался" выполнить эти условия, но выполнил только половину, а пример справа даже не пытался. Возможно, что это и хорошо, по крайней мере именно эту мысль, как я понял, Вы и пытались донести, но тут скорее применима Теорема Эскобара.
Шикарный разбор!
Лайк за Макконнелла!
Интересное видео! Спасибо!
А как насчет того, чтобы правую часть в try..catch засунуть, и в случае исключения возвращать NaN
Классическое GIGO - garbage in, garbage out. Наглядно
Отличное видео. Побольше бы именно такого контента
Не успел поставить лайк после завершения, но вернулся и поставил! Спасибо за разбор!
А что на счёт языков со статической типизацией?
А что насчёт них?
Они молодцы.
Они делают то, что соер предлагает прописывать руками, а именно ограничивать функцию от данных, с которыми она не работает, добавляя полотна проверок.
Так что с ними все ок.
Т.е. если параметр функции был условно integer n, то при мусоре на входе было бы исключение.
И все норм. 3 состояния результата работы функции: число, бесконечность (это вроде как тоже число в жсе, я хз), и экзепшен.
@@dasauser uint
@@dasauser а что на счёт проверки x
@@Algok17 unsigned int - и число всегда будет неотрицательным.
@@0imax ну это для данного примера подходит, а что если в агрументы передаётся класс, части которого могут быть некорректными и из-за этого не отработает дальнейшая логика?
очень интересно,спасибо !
Если идти по пути проверок, я б эту функцию завернул в ещё одну функцию, где бы и разместил проверки. Чтоб ещё и рекурсивно их не повторять.
Здравствуйте! Меня волнует один вопрос: вы говорите, что, обработав одну ошибку в правой функции(пример с факториалом), мы обработаем одновременно и остальные. А как же правило, что ошибки мы обрабатываем каждую отдельно? Try{} catch ( SomeError1 e){} catch (SomeError2 e) ….
А если не использовать рекурсию в данном примере, а применить цикл, как быстро произойдёт переполнение?..
Насколько я понял основная идея этого видео в том, что поведение функции должно быть детерминированным. И я с этим полностью согласен.
Но примеры на JS лично для меня только добавляют путаницы.
Я, признаюсь, плохо знаю JS. Видимо поэтому у меня осталось только больше вопросов, чем ясности:
- я правильно понимаю, что в правой функции выбрасываются одинаковые ексепшены для разных инпутов?
- Если ексепшены разные - то это мало отличается от левой функции, так как надо их все обработать.
- Если они все одинаковые и сообщают о переполнении буфера - то, вроде бы лучше, но не совсем понятно, как это поможет вызывающему коду понять, что он передал неправильный аргумент?
- И вообще, если, я увижу, что при передаче неверного аргумента в функцию она выбросила ексепшен, который говорит о том, что она пыталась выполнить свою логику с этим заведомо неверным типом аргумента, мне будет не по себе. Просто представьте, что функция не считает факториал, а обновляет состояние в базе данных, например.
чтобы избежать переполнения стека:
function sum(n, s = 0) {
return (n) ? () => sum(n - 1, s + n) : s
}
function trampoline(fn) {
return function (...args) {
let res = fn.apply(null, args)
while (typeof res === 'function') {
res = res()
}
return res
}
}
const num = 1000000
const trampSum = trampoline(sum)
const res = trampSum(num)
console.log(res)
Мудро и познавательно
Затея интересная, яп не известный.
Это же все самое простое и примитивное, что дается на курсе по алгоритмам.
И из квадратичного делается линейный алгоритм исполнения.
Но это же правда просто, неделя - две над одним псевдокодом и решено...
Запомнил...
А вот решение задачи с 4 вложенными циклами и создание линейного алгоритма, это на Яндекс стажировке выдали, второй из 6 задачей, вот тут только мастер поможет)
Спасибо.
Спасибо за ролик!
спасибо за контент
Отличное видео!
может так еще лучше
function factorial(n)
{
return fact(n);
}
function fact(n)
{
if (n === 0) return 1;
return n * fact(n-1));
}
таким образом мы уберем из рекурсии проверку (многократную) хоть белого хоть черного списка
Такой подход требует, чтобы во время рекурсивных вызовов входные данные оставались корректными, гарантии чего наш код не даёт. Например, при проверке "входное значение должно быть числом большим или равным единице" указанный код крашнется при n=1.2
Да, но у нас вместо одной функции теперь две, что не очень красиво
@@enkryp
Чтобы не создавать 2 глобальные функции лучше поступить так:
function factorial(n) {
if (!Number.isInteger(n)) {
throw new TypeError('Variable n must be and integer type.');
}
if (n < 0) {
throw new RangeError('Variable n must be positive integer.');
}
return (subf = (i) => {
if (i === 0) return 1;
return i * subf(i - 1);
})(n);
}
По хорошему факториал вычислять лучше через reduce в функциональном стиле.
А если делать нечего и хочешь использовать рекурсий то пожалуйста, используй хвостовую рекурсию, чтобы не вызвать исключение переполнения стека.
Почему не рассмотрено исправление левого кода с учетом белого списка и выброса исключения? Или это тоже плохой вариант, почему?
а есть книга, в которой поподробней про все эти принципы (типа мусор на входе - мусор на выходе) можно почитать? это у Макконела написано?
Да, про мусор и не только писал Макконнел, у него же про защитное программирование, про контракты лучше всего у Мейера написано, так же можно кое что у Лармана прочитать по безопасному программированию.
@@S0ERDEVS понял-принял, спасибо. был бы очень признателен если подскажешь название книг
А ещё будет?
Что это за 25 кадр? Я уже чувствую желания писать код разными кисточками на графическом планшете)
Хм, хоть я и питонист, тем не менее - интересная концепция, особенно при переполнении стека (хотя это будет сделать проблематично - скорее комп положишь...) Ну и отсюда понимание, что питонячий обработчик исключений - просто топчик
Не знаю, на си и c++ - изи, причём если не сталкивался с таким, то можно по первому разу сильно удивиться, когда делаешь какой-то итеративный расчёт.
Исчерпывающе! Но мне вариант с белым списком кажется предпочтительнее,так как операция факториал определена на неотрецательных целых числах.
А почему в правой функции не перехватить Exception и вернуть Nan ? И вообще если надо вернуть хорошо или плохо, то надо функции немногие варианты плохо и превратить в одно возвращаемое плохо!
Хм, здесь что-то очень специфичное относительно языка, что требует объяснения. Я не понял, как проверка на отрицательное число в итоге приводит к разным результатам в еще двух случаях?
Спасибо, расширили сознание.
Круто... Подписался
Спасибо!"!!!!!!!!!!!!!!
Имхо для аргумента правой функции нужно ввести проверку на положительный инт из заранее определённого диапазона, иначе все эксепшионы про переполнение стэка, что не отражает реальную проблему. Естественно, это нужно делать вне factorial.
для начала добавить проверку типа аргумента
На самом деле все довольно сложно.
Если более комплексный кейс рассмотреть, допустим с парсингом Http - все будет не так уж и очевидно. А люди из команды еще тебе и скажут, что им лень обрабатывать исключения и потому твой код не должен их бросать. Ну и в итоге - все придет к тому, что у тебя все публичные функции будут выглядеть как-то так: IResult, а в контексте веба - всегда 200 и пусть тот кто получает результат - смотрит, все ли нормально.
По поводу лени обработки: пускай тогда просто делают пустой catch раз уж так лень. По мне, подход: "появилась проблема, выдай исключение и умри" в разы лучше мусора на выходе, потому что мусор может потом уйти очень далеко от источника, и источник уже можно и не найти.
@@liravesnovaya242 Да я сам так же считаю(относительно принципа fail-fast), но что поделать, я же не один в команде. Потому и сказал что в конечном итоге все сведется к IResult. Как по мне - тупое решение, потому что не дает до конца понять - что не так, но что поделать.
чем мне джава нравится
int factorial(int n) throws NegativeValueException,IntegerOverFlowException
не валидные типы отсечет компилятор , также функция показывает какую ошибку можно ожидать
в golang принято возвращать значение и error
func factorin(number int) (int,e)
в случае ошибки e будет != null , но тут неявно в чем причина ошибки и мне как пользователю метода не понятно что делать с цепочкой вызова
отличное видео
Спасибо!
На любом вменяемом языке тип int убрал бы возможность передать передать 1.2, {}, [] остался бы только вариант < 0, uint снимает и её. Поэтому если нужен JS хотя бы используйте TS
Ну и рекурсивный факториал так себе, надо было сразу подчеркнуть для смотрящих ролик джунов, что тут рекурсия не требуется и простой цикл решает
Тут ещё нужно смотреть на ЯП.
В golang, на котором в основном я пишу код, левый вариант лучше.
При < 0 возвращается ошибка, которую необходимо обработать. При невалидных входных данных (< 0) функция уйдёт в бесконечную рекурсию.
Я снова не досмотрел видео и написал комментарий.
Прошу прощения.
А почему просто не выбрасить исключения вместо оператора return? Очевидно же, что программист хотел избежать ненужных операций, которые приводили бы к ошибке "Maximum call stack size exceeded", но выбрал неудобный путь.
я 1 кто пытался поймать кадр на 07:12 и очень сильно разочаровался ???((((
Оба примера - плохой код.
Должна быть валидация входящих данный и должно выбрасывать ошибку, либо в крайнем случае 1 (в зависимости от задачи).
Также должно быть преобразование типов - это же JS *!!!
Левый код более правильный. Валидация слабая, но она есть! Надо просто сделать это лучше.
Потому что потом , когда во время разработки будет пойман результат Exception, хотя по факту будет ожидаться выполнение, а не переполнение, потому что якобы должно поступать небольшое число на вход - вот тогда разработчик "хорошего кода" будет рвать последние волосы на мягкой точке, ища что же не так.
Так для примера на 8:00 идёт рассуждение "либо результат, либо Exception". А на каком результате он будет наступать? На 20000, на 10486? А если комп и движок JS из будущего и потянет и даже 100 000 на расчёт?
* данная функция из-за особенностей JS автоматически будет приводить всё в числа. Но "0" ноль, она не переварит! Что выдаст factorial("0")?
А если математическая функция использует сложение?
Очень наивный подход, на практике так не делается, уже в следующем видео на примере redux показал, что боятся исключений не стоит, и умение проектировать как раз в том и заключается - не делать лишних проверок, пытаясь подстелить соломинку.
@@S0ERDEVS ага, а потом разгребать багрепорты от пользователей. Вот так джуны почитают вас, ещё и формы проверять не будут, потому что наивный подход на практике.
У вас вероятно свой опыт умения проектирования, а на практике - чем больше вариантов учтёшь, чем больше проверок и тестов предусмотришь, тем проще будет поддерживать. Не придётся искать ошибки. И хорошо, если эта функция не жизненноважные. А то может со счёта уйти пару миллиончиков долларов и будешь всю жизнь отрабатывать.
@PostoiParovoz причем тут мой опыт? Открой любую публичную популярную библиотеку и посмотри как они написаны. Было бы желание, а информации на счет проектирования более чем достаточно. Если в коде куча проверок и явных приведений - это первый признак, что никто его не проектировал, а код написан по интуиции.
еще раз убедился, что яваскрипт гавно
как можно было додуматься до нан в качестве результата некорректной операции?
а если функция и не должна выдавать числа, а строки там или массив или даже объект?
Респект!
С видео становится понятно что не стоит писать на js :/
не смотря дальше первой части видео, могу сказать, что правильным будет второй код, НО с добавленным типом
Расскажи, пожалуйста, как бы ты построил SPA веб приложение, в котором много связей между отдельными компонентами (например чекбокс на одной странице должен менять состояние элементов на другой). Некоторые данные используются сразу несколькими компонентами. Или хотя бы подскажи, где можно почитать про решение конкретных архитектурных проблем.
Посмотрите в сторону архитектуры Flux и хранилищ типа NgXs/Redux
Если некоторые данные используются разными компонентыми, то их можно хранить в виде стейта в общем компоненте прородителе, это если без всяких state management. По хорошему конечно же завести глобальное хранилище состояния приложения, например вышеупомянутый редакс, и из него получать конкретные курски стейта в разных компонентах.
"например чекбокс на одной странице должен менять состояние элементов на другой". Это штуку тоже можно редаксом решить, просто изменения состояния чекбокса должно запускать определенный actionCreator, который будет менять глобальное состояние, от которого и будет зависеть тот самый компонент, который вы хотите менять чек боксом, и не важно в каком месте, потому что как только тот кусок состояния в редаксе, от которого он зависит, изменится, он автоматически перерисуется. Так же черед редаксовские actionCreator's можно отправлять ассинхронные запросы на сервер, если подключить redux-thunk, что тоже добавляет удобства. Чтобы понять что к чему в редаксе потребуется определенное время и пытливость ума, но если есть время и желание, то это решение должно вам помочь.
Код хороший. Код плохой. Код злой.
Образца 1966 года.
А я правильно понимаю, что правый вариант функции от -1 будет крутить рекурсию пока не кончится стек? Это тоже как то не слишком здорово выглядит.
он очень быстро кончается в жабаскрипте. можно в браузере проверить, падает почти мгновенно. там просто ограничение на глубину вызова стоит, оно не ждёт фактического окончания памяти
В этом и есть главная идея видео - для стандартного разработчика код справа выглядит некорректным, хотя в определённой перспективе он лучше левого.
@@enkryp Ну да, в этом смысле понятно, что правый вариант лучше. Просто они оба плохие)
Мне кажется или картинка стала лучше?
TypeScript поможет избежать мусора на входе. Типизация + аннотация параметра функции.
Не поможет, если сам разработчик дундук и поставил условный any как тип.
Тут надо понимать что и зачем, язык лишь даёт (или не даёт) инструменты для этого.
@@robertdowneyjr8494 В таком случае поможет проверка в аннотации. Чтоб обойти, дундуку придется лезь в аннотацию и там ломать проверки.
@@azamatk4302 Вы не верите в могущество идиотизма? :)
Не во всех случаях, от микса эксепшенов, НаН и обычних чисел не защитит даже при правильной типизации.
особенно смешно этот комент выглядит, если вчитаться в повторение "избежать" )))
Интересно конечно, но по-моему этот конкретный пример чито js заморочка. Например, я начинал с Паскаля, как учебного языка и потом сразу пошли C и С++. Там такое даже принципиально не возможно. Если тип возврата специфицирован, то и будешь сидеть и думать, что же вернуть при ошибке или вообще результат возварщать через указатель или ссылку в аргументах функции. А в return возвращать только код результата или ошибки.
Жень, ударение такое "Сти́вен Макко́ннелл "
Мне кажется, если уж дорабатывать левый код, то правильнее было бы бросить исключение в случае не валидных входных данных. это так же позволит разделять исключения "общие" и относящиеся к бизнес логике задачи.
Дякую за відео
Почему ты не продаешь курсы по JS? Разбогател бы, уехал жить на остров, может даже и на свой :)
спасибо
О. Хорошо однако, элементарнейшие вещи но хорошо
в java действительно нужно === поставить?)))
В плане кода да. В плане юзабилити мусор на входе на выходе купи пожалуйста эти наушники. Найдется все, даже то, что ты не искал.
В любой непонятной ситуации кидайте exception :)
Посмотрел ролик и вспомнил, почему у меня горит от языков с динамической типизацией. Спасибо за видео.
На первой минуте какой-то чистой джаваскриптовый пример. Если б были типы, то n выбрать как uint и всегда все будет выполняться идеально без ошибок.
Очень полезно, но очень растянуто, суть ясна уже в первые минуты
Услышав про мусор, ожидал услышать что-нибудь про контракты.
Хотя не знаю, есть ли какое-то подобие контрактов в JS.
Из этого всего мы делаем вывод - js плох, ибо у него есть ничего, и другое ничего
NaN есть во многих языках. Это результат вычисления к примеру 0/0
Это число, но не число при этом.
Inf/-Inf тоже в любом языке есть, это число в типах с плавающей точкой.
undefined фишка js. По сути аналог null, но не null так как у null есть своё отдельное значение в js.
А исключение Exeption тоже есть во многих языках, к примеру Java, C#
Откуда взялся NaN? Сначала функция вернула просто return; -> undefined. Потом undefined попытались математически умножить с числом и получили число "нечисло", то есть NaN.
а че бы не проверить тип параметра? явное лучше, чем неявное. в правой функции отсутствие побочных эффектов достигается за счёт того, что нет условия ‘n < 1’. всем программистам в команде нужно знать и держать в голове, что доп проверка приводит к сайд-эффектам.
Смотрю , очень проявляется «синдром Одина» , это - хорошо )))
А для чего вообще подобный кусок кода используется?
вычиляет факториал - бесполезный абстгактный пример из мудематики, когда хотят показать программирование
@@kalobyte да судя по комментам я тут один не понимаю всего праздника жизни)))
@@Poloskun4ik
ну это классика же, когда начинают рассказывать про программирование в быдловузе и вместо конкретных кусков кода показывают непонятный факториал
если быть точнее, то факториал это классика примера рекурсии
типа показать, что функция может вызвать сама себя
но за столько десятилетий никто не додумался придумать боле менее наглядный пример рекурсии нежели вычисление бесполезного ряда чисел
а я таки один раз использовал рекурсию, что упросило и сократило мой код аж в 3 раза по визуальному объему
я аж сам с себя нереально проперся
@@kalobyte ахахах, молодец
14:09 а в случае с NaN нам не придется добавлять аналогичные проверки на isNaN(i)?
Здесь имелся в виду популярный среди JS-ов подход, когда выход из функции делается через пустой return и по факту эта функция возвращает не осмысленное значение, а undefined. Т.е. не конкретно выбор между NaN и undefined, а в любом непонятном случае. В результате в программе постоянно возникают ситуации, когда неопределенные переменные просачиваются в функции и появляется привычка проверять на undefined на всякий случай.
@@S0ERDEVS да, я это понял. Просто пытаюсь уловить момент, чем проверка на undefined хуже проверки на любое другое возвращаемое "ничего" (NaN, null)?
@@Maiq-The_Liar Тем что если ты ожидаешь число, то NaN - это число (по типу), и тебе не надо делать проверки на NaN чтобы оперировать с ним как с числом, т.е. например, (undefied).toFixed() - выдаст ошибку, NaN.toFixed() - нет.
Про null, там я сказал null-object, т.е. объект-пустышка.
@@S0ERDEVS уловил, благодарю.
@@S0ERDEVS по факту получается NaN это null-object для числа. Вроде логично, но как-то раньше не приходило в голову.
Еще один интересный метод реализации - через построение бесконечного списка - обращение к нему производится только по индексу поэтому получается "мусор на входе - исключение на выходе". Пример на Haskell:
factorial = scanl (*) 1 [1..]
:t factorial -- Тип списка: factorial :: [Integer]
factorial !! (-1) -- Обращение к списку: *** Exception: Prelude.!!: negative index
factorial !! 3 -- 6
factorial !! 300 -- Большое число не буду сюда копировать.
factorial !! 30000 -- Очень большое число.
Джун: ничерта не понятно, но очень интересно 😂
Не думаю что я - джун. Я никто вообще... Но что, зачем, как и почему - мне понятно.
Думаю тут в принципе ничего такого нет. Ну разве что гуманитарий запутается...
@@covid-2284 гуманитарий. Не запутался :)
правая функция вместо моментального выхода при отрицательном целом будет крутиться до переполнения стека. это что угодно, но не хороший код.
можно было бы проверить тип, но динамикодебилы не догадаются. поэтому весь фронтенд в такой жопе, ждём переполнения стека
На чем основывается утверждение? Переполнение стека в JS в данном случае произойдет для тебя так же моментально как и выход. Страх ради страха?
@@S0ERDEVS А на медленном компьютере? А если это происходит тысячу раз подряд? Тоже будет "так же моментально"?
Что это вообще за больная логика: "компьютер быстрый, давайте делать лишние (!) действия, не писать же нам более аккуратный код в конце концов"?
Поверить не могу, что приходится это объяснять серьёзному специалисту. Но, как я уже сказал, это объясняет, почему веб-страницы долго грузятся и медленно работают, а браузеру потреблять 2гб ОЗУ для отображения картинок и текста (!!) считается нормой. Печально, очень печально.
@Solitary Subscriber
Поверить не могу, что для тебя нормальная ситуация - тысяча исключений подряд, в моей практике вероятность исключительной ситуации в 0.01% - это уже очень много, но даже в таком случае, если посчитать вероятность того что такие события произойдут 1000 раз подряд, то окажется, что это практически нереально (очень уж маленькая вероятность такой ситуации).
Т.е. у тебя получается, что ради того чтобы сэкономить процессорное время 1 раз на миллиард вызовов (на самом деле даже больше, но считать точно не хочу), ты в каждом из этих вызовов готов делать доп проверку (которая не бесплатно выполняется).
Вот поэтому и получаются медленные приложения, что пихаете в каждую функцию проверки, которых можно избежать, если перестать руководствоваться интуицией, а подучить тервер и посчитать реально насколько вероятно наступление события, ради которого вы вносите неопределенность и сложность в программу, убивая производительность в нормальном потоке выполнения.
Еще раз подумай - речь идет не о нормальном потоке исполнения, а о исключительной ситуации, т.е. в нормальном потоке вообще не будут использоваться доп. ресурсы. В твоем же случае ты хочешь экономить ради редкого исключения, повышая нагрузку на нормальный поток.
@@S0ERDEVS просто язык гавно вот и всё