Здравствуйте, vsb, Вы писали:
vsb> Проблема в том, что во-первых есть очень много исключений, которые вылетают очень редко. Например ошибка связи с БД. У нас БД надёжная и работает надёжно и с ней нет ошибок связи. Но теоретически то вылететь может! А значит придётся обкладывать try-catch каждую операцию с БД.
Что значит теоретически? Проблемы связи очень даже реальны. А если программа рассчитывает на то, что эти проблемы теоретические, то работает обычно хреново.
vsb> Во-вторых ряд исключений не вылетит вообще никогда. Например Integer.parseInt("1"). Никогда тут не вылетит исключение NumberFormatException. А компилятор попросит обложить try-catch-ем.
Реально таких случаев не так уж много. Может пяток штук наберётся. Если, конечно, не учитывать случаи когда программист просто мамой клянётся, что не вылетит. А оно вдруг таки вылетает.
vsb> В-третьих ряд исключений могут вылететь в любом месте. В разных языках по-разному. Например NullPointerException теоретически может вылететь на любой строчке, где есть ссылки или вызывается функций. StackOverflowError может вылететь опять же везде, где вызывается любая функция. Что с ними делать?
Ловить и соответственно обрабатывать, что ж ещё. Ну или писать программы без багов. Хотя, думаю первое на порядки проще.
vsb> Первая и третья проблема решаются выделением NonChecked Exceptions. Опять же возникает исходная проблема, что хотя исключение и вылетает раз в год или "по идее" не должно вылетать, но оно вылетит. NullPointerException особенно. И это надо помнить и обрабатывать.
vsb> Вторая проблема вообще никак не решается. Вот получил я userId от API, вызываю userService.getUserInformation(userId). А он мне говорит обрабатывай UserNotFoundException. Не может такого быть, я только что этот userId из базы считал, он там 100% есть.
Ага, а 3 миллисекунды назад этого юзера удалили в другом треде.
vsb> Как я буду обрабатывать? Только выкину UnexpectedSituationException, а вы там выше логгируйте и разбирайтесь. И таких catch-ей на каждом шагу очень много будет. Синтаксический хлам.
Чаще такой результат — сигнал плохого дизайна кода. Если ты получил userId, то может сразу вернуть User, зачем эти бессмысленные однотипные ID? А раз у тебя уже есть User, то и UserNotFoundException станет не нужен.
vsb> Кстати ещё одна интересная проблема с наследованием. Вот описали мы
vsb>
vsb> теперь примеряем его на FileInputStream, а оказыавется, что close там хочет кидать IOException. Чего нам делать? Либо добавляем в throws IOException, либо перекидыаем как непроверяемое исключение. И имеем опять что имеем.
Вроде эту проблему решили, правда не помню с какой Явы, сделав ковариантные сигнатуры методов.
Можно посмотреть на примере java.io.StringWriter.flush(). Он не содержит throws, хотя Writer.flush() содержит. А значит, если у тебя объект типа StringWriter, то можно делать flush без try-блока, а если Writer, тогда блок обязателен. Интересно почему подобное не сделано для метода close, может какая-нибудь совместимость ломается?..
Хотя это в данном конкретном случае не важно, если ты знаешь, что у тебя StringWriter, то его можно и не закрывать.
Кстати, по поводу исключений в finally-блоках ("деструкторах"), в 1.7 ввели getSuppressed. По-моему, вполне хорошее решение.
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, uncommon, Вы писали:
U>>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип! C>Ну вообще-то, на Rust уже достаточно много кода написано. try! не так уж и много получается. И принципиальность — это как раз круто.
Чем круто? Опыт показывает, что в дизайне языков программирования прагматизм имеет больший успех чем принципиальность. Но насчёт раста, время покажет. Пока что они перелопачивают свой код на новый лад каждые несколько месяцев.
C>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!.
И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения.
На самом деле опыт С++ в отношении исключений не так интересен, потому что он как раз пестрит примерами того, как люди продолжают писать код в стиле С (с кодами возврата, плохим уровнем абстракции, макросами где попало, и другими прелестями С). Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. Ещё Брюс Эккель в своё время писал, как он попробовал Питон, и все проблемы с исключениями, которые до этого у него были в других языках, исчезли. А на С++ (как показывает практика особенно в последнее время на С++11/14) можно писать код практически как на Питоне.
Здравствуйте, uncommon, Вы писали:
U> Ещё Брюс Эккель в своё время писал, как он попробовал Питон, и все проблемы с исключениями, которые до этого у него были в других языках, исчезли.
Интересно, а какие у него были проблемы с исключениями и как их помог решить Python?
Здравствуйте, Ikemefula, Вы писали:
I>Сценарий I>"по требованию сохранить данные пользователя в конкретный файл. Открыть, получить данные, серилизовать, записать, закрыть." I>Полагаю, ты можешь продемонстрировать заявленое чудо, а именно "запись в файл который невозможно открыть".
Я говорю тебе, что всё зависит от точки обработки ошибок, а не от наличия/отсутствия цепочек. Если у нас точка обработки идёт на следующем уровне, то разницы между исключениями и кодами возврата не будет. Ну или даже исключения похуже выглядят:
void Save()
{
...
if(...) throw MyException("не могу открыть файл");
...
if(...) throw MyException("не могу сериализовать данные");
...
if(...) throw MyException("не могу записать данные в файл");
...
}
...
try{
Save();
}catch(MyException& e){
ShowError("Не могу сохранить данные.");
}
int Save()
{
...
if(...) return ERROR_OPEN;
...
if(...) return ERROR_SERIAL;
...
if(...) return ERROR_WRITE;
...
return OK;
}
...
if(Save()!=OK) ShowError("Не могу сохранить данные.");
Преимущества исключений появляются, только если обработка ошибки идёт где-то намного выше (по стеку вызова, не по линейному коду) вызова Save.
I>То есть, ты предлагаешь переписывать весь слой, если мы начнем вызывать его немного другим способом. Правильно тебя понимаю ?
При проектирование приложения такие вещи (что критично, а что нет) сразу понятны, так что по нормальному никаких переписываний не должно быть. А вот в случае библиотек оптимальным было бы наличие обоих видов интерфейсов...
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Я не знаю, можно ли это как-то отключить или изменить. Если кто-то знает — буду рад услышать.
Можно изменить взяв компилятор C++ с другой реализацией исключений. К примеру mingw есть во всех трёх базовых разновидностях. )))
Но не вижу особого смысла это делать, т.к. SEH вполне хороший механизм с практически нулевыму накладными расходами (при работе без кидания исключение, что собственно главное, т.к. производительность исключительной ситуации никого не волнует).
А в опциях компилятора можно включить/выключить перехват этих исключений с помощью catch. В принципе это конечно приятный механизм (иногда полезно ловить аппаратные исключения), но к сожалению не кроссплатформенный, что в современном программирование весьма печально.
Здравствуйте, uncommon, Вы писали:
U>Где здесь обработка ошибок? Вся прелесть в том, что её как бы нет, но она на самом деле есть.
Вот это как раз совсем не прелесть. Особенно для тех, кто будет потом читать этот код.
Прелесть, это когда ничего дополнительного самому писать не надо, но при этом всё ясно видно. В области обработки ошибок я к сожалению такого решения не знаю...
Здравствуйте, alex_public, Вы писали:
_>Прелесть, это когда ничего дополнительного самому писать не надо, но при этом всё ясно видно.
Всё ясно видно: обработка ошибок делегируется на уровень выше. Абсолютно одинаково с кодом на расте, только там делегирование надо производить явно. С исключениями надо привыкнуть к одному лишь правилу, что если нет обработки на данном уровне, то она делегируется на уровень выше.
Это как с деструкторами. Слышал аргумент сишников о том, что деструкторы это плохо, потому что их не видно в коде? С деструкторами нужно просто запомнить одно правило: они вызываются при выходе из области видимости.
Здравствуйте, uncommon, Вы писали:
U>Чем круто? Опыт показывает, что в дизайне языков программирования прагматизм имеет больший успех чем принципиальность. Но насчёт раста, время покажет. Пока что они перелопачивают свой код на новый лад каждые несколько месяцев.
Давно не хватает, на самом деле, принципиальных языков. В С++ был принцип "не платить за то, что не используется" — это стало сущесвенным стимулом в развитии.
C>>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!. U>И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения.
Стоит задуматься почему.
U> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем.
Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида:
for ln in open("somefile"):
....
на
with open("somefile") as fl:
for ln in fl:
....
В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает.
Но и это фигня. Например, надо поймать исключение о нарушении констрейнта внешнего ключа. С трёх раз догадайтесь как это правильно сделать.
Здравствуйте, Cyberax, Вы писали:
C>Вот теперь делаем так, чтобы write кидал исключение, а потом в деструкторе socket'а ещё раз исключение вылезало (вполне реальная ситуация!).
Вот так говоришь с человеком, вроде как всё нормально. А потом бабах! Он выдаёт тебе такое. И не знаешь, что думать.
Здравствуйте, Cyberax, Вы писали:
C>Уже не совсем последние. Почти решили сделать оператор вызова с передачей исключения и встроенный в компилятор сахар для Result: C>
Кстати, можно идти еще дальше: оставить только в сигнатуре "throws". Будет не видно, что именно возвращает Result<>, но в данном случае это не вредит, поскольку код тупо переписывается в цепочку ифов и можно статически проконтролировать все, что требуется (инварианты, разрушение ресурсов и т.п.).
Здравствуйте, Cyberax, Вы писали:
C>Уже не совсем последние. Почти решили сделать оператор вызова с передачей исключения и встроенный в компилятор сахар для Result: C>
Здравствуйте, uncommon, Вы писали:
U>Они ещё добавили сигилов? Я даже не знаю, что сказать. Творят, что хотят, управы на них нет.
Ну до этого они поубирали нафиг @ и #. Так что баланс пока всё ещё положительный.
U>Подожди, тут вроде как похоже на обработку Nullable<T> в C#. Или нет?
Это один из минусов именно оператора "?".
Кстати, "try!(open_socket(...))" тоже заменяется на "open_socket(...)?"
Мне офигительно не нравится предложение с Carrier-трейтом, но FromError выглядит очень правильно. Я подобный подход и в С++ использовал.
Здравствуйте, AlexRK, Вы писали:
ARK>Кстати, можно идти еще дальше: оставить только в сигнатуре "throws".
В Rust принципиально в сигнатуре функции указывают весь контракт для типов. Хотя можно в качестве сахара на уровне компилятора, конечно, сделать.
Здравствуйте, uncommon, Вы писали:
C>>Вот теперь делаем так, чтобы write кидал исключение, а потом в деструкторе socket'а ещё раз исключение вылезало (вполне реальная ситуация!). U>Вот так говоришь с человеком, вроде как всё нормально. А потом бабах! Он выдаёт тебе такое. И не знаешь, что думать.
Ну а что делать? В деструкторе сокета, очевидно, будет вызываться библиотечный close(). Который может возвращать ошибки: http://linux.die.net/man/2/close
В некоторых условиях эти ошибки будут важны, так как они могут означать, что буферизованные данные не были записаны в сокет/файл/пайп.
Здравствуйте, Cyberax, Вы писали:
C>Давно не хватает, на самом деле, принципиальных языков. В С++ был принцип "не платить за то, что не используется" — это стало сущесвенным стимулом в развитии.
Сравни принцип "не платить за то, что не используется" с "исключений не будет, потому что принцип". Какой-то один из них высосан из пальца.
C>>>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!. U>>И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения. C>Стоит задуматься почему.
Если честно, то мне нечего сказать людям, которые не хотят учиться. Говнокодеров полно, и ничего с этим не поделать. Пусть они лучше возятся с PHP, который идеально подходит для этих людей (по идее и реализации). А я буду работать отдельно от них.
U>> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. C>Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида:
C>В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает.
Это отношение к исключениям не имеет. Тут эффект одинаковый вылетит исключение или нет.
C>Но и это фигня. Например, надо поймать исключение о нарушении констрейнта внешнего ключа. С трёх раз догадайтесь как это правильно сделать.
Почитать документацию, и из IntegrityError извлечь pgcode? Какое отношение косяки каких-то API имеют к существу вопроса? Я могу какое угодно кривое API придумать, но это не значит, что используемый язык или идиомы кривы. Это всего лишь значит, что я придумал кривое API.
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, AlexRK, Вы писали:
ARK>>Кстати, можно идти еще дальше: оставить только в сигнатуре "throws". C>В Rust принципиально в сигнатуре функции указывают весь контракт для типов. Хотя можно в качестве сахара на уровне компилятора, конечно, сделать.
Я не очень понятно выразился, я имел в виду — оставить в сигнатуре все, что есть, но убрать из тела "try" и "?".