Всем привет, друзья! С вами подкаст «Подлодка». В эфире Женя и Стас. Женя — это я и Стас со мной. Стас, привет! Привет-привет! И сегодня мы будем... говорить про обработку ошибок. Все знают эти ваши эксепшены, у кого-то не эксепшены, у кого-то эррор-коды. У кого-то даже двухсотка. У кого-то может быть даже двухсотка, да. Двухсотка с вложенным объектом error с деталями вашей ошибки.
В общем, да, будем говорить про то, какие есть подходы к обработке ошибок, чем они хороши, чем они плохи, как это вообще принято в разных языках программирования. Но не будем забегать далеко вперед. И представлю сначала нашего гостя. У нас в гостях Дмитрий Свиридкин. Дима инженер в AWS, Amazon Web Services, автор книги про лучшие ошибки в C++, которую невозможно дописать, но можно издать, поэтому еще поговорим. А также доказательства существования и вакансий нараст вне крипты.
Офигенно. И еще и, я думаю, многим твиттерским очень хорошо известный как Дмитрий Undefined Behavior Свиридкин. Дим, привет! Привет, привет, Дженя, привет, Стас. Во-первых, рады видеть тебя в очень даже Defined Behavior у нас на подкасте. И у меня сразу несколько вопросов. Обычно мы вообще идем сразу в бэкграунд человека, чтобы понять, чем человек занимался, что такое интересное делал. Но я хотел сначала спросить про твой опыт, про точнее то, что ты написал в разделе «Как представить тебя».
автор книги про самые лучшие ошибки в C++, которую невозможно дописать, но можно издать. Что это значит? Да, это значит много и почти ничего. Значит, я уже два года как веду у себя Похоже, что самый популярный репозиторий из всех, которые у меня есть, это Ubbook. Я периодически... Ссылки на его часть я публикую в Твиттере, где я его постоянно дописываю, дописываю, дописываю. Там у меня сборник ловушек, мин и всяких неопределенных поведений в...
в самом замечательном языке C++, и немножечко в C, потому что они как бы рядом живут. Оно начиналось как набор заметок, да, а репозитория разросла уже до масштабов, там не посчитали, там уже... где-то 140 страничек вордовских набежало. Потенциально я оформлю ее как книжку и... Будет она напечатана. Возможно, будет когда-нибудь продаваться. Возможно, не будет продаваться. Но книжка будет. Офигенно. Ну, мы в Show Notes...
В описании к подкасту приложим ссылку на вот этот репозиторий, чтобы наши слушатели могли посмотреть. Да и на Twitter твой, я думаю, чтобы знали, что читать избранные выдержки от автора. Окей, ну тогда давай к нашему классическому вопросу. Расскажи немножко про свой бэкграунд, чем ты сейчас занимаешься, чем занимался до этого, и почему про ошибки так много всего интересного знаешь. Да, ну, бэкграунд у меня...
Обширный, но немножко узконаправленный. Я в основном... Сколько уже? 10 лет. Это, конечно, уже... Большой, приличный опыт, но недостаточно приличный для тех, кто зовут себя principal software developer. Так, сеньорчик. 10 лет занимаюсь всякой низкоуровневой ерундой. Начинал я с... Распознавание речи. Модельки обучал. Это, конечно, не очень низкоуровневая фигня. Но обучал я их буквально пару месяцев. Потом начал пилить и допиливать фреймворк для, собственно, инференса. А это уже...
более низкого у меня штука, и писал его там на C++, дописывал, оборачивал там дальше в питон, чтобы его можно было вызывать. Потом я работал в таком... около военном НИИ, всякие радары программировал, там мы молотили. Байтики в драйверах Линукса. Антенна снимает, сигнал, да. Байтики идут с огромной скоростью, их нужно быстро забирать, потом обрабатывать. Вот этим занимался, там применял...
Кучу всяких разных методов обработки сигналов. И в распознавании речи применял, и здесь применял. Потом я работал в Центре речевых технологий. Также занимался инференсом распознавания речи. тоже преобразование Фурие гонял, и там на графах всякие интересные были подходы. Они уже сейчас совершенно устарели, потому что все победили нейросетевые подходы, end-to-end нейросетевые подходы. Но вот раньше...
Еще 6 лет назад еще более-менее были популярные подходы классические. Я вот там алгоритмы на графах применял, все очень так серьезно. Потом мне довелось поработать в автомоте в стартапе. Arrival, ну, у него судьба трагическая, к сожалению. Там я гонял также преобразования Фурье, еще и байтики писал, ГПУшку, из ГПУшки читал для... компьютерного зрения. И сейчас работаю в AWS, в CloudFront, работаю над таким продуктом, совершенно публичный, каждый может пойти заплатить.
Ну и как заплатить? Там есть фритир, да, после него какие-то... по Invocation тарификация есть CloudFront Functions. Это такая customization point для ваших реквестов и респонсов перед CDN и после CDN. Вот, работа сейчас над ней, и конкретно работал, и даже зарелизили, работал над фичей Kivalist Store для нее. Вот, про нее, возможно, мы еще поговорим в контексте обработки ошибок. Тоже очень интересная штука. Вот. И здесь, как-то не странно, мы, может, AWS там тоже JSON перекладываем.
Тоже низкоуровневая ерунда. Мы работаем максимально близко к железу, чтобы эти JSON-ы ваши перекладывались максимально быстро и эффективно, потому что если вы откроете спецификацию вот этого самого... CloudFront Functions, вы увидите, что latency меньше 5 миллисекунд. Даже меньше, потому что квота 500 микросекунд. Это вообще кошмар.
Круто, круто. Вырисовывается образ такого настоящего хардкорного системщика. Один из тех людей, которые, возможно, начинают ругаться, когда им говорят, что перформанс... не имеет значения. И, кстати, раз уж мы тут упомянули перформанс, не могу не воспользоваться моментом и не отправить тех, кто еще не смотрел, не слушал наш выпуск абсолютно недавний про перформанс. Он был совершенно замечательный.
Пойдите послушайте. Если вдруг считаете, что перформанс это скучно и неинтересно, нифига подобно. Окей. Я думаю, мы тогда готовы погружаться в мир ошибок. Хотел придумать какую-нибудь смешную подводку, но, к сожалению, не продумал я заранее. Давайте начнем с чего-нибудь, с какого-нибудь вводного, наверное, обзора и вообще поговорим...
как можно систематизировать, каталогизировать, не знаю, короче, какие вообще бывают виды ошибок? Поговорить про это для начала. Это бы здорово, но я бы хотел предложить сначала обрисовать вообще всю ситуацию. такой очень-очень высокоуровневый. Когда мы говорим об ошибках, у нас есть три компонента. Первый компонент — это...
детектирование ошибочной ситуации. Второй пункт — это, собственно, уведомление об ошибке. И третий пункт — это обработка ошибки. Ну и вот тут, конечно же, такой привередливый слушатель сразу говорит... Три, я сказал. А на самом деле их два, потому что первый и третий — это суть одно и то же, но только с разных точек зрения. Одна со стороны того, кто, собственно, собирается выбросить ошибку, а вторая — тот, кто собирается ее поймать. Так что у нас есть только два.
пункта. Один это уведомление об ошибке, а второе это его обработка. И теперь мы, в принципе, можем перейти к тому, какие у нас вообще бывают ошибки. Ошибок у нас... Может быть бесконечно много самых разных. Принято делить на ошибки. Такие, которые происходят до выполнения вашей программы. Это всякие compile-time ошибки, ошибки синтаксиса, ошибки типизации. Они тоже compile-time обычно бывают, хотя не всегда. И runtime ошибки.
Те, которые уже в процессе выполнения происходят. Их бесконечное множество можно придумать и в ошибке ввода-вывода, и логические ошибки, и ошибки валидации данных, там, out-of-bounce ошибки. и какие угодно можно придумать свои собственные.
Про синтаксические ошибки я сейчас хотел вернуться, потому что, кажется, там, по моим ощущениям, не то чтобы много о чем можно говорить. Но я же правильно понимаю, что в данном случае, смотря в разрезе тех двух аспектов, про которые ты говорил, у нас... Момент, как нужно уведомить об ошибке, это, грубо говоря, то, что нам синтаксическую ошибку, наверное, может подсветить наша среда разработки, если она поддерживает динамический анализ происходящего, либо...
Точнее, динамический, статический. Короче, вы поняли. Когда ты пишешь, тебе Ильяшка говорит, что фигня какая-то даже не скомпилируется. И... типа обработка, это значит, что мы видим красненькую подсвеченную ошибку и исправляем у себя в IDE или в текстовом редакторе и идем дальше заниматься другими вещами. Да, как бы все верно.
И в идеальном мире оно так и должно быть, но у нас же бесконечно много языков программирования. Ну как, пока конечно, но потенциально бесконечно. И каждый день новые и новые появляются. У нас есть замечательные языки. программирования, в которых синтетическая ошибка выявляется в рантайме. Какой пример такого языка ты, Женя, например, знаешь? Мы все им пользуемся, к сожалению.
Я в любой непонятной ситуации обвиняю JavaScript. Ну вот, к сожалению, нет. JavaScript все-таки синтаксические ошибки может проверить заранее. Но, опять-таки, если мы... генерируем JavaScript, потом облием куда-нибудь там в браузер, то вот там в процессе его загрузки будет ошибка. Она как бы в рантайме, но как бы не в рантайме. Но есть другой замечательный язык — bash.
И в нем синтаксические ошибки могут приводить к совершенно чудовищным вещам, потому что скрипт может продолжить выполняться. Да, да. Об этом я не подумал. Я просто, мне кажется... Ладно, надо честно в этом признаться. Башем пользуются все, но я просто после каждого раза, когда мне приходится пользоваться им достаточно долго, мне кажется, пытаюсь это просто из своей памяти стереть. Но в последнее время с помощью...
какого-нибудь Капайлота или чат GPT в основном Капайлота. Все делается очень быстро и работает даже приемлемо, и мне почти не приходится ничего писать. Слава, слава богу. Ну ладно, не будем сильно о личном. Окей. Про синтактические ошибки понятно.
Не знаю, там вообще что-нибудь в этом мире интересного есть. Ну, понятно, что с башем все понятно. С другими языками в целом, если они плюс-минус мейнстримовые, то там тоже все понятно. Тулинг, благо, должен помогать таких ошибок избегать довольно легко. С синтаксическими ошибками... еще есть иногда проблема, что окей, мы их умеем детектировать и умеем уведомлять, но мы о них уведомляем таким чудовищным образом, что
Обработчик ошибки, то есть человек, который их читает, ничего понять не может. Пример из таких популярных менструировых языков. Вот он у меня тут. Вот здесь. Я думал Swift, особенно на заре. То, что там даже мемчики делали из-за того, что тебе просто присылают какое-то бесконечное количество скобочек непонятно чего.
Как в этом увидеть какой-то тип, науки неизвестно. Это тоже правда. Но вот как-то не странно, вот про Rust говорят, что... у него там самый дружелюбный компилятор, но при этом ошибки компиляции у него могут быть, ну и довольно часто для тех, кто вот особенно работает с веб-сервисами, потому что... Асинхронное программирование, асинка, вейт и вся эта прелесть. Она чудовищно работает с лайфтаймами.
И ошибки получаются просто восхитительные. Expected lifetime, нижнее подчеркание, find lifetime, апостеров 1. Что это такое? Никто не понимает. Но ошибка очень подробная. Как починить, никто не знает. И есть еще один такой пример, про него мы еще поговорим. Мне с ним завелось столкнуться еще в универе в магистратуре.
У меня был курс по FPG-шкам. FPG-шки, плисины, программирование интегральной схемы. Мы писали там модульки для этой FPG-шки на... квартусе это он был интеловский нет он был альтеровский потом интел его купил это и дешка которая поддерживала вроде бы работу с vhdl и с вере логом но мы писали на VHDL. И вот на VHDL синтаксические ошибки там тоже совершенно чудовищные. Тебе просто он говорит, я не компилируюсь, я не могу собрать твой модуль, а почему ты не можешь понять вообще?
Он даже не показывает, он просто говорит, что у тебя что-то неправильное. Где? Ну вот где-то здесь. Ох. Я воспользуюсь привилегией того, что я единственный ведущий подкаста, который не работает в JetBrains и не работает над Kotlin. Ни в какой мере. И пожалуюсь на Kotlin. У него в целом все хорошо с ошибками компиляции.
но есть один отдельный класс, который всегда меня заставляет рыдать, страдать и все такое. Допустим, бывает, когда нужно в какую-нибудь жавовскую библиотеку передать, не знаю, реализацию какого-нибудь очень generic интерфейса или какой-нибудь generic объект, допустим, со сложными generic, где у тебя какой-нибудь...
мэп с двумя generic типами, которые тебе нужно как-то переопределить. И, короче, если ты где-нибудь допустишь небольшую ошибку, то настолько криптик сообщение выдает компилятор, когда он тебе говорит, что...
ожидается, что ты передашь вот такой и вот такой тип, а ты передал вот такой и вот такой тип, и ты смотришь, что все одно и то же, но что-то не работает. И, короче, в итоге можно провести просто там, не знаю, 40 минут, пытаясь разобраться, где ты перепутал запятую или угловую скобочку. Ужасно, ужасно. Kotlin все очень хороший язык, но кто, если не я, скажет это вам в эфире в контексте тем более такого выпуска. Ну и, конечно же, нужно не забыть про C++.
По нему даже иногда бывают... Возможно, даже сейчас все еще проходят соревнования по генерации самой длинной ошибки компиляции. Ну, все, спасибо. припроцессоры, все спасибо шаблонам, и все спасибо замечательному с Finae, Substitution Error и с Note Error, благодаря которым можно генерировать просто бесконечно длинные.
сообщения об ошибках компиляции. Даже есть некоторые отдельные категории в этих соревнованиях без использования припроцессора, без использования шаблонов. Но спасибо. По-моему, это все плюс чемпион. по генерации нечитаемых ошибок. То есть про соревнования это ты не пошутил? Нет, я не шучу. Серьезно есть. Великолепно. Окей. Так, с синтаксическими вроде разобрались. С ошибками компиляции тоже вроде подразобрались. Остались у нас ошибки рантайма.
Вот. И тут у меня, наверное, первый вопрос философский. То есть, понятно, могут случиться ошибки серии там null pointer какой-нибудь. Закосячили. Неправильно написали код. А можно ли считать баг? ошибкой тоже вот это такой да действительно философский вопрос вопрос такой для кого ошибка кто пользователь ну вот такой, наверное, профдеформация от воздействия Амазона, у нас же есть эти leadership principles и всякие, forward, work backward from customer.
хуй с кастомер и так далее. Кастомер-центристик организация. Вопрос такой, кто потребитель ошибки? Если потребитель ошибки какой-то сторонний... пользователь, и у нас баг в нашем программном обеспечении, это не ошибка для пользователя. Это что-то более серьезное. Это availability risk. Наш сервис будет для него совершенно недоступен. Не потому что ошибка там произошла, а потому что баг. С другой стороны, баг может просто провоцировать ошибку. И она показывается пользователю. И тогда баг это...
Ошибка, которая является rootcos, другой ошибки, да? Вопрос, кто смотрит? Ага, понятно. Ну, давай тогда, чтобы просто эту историю зафреймить для нашего выпуска. решим сейчас, будем ли мы в выпуске обсуждать ошибки, которые баги. Мне кажется, это немножко в сторону уходит. Да, мы не будем обсуждать ошибки, которые баги, но мы немножко затронем, наверное, всякие последствия тех или иных багов, которые можно потом превратить в ошибки, которые можно обработать.
или нельзя обработать ну например null pointer the reference в C++ его как мы узнаем что вообще-то можно поймать и обработать Окей, здесь все ясно. Наверное, последний вопрос в теме каталогизации, типизации, как угодно называйте, в общем, это про то, насколько низко мы пойдем.
потому что я предполагаю, что на уровне всяких железяк тоже может много чего интересного происходить, но я боюсь, что мы закопаемся, эту часть я бы тоже, наверное, отпилил и туда не пошел. Ну, я думаю, мы начнем быстренько с железяк и пойдем дальше. Почему мы пойдем в железяк? Ну, потому что, давайте так, мы все знают, что есть три типа механизмов обработки ошибки.
Один вот совсем такой примитивный, это error-коды, куда-то писать, показывать. Второй это exception. И третий это вот монадический подход, результат type, и вы там что-то с ними делаете. Все остальные варианты, они сводятся к одному из вот этих трех, так или иначе. Ничего другого пока не придумали, к сожалению. Вот, если мы пойдем от железяк, сразу будет понятно, что... Нельзя просто так взять и как бы избавиться от error-кодов. Потому что вот железя. Вот я как раз-таки этот самый VHDL вспомнил.
Если мы спустимся на уровень железяк и программирование таких плесен, у нас как бы нету обработок ошибок. У нас как бы нету особо ошибок. Мы сами их создаем. Мы сами создаем, что значит вот при... В вычислении такой-то комбинаторной логической функции получаются такие-то выходы, один из них мы считаем ошибочный.
Но при этом мы все выходы все равно обрабатываем, потому что мы не можем остановить железяку, мы не сможем остановить ход этих самых электронов. Вот у блока есть несколько выходов, и на все эти выходы должен подаваться какой-то сигнал, нолик или единичка. И все параллельно обрабатывается. И вот после осознания того, что в железяке все работает параллельно, в том числе ветка, которая обрабатывает ошибку, мы можем прийти к пониманию, что...
Вот error-коды, они очень сильно специфичны для железяк. Что случилась ошибка, ты на такой-то выход выставил error-код не нулевой, и дальше все заблокируется, а вот эта часть будет работать. Причем параллельно заблокируются, там нули будут обрабатываться какие-нибудь в одной ветке, которая хорошая, а в другой ветке у тебя будут проблемы. И там каким-нибудь...
мультиплексором. Один из двух входов будет выбран, один будет с ошибкой, второй будет без ошибки, и как-то это обработается. Ну и еще про железяки тоже довольно интересно, что поскольку как бы обработки ошибок нет, вы сами за это все отвечаете, придумываете ошибку, можно вспомнить, например, арифмометр механический. И самую популярную ошибку, причем самую популярную не только в программировании, а вообще в принципе в мире и у гуманитариев, это...
Что, если мы про рифмометр какой-нибудь? Это деление на ноль. Вот. И вот здесь, наверное, должен появиться такая вставка самого популярного YouTube-видео про деление на ноль. Там машинка, запускаешь, она начинает делить на ноль, и она бесконечно делит. Она вычитание производит бесконечно. И не может остановиться. Вот примерно так работает обработка ошибок в железяках. Блин, надо себя зафиксировать, чтобы мы на Ютубе обязательно в этом месте сделали отсылку к этому видео.
Окей, смотри, раз ты говоришь, что есть смысл все-таки пойти от железяк в контексте рар-кодов и всякого того... подобного. Мы начали с того, что ты сказал, что есть две составляющие. Одна — это как нам сообщить про ошибку, и вторая — как ее обработать. Я так понимаю, обработка — это уже немножко дальше. Наверное, можем начать с того, как...
как вообще принято сообщать об ошибках, какие механизмы есть. Вот, что думаешь, давай с этого начнем? Ну давай. В контексте железяк, ну как я сказал... вариант, что вот у тебя отдельная линия, на которую теперь там выставляется единичка, ну, линия под ошибку, когда нолик, там все хорошо, и она, соответственно, запирает какие-то пути вычисления.
Когда там единичка, она переключает на другие пути вычисления. Соответственно, для железяк вот error-коды, они очень сильно естественные там. Но еще важно, что другой способа особо и нет в железяке. Ну, железняка тупая, к сожалению. Она не может остановиться и пошагово, значит, мы сейчас делаем вот это, потом мы делаем вот это, потом мы делаем... Мы, на самом деле, делаем все параллельно. Просто какие-то пути у нас отсечены, потому что там сигнал нулевой.
Но в контексте железяки можно еще и говорить про обработку ошибок тоже, не только про уведомления. И как-то не странно, в железяках имплементировали обработку ошибок до... того, как появилась обработка софтварная. И вот была такая машина, американская Univac, в 1951 вроде бы году. Там... имплементировали хардварную обработку ошибок. Каким образом? По нулевому адресу записывались коды для обработки ошибок, деления на ноль и еще чего-то.
И когда возникала ошибка, выставлялась на каких-то пинах вот эта единичка, control flow передавался обработчику ошибки. То есть фактически там была обработка исключений. Неплохо. Уже представляю, как можно в новый какой-нибудь смартфон добавить сопроцессор обработки ошибок и сделать это маркинговой инновацией. Ну, она на самом деле и у сегодняшних тоже есть. У нас есть механизм прерываний.
который примерно точно так же и работает. У вас есть специальная таблица, куда вы записываете адреса, по которым у вас в памяти живут обработчики тех или иных прерываний. Прерывание происходит, и этот обработчик вызывается. По какой причине происходит прерывание? Да, по какой угодно. Произошла у вас ошибка ввода-вывода с какой-то железки? Пожалуйста, выполните оборотчик прерывания для этого. Произошло деление на ноль? Пожалуйста. Произошло...
переполнения нет, произошел какой-то ивент, но это уже ивент, это уже не ошибка, может быть, что-то запланированное, тоже выполняйте, пожалуйста. Так что на прерываниях можно построить обработчика исключений. Ну, таких хардварных. Сейчас у меня будет очень, наверное, дилетантский вопрос, но я все-таки его задам. Когда мы говорим про прерывание, справедливо ли будет сказать, что это обработка ошибок на уровне операционной системы?
Или это все-таки ниже? Прерывание штука такая, довольно абстрактная. Мы можем говорить про прерывание в принципе, которое применимо к чему угодно. Например, у вас конвейерное производство и... какую-то деталь заживал конвейер, у вас загорелась лампочка, и работа прерывается. Если мы говорим чуть более конкретно
применимо к компьютер-сайенсу и программированию. Ну да, обычно про прерывания говорят, что это что-то вот... и оно обрабатывается ядром операционной системы, оно там обрабатывается асинхронным образом, причем очень хитрым асинхронным образом, что исполнение кода основного... именно прерывается, он дампится куда-нибудь в память, мы меняем контекст и начинаем выполнять код обработчика прерываний. Потом обработчик прерываний закончил свое дело, и мы возвращаемся обратно.
Такой же механизм, на самом деле, есть еще чуть-чуть повыше. У нас есть в рамках процесса, ну, в юзер-спейсе, есть такая штука, как сигналы. который работает точно так же. И сигналы тоже могут сигнализировать ошибку, и мы можем обрабатывать их. Самый такой, наверное, известный пример из мира низкоуровневого системного программирования — это... и программирование C++, это сексек, сегментация, он прилетает к вашему процессу, и по умолчанию процесс должен умереть.
Потому что что-то не то случилось с памятью, лучше, наверное, не продолжать работать. Но никто не запрещает вам его поймать и обработать. Ага. Вот, мы, наконец, выплали с совсем низкого уровня на уровень повыше, до сигналов. Куда пойдем дальше? Дальше мы можем пойти опять, конечно, в error-коды, потому что они могут быть везде. И самый... Известный пример — это error-коды, которые выставляются после исполнения тех или иных системных вызовов. У нас есть переменная RNO.
по крайней мере, под Unix-системами, и что-то хитрое под Windows, которое можно тоже почитать, я вот конкретно не скажу, что именно под Windows, потому что я не прямо так Windows-таргет-программист. Я в основном под Linux. Под Unix-системами все системные вызовы, если у них что-то пошло не так, они выставляют ошибку в этот самый ERNO. Иногда они еще, конечно, в регистр...
RX пишут тоже код ошибки, но не всегда. И мы можем почитать, что-то сделать. И это тоже вот такой самый тупой и примитивный вариант, который очень похож на железячный, что... Случилась ошибка, мы ее записали и пошли дальше молотить наши байты. Кому надо, тот посмотрит и перестроит свои вычисления согласно этой ошибке. Или не пересмотрит и получит ошибку более серьезную.
Этот подход еще очень сильно любят тоже вот люди, которые близки к железякам. Есть старые графические библиотеки типа OpenGL, также CUDA сама по себе, поскольку... Ну, железяка, как бы, железячники будут работать, естественно, сообщать об ошибке через специальную переменную, которую можно почитать и забыть и обработать.
Я надеюсь, что я не сломаю план, вырывавшись, а я внезапно задумался на тему того, а как для того, чтобы отправить сообщение об ошибке, для начала надо понять, что мы оказались в ситуации, в которой... Ну, то есть ошибка. И здесь вот я начал задумываться, а как вообще понять то, что мы в такой ситуации? И у меня пока... В голове только выстроилось то, что, во-первых, мы с поля низкого уровня можем получить, например, там, опять же, от железа какую-то ошибку и в тупую ее...
Соответственно, просто проксировать наверх, передать наверх в неизмененном виде или как-то там, не знаю, дописав какой-то своей информации. Второе, это кажется то, что мы как-то сами логически понимаем, что что-то идет не так, или мы... Ну, я не знаю.
В принципе, мы как разработчики, мы сами можем принимать решение о том, в какой момент ошибку выбросить. Мы валидные даже для нашего соседа-разработчика повезения можем считать ошибочным, можем не хотеть это обрабатывать и можем тоже... возвращать ошибку.
Как думаете, какие... Может быть, я вообще саму даже вот эту классификацию, тот подход, которым я предлагаю подумать про это, может быть, он вообще неправильный. При этом, может быть, есть какая-то готовая классификация. Про готовую классификацию, к сожалению, не скажу. Я прям не методист. Эту штуку я не копал, я такой практик, практик. Но как я вот начал, что у нас есть три, на которых на самом деле два момента.
детектирование, уведомление, обработка. И обработка, и детектирование суть одно и то же, но для разных. Правильно. Мы можем либо проксировать ошибку с какого-то другого источника, Либо второй вариант – сами ее каким-то образом придумать. Самый простой вариант, который можно примести, и, наверное, всем разработчикам, неважно от какого уровня, понятным будет – это обработка, наверное, пользовательского ввода.
какой-то пользовательский бот произошел, ну, реквест, скажем, да, к вам пришел, какие-то хедеры, какие-то куки, какие-то query string. Вы посмотрели на них и говорите, авторизейшн хедера нету. Это ошибка. Или прислали в поле, в котором должен был быть определенный тип, например, я не знаю, e-mail, нам прислали обычно какой-то стрим, в котором мы поняли, что это не e-mail. Например, так. Ну, что-то...
Более сверхъестественная, какой-то третий способ родить ошибку очень сложно придумать. Она либо приходит... с другого уровня, либо мы в рамках нашей expected рабочей логики ее вывели. У меня есть идея, к нам уже пришли с ошибкой. Ну, то есть к нам клиент пришел с ошибкой и предложил нам что-то с ней сделать, а мы ее дополнительно... Ну, хотя это на самом деле так вот, если задуматься, она ничем не отличается глобально от варианта с проксированием. Какая разница? Ну, то есть если у нас...
источник совпадает с нашим клиентом, ну ладно, но глобально нам плюс-минус без разницы. Мы одним и тем же образом можем ее обрабатывать или наоборот не обрабатывать. Это правда, но вот вариант с... к нам уже пришли с ошибкой, он может быть в том смысле, что ошибка для нас это валидный input. Например, есть... чтобы не сразу Google приходит, в Rust-Экосистеме есть такая тула, как CargoFix, которая ваши ошибки фиксит.
Ну, какие ошибки? Там ворнинги в основном она фиксит. Но если мы считаем, что ворнинг — это ошибка, то она подается вот этой утилите, и она ее фиксит. Пример. У вас есть неиспользуемая переменная, и у вас включены ворнинги, и считать ворнинги как ошибки. ClipFix видит вот эту ошибку, обрабатывает ее, и вашу неиспользуемую переменную ей добавляет нижнее подчеркивание внизу.
Такое тоже может быть, да. И тут вопрос, считать это мы обрабатываем ошибку или это вот прям нормальный для нас ввод и мы просто работаем. Ошибка для нас это ввод такой. Почему бы и нет? Очень философская тема у нас получается. Но я хотел перед тем, как мы пойдем чуть-чуть выше на более, наверное, прикладной уровень, на уровень прикладных...
программ, приложений и так далее. Сделать небольшой шажок назад, вот ты рассказал там про какие-то глобальные штуки вроде, точнее глобальные, скорее ближе к операционной системе. Ты говорил про RNO, ты говорил про прервания. А вот кто... должен про это задумываться. Сейчас я объясню свой вопрос. Сижу я, обычный Васян джавист, Федя джавист или Маша джавистка, неважно, как угодно. Сижу и пишу на своей джаве, у меня там все обмазано эксепшенами.
для меня в целом скоп ошибок, которые могут происходить, он довольно понятен, и модель работы с этими ошибками тоже понятна. Про exception еще потом поговорим. Когда и кому нужно задумываться про те более низкоуровневые подходы, про которые ты говорил? То есть какая это сфера разработки? Кто это? Разработчики драйверов или каких-то низкоуровневых компонентов операционной системы? Ну, да, во-первых, прежде всего, конечно же...
Если говорить о таких железячных ошибках, прерываниях и прочем, это разработчики драйверов. И не обязательно в kernel space иногда бывают драйвера в user space, как там есть знаменитый спор. Торвальдса с Тенненбаумом, да, где нужно писать драйвера в юзер-спейсе или в гернал-спейсе, монолитное или микро-ядро. Вот тем людям, которые этим занимаются, им, конечно же, нужно...
задумываться о прерываниях хардварных, а также других генерируемых ошибках, которые происходят на уровне ядра и хардвея. Но внезапно... Внезапно. Могу сразу же примести примеры своей практики, которым тоже нужно задумываться об этих ивентах. Причем я пишу как бы не вот это вот самое для ядра низкоуровневое программное обеспечение. Пишу все-таки чуть-чуть в юзерспейсе. Пример такой. Наш сервис, он крутится на dedicated, изолированном ядре, чтобы никак не интерферировать со всем остальным миром.
Окей, мы захотели изолировать ядро, и мы должны, чтобы понять, как мы хотим изолировать ядро, мы должны понимать, что существует прерывание, мы должны понимать, как работает примерно хотя бы ядро. И пойти его сконфигурировать так, чтобы он не планировал обработку прерываний на вот это ядро, которое мы собираемся застолбить себе. И мы это можем действительно пойти и сконфигурировать.
Потому что иначе вы хотите обеспечить стабильную, детерминированную latency меньше 1 миллисекунды, меньше 500 микросекунд, и тут внезапно у вас прилетает прерывание. и ядро пошло что-то там, ошибку ввода-вывода жесткого диска обрабатывать. Внезапно. В таком случае нужно задумываться об этом. Даже если ты не пишешь драйвера. Понял. Если мы пойдем дальше чуть-чуть в юзер-спейс...
Ну, проверку RNO тоже стоит о ней задумываться, если ты пишешь прикладной код, который... Ты читаешь что-то из файла, открываешь файл. Если ты не будешь проверять этот RNO, ты не прочитаешь из файла. Или прочитаешь мусор, или просто упадет в твое приложение. Нужно тебе об этом думать. То есть если ты пишешь на низкоуровневом языке, который оперирует вот этими всеми эррор-кодами, низкоуровневыми, страшными, неудобными,
ты должен о них думать. Если ты пишешь на чем-то более высокоуровневом, где за тебя уже подумали об этом, то у тебя, наверное, другой интерфейс для обработки сообщений об ошибках. Например, эксепшены или вот эти модернические интерфейсы. Есть еще один вариант. Это ты пишешь ногой, и тебя просто сам язык насилует и заставляет обрабатывать ошибки. R не равно now, то делай то-то. Потому что сам язык требует, чтобы ты все проверил.
Ага. Я вот, кстати, на Go никогда ничего о ни одной строчке, по-моему, в жизни не написал, поэтому я даже понятия не имею, какой там механизм обработки ошибок дефолтный для языка. То есть там через такие олдскульные рар-коды? Это не error-коды от тебя на уровне типов, как я понимаю? Не совсем. Я не могу сказать, вот сейчас есть ли тенденция в GOP, после того, как туда добавили дженерики, все переписать на вот эти монотические интерфейсы, но до того, как там...
были дженерики, до того, как они там появились, у тебя все функции, которые могут вернуть ошибки, они возвращают пару. Значения и код ошибки. И поскольку в Go нельзя оставлять переменные неиспользуемыми... Ты обязан написать if r не равно nil return. Да, не равно nil, тогда return. Ты просто обязан, тебя заставляют. Офигеть. А давай...
Тогда попробуем разобраться в том, какие есть виды. Я даже не знаю, как это правильно называть. Коды ошибок, коды возврата. Потому что, короче, как у меня это в голове выглядит. Откуда-то очень из подсознания есть воспоминания, что там может быть несколько разных подходов.
пытаюсь вспомнить они где-то были разные где-то это было как ты вызываешь какую-то функцию и она не предполагается, что она, допустим, что-нибудь вернет, либо ты в нее передаешь аргументом то, в чем тебе нужно получить результат, а сама функция возвращает тебе либо нолик, либо код ошибки.
Или я вспоминаю, где-то было, может быть, ошибаюсь, что ты передаешь функцию свои аргументы и отдельную переменную под ошибку, и потом проверяешь, она null или не null, или есть в ней нолик или не нолик. И, наверное... Где-то я тоже такое видел, но я сейчас не уверен. Возможно, это то, что ты говорил. Когда ты просто вызываешь какую-то функцию, а потом проверяешь вообще абсолютно никак с ней не связанный объект, появилась ли в нем какая-то ошибка или нет.
Вот у меня в голове это выглядело как-то так. Да, примерно так. Есть... Первый вариант с кодом ошибки. Самый лобовой и любимый как раз таки железячниками. Ты заводишь отдельную специальную линию, отдельную специальную глобальную переменную. Если у тебя произошла ошибка, ты в нее записал этот код. Дальше пусть разбираются. Если у тебя произошла ошибка, ты записал ее там в какой-то регистр, пусть его читают. Ну, нормальный подход. Его в OpenGL любят, его в Куди любят.
Возможно, есть, я не проверял, возможно, есть высоковровневая, более высоковровневая C++ API для куды, которая так не делает, но есть. Ну и, конечно же, Unix почти все у тебя возвращает. и возвращает код ошибки в return значение, и одновременно ставит его в эту глобальную переменную. Такой самый печальный пример — это когда у тебя функция одновременно может вернуть и ошибку.
И не ошибку, а возвращаемое значение ровно одно. И если там отрицательный, тогда это ошибка. Если там положительный, то хорошо. И самая мерзкая функция это fork. Потому что есть комбинация fork-kill. И если форк возвращает ошибку, то есть отрицательное, там, минус единичку, и ты передаешь это значение в килл, ты убиваешь всех. Вообще всех. Неплохо. Первый вариант – глобальная переменная. Обсудили. Второй вариант – чуть-чуть более высокоуровневый. Его любят скрещивать.
в CIS-классами, C-классоподобных API-ах. Почему-то математики очень любят, если мы возьмем математические библиотеки типа... OpenFST, OpenGRM, с которыми я работал. Это для работы с языковыми моделями, статистическими, которые не на нейросетях, а вот триграммы, статистики считали по текстам. Играфы строили еще по ним. Там у вас объект, который очень высокоуровнево представляет собой эту модель. У нее есть куча методов. Сделай то, сделай это. А в конце есть метод getError.
И ты должен постоянно вызывать этот метод getError, чтобы узнать, не произошла ли у тебя ошибка. То же самое, кстати говоря, в OpenGL, потому что эта ошибка, она может быть ассоциирована к OpenGL в контекст, а не только глобально, и ты должен проверять ее у контекста, но тоже такое себе дело.
Сообщение об ошибке приходит не тогда, когда ты вызвал какую-то функцию, а когда ты вызвал какую-то функцию, а потом проверил. И фактически можно транжировать все подходы по сообщению об ошибках от... такого очень пассивного, когда... Ну, произошла ошибка, ты ее записал куда-то на бумажечку и положил в ящичек, забыл. Возможно, разрешится. Это первый вариант. Это, кстати, глобальная переменная с error-кодом. Это вот это оно. Второй и самый такой...
Крайний вариант самый активный. Ты просто орешь постоянно, пока тебя не заткнут, а происходит ошибка. Это вот исключение, это прерывание. Все то, что ты никак не можешь не отреагировать. Но возвращаемся к... error-кодом. Глобальную переменную — да. Переменные, ассоциированные с объектом — да. И третий вариант, как раз про который я говорил в Go, это error-код возвращается с самой функцией.
Ну, не только в Go, вот куча этих посек с API, сишных, они тоже возвращают код, ну, как я на примере этого форка сказал. Код возвращается, да, в особом случае. Но... Более такой прямолинейный и простой, как в Go. Ты просто возвращаешь пару всегда. Что-то ты насчитал и ошибку, если она есть. И поскольку у тебя всегда все nullable, ты можешь возвращать null, если нет ошибки. Очень удобно. Наверное, поэтому гои любят.
Ну еще и за то, что он заставляет это все проверять. Блин, это звучит, если честно, что если подключить очень сильно воображение, то можно просто представить, что каждая функция в Go возвращает по сути результат. Да, с которым не очень удобно работать. Ну, не каждая. Те, которые должны возвращать ошибки, они обычно возвращают пару. И четвертый вариант – это мы передаем параметр, куда нам положат ошибку.
Тоже довольно распространенный. И этот параметр можно иногда передавать как на ушки, когда тебя не интересует ошибка. Очень распространенный случай. Во многих опишках... сишных библиотек применяется. Не видел такого... в более высокоуровневых языках, типа Java, Go, как-то не принято там. Ну, вероятно, в сложности с мутабельностью вот этой переменной, потому что тебе нужно передать ссылку, и ты туда что-то положишь.
Там начинаются эти проблемы с боксингом. Как-то сложновато начинается наворачивать. Такое API проще... исключения или просто вернем пару или еще что-нибудь того же сорта. В C++ есть много дублирующихся API, которые принимают ссылочку на объект с ошибкой. Потому что C++ сделал много ошибок, о которых мы поговорим в дизайне. И исключения иногда нас очень сильно не устраивают. Ошибку получить надо. Поэтому многие...
опишки дублируются, одно кидает исключение, а второе, если добавить параметр, мы туда ошибку положим. Тут сейчас еще один вопрос в голову пришел, потому что как раз хочется уже двинуться дальше и поговорить про исключение. И правильно ли я понимаю, что когда мы говорим про всякие разные коды ошибок, коды возврата, как угодно,
Это как раз то, про что ты говорил чуть-чуть раньше, что это подходы к ошибкам максимально мягкие, которые тебя ни к чему как будто бы не принуждают. То есть, ну, вернула тебе функция код ошибки, и если ты решил на него забить, то ничего не получится. не произойдет, у тебя ничего не крашнется. Это, в общем, на твоей совести. Да, это самые такие мягкие подходы. Ну, вот, самый мягкий, это когда ты пишешь в какую-то глобальную переменную или...
говоришь пользователю, читай ее, и если ты не читаешь это все на твоей совести, у тебя будет неопределенное поведение и прочее. Это очень близко к тому, чтобы ты вообще не сообщаешь об ошибке. Ну, зачем? Чуть менее мягкий — это возврат кода ошибки из самой функции, который вместе сопряжен с настоящим результатом, типа того же самого fork, но это такой...
Он, конечно, мягкий, но чудовищно небезопасный. В плане совершенно нет защиты от дурака. Не в том, что там что-то взорвется небезопасно или память поломается. Совершенно нет защиты от дурака, придешь и сломаешь себе ноги об него. Более такой настойчивый, это Гошный вариант, но он настойчивый не потому, что возвращается пара, а потому что в самом языке сказали, что у вас неиспользованные переменные должны быть обработаны. Нельзя неиспользованную переменную оставлять.
Последний вариант с передачей параметра placeholder для того, чтобы получить ошибку, такой себе тоже передал ты и забыл про нее. Тоже ни к чему тебя не обязывает. Окей, звучит как отличный момент поговорить про... Эти самые наши эксепшены. Мы тут просто сидим. Я с Kotlin Java бэкграундом. Стас с Kotlin iOS Swift.
Кстати, в Objective-C же, наверное, эксепшенов никаких даже близко нет, да? Слушай, ну, с моей колокольни вот эта вся история про эксепшены, это больше такой джава-мир все-таки. Вот, давайте разбираться. Джавесовой особый мир, исключение, да. Но, в принципе, везде особый мир. Вот такой. Можете, Стас Женя, попробовать так... Чисто прикинуть. Ну, я сказал, что хардварные исключения, да, там, 51-й, 52-й, 53-й год в Univac реализовали впервые. А когда софтварные исключения появились?
Блин, ну хочется что-то прибить каким-нибудь, не знаю, луноходом, ракетом, чему-то такому в том числе. Хотя, блин, там же, наверное, больше всего хардовое было. Мне хочется сказать 70-е почему-то, но я не знаю почему. У меня нет никакого конкретного языка даже, например. Ну, эти годы, они так, да, они относятся к годам развития языков программирования, которые ближе к нам, да, там C.
Оттуда пошел, до сих пор живется чудовищная вещь. Но на самом деле, если я не ошибаюсь, 1953 или 1954 год внезапно в Лиспе. В Лиспе исключение. Там была пара функций, ну, таких псевдофункций. Одна, чтобы выкинуть исключение, а вторая принимала, собственно, вычисления. Если это вычисление кидает ошибку, то возвращался пустой список, этот Нил. А иначе возвращался список из одного этого элемента, в который вычисление заризовалось. Ну вот как бы самый примитивный, но обработчик исключений есть.
был. Слушай, ну сейчас, пока мы не начали обсуждать разные варианты, подходы и так далее, давай попробуем сформулировать, потому что как бы на уровне общего понимания Кажется ясно, что такое исключение, особенно всем, кто поработал с какой-нибудь там джавой. Но, наверное, какие-то характерные особенности можно попытаться сформализовать. В моей голове это выглядит так.
что это обычно какая-то конструкция уровня языка самого, то есть уровня того, что может проверить компилятор, и там, где компилятор тебя может чему-то принудить, например, и что эта конструкция, когда ты ее используешь, она... В месте использования она прерывает флоу исполнения программы, вплоть до того, что неважно, сколько уровней у тебя вложенности вызовов было,
тебя будет выкидывать до самого конца, пока кто-нибудь это исключение не обработает где-нибудь. А если нигде не обработает, то приложение грохнется. Ну, как бы да. Исключение — это высокоуровневое прерывание. Причем прерывание такое очень... Ну, в нашем понимании, к которому мы сейчас привыкли, исключение — это что-то, что полностью прерывает исполнение целого стака вызова, летит наверх.
И кто-нибудь до его обработает. Причем вот все, что должно было дальше в нормальном потоке исполнения исполняться, оно все забыто. оно не будет исполнено, у нас об нормальный путь исполнения пошел, поэтому исключение, исключительная ситуация. И эти исключения его, возможно, поймают, возможно, нет. Но на самом деле... Не обязательно. И было и есть как бы сейчас два подхода к исключениям. Один подход — это терминейшн, что выкинули исключение, все, последующие вычислений забыто.
Если надо, возобновляй ее с нуля. Делай retry, каким образом хочешь. А второй вариант – это suspend and resume. Ты можешь как прямо, так и в прерываниях. сказать, что окей, я кидаю исключение, я приостанавливаю исполнение своего вот этого стыка вызовов, оно летит, его кто-нибудь поймает, и тот, кто поймает, должен решить, что должно произойти дальше. Я должен его...
обработать и прервать это вычисление, все, которое у меня было. Я могу ее обработать и продолжить вычисление. И третий вариант забить. И делать что-то другое. И внезапно... Такой подход тоже практикуется. Он, конечно же, не прижился очень сильно в мейнстримовых языках, потому что очень error-prone, очень легко сделать не то, что хочешь. тем более оно еще и память, наверное, жрет, вот этот вот прерванный стэк вызовов.
Он же прерванный, но его не выкинули. Что-то с ним нужно сделать. Если я забыл про него, то мне, наверное, как-то клинап нужно правильно произвести. А я не знаю как. Мы про это еще поговорим. очень сложно. Сложно готовить их правильно. Но при этом это более такой дженерик подход, потому что подход с терминейшн, он как бы вложен в него, да? Ну, ты просто всегда останавливаешь вычисления.
Поэтому почему бы не предоставить всем более дженерик подход, и пусть они решают. Так получается, что программисты, они как бы ленивые, они забывают правильно пользоваться фичами, которые им предоставляют. Забывают читать документацию, поэтому лучше давайте предоставим им более надежный вариант и пусть они им пользуются, даже если он менее функциональный. А вот такой более функциональный подход, он сейчас...
Живет и здравствует в языке R. Или R, как правильно. Вот. R. Которая яд зеркаленный. Простите мой пронанс. Он там есть, да. Не уверен, что им всем так очень сильно любят пользоваться, но он там есть такой. А также, если подумать, у нас во многих высоковольных языках есть такой отголосок этого дела. Это карутины. Ну, фактически это же карутины. Если ты можешь в любой момент сказать, я хочу остановиться и передать управление кому-то другому, чтобы тот решил, что делать дальше.
Карутины примерно так и работают, да. Это вот такие кооперативные вычисления, кооперативная многозадачность. Ты доходишь до какой-то точки, не знаешь, что делать, говоришь, устанавливаешься. Вызывайте меня дальше. они есть в Go. Генераторы можно использовать как карутины. Мало кто знает, но вот этот вот Yield в Python, он не только возвращает управление вызывающей стороне,
он еще и возвращает некоторый результат тебе как генератору, и ты дальше можешь решить, что тебе делать с этим. Ты можешь узнать, как вызывающая сторона отреагировала на то, что ты остановился. Ну и, конечно, всякие фьючи. И C++-ные коррутины в Rust, даже, может быть, немножко можно приплюсти сюда джаваскриптовые промиссы, они тоже очень похожи. Джава-скрифтовые промисы, наверное, в меньшей степени. Вот в большей степени это, конечно, плюсовые каротины, как их сделали, и ростовые фьючи.
Это практически то же самое. Это вот те исключения, только они не исключения. Это другая штука, другая монада. Исключение. Что же можно сказать? Тоже можно сказать. Монада exception. Окей, про такой подход понятно. Давай, может быть, все-таки классика это все заполируем. Классика в данном случае, я понимаю. Как это? Простой советский джавовый эксепшн.
Там же тоже с ними все хитро. Могут быть всякие checked, unchecked. И вот мне, кстати, интересно. Вся эта история... Ну, во-первых, сейчас давай разберемся, что это значит, потому что я уверен, что у нас далеко не все наши слушатели с Java мира знакомы. И мне интересно было бы узнать... а вот checked and checked подходы это чисто живого явления или кто-то тоже таким балуется? Ну, прежде всего надо понимать, что checked and unchecked подход это больше про то, как навязать обработку.
а не про то, как что-то там выбрасывать сообщать. Это про то, чтобы сообщить, не сообщить, а сделать более явным для разработчика, что вот эта функция кидает исключения. Потому что основная проблема с исключениями в том, что ты не знаешь, откуда они прилетят. Они могут прилететь откуда угодно. И с этим как бы... Проблема. Вдруг тебе не хочется вообще ловить исключения, и тебе они очень сильно вредны. Например, у тебя FFI какой-нибудь с C.
или еще каким-то другим языком, и на вот этой границе у тебя, в принципе, не должно быть исключений, и ты вызываешь какую-то функцию, она кидает исключения, и тебе крашат стэк. Не должно быть так тебе, ну, в твоем проекте, да? А с исключением проблема. Ты не знаешь, ты вызываешь какую-то функцию,
Она кинет исключение или нет? Тебе что, все оборачивать в трайкетч? Ну, да. Иногда тебе нужно оборачивать все в трайкетч. А иногда, если обернуть все в трайкетч, это может привести к каким-то дурным последствиям, потому что ты... переусердствовал и схватил исключение, которое тебе не нужно было ловить, обработал его не так, как надо, и у тебя работа идет логически некорректно. Ну, отчект, отчект про то, что...
Чект мы явно специфицируем, что эта функция кидает то-то, то-то, то-то. Вы должны это дело обработать и прокинуть. Правильно? Правильно. А чект не сказал и не обрабатывайте. само пролетит. Это очень удобно. Исключения очень удобны для всяких прототипирований, MVP-шек. Ну, ты пишешь просто код линейно, не думаешь о том, что тебе нужно обработать какую-то ошибку. Пишешь просто хэппи пасс свой и радуешься. Счастливый. Потом исключения летят, и тебе нужно что-то с ними делать.
Тут, наверное, я просто единственное могу, чтобы у кого-то это все приземлилось на конкретику, привести там типичный пример из Java мира, когда вы вызываете какую-нибудь там особенно IO функцию. метод, который читает из базы данных, читает из файла или что-то еще, он у вас прям на уровне сигнатуры, у него написано, что он кидает IO exception, прям написано там, что функция там... public, какой-нибудь string, что-нибудь, там, файл, line, что-нибудь такое. Throws are your exception.
И у вас есть два выхода. Либо в месте вызова обернуть все в trycatch, вы как бы говорите компилятору, чувак, я все обработал, пусти, пусти скомпилироваться. Либо вы можете в своем месте вызова, функции, которые вызывает, сказать, что она тоже. бросают IO-эксепшн и переложить эту ответственность на кого-нибудь другого. Да, и эти чех-эксепшны, соответственно, так инфицируют твою кодовую базу потихонечку, плывут, плывут, плывут до тех пор, пока тебе не надоест, ты не обработаешь все.
Да. Еще очень люблю стиль YOLO программирования, что называется YOLO Live Ones, когда ты оборачиваешь вызовы вот таких... функции с check exception в try catch, а в блоке catch просто перепробрасываешь этот exception, как в runtime exception, в который ты завернул исходный. Дальше разберемся в продакшене. Да. Есть.
Альтернативный подход, когда ты говоришь, что функция не кидает исключения вообще. Это вот его практикуют у нас в C++. У нас есть спецификатор noexcept, и ты говоришь, что функция не кидает исключения. Это значит, что при вызове этой функции, когда пойдет раскрутка стека и докрутившись до момента вызова этой функции,
у тебя произойдет терминейт, потому что дальше никто не будет искать никакие exception-хендлеры. То есть может произойти такая матрешка. У тебя функция, которая кидает исключения, ты внутри нее вызываешь функцию, которая не кидает исключения. И это все так у вас оборачивается в большой try-catch. И теперь внезапно по какой-то ошибке вот та функция, которая помечена как no-accept, кидает исключение неважно какое-то.
оно кидает, у вас есть большой try-catch вокруг, но внутри вот это no except дрянь. Из-за того, что она там внутри, вот этот большой try-catch, до него не дойдет исполнение. Исключение не будет поинанное. Произойдет терминейт. Это очень удобно. Вот. И еще такой тоже забавный момент. Был аналог checked исключений. C++, но его задеприкетили и выкинули, можно было сказать, что вот эта функция кидает такие-то исключения, и только их. А если она кинет что-то другое...
Тогда произойдет непредвиденная ситуация и будет вызвана функция std unexpected, которую можно, соответственно, перегрузить, подставить что-то другое и что-то ловить. Ну... Как-то не понравилось, наверное, вдохновившись ситуацией в Java-мире, решили от этого дела отказаться, потому что она как бы не совсем чек-то, она просто говорит, что эта функция не кидает что-то другое, а если кинет, у вас произойдет еще больше проблем, чем если бы она просто кинула.
Потому что произойдет совершенно абнормальная последовательность исполнения с вызовом вот этой вот unexpected, которая непонятно как рекаверится и прочее, прочее, прочее. Так, ну это мы сейчас про Java. И не только. И не только. Ну, и вот про пример с C++. А еще я сейчас вспомнил, что в питоне же тоже есть exception. В питоне есть exception. Ну, работает так же примерно, как в Java, только, конечно, не checked, unchecked. Ну, просто исключение.
Вот у нас как раз в Твиттере к этому выпуску накидали вопрос, один из вопросов. Он получился очень философский, он скорее был жалобой, почему в питоне сделано так плохо. Но если вопрос переформулировать, то вопрос прозвучал на самом деле так. Есть ли языки и подходы, где нет проблем с эксепшенами? Потому что в питоне...
Роуинг и оверкетчинг, когда слишком много кидают, слишком много ловят и все этим обмазывают. В Гуо, ну, это вообще не эксепшены, так что это даже опустим. И в Джаве пишут слишком сложно на вкус, задающего вопрос. Короче, есть ли нормальный подход? от exception? Скорее нет, чем да. С exception и совсем, что похоже на exception, всегда есть проблемы. Проблем две. Даже, наверное, больше, чем две. Но как минимум две мне сейчас сразу же в голову приходят.
Первая проблема, да, неявность, и ты обязан все обмазывать, либо если ты принимаешь вот эти чект-подходы джабы, тогда у тебя... расползается, инфицируется весь код этими exception-спецификаторами. И та же самая проблема специфична также для вот этого монотического подхода. тоже у тебя инфицируется весь код вот этим вот error-type, и ты с ним что-то должен делать. Это первая проблема. Вторая проблема — это exception, это также...
Метод воздействия на control flow, он тебя меняет control flow. И как бы это очень немножко неожиданно может, неожиданное последствие может приводить. особенно если программист очень уперт и хочет это абьюзить, потому что, окей, у тебя есть функция, которая возвращает какой-то результат, допустим, булевый, произвел какую-то валидацию.
и ты на основе true или false делаешь одну ветку или другую ветку, и у тебя нормальный control flow. Но у тебя есть еще один специальный случай, если эта функция кинет исключение, у тебя будет третья ветка control flow. И что ты будешь там делать?
Это очень неожиданно, неудобно, плохо композится иногда даже. Кстати, да, про композабилити с исключениями тоже сложно. У тебя начинается проблема с тем, что ты должен... объявлять, ну, в статически типизированных языках ты должен объявлять переменные вне блока try, там дальше инициализировать, потом как-то их инициализировать, потому что expression-driven подход...
он для старых языков типа Java, C++, C, он был незнаком, и ты не можешь нормально произвести инициализацию переменных одновременно с обработкой исключений. Следующая проблема, это уже четвертая получается, это кленап ресурсов. Это, по-моему, самая серьезная проблема. Она справедлива и для Java, наверное, даже особенно для Java, Python и других garbage collected языков.
Ты не можешь просто довериться garbage-коллектору, что он очистит, удалит объект. Тебе нужно еще какую-то кастомную специальную логику вызвать, если тебе нужно закрыть файл, допустим. Флаж файла, да, произвести. или закрыть connection в базе данных, или еще что-нибудь. Ты не можешь доверить это дело garbage collector, потому что он просто уничтожит объект, но не вызовет этот close connection, флаж и прочее.
и у тебя начинаются всякие проблемы. Ты либо пишешь какую-то дичайшую лапшу из нескольких вложенных трайкетчей, если у тебя несколько таких переменных, либо у тебя специально придуманный синтактический сахар для этой лапши, типа трайвизрисов. Сейчас какие-нибудь Try Catch Finally, всякие такие штуки. Это так. Try Catch Finally также для этого любят.
и практикуют, а что делать, если у тебя, смотри, у тебя, допустим, два файла, ты один открыл успешно, второй открыл неуспешно, а что ты будешь писать в файл или блоке на их закрытие? Тебе нужно попробовать закрыть один, он кинет исключение. а потом ты второй не закрыл, что-то чудовищное начинается.
Да, можно заплакать просто и закрыть программу. Да. Спасение от этого какое-то очень странное есть в виде подхода ресурса Accusation is Initialization, который вот... C++, а также как, ну, раз не любят это слово RAI, потому что чудовищная аббревиатура, как там Строуструб сказал, простите меня за то, что я придумал эту аббревиатуру.
Она чудовищная. Там говорят, что это детерминированное освобождение ресурса. Вот так. Такое абстрактное, пространное словосочетание. Это конструкторы и деструкторы, когда в конструкторах у тебя ресурс. как-то выделяется, а в деструкторах, который всегда будет вызван статически детерминированным образом, в каком месте программы, будет вызван твой деструктор, в котором ты клинапишь ресурс.
И с этим подходом в рамках исключений, конечно же, тоже есть проблемы. То есть, хорошо, мы говорим, что у нас РАИ, у нас... файл — это вот такой RAI-based объект, который в деструкторе сам себя закроет, вызовет флаж и прочее, прочее. А что, если в деструкторе будет исключение? Нужно пойти еще глубже. Да. Я посмотрел, как в разных языках состоит дело с повторным исключением в Destructor и в Final и в прочих местах.
С garbage collected языками, с managed языками, там все очень просто. Если в блоке Final вылетает исключение, ну, оно просто... перетрёт предыдущее исключение или как-нибудь обернёт предыдущее исключение и ничего страшного потому что исключение это тоже reference-counted объект мы там сбросим на него ссылку Garbage Collector его когда-нибудь почистит. Если исключение — это какой-то объект сумасшедший, у которого нужно вызывать метод Flash, который запишет что-то на диск в конце...
Я не знаю, кто-то так делает, наверное. В общем, если для уничтожения исключения должна какая-то специальная логика, ну, ты явно что-то делаешь неправильно. У тебя что-то не так. Поэтому в Python, в C Sharp, в Java нет никаких проблем с тем, что у тебя происходит повторное исключение. Ну, произошло и произошло. C++, а также Rust. В Rust нет исключений. Есть исключения в Rust.
Есть. И не только в России. И в ГО есть исключение. Только они называются по-другому. Паники. Механизм тот же самый. Просто название другое? Просто название другое. Подход такой, что паника... это что-то совершенно исключительное, ну, как исключение, совершенно обнормальная ситуация. Ее не надо обрабатывать в нормальном коде. Она должна приводить к аварийному завершению.
Но, конечно же, вы можете ее обработать. И тогда у вас получается фактически обработка исключений, только она называется по-другому. А механизм тот же самый, происходит паника, у вас раскрутка стека, ссылки уничтожаются, стека очищается, ну, как обычно. Тот же самый механизм. Вот, Rusty, C++, не знаю других особо языков, которые очень сильно практикуют РАИ, но наверняка их очень много любительских, прям в мейнстримах вот два, в них есть проблема. Окей. Как происходит исключение?
И происходит исключение. Мы лоцируем объект в куче. Один. Один объект в куче. Начинаем раскручивать стек. Начинаем раскручивать стек в поисках exception-хендлера. Хорошо, мы нашли. Крутим. Начинаем вызывать деструкторы. Вызывается деструктор, он кидает исключение. А у нас как бы одно. У нас один слот под исключение.
Что делать? Ну, если там есть try-catch-блок, то будет эллицирован еще один слот, и будем искать новый обработчик исключения. Если мы его найдем, ну окей, все нормально. То есть если исключение не вылетит за деструктор, все файн, отлично. А если оно вылетело, что делать с вот этим текущим исключением? Его нужно делоцировать, правильно? Хорошо, мы его делоцируем, и мы вызываем деструктор у него. А что, если этот деструктор тоже кинет исключение?
Ведь C++ exception-то может его деструктор делать тоже что угодно, но тоже может кинуть исключение. А у тебя опять один слот. Что делать? Никто не знает, поэтому terminate. Вызываем стадо терминейт и заканчиваем на этом. Не кидайте исключения, когда у вас идет процесс раскрутки стека. Еще один. После этого эпизода нам нужно поставить к выпуску
аннотацию, там, не рекомендовано до 18 лет, все такое, чтобы люди не впали в экзистенциальный кризис. Потому что, ну, по-хорошему, про это все же нужно думать. Да, к сожалению, про это все нужно думать, и... В расти с паниками то же самое. У тебя начинается паника, раскрутка стека, вызываются деструкторы. Если деструктор опять паникует, беда. Что делать с этой паникой? Terminate.
Но мы тут как бы много поднабросили на механизмы исключений, какие с ними есть сложности, но я бы хотел еще все-таки пару слов сказать про... преимущество, потому что как раз сегодня тоже в одном из твитов, короче, в твиттере в одном месте состоялась очень короткая дискуссия про checked и unchecked exception и все, что с ними связано, что, ну... Хотя бы эксепшены дают возможность в языках.
с сильными строгими системой типов, эту систему типов применять в том числе и к ошибкам. То есть ты не просто обрабатываешь абсолютно непонятную, непонятно откуда взявшуюся хрень, ты... Если вся иерархия исключений у тебя написана правильно и использование их написано правильно, ты же как бы знаешь, чего ждать и, соответственно, можешь каждый-каждый кейсик обработать. Да.
Соответственно, как это обычно там. If IOException, то там можно сделать retry HTTP запросы, допустим, если IOException, можно посмотреть, какой там был... код исключения. Если там какой-нибудь другой exception, то что-нибудь еще другое сделать. А если runtime exception, то printlm, все плохо, return. Да. Но ведь ты можешь это сделать и не только с исключениями. Ты можешь это сделать и с error-кодами. Ты можешь это также сделать с вот этим вот результатом замонадическим. И, кстати...
Если мы можем начать с error-кода. У нас есть замечательный язык ZIG. В нем как бы result, но там error-коды. Там функция возвращает либо error-код, либо result. Причем... Там этот странный синтаксис, тип ошибки, восклицательный знак, результат, либо наоборот, я не помню. Причем... В чем проблема с error-кодами? У вас есть разные функции, которые делают совершенно разные вещи. Скажем, одна функция делает чтение из файла, вторая пишет в базу данных.
прочитать, неважно. У них ошибки могут быть совершенно разные. У первой может быть файл не найден, у второй может быть connection closed, cannot connect и так далее. И... Мы хотим их объединить в один большой error-код. Что нам делать? А если самая большая проблема может начаться с тем, что они предоставляются разными библиотеками, и там числовые значения одинаковые для разных ошибок, как нам их объединить?
Ну, можно там добавить какой-то оффсет. Значит, если упало это, мы, значит, возвращаем X плюс error-код вот этот. Очень удобно, здорово, можно. Но руками нужно делать. Зиги это сделали автоматически. То есть если у вас функция, которая возвращает ошибку, причем не обязательно самому указывать, что...
там какой-то error-код вот именно такой возвращается. Ты можешь просто написать восклицательный знак result, а какие error-коды... Компилятор сам выведет. Он же видит, какие функции ты вызываешь. И он все эти error-коды... в один большой рар-код соберет. Сам оффсеты применит, какие надо, и в один большой интеджер запихнет. А на верхнем уровне, где ты хочешь уже начать обрабатывать ошибки, ты просто пишешь свой свитч.
кейс, и поскольку он в зиге exhaustive, то есть он тебя заставляет перечислить все варианты, ну ты их все перечисляешь. Блин, прикольно. Это получается не на уровне типа, но, по сути, механизм тот же самый. Ну, он как бы на уровне типа, но может быть таким не очень инвазивным. То есть ты можешь везде писать просто восклицательный знак «Твой результат». А error code, ну, он агрегируется со всего многообразия, которое у тебя там есть. Блин, прикольно.
Прикольно. Но это мы уже пошли в сторону обработки, на самом деле. Да, можем вернуться дальше. Да, потому что после эксепшенов же у нас самые как бы... фэнсишменси модные подходы. Любители функционального программирования сейчас, наверное, в голове. У них зазвичали слова монады и всякое тому подобное. Короче, если не эксепшены, то что? Если не эксепшены, то сделаем свои кустарные эксепшены. Будем возвращать результат. Изобретем монаду.
Razer, Monado Result, Monado Exception, как угодно вы ее назовите. Иногда это просто Monado Maybe, потому что None... Тоже может сигнализировать, что у нас произошла ошибка. Нет результата, значит ошибка. И для них у нас... Синтаксический сахар часто поставляется либо в виде дунотации, либо в виде всяких вопросиков, как в Rust, либо в виде всяких ключевых слов try, как в ZIG.
Либо нет никакого синтаксического сахара, вы его сами себе придумываете, например, макросами в C и C++. В Kotlin что-то можно... Если не ошибаюсь, можно тоже вопросик писать. Там Elvis operator, да, такой? Красивый есть для этого. И вопросик-точка, который можно эксплуатить для этого. Что еще у нас есть внезапно? JavaScript. Там как бы нету резалта. Но там есть промиссы. А промисс может хранить в себе либо result значение, либо rejected исключение. И вот...
как раз-таки, наконец-таки, должно выстрелить то ружье, о котором говорил заранее, в самом начале, про наш продукт, что Киев или Стор, и как он в рамках обработки исключений по себе проявит. У нас был большой такой... Большое обсуждение внутри команды, какой API предоставить кастомерам для доступа к K-Value Store и с функцией. Предоставить его синхронным, без промисов, либо предоставить его...
с промисами. Не важно, что он может быть сейчас и не асинхронным, когда-нибудь он будет асинхронным, но... Читающий между строк может понять, что если у функции лимит на исполнение меньше миллисекунды, там, наверное, точно никакого Network Call нету. Поэтому он может быть синхронным.
И была дискуссия за и против. Одна из мотиваций за то, чтобы предоставить промисы, потому что промисы можно использовать для удобной обработки ошибок. Потому что можно писать вот этот вот... kvs.get ключ, дальше zen, если ключ есть, catch, если ключа нету. Очень удобно, очень красиво. Не нужно писать вот этот вот try-catch-блок, что-то там разворачивать, и все здорово. При этом, если у нас все в кромисах построено, и мы пишем await,
и вызываем функцию, которая возвращает promise, у нас этот promise будет развернут, и если там exception, то будет выброшен. То есть у нас как бы вот эта вот прекрасная, красивая донотация, которая... проброс ошибки делает за нас, она работает, и в этом случае у нас это ключевое слово wait. Но в языках, где нет этого синтактического сахара, это обычно боль, да. Обычно боль, начиная с этими результатами.
И даже в Rust, в котором оно есть, я очень часто вижу у новичков, что они начинают, значит, вызывают какую-то функцию. Навернула result, дальше они пишут if result is ok. Дальше начинается ZenBranchResultUnwrap. И вот начинают писать такое вот безобразие. И все плюс-плюс так делают. Но как бы идиоматически корректно должно быть вызов всяких функций. методов nzen, map, mapR и прочей красоты функциональной для манипуляции этим результатом. Ну, либо вопросик.
Смотри, мы сейчас прошлись по самым разным способам. уведомления, сообщения о том, что есть ошибка, о том, какие механизмы для этого существуют. Кажется, можно двигаться к тому, чтобы поговорить про то, а как с этим правильно работать, как эти ошибки нужно обрабатывать. Мы, на самом деле, по ходу дела уже много обсудили. Но я бы сначала сделал вот что.
обсуждали все очень, на самом деле, так, как бы сказать, непредвзято, отстраненно, академично рассмотрели просто разные подходы, где и как они существуют. Теперь я хочу спросить тебя, твое личное мнение. А как ты думаешь, какой из вариантов, которые мы перечислили, может быть, не перечислили, но он к какой-то из категорий относится, какой вот сейчас самый крутой, самый удобный, самый классный? И почему?
Самый крутой и самый классный – это тот, который тебя не сильно раздражает и при этом позволяет тебе решать задачу эффективно и продуктивно. И тут есть... Два момента, как вот как раз-таки с checked exception в Java. Вот эти результаты, их специфические ошибки, они очень инвазивные. Тебе их нужно везде опять указывать, специфицировать. Они вот страдают от той же самой проблем, как чекты и эксепшн. Тебе нужно либо везде их просовывать, либо обрабатывать их.
либо запаковывать во что-то более generic exception и дальше радоваться. Поэтому если пользоваться результатами, есть совет такой мейнстримовый, применимый. наверное, ко всем языкам, которые практикуют эти результаты. Если вы пишете библиотеку, окей, вы должны делать вот эти подробные, красивые типы ошибок, которые потом... Кто-то будет страдать и обрабатывать. А если вы пишете аппликейшн конкретный, тогда просто используйте generic error tip.
В Rust это Anyhow Result, Anyhow Error, самый такой популярный сейчас. До него еще какие-то были типы ошибок проспоставляемые. create chain error. Но что-то как-то они сейчас отмерли. Сейчас anyhow. А пофиг, какая у нас ошибка. Да, поэтому ты просто везде пишешь, что у тебя вот этот результат с дженерик ошибкой. который кастится вообще все подряд. И не паришься. И тебе хорошо. А потом видим в мобильных приложениях наверху алерт такой, что-то пошло не так, и там ок, или...
Хорошо. Да-да-да. Вот с исключениями примерно то же самое. Ты либо пишешь и не думаешь о них, и у тебя там в конце просто кетч все, или вообще ничего не кетч. Либо специфицируешь все подряд.
у тебя checked exception, и ты замучаешься их все проверять постоянно. Вот когда я пишу библиотеку, я тогда радуюсь и... страдаю от того, что у меня все явно, перечисляю явный тип ошибок, явно их собираю вместе в больший тип ошибок, в котором один вариант — это ошибка ввода-вывода, второй вариант — это ошибка валидации, третий вариант — это еще какая-нибудь...
фигня. И вот это так все наслаивается, наслаивается, наслаивается. Есть всякие инициативы и библиотеки, которые позволяют немножко автоматизировать вот эту работу, типа собирать эти эротипы в один больший. автоматически каким-нибудь макросом, а не в руками. Ну, да. А когда пишешь application? Anyhow. Ну, либо просто исключениями пользуешься, если у тебя язык позволяет. Ну, раз не позволяет.
Панику использовать категорически не рекомендуется, поскольку паникать слишком долго. Слишком долго, слишком неудобно, и вообще говоря, она может просто завершаться абортом. И на этапе компиляции это регулируется, что ее можно не поймать. А паника, ты говоришь, слишком долго в плане оверхеда на... Перформанс какой-то? Да, вообще я не смогу сказать прям про большой или маленький оверхед в Java, Python, Go. В Go Panic там тоже есть, они...
работать примерно так же, как исключение в Java и в Python. Потому что Go garbage collected based язык. Отключаем, но все равно. Там вроде как оверхед минимальный. Я не мерил, я читал только. Оверхед там минимальный. Потому что как бы ожидается, что это нормальная ситуация. Может быть, паника в ГО не очень, но исключение в Java и Python нормальной ситуации. А вот C++ Rust, поскольку там...
включается раскрутка стека и вызовы всех деструкторов, а также поиск, где там этот exception handler у нас установлен. Это очень долго, оно до... в 50 раз медленнее, чем вернуть error code или вернуть этот самый результат. Поэтому в performance critical случаях используют либо вот эти результаты,
Даже C++, несмотря на то, что они уродливые, получается, у всех. Даже те, которые стандартизировали, тоже уродливые. Либо RR-коды. Причем, если настолько перформанс-критиков, что даже оверхед от этого...
вариант с результатом тоже критичен, то error code, даже глобальная переменная иногда. В итоге, получается, если там прям совсем как-то общего пытаться из-за одного сделать отсылочку к нашей следующей части, то... какой был бы идеальный иметь способ, собственно, отправки сообщения, вот упирается в то, а как ты вообще обрабатывать будешь его?
То есть, опять же, если, ну, как вот ты приводил пример, если тебе вот в клиентском коде обрабатывать не надо, то, наоборот, вот эти вот все чекты истории и вот эти все дополнительные проверки, они будут в конечном счете только мешать и раздражать. И, наоборот, для библиотечки, получается, все упирается в то, как мы будем обрабатывать ошибки. Жень, идеально подвел к следующей части? Вообще великолепно. Можно даже немножко добавить, что не только то, как мы будем обрабатывать, а еще то...
Насколько там критична перформанс, и что-то у меня из головы вылетело, к сожалению. Отказоустойчивость. Отказоустойчивость – это хорошо. Подробность – это вот тоже здорово. Нет, что-то из головы вылетело. Только что помнил, но забыл. Ну ничего, нам еще писаться. Может быть, вспомнишь. Все, вспомнил, только что вспомнил. А также в то, является ли твоя ошибка прям-таки ожидаемым событием или это прям exceptional case. То есть если у тебя библиотека, которая файлы читает,
то это нормальное поведение, что файл не найден, ошибка и прочее, прочее. Если у тебя библиотека, которая молотит числа, и у тебя происходит страшное переполнение...
Это unexpected ситуация, и у тебя тогда допустимо использовать какие-нибудь исключения, или сигнал кинуть, или вообще просто умереть. Я тут вспомнил сразу пример, опять же, из мира Java, где тоже... решили как-то эту грань провести, потому что у тебя есть понятные exception-ы, есть там exception-ы как-то плюс-минус типизированные, которые ты можешь...
понять, что произошло условно, там, моё exception, legal argument exception и так далее. Ну, понятно, то есть у них есть какое-то говорящее слово. А есть эроры, которые прямо эроры, которые не exception. И это значит, что что-то пошло совсем не так, и, скорее всего, это напрямую... и не связаны с кодом, который исполнялся. Условно, out of memory error, как бы он может стрельнуть в любой момент времени, в любом месте твоего кода, и ничего ты с ним не сделаешь. Это да.
Ну вот как раз таки тоже разница между эксепшенами и паниками, что эксепшены ты как бы ожидаешь в нормальных языках, не в C++. А вот паник это что-то такое неожиданное, что чего не надо обрабатывать, точно не надо. по крайней мере, нормальным людям, ненормальным людям, хардкорщикам, которые на очень низком уровне работают, там им надо. И вот, собственно, Rust в ядро Linux.
очень долго проталкивали, и такое основное сопротивление было в том, что вы паникуете в непонятных местах, вы этот самый бедолок не обрабатываете, паникуете, терминейт делаете на нем. Как вас можно в ядро пускать? Нельзя такое. Так, вот теперь, я думаю, мы можем отмотать время на 3 минуты назад. Вспомнить, как Стас очень хорошо нас подвел к тому, что про выдавление об ошибках мы поговорили.
Теперь время поговорить про их обработку. Мы на самом деле уже много чего здесь обсудили. Но давай попробуем как-то все-таки систематизировать по порядку. Ну вот, случилась ошибка. Что мы дальше можем? должны сделать. Если случилась ошибка, у нас есть три варианта. Вариант номер один. Ничего не делать. Это вариант такой. Он, в принципе, применим на практике иногда. Например, у тебя код, который удаляет файл. Ну, если не удалось удалить файл, ну и ладно.
Чего обрабатывать ошибку? И так, что такое часто встречается? BackgroundSync отвалился по таймауту, кажется. Ну, ладно, попробуем через какое-то время еще. Почему бы и нет? Второй вариант. Ты не можешь обработать ошибку. но ты не можешь продолжить работу. Ты должен прокинуть эту ошибку дальше, например. И вот в случае исключений тебе ничего не нужно делать, оно само делается. Третий вариант. Ты можешь обработать ошибку.
И ты пишешь свой код, который ее как-то... Что-то с ней делает. Например, ты делаешь retry. Например, ты обращаешься к другому endpoint, чтобы что-то узнать. Либо, как мы в самом начале даже... обсудили, для тебя ошибка может быть нормальной ситуацией. Тогда ты на основе этой ошибки что-то делаешь. То есть, скажем, если у тебя пришла ошибка, она вторая с таксес по какой-то причине. Ты идешь и генерируешь респонс-бадди, которым пишешь, а на вторая есть аксесс и отправляешь. Негодяй.
Я не знаю, это сюда-не сюда вкладывается сценарий, когда мы такое делаем фейловер, когда у нас, например, я не знаю, мы попробовали отправить пуш, пуш, видим то, что он там не доставился, дальше потом, я не знаю, смс, потом e-mail, там потом... звонок на телефон. Тоже такое кажется, что это не совсем прям мы ретраем, но по сути у нас есть какая-то готовая схема, по которой мы просто движемся один за одним. Пытаемся в конечном счете выполнить задачи. Да, если мы дошли до момента.
где ошибка уже стала ожидаемой, и здесь мы как бы можем ее обработать. Для нас это нормальная ситуация, мы ее обрабатываем. Если ошибка необрабатываемая, она какая-то критическая, типа вот той паники, Ну... умри, поток, умри, программа, этот самый, crash-driven development, все нормально. Как с самого начала начали говорить про игнор, если честно, я прям сразу вспомнил все страшные истории, которые я видел.
своем опыте. Мы это называли паттерн-тихушник, что ли, когда try catch, а в блоке catch у тебя блок с комментарием, в котором написано, да хз, что здесь делать. ничего не сделаешь. Ошибка произошла. Максимум println или system.out.println или console.log, смотря кто на чем пишет. Сейчас уже так, конечно, не пройдет. Ну, и пройдет где-нибудь в другом месте.
Ну, в смысле пройдет, но как потом коллегам в глаза смотреть? Это да, это да. Иногда, кстати говоря, такой паттерн может очень сильно навредить в плане обсервабилити. твоей системы. Есть такой забавный пример. Он из мира C++, к сожалению, тоже. Если обернуть весь мейн в тройкетч, да? Ну, тройкетч, который ловит все.
у вас будет установлен самый-самый топ-левел exception handler. И он всегда будет найден. Поэтому, когда пойдет раскрутка стека, оно всегда докрутится сюда. А если мы хотим... если у нас произошла какая-то критическая ситуация, если мы хотим получить хороший stack trace в core dump, чтобы показал нам, где конкретно у нас произошла ошибка.
Вот эта раскрутка нам мешает. Она раскрутит весь стэк. У нас в конце, когда программа выйдет и упадет, возможно, у нас будет стэк, который состоит из одного мейн. Поэтому если мы не поставим такой трайкетч, будет лучше. Потому что мы упадем вот в том месте, где у нас началась раскрутка стека. Да, вот забавно. Поэтому иногда вот не обрабатывать ошибку совсем, это во, зашибись. Это намного лучше, чем обработать.
Ой, ну я даже не знаю, про обработку еще есть что-нибудь интересное, что мы еще не обсудили? Мне кажется, так или иначе у нас уже примерно все, что может прийти в голову, и всякие разные трайкетчи. И там даже про ситуации, когда, прости господи, тебе API возвращает 200-ку с ROR внутри. Что тут даже обсуждать-то в конце концов? Ну, API, возвращающий 200-ку с ROR, это...
как бы это неудобно. Я понимаю тех людей, которые говорят, что это плохо, это неудобно, потому что программисты, не только программисты, просто девелопер, абстрактные, существо ленивое. Ему удобно, что если пришла ошибка, значит ошибка. А тут как бы окей, а внутри еще что-то. Если мы посмотрим это так формально со стороны, как такой API абстрактный.
типизированная его сигнатура, то он возвращает, получается, result-result. Это очень неудобно, это не любят люди. Они постоянно делают ошибки с этим. Я видел в... Rusty есть в стандартной его библиотеке как раз-таки функции для работы с файловой системой. Поскольку на каждом этапе у тебя может произойти ошибка, функция, которая итерируется по каталогу, она возвращает результат резалтов.
И я видел страшную ошибку, которую допустили. Написали, значит, read directory, а дальше точка flatten, а дальше точка count. И думали, что посчитают количество файлов. В итоге всегда получалось 0 либо 1. Я тут хочу немножко порассуждать про это в защиту вот этого способа и как раз возвращать к истории про то, как ты будешь обрабатывать. в некоторых случаях не очень удобно брать и замешивать вместе какие-то ошибки в некотором смысле бизнес-логики.
замешивать их вместе с ошибками, которые условно сетевого уровня. То есть у тебя в конечном счете твой клиент, HTTP клиент, на уровне клиента, он когда будет это обрабатывать, если он... все вместе замешает, и условно там тайм-аут, и вот ошибку то, что типа, чувак, давай ты.
будешь имейл мне присылать в поле для имейл, например, или что-то вот в таком духе, или там, не знаю, какую-то еще проверку, которую мы делаем именно на стороне бэкэнда. Вот. Если это все вместе замешивать, то это потом становится не очень удобно обрабатывать. Здесь, конечно,
тоже можно, в свою очередь, поспорить, насколько это вообще хороший стиль. Ну, типа, все честно обрабатывай, нечего лениться и, в общем, разбирайся, в общем, по-настоящему в том, что идет не так. Но в каких-то случаях это действительно удобно. То есть ты берешь и видишь там... ошибки уровня
сети, ты в тупу их обрабатываешь, в духе пишешь человеку сообщение, типа попробуй еще раз, он такой, ну хорошо, раз попробуй еще раз, нажимает еще раз ту же самую кнопку и смотрит, как все замечательно, еще раз пытается отправиться, и возможно будет все хорошо.
А вот те остальные ошибки, их уже приходится в любом случае обрабатывать более интеллектуально в зависимости от контекста, от конкретной формочки, экранчика и так далее. И как будто бы вот здесь вот такой водораздел, поэтому мне кажется, что изначально... вот эта вот идея, она вот как раз вот оттуда пошла. Это правда. Она уперлась в то, как мы хотим обрабатывать, как нам удобно обрабатывать. Это правда. Это правда. У нас как бы все завязано то, к кому...
что удобнее. То есть, если формально подходить, разделить транспортную ошибку и ошибку бизнес-логики, это как бы правильно. Но... Мне, как разработчику, который пишет MVP, который нужно будет релизить завтра и показывать инвесторам, мне хочется один результат, а не результат. Так какая тебе разница, если ты все равно обрабатывать не будешь эту ошибку? Это да. Я обрабатывать ошибку не буду, но я ожидаю, если там окей, то значит я покажу сразу, и все. А там не окей. Там еще один результат.
Я должен еще глубже пойти. Вообще, мне кажется, в этом есть некоторая проблема самого по себе даже HTTP стандарта. Потому что коды 400-ки, они такие, что некоторые из них выглядят как чисто... как это сказать, ну, я не знаю, правильно будет сказать или нет, инфраструктурного, короче, уровня, когда тебе сервер говорит, у тебя payload слишком большой. И ты такой, блин, ну, come on, payload — это что-то, о чем я, конечно же, не думаю, когда собираю свою бизнес-логику.
И тут же у тебя есть ошибка, что ты не авторизован. Это ладно, еще можно притянуть за уши, ты там какой-нибудь хедер не передал. Но не знаю, есть код 402 payment required. Написано, что он не используется. Но все равно он как бы намекает, что ты пытаешься воспользоваться чем-то, за что ты не заплатил.
Это очень странно. Или там 404, но это абсолютно валидный кейс, который можно положить на бизнес-логику, потому что ты пытаешься дотянуться до ресурса, которого нет, и ты такой, сорян, такой странички нет, или такого товара нет, или чего-то еще. Короче, мне кажется, это они вот напутали все в разных... Не напутали, точнее, они попытались предусмотреть какие-то кейсы, и в итоге ты смотришь и пытаешься понять.
А вот коды ошибок HTTP, это все-таки про мою бизнес-логику, и мне норм вернуть 400-ку как признак того, что пользователь... ничего не сломалось, он просто делает что-то не совсем правильно. Или это правда проблемы, которые, там, интернет отвалился, и тоже надо показать. Короче, вот, я бы на них всю вину свалил, и ответственность за это. Ну, да, в рамках HTTP, да, надо все сваливать на них. виноваты, а в рамках API, который у вас внутри вашей системы.
не касаясь network взаимодействия, ну, как бы отлично иметь result-result. Если у вас статически типизированный язык, если у вас... JavaScript, в котором промес-промеса не бывает, то у вас такого и не бывает никогда. Я с JavaScript познакомился глубоко только за последние два года. Был очень сильно удивлен, что ты не можешь создать промес, который возвращает промес.
Они схлопываются. А что если я хочу сделать, использовать промис как резалту, как мы как раз таки сделали? Он же схлопнется. Неплохо, неплохо. Окей, смотрите, если есть еще что нам обсудить про обработку, можем еще обсудить. Если нет, я бы двинул дальше. И мне кажется, отличный... закрывающий блок был бы поговорить про то, а как со всем этим делом жить и, может быть, попробовать выработать какой-то пул рекомендаций.
как вообще подходить к разработке, к программированию и, в частности, к обработке ошибок, так, чтобы не было потом мучительно больно расставлять везде трайкетчи и вот это вот все. Короче, есть ли какие-то подходы к...
не знаю, мышлению, майнсет какой-то, чтобы обработка ошибок уже сразу была у тебя, ну, заложена в твой мыслительный процесс. Да, это хорошая тема. В принципе, даже есть книжки по так называемому defensive который как раз таки предполагает, что ты начинаешь писать свое приложение, не обязательно приложение, библиотеку, код, маленький компонентик, сразу же в предположении, что у тебя будут ошибки.
и ты сразу же начинаешь их обрабатывать. Б. Ты сразу же предполагаешь в том, что твой код будет майнтейниться и расширяться, и как его сделать защищенным от дурака-майнтейнера. Вот это второе иногда обходится стороной, не включается в этот defensive programming, но вообще-то тоже есть такое. И тут все зависит от нескольких факторов. Первый фактор — это, собственно, что вы используете, какой тулинг.
И второе, что вы можете еще с этим тулингом сделать. Потому что сейчас такой тренд, что новые языки программирования пытаются учесть ошибки предков. и сделать что-то более явным, избегать вот этих undefined functions, которые происходят непонятно где, предотвращают ошибки самые популярные, типа разыменования.
нулевых указателей, null pointer exception избегают. Kotlin известен тем, что он сразу же задумался с тем, что надо победить null pointer exception. Поэтому у нас будет null safety, и ты не можешь... обратиться к объекту до того, как ты проверишь его на null. А когда ты проверил, у тебя уже внутри if все отлично будет. Ну вот как-то так оно у нас сейчас происходит. Языки...
статические могут себе очень хорошо это позволить, потому что ты на этапе компиляции можешь вынести все больше и больше вещей. Ты его, конечно, во-первых, усложняешь, удлиняешь этот этап компиляции. Во-вторых, ты усложняешь... сам процесс работы с этим языком программирования, потому что чем больше ты хочешь вынести проверок на этап компиляции, на...
систему типов, тем более сложная и богатая система типов тебе нужна, и тем более сложно этим пользоваться. Я вот смотрел и упарывался немножко по всяким языкам с зависимыми типами, которые тебе могут позволить выразить проверки обращения по невалидному или валидному индексу в массиве на этапе компиляции. Вот просто на этапе компиляции ты ловишь ошибку обращения out of bound. Замечательно. Но это настолько сложно становится.
что это просто практически не очень применимо на практике для того, чтобы именно писать быстро код, чтобы писать надежный код для верификации. каких-то систем, верификации компиляторов. Да, это отлично, здорово. Доказательство терем, так что из этого всего можно строить. А вот на практике, для практической применимости, чтобы HTML-страничку сверстать и выплюнуть, это как-то увертил и очень тяжело идет.
Динамически всякими типизированными языками, скриптовыми языками. Там линтеры помогают иногда. Можно статический анализатор дополнительно прикрутить. Но чтобы... Если брать, например, питон какой-нибудь, чтобы у тебя все завертелось отлично с статическим анализом, ты должен использовать аннотации типов. Иначе как бы что-то будет, конечно, поймано, но...
Большая часть не будет. А это требует какой-то определенной культуры, что должен писать код, указывая явно типы по возможности. И это требует, конечно, перестройки, а перестройки майнсета, а времени и затрат. Хотя сейчас с развитием популярности чатов GPT всяких, кода генерации к опайлотам, возможно, мы все перейдем на TypeScript, на Python с аннотациями типов, и все будет замечательно.
Вот по умолчанию код будет именно таким. Но кто знает. И ошибку мы напрямую не нужно будет копировать и вставлять в чат, а напрямую мы через опишку подключаем. отправляем ошибку через опишку в чат GPT, и он дальше уже сам все делает. Ну, это просто будущее какое-то будет. Не факт, что оно будет именно таким, не факт, что чат GPT не будет галлюционировать, но...
Но да, в идеале, почему бы и нет. А так, тенденция и запрос на то, чтобы программное обеспечение было с меньшим числом ошибок, оно как бы есть. особенно в сфере вот этих клаудов, потому что наш продукт CloudFront Functions, CloudFront весь, целиком как CDNK, Key Value Store и прочее. Но у нас цена ошибки очень высока. Причем она, конечно, не в человеческих жизнях напрямую. Она в миллиардах денег. Если наш сервис внезапно из-за какой-то дурацкой ошибки будет недоступен, это огромные потери...
не только для Амазона, но и также для кастомеров, которые им пользуются, потому что мы недоступны, кастомеры недоступны, соответственно, они не получают деньги, мы не получаем деньги, все плачут. Ты вслух этого не сказал?
Но как я это услышал? В общем, если сводить все рекомендации, еще не рекомендации, а подход к безопасной работе с ошибками и безопасному программированию, защищенному от ошибок, то это звучит так, что если вы пользуетесь... языком с строгой типизацией и вот этим всем статической, то, ну, короче, смотрите куда-нибудь в сторону более современных языков, которые все это уже учли, и вообще, по возможности, чем больше...
Работа с ошибками выведена на уровень типов и может быть предотвращена в CompileTime и тем лучше. А если мы говорим про языки динамические, то чем больше вокруг них вы навернете себе тулинга, который хотя бы постарается, их может быть не так принудительно, но хоть как-то приблизить к языкам с эстетической типизацией, тем опять же вы больше защищены.
Насколько я правильно это все... Ну, близко, да. Чем больше ты можешь делегировать машине, тем лучше. Чем больше ты можешь положиться на машину, чтобы она... Причем на машину именно детерминированную, не на чат GPT. Чат GPT, ну, это такое себе дело. На детерминированных, что может проверить и доказать, чтобы понятно, как работает, тем лучше. Чем... больше ты можешь заиспользовать возможности этой машины, чтобы она тебя заставила писать код правильно.
тем лучше. Вот все вот эти строгие языки, которые требуют не оставлять неиспользуемые переменные, типа Zik, Go, возможно, еще какие-то есть из мейнстривовых. Все языки, которые требуют... чтобы всякие свитч-кейсы были со всеми перечисленными вариантами, чтобы exhaustive pattern matching был. Pattern matching, в принципе, такая тема скользкая, потому что он иногда и вредит.
Иногда, если он сделан хорошо, как, например, в Rust, потому что там от порядка не зависит, от порядка имен не зависит, ничего. Отличная штука. Когда у нас... Достаточно удобная система типов с достаточно удобными фичами, которые, ну... как бы не сильно надоедливые, не заставляют тебя прям писать очень-очень много бойлер-плейта, но при этом предоставляют возможность избежать каких-то ошибок. Я вот хотел...
немножко поговорить о очень интересный язык, на букву В называется, не Вилэнг, какой-то другой, я забыл. Вейл или что-то типа того? Вейл, вейл, да. Там еще вдобавок ко всей красоте, которая есть сейчас в Расте, добавили линейные типы. И ты можешь сказать... что не просто переменная должна быть использована, она должна быть использована один раз. Если она не будет использована, то все плохо. Ты не можешь сказать просто, ну...
Нижнее подчеркивание и все. И выкинь. Я бы забыл про нее. Нет, ты должен обработать ошибку. Если ты можешь использовать такие фичи, то да, используй. Если они тебе... Стопорят по разработку. Это другой момент. Очень скользкий. Вопрос, что важнее. Зарелизиться сейчас и потом огрести проблем, но заработать прямо сейчас?
Или все-таки зарелизить это попозже, потерять немного денег. А может быть и много денег. Кто знает. Зависит от команды, штата, сколько у вас там затраты. Но сделать все более правильно. и избежать ошибок. Это что можно заиспользовать в плане тулинга и фич языков. А также второй момент. Не все мы можем переложить на автоматику. Мы должны писать тесты. Поэтому...
Пишите еще дополнительно тесты. Считайте coverage. Это, конечно, тоже дурная метрика, но она может показать вам, что вот этот вот код пас у вас не покрыт. Ну покройте его как-нибудь. Может быть, стоит обратить на него внимание, а может быть и нет. То есть это не обязательно хорошо, конечно, целиться на 99% кавериджа, потому что можно его достичь очень дурным способом нагенерировать кучу ненужного кода и его покрыть.
что-то критически важное забыть. Но все равно как намерение хорошее тоже для предотвращения ошибок. И очень часто, когда мы пишем код, мы покрываем хэппи-пасс без ошибок, который... А вот всякие утилиты, которые посчитают каверидж, они очень хорошо могут подсветить, что ваш error pass не покрыт. Совершенно не покрыт. Да, оптимизм в этих вопросах, по-моему, еще никому никогда не помогал.
Потому что надеяться, что ошибка, которую вы решили обработать когда-нибудь потом, не случится. Ну, просто значит, что потом придется обрабатывать и делать багфикс с горящей задницей, когда все уже случилось в продакшене. У меня была такая ситуация. Причем я написал, значит, assert. Есть такой тоже.
холиварная тема, что, значит, если у нас какая-то логическая странная ошибка, давайте не кидать исключения, давайте ее ассертить в дебаге, и у нас как бы все должно быть хорошо. Если тесты все покрывают, а нас ассерты все проходят, все здорово. Я написал assert. На случай, которого, как я предполагал, не должно быть. Не должно быть никогда. Никогда не может прийти version number, который меньше, чем...
Тот, который у нас сейчас есть, потому что мы как бы гарантировали, что у нас только инкрементируется счетчик, все будет здорово. Написал assert, тест у меня все работает, прошли, релизим, я ушел в отпуск. Мне пишут Асерт упал. И сервис упал полностью. Поскольку Асерт панику кидает. Нормально. Да. Надежно. Что могло пойти не так? Ну, быстренько закоммитил один вонлайнер фикса, чтобы этот асерт убрать.
Так, сейчас я пытаюсь художественно вспомнить, ничего ли мы не забыли, чтобы не допустить что? Правильно, чтобы не допустить ошибку. Но мне кажется, мы уже прошлись по... всему, и даже как-то все это дело за финальными рекомендациями. Сейчас я понимаю, что это совершенно странный будет вопрос, но мне просто интересно, а кто-нибудь вообще...
делает какие-то исследования, посвященные обработке ошибок, пишет про это книги или что-нибудь такое. Или это все чисто прикладные знания, которые хранятся в головах. практикующих, специалистов или разработчиков языков. Чисто любопытный вопрос из любопытства. Никакого практического смысла у него нет. Не, ну это хорошее...
Это вопрос. В принципе, в нем есть какой-то практический смысл. В том смысле, что... А где почитать про то, как делать правильно, да? Я... Прям книжек не искал, но я точно видел очень много разных блок-постов и на Хабре. И там на Medium, и еще где-то. В Твиттере сейчас, после того, как Илон Маск разрешил писать сколько угодно синегалочным, он тоже есть.
Да, люди пишут почти постоянно про то, как правильно обрабатывать ошибки. Там есть вот эти методологии с Railway программированием, что у тебя сначала ты обрабатываешь ошибку, и там даже рисуют эти красивые схемы, что... вот у тебя основной путь, и там вот в сезде эти ответвления. Как рельсы поездов. Railway approach. Вот именно так надо. Книжки...
Наверняка тоже есть. Но они, скорее всего, не прям-таки про обработку ошибок, а они про какой-нибудь надежный дизайн и архитектуру. И вот там... потенциально будет глава про обработку ошибок. Ну окей, я примерно это и подозревал, просто любопытно было. Я хотел как раз еще тоже бросить, что у меня какое-то, если говорить применимо конкретным стекам, мне кажется, что во многом то, как правильно правильно обрабатывать ошибки в конкретном языке, во многом продиктовано комьюнити.
Потому что на самом деле глобально тебе никто не мешает взять и прийти и начать, я не знаю, обмазываться эксепшенами в iOS-разработке. На тебя будет странненько смотреть, но ты будешь решать свою задачу. Мне кажется, то, что это можно сделать... достаточно эффективно и начать использовать там exception вместо ошибок. Тут, как бы, да, получается, все в конечном счете уперлось во что? Уперлось именно в то, как принято в комьюнити, какие статьи
статьи были написаны как раз вот на всех ресурсах, и как, собственно, уважаемые люди, что они написали там в своих книжках про error handling. Да. Так что все очень, так сказать, завязано на людей в кабинете. Концептуально можно исключения, теризалты, error-коды использовать где угодно. Даже в C можно изобрести исключения. Там есть SetJump, LongJump. Пожалуйста, на них можно построить исключения. Но я не думаю, что вам это понравится, если вы будете строить на них. Results...
И error-коды точно можно изобрести где угодно. Но опять-таки все, сколько будет упираться в то, насколько это будет удобно пользоваться, потому что, ну... Для резалта нужно, чтобы был красивый синтаксический сахар в виде дунотации или еще чего-нибудь. Потому что иначе лапша получается. Энзен, энзен, энзен, энзен. Кошмар.
Колбаса такая большая. Из последовательности методов. И тут сторонники реактивного программирования на какой-нибудь RxJava такие сейчас скажут, не вижу ничего плохого в последовательности вызовов. And then, and then, and then. Ну, да. Если это просто NZN, значит, еще один NZN, еще так линейно, да? А если они начинаются быть вложенными, вот это уже начинает быть проблема. Это правда. Ну, плюс еще...
Вот многие языки, они впитывают в себя огромное количество фич, которые плохо дружат друг с другом. Пример, конечно, самый всеми любимый язык, у нас такое фло, который. вот этот вот с крабом. У него есть асинхронная часть, есть не асинхронная часть, есть вот эти резалты. Если ты начнешь комбинировать асинхронные вызовы с вот этими комбинаторами NZ, они у вас там не работают.
поэтому он тебя форсит к использованию этого вопросика. Либо, если ты не должен выкидывать ошибку из этой функции, ты должен писать eFails. Вот это вот все красота. Ну, фичи друг с другом. плохо дружат. Возможно, в идеальных функциональных языках программирования они все отлично друг с другом сочетаются, но я смотрел даже в Хаскеле... Что-то не очень хорошо. Монада в монаде дружат. Дербозно. Ужасно. На этой философской ноте, как всегда, серебряной пули у нас нет.
Как вы поняли, друзья, надо смотреть, что работает хорошо и это использовать, а что работает плохо и не использовать. Принимаю благодарности за лучшие советы. На этой ноте предлагаю подводить черту под этим выпуском и в двух словах вспомнить, о чем мы сегодня поговорили. А мы сегодня поговорили про обработку ошибок. И сначала мы начали с того, а какие вообще бывают ошибки, где они могут происходить. потому что...
там аппаратные всякие проблемы, грубо говоря, в железках. Конкретный выход — это тоже может считаться ошибкой, смотря как вы к этому относитесь. И вообще, на самом деле, ошибка — понятие довольно философское, но, к счастью, у нас для этого есть очень много способов.
обставить это в языках программирования, которыми мы с вами пользуемся, и об этом мы поговорили во второй части выпуска, где говорили, собственно, про то, как вообще нам наши программные системы, языки программирования, операционные системы сообщают о том, что произошла ошибка. здесь разнообразие способов, которые мы покрыли, просто какое-то невероятное. Это и начинать с системных прерываний, и через всякие разные коды ошибок, когда вам...
метод возвращает какой-нибудь объектик эрора и заканчивая нашими, лично моими, естественно, любимыми эксепшенами, которые могут вам все сломать, а могут и не сломать, если вы написали trycatch и в catch, естественно, ничего не сделали. Все, как я люблю. И заканчивая, опять же, всякими монадическими штуками, когда вы выводите ваши ошибки на уровень типов и дальше уже с ними работаете, ну, удобно или неудобно, тот уж как создатель языков или фреймворков для вас сделан.
И, собственно, как по пути, так и в конце выпуска мы поговорили про то, а что же делать с этими ошибками, как правильно относиться к их обработке, какие подходы хорошие, какие не очень, где есть свои минусы или плюсы.
Сложностей очень много. В любом языке можно все сломать так, что и до... Кризиса самоопределения недалеко, но мы, к счастью, проговорили, что есть подходы и тулинг, и языки, которыми можно пользоваться и себя хоть как-то защитить от того, чтобы ошибки происходили непредсказуемо и ломали вам всю логику, потому что язык и ваш тулинг вас заставят подумать об этом до того, как ваше приложение вообще запустилось, когда оно там пока еще компилировалось, собиралось, интерпретировалось.
все что угодно, все что у вас в языке происходит, что можно зафорсить проверку на ошибки как можно раньше, до момента, когда стало уже слишком поздно. Вот, в общем, пробежались мы по всем этим вопросам, все неплохо систематизировали. Дим, спасибо большое, что пришел к нам. Мне кажется, мы прям сегодня оправдали нашу репутацию.
такого энциклопедического подкаста, когда нам говорят, что тебе нужно разобраться в теме, слушаешь подлодку, и там тебе прям все подробно расскажут, потому что, мне кажется, сегодня мы... Про ошибки поговорили вообще абсолютно исчерпывающе. Я думаю, этот выпуск можно еще много лет показывать людям, которые хотят разобраться, как вообще с ошибками можно в разных языках программирования работать, как про них можно думать.
У меня есть одно большое добавление. Я просто вспомнил, или даже в ней продолжаю помнить, о том, что я сказал, что у нас есть C++, и там null pointer, dereference, как бы, нельзя поймать. Можно. Но если вы используете Microsoft Visual Studio, там есть специальная конструкция для того, чтобы ловить так называемые структурные исключения.
И вот если вы разименовываете нулевой указатель, возникает структурное исключение, которое можно поймать и обработать. Вот удивительная штука. Меня коллега, которому поручили писать на C++, спросил, а как ловить? Просто он с Java пришел. У нас как бы null pointer exception, да? И все здорово. А тут у него segmentation fault. Есть решение. Мне кажется, это замечательный, на самом деле, пример, потому что он как раз подытоживает...
тему, которая на первый взгляд простая. Ну, типа, что там ошибки? Ну, у тебя программа кинет, ты что-нибудь как-нибудь перехватил, обработал и все. Здесь, мне кажется, твой пример сейчас прям на нескольких уровнях показывает, что... Есть ситуация, которая не является эксепшеном, но является ошибкой, но при этом в рамках одного конкретного вендора есть одна конкретная реализация, которая превращает изначальную ошибку в совершенно другую, и с ней можно...
работать совершенно по-другому и как бы такая простая тема уже внезапно оказывается совершенно непростой круто круто так ну Еще раз спасибо за все замечательные примеры. Мне кажется, мы сегодня сдобрили выпуск просто миллионом разных примеров из разных языков программирования. Я просто искренне восхищаюсь твоей насмотренностью. Это было очень круто. Спасибо, Женя, что позвал. Спасибо, Стас, что поучаствовал и очень интересные вопросы задавал. И Стас.
А можно задать тебе вопрос? Ну, конечно. Что тебе нравится больше, чем знать, что в нашем подкасте ты можешь совершить абсолютно любую ошибку, И наши доблестные монтажеры в любом случае потом все исправят. Это прекрасно. Я тут как-то добавлю, сломаю третью стену и скажу, что... Мы и так общаемся, у нас она и так сломалась. Окей, и короче, я тут посмотрел внезапно. У нас кусочек видео, который я записывал. Квиктайм упал с ошибкой.
Вот так вот, в котором мы записываем видео. Я уже перезапустил. Будем надеяться, что, в общем, все в порядке. там не слишком много потеряется, и все удастся восстановить. Посмотрим. Посмотрим, как монтажеры попробуют обработать вот эту ошибку. Да, а больше этого, дорогие друзья, мне нравится, когда вы ставите 5 звезд в iTunes, и твиттере, и твиттере, рассказывайте о нас своим друзьям, а самое главное...
Слушайте подкаст Подлодка. Мы сегодня говорили про обработку ошибок. С нами был Дима Свиридкин. Всем спасибо и всем пока-пока. Всем пока.