ARK> foreach (var item in _myArray)
ARK> {
ARK> ProcessInternal(item); // throws Exception
ARK> }
ARK> // здесь _myArray оказался в неизвестно каком состоянии
ARK>
То есть имеем плохо написанный код и обижаемся на исключения за то что он по волшебству не стал хорошо написанным? Ну ОК, только как проблему решают коды возврата? Учитывая в Rust анти-паттерн появился try!() — как только его освоят джамшуты — в коде ты увидишь !try(ProcessInternal(item)) и поехали наверх с настолько же неизвестным состоянием. Кстати само появление этой конструкции до того как язык даже вышел — уже должно было заставить авторов сесть за круглый стол и начать осознавать то что они идут куда-то не туда.
Здравствуйте, AlexRK, Вы писали:
ARK>Обычные коды ошибок действительно обладают этим недостатком. Но возврат в стиле Rust — нет.
Чисто для справки — в Java давным давно пытались заставить насильно обрабатывать исключения, привело это только к автоматически генерируемым заглушкам. Сейчас общее мнение в сообществе, что checked exceptions — зло. Не вижу, почему в Rust будет иначе.
Здравствуйте, MTD, Вы писали:
ARK>> if (ProcessInternal(item) != ERR_OK) return -1; // error!
Добавлю, что при этом исключение хоть стек трейс хранит и имеет тип, что существенно упрощает поиск проблемы и исправление ошибки, а с кодами ошибки только хардкор, только дебаг!
ARK>Гарантированная корректность операций с памятью — это "0"? Хм.
Тебе в C++ форуме враз объяснят что обложившись смарт-пойнтерами и используя только подмножество C++ можно добиться результата не хуже. Вот это и есть "0".
ARK>Убить С++ мешает не его "крутизна", а то, что в него вложено огромное количество ресурсов. В обозримой перспективе смерть С++ абсолютно исключена и разговоры об убийстве С++, ИМХО, не от большого ума. Что не отменяет того факта, что С++ — это говно мамонта, перегруженное фичами и отягощенное наследственными болезнями.
По слухам в Кобол и особенно в Аду было вложено больше и при этом до наследственных болезней дело не дошло
ARK>Могу. Главный минус: исключения оставляют программу в несогласованном состоянии, с разрушенными инвариантами, и формально проконтролировать этот момент нельзя. Теоретически могло бы помочь STM, но у него свой неустранимый минус — не работает с IO.
Почитал и ничего с эпохи флеймов C vs C++ нового в тех статьях не увидел. Задача "Recognize that code is как-то written" вообще стоит чаще перед фрилансером или аналитиком конторы когда решается вопрос ввязываться в очередной умирающий проект или нет. Ну может ещё перед программистом на коротком этапе "хочу добавить зависимость, но прежде гляну не поимею ли геморроя".
Аргумент что исключения затормаживают компилятор в пролёте из-за того что компилятора способного соревноваться с С++ сейчас на горизонте не видно, а в обещания что "вот потом в будущем он всех уделает и тогда..." я не верю — они всё равно не взлетают, а в это время стандарты C++ и C# принимаются на ура один за одним. Кроме того я помню как Rust начинался как zero cost, и как потом к этому zero cost добавилась сносочка "бла-бла-бла, мы делаем всё что минимально нужно для выполнения это и есть наш более правильный zero cost" — хи-хи два раза.
Остаётся, видимо, аргумент про корректность состояния программы написанной с использованием исключений. Но коды возврата не делают программу автоматически корректной. Точно также можно забить на код возврата, или тупо пробросить его наверх не вникая в то что повсюду осталось нагажено. И что пайпинг что try! — имхо два пути в никуда. Ну тем кто пропустил 70-е кажется что это путь куда-то, но честное слово, люди туда уже ходили, просто тогда интернета почти не было, все заметки в старых книжках остались.
Имхо выход из этого тупика я вижу только один — автоматические верификаторы кода и язык дружественный статической верификации. По тому же C++ глянув на код можно на глазок оценить — если смарт-пойнтеры и const повсюду — значит есть надежда что код сильно выше среднего по больнице, и скорее всего обработка исключений там тоже будет правильная. Уже сейчас ReSharper очень неплохо подсвечивает и подчёркивает мягко говоря "неудачные" места. Причём иногда настолько хорошо, что просто загружаешь какой-нить проект и просто взглянув (кого там беспокоила проблема recognition?) как методы один за одним подчёркнуты в каждой строчке с собщениями "Possible ... exception" — и сразу видишь — код говно, и исключения его не спасут. Расширить и углубить эту самую верификацию, так что бы понимался всякий pure, const, no throw, guarded и т.п. — и получится годный надёжный код.
ARK>Прочел статью. Не увидел в Nim ничего интересного... Ну обсыпали Ц сахаром слегка, не более того. Не вижу ни цельной концепции, ни каких-то новых фич. Да еще эти отступы.
Решатель для определения разделяемого состояния же. В отдалённой перспективе такая штука может и корректность lock free алгоритмов проверять и много чего ещё. Другое дело что реализация может быть никакая, но сама идея определённо стоящая. Ну и макросы/шаблоны/дженерики — вместо разделения вот так православненько, а вот так — анафема, не поленились и сделали сразу всё.
Здравствуйте, hi_octane, Вы писали:
_>Учитывая в Rust анти-паттерн появился try!() — как только его освоят джамшуты — в коде ты увидишь !try(ProcessInternal(item)) и поехали наверх с настолько же неизвестным состоянием. Кстати само появление этой конструкции до того как язык даже вышел — уже должно было заставить авторов сесть за круглый стол и начать осознавать то что они идут куда-то не туда.
В принципе, согласен с этим. Плюс, который я вижу — таки явный "try!", который можно тупо найти поиском.
Здравствуйте, MTD, Вы писали:
MTD>Чисто для справки — в Java давным давно пытались заставить насильно обрабатывать исключения, привело это только к автоматически генерируемым заглушкам. Сейчас общее мнение в сообществе, что checked exceptions — зло. Не вижу, почему в Rust будет иначе.
ИМХО, checked exceptions требуют таки гораздо большей кучи бойлерплейта. Впрочем — посмотрим.
Здравствуйте, hi_octane, Вы писали:
_>Остаётся, видимо, аргумент про корректность состояния программы написанной с использованием исключений. Но коды возврата не делают программу автоматически корректной. Точно также можно забить на код возврата, или тупо пробросить его наверх не вникая в то что повсюду осталось нагажено. И что пайпинг что try! — имхо два пути в никуда. Ну тем кто пропустил 70-е кажется что это путь куда-то, но честное слово, люди туда уже ходили, просто тогда интернета почти не было, все заметки в старых книжках остались.
Здесь главное отличие — это явно видимый try и невозможность пропустить его. Понятно, что наговнокодить можно и в этом случае.
Здравствуйте, MTD, Вы писали:
MTD>Здравствуйте, AlexRK, Вы писали:
ARK>>Ну вот примитивный пример:
ARK>>
ARK>> class Test
ARK>> {
ARK>> private MyItem[] _myArray;
ARK>> protected abstract void ProcessInternal(MyItem item);
ARK>> publicint Process()
ARK>> {
ARK>> foreach (var item in _myArray)
ARK>> {
ARK>> if (ProcessInternal(item) != ERR_OK) return -1; // error!
ARK>> }
ARK>> // здесь _myArray оказался в неизвестно каком состоянии
ARK>> }
ARK>>
MTD>Отличный пример
Не совсем так:
class Test
{
private MyItem[] _myArray;
protected abstract void ProcessInternal(MyItem item) throws Error; // видим возможность вылета и можем найти по Ctrl-Fpublic void Process() throws Error// видим возможность вылета и можем найти по Ctrl-F
{
foreach (var item in _myArray)
{
try!(ProcessInternal(item));// видим возможность вылета и можем найти по Ctrl-F
}
// здесь _myArray оказался в неизвестно каком состоянии
}
Здравствуйте, hi_octane, Вы писали:
_>Кроме того я помню как Rust начинался как zero cost, и как потом к этому zero cost добавилась сносочка "бла-бла-бла, мы делаем всё что минимально нужно для выполнения это и есть наш более правильный zero cost" — хи-хи два раза.
Начинался язык как раз немного иначе — у них и ГЦ был. Сейчас как раз вполне до нормального "zero cost" дошли.
В остальном более-менее согласен. Правда в расте паника вполне перехватывается и обрабатывается на границе потоков. То есть работает, по сути, как исключения. Так что если они не упрутся из упрямства, то могут и нормальные исключения появиться, по идее.
Здравствуйте, hi_octane, Вы писали:
_>Остаётся, видимо, аргумент про корректность состояния программы написанной с использованием исключений. Но коды возврата не делают программу автоматически корректной. Точно также можно забить на код возврата, или тупо пробросить его наверх не вникая в то что повсюду осталось нагажено. И что пайпинг что try! — имхо два пути в никуда. Ну тем кто пропустил 70-е кажется что это путь куда-то, но честное слово, люди туда уже ходили, просто тогда интернета почти не было, все заметки в старых книжках остались.
Я не пойму, почему при обсуждении Раста исключениям по старинке противопоставляют коды возврата. В Расте же есть нормальные алгебраические типы, и обработка ошибок строится как в хаскеле и прочих ML-ях: паттерн матчингом, там забыть что-то обработать компилятор не даст. Это вообще не коды ошибок, нет смысла даже сравнивать.
Здравствуйте, D. Mon, Вы писали:
DM>Я не пойму, почему при обсуждении Раста исключениям по старинке противопоставляют коды возврата. В Расте же есть нормальные алгебраические типы, и обработка ошибок строится как в хаскеле и прочих ML-ях: паттерн матчингом, там забыть что-то обработать компилятор не даст. Это вообще не коды ошибок, нет смысла даже сравнивать.
Там есть и вполне традиционные коды возврата в случаях, когда успех не несёт особой информации. Да, на такой тип возврата можно навесить атрибут unused_must_use, но это даст всего-лишь предупреждение от компилятора.
Опять же, хватает случаев когда мы "обязаны обработать", но не можем сделать ничего содержательного, кроме передачи ошибки и раннего выхода (как раз try! сахар). Чем это отличается от "старых-добрых" сишных кодов возврата?
Имхо, исключения отлично смотрелись бы в языке. "Всего-то" сделать панику исключением и готово.
DM>Я не пойму, почему при обсуждении Раста исключениям по старинке противопоставляют коды возврата. В Расте же есть нормальные алгебраические типы, и обработка ошибок строится как в хаскеле и прочих ML-ях: паттерн матчингом, там забыть что-то обработать компилятор не даст. Это вообще не коды ошибок, нет смысла даже сравнивать.
Дык читал доки и туториалы когда там ещё не было столько чёрточек и других символов. Позже осилил "Error Handling in Rust" и прочие, и пришёл к выводу что упаковали тупл (значение, код_ошибки), и дают на выбор — тянуть его везде при этом чем выше по стэку он пролез тем непонятнее что с ним делать, явно или не очень игнорировать его, unwrap с паникой, или пытаться что-то исправить. В общем коды возврата как они есть и все проблемы к ним относятся в полной мере, пусть даже компилятор и заставляет формально проверку влепить. Иногда натыкаюсь на код типа такого — и ещё раз убеждаюсь, что что-то не так. Ну и сам panic! как бы намекает что сделать совсем без исключений вроде как не смогли.
Здравствуйте, hi_octane, Вы писали:
_> и пришёл к выводу что упаковали тупл (значение, код_ошибки)...
Не, тут как раз принципиальная разница между произведением (туплом) и копроизведением (алгебраиком Result). Использовать для этих целей тупл крайне тупо, так делают только в самом имбецильном языке Go, и там как раз игнорировать ошибки проще простого. Растаманский Result же это не тупл, а "или-или", у тебя везде два пути в коде — что делать если ок и что делать если ошибка. И можно с этим работать как со значением, накапливать там, трансформировать и т.д. Скажем, пройтись по коллекции, все попробовать обработать, все ошибки собрать в один отчет, что-то с ним сделать. Отличие от привычных кодов возврата — хрен забудешь и удобно комбинировать и обрабатывать как данные. Но писанины, конечно, намного больше получается чем с исключениями. Раст вообще довольно многословный (многобуквенный).
Здравствуйте, AlexRK, Вы писали:
ARK>Ну вот примитивный пример:
ARK>
ARK> foreach (var item in _myArray)
ARK> ProcessInternal(item); // throws Exception
ARK> // здесь _myArray оказался в неизвестно каком состоянии
ARK> }
ARK>
"Здесь" управление просто не появится. Соответственно никаких проблем ни _myArray, ни с item не будет. А, вот, с обработкой кодов возвратов управление может и дойти до этого места. Конечно это тоже ошибка в программе, но сделать ее проще, так как для этого достаточно просто халтуры (специальные действия предпринимать не надо).
Исключения же обрабатываются (людьми умеющими ими пользоваться) в тех местах где с ними понятно что делать. Все "испорченные" объекты просто выбрасываются (о них забывают). GC позволяет не париться о их уничтожении и вообще о лежении за ними.
Кстати, _myArray будет во все том же состоянии, так как во время итераций его изменять нельзя. Вот в item может быть фигня. Только она будет в нем не зависимо от того произошло ли исключение или обработка кодов возвратов. Чтобы избежать этого нужны какие-то транзакционные системы. Коды возврата проблему не полной инициализации не решают никак.
Итого, ты снова ничего нам не доказал.
ARK>Обычные коды ошибок действительно обладают этим недостатком. Но возврат в стиле Rust — нет.
Основная проблема с кодами возврата не контроль компилятора, а куча болепрлэйт-кода засоряющего код, и необходимость тратить на него время. Людям нужно писать основной алгоритм, а их компилятор заставляет отвлегаться на какие-то не относящиеся к его суди мелочи. Причем, зачастую, таких мелочей может быть очень много. В языке без контроля использования возвращаемых значений это порождает дырявый код. В языка с контролем возвращаемых значений — это превращается в Ад для программиста.
Немерл тоже контролирует использование возвращаемого значения и создать структуру аналогичную растовскому Result-у не представляет труда. Есть даже похожие стандартные структуры вроде optoion[T] (позволяет хранить значение обернутое в подтип Some или отсутствие значения None). Так вот optoion позволяет явно описывать отсутствие значения. Но применение его усложняет код. Приходится во всех местах где происходит вызов функций возвращающих этот тип писать код проверок. Это муторно, засоряет код и отвлекает от дела. Но тут хотя бы ясно что мы за это получаем.
С обработкой же ошибок, чаще всего, это не так. 99% обработки кода возврата заключается в возврате управления наружу с протаскиванием этого самого кода возврата. Изредка нужно его перезавернуть. И уж совсем редко нужно их реально обрабатывать и что-то делать. Причем, в большинстве случаев, обработка заключается и уведомлении пользователя о возникшей проблеме, логирование оной или перезапуск некоего процесса. Никаких проблем с инвариантами, при этом, не возникает, так как все порожденные в процессе работы данные попросту выбрасываются.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, AlexRK, Вы писали:
ARK>Не совсем так:
Совсем.
ARK> protected abstract void ProcessInternal(MyItem item) throws Error; // видим возможность вылета и можем найти по Ctrl-F
Не видим, так как тебе этими типами нужно будет утыкать все возвращаемые значения всех функций вызывающие эту.
ARK> public void Process() throws Error // видим возможность вылета и можем найти по Ctrl-F
Это вообще пипец! И об этом тебе уже говорили. Явские checked exception доказали свою полную непригодность для реальной жизни. Геморрой вызываемый ими нивелируют все их гипотетические преимущества.
ARK> try!(ProcessInternal(item)); // видим возможность вылета и можем найти по Ctrl-F
Не видим, так как этим дело мужно обложить каждую строчку кода. По сути — это закат солнца вручную (эмуляция исключений на кодах возврата). Вот это ваше try!() — это чистой воды болерплэйт. Просто его немного засахарили.
ARK> // здесь _myArray оказался в неизвестно каком состоянии
Вранье! Здесть или не будет управления или он будет в испорченном состоянии, но мы не будет знать об этом, так как где-то выше проигнорировали или неверно обработали код возврата.
ЗЫ
Итого, мне очевидно, что ты не смог доказать ни одного из своих утверждений. Мой тебе совет признать ошибочность своего мнения, а не упираться рогом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, AlexRK, Вы писали:
ARK> try!(ProcessInternal(item)); // видим возможность вылета и можем найти по Ctrl-F
Кстати, у этого метода есть еще одно неприятное следствие. Вот представь себе, написал ты свой метод ProcessInternal год назад. В то время необходимости обработки ошибок в нем не было. Заиспользовал ты его в сотне мест, из сотни других функций. И вдруг, у тебя малость изменились условия и где-то этот методу потребовалось записать пару байт в файл. Файл этот обязан быть на диске в некотором каталоге. Но ведь файл могут нечаянно грохнуть или место на диске может кончиться, а приложение — это сервер который дергает GUI-ёвое приложение, и ошибку нужно показать в этом самом GUI-е. Что делать будем?
Правильно! Нужно долго и нудно протаскивать код возврата через все методы где использована эта функция.
В случае же исключений мы просто обрабатываем исключение прямо в корне GUI-ёвого приложения (ведь, они же прозрачно бегают между клиентом и сервером) и показываем тот самый диалог.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.