Comments 40
по-моему круто. Кодекс чести функции :)
А вообще сильно облегчает размышления по поводу «что делать с ошибками и исключениями». Для меня это пока мутная тема, привык к С-подобной обработке, когда возвращается код ошибки или null какой-нибудь.
А вообще сильно облегчает размышления по поводу «что делать с ошибками и исключениями». Для меня это пока мутная тема, привык к С-подобной обработке, когда возвращается код ошибки или null какой-нибудь.
+1
По-моему, это называется контрактным программированием — проверкой предусловий и иногда постусловий. В Qt я проверяю входные значения static assert'ами; в библиотеке boost, вроде, есть собственная реализация ассертов, а в некоторых языках даже нативная поддержка контрактов.
+2
Кстати, по моему, это называется точно так же. Я ж в сносках черным по русскому об этом и пишу:) И там даже ссылка на цикл статей о проектировании по контракту и даже с примерами нативной поддержки;)
+2
Я раньше тоже пользовался ассертами, но после пары случаев, когда в дебаге всё работало, а в релизе у клиента программа падала из-за того, что передавались какие-то не такие данные (а ассерты из релиза выбрасываются) — перешел на проверку условий без ассертов.
0
Контракты != Ассерты. Их можно рассматривать как ассерты на стероидах. Контракты предусматривают, что во время компиляции можно указать, будут ли предусловия преобразовываться в ассерты, в генерацию исключения или они вообще будут выкинуты из кода.
+3
Это лишь значит, что вы не учли всех возможных неправильных данных. Впрочем, их там много может быть, да… Вообще, asserts — это лишь поручень для подстраховки, когда идешь по узкому карнизу. Без поручня оно как-то боязно, из-за чего и падаешь, а с ним — хоть сто метров пройти можно. Для ранней разработки asserts незаменимы, поскольку локализуют ошибки с точностью до функции, а иногда даже — до ошибки. Но куда важнее продумать и реализовать архитектуру, настолько хорошую, насколько это возможно: значительная часть ошибок будет отсеяна автоматически.
+1
Для таких случаев часто применяются guard-классы шаблона GuardClause для пре/пост валидации. Для C# есть целые библиотеки напр. CuttingEdge.Conditions
0
Для C# есть Code Contracts;)
+2
Получается этот принцип отличается от Контрактного Программирования тем, что нужно обязательно «умирать» при невозможности выполнения.
P.S. Вы же писали на хабре про технический долг и эффект второй системы, давали бы уже ссылки на хабрастатьи :) В одном месте читать удобнее.
P.S. Вы же писали на хабре про технический долг и эффект второй системы, давали бы уже ссылки на хабрастатьи :) В одном месте читать удобнее.
0
В этом случае нужно либо пробрасывать исключение, либо завернуть его в другое исключение.
Если пробрасывать не заворачивая, то код более высокого уровня будет получать низкоуровневые ошибки о которых он знать не должен по определению.
+1
Заворачивая исключение в другое мы теряем информацию, но не очевидно, что приобретаем. В чем опасность того, что летят низкоуровневые исключения? (Если вы про Java, то я говорю только про unchecked exception)
0
Сори, я чего-то недопонял. Если мы «заворачиваем» исключение, то во вложенном исключении находится исходное исключение. Где мы чего теряем, я не знаю. Потенциально же, мы можем потерять информацию если пробросим *другое исключение* и не оставим информации об исходном. Иногда лучше делать так, иногда — эдак. В каждом конкретном случае нужно решать отдельно.
0
Ага, нужно, но, мне кажется, что по умолчанию заворачивать более неудобно и опасно.
1. Неудобно.
Если исключение, которое ты ожидаешь поймать в catch завернуто, то нужно ловить все, которые могут его завернуть, разворачивать их (а тут может быть не один уровень вложенности) и проверять is'ом.
2. Опасно.
a) когда я передаю делегат, который может кинуть мое исключение, в библиотеку, по умолчанию, я не предполагаю, что библиотека может завернуть его, следовательно, мой catch не сработает и программа будет работать некорректно.
b) нужно перехватывать *все* исключения, которые могут завернуть мое, и знать как они заворачивают, а на этапе компиляции этой информации запросто может не быть.
1. Неудобно.
Если исключение, которое ты ожидаешь поймать в catch завернуто, то нужно ловить все, которые могут его завернуть, разворачивать их (а тут может быть не один уровень вложенности) и проверять is'ом.
2. Опасно.
a) когда я передаю делегат, который может кинуть мое исключение, в библиотеку, по умолчанию, я не предполагаю, что библиотека может завернуть его, следовательно, мой catch не сработает и программа будет работать некорректно.
b) нужно перехватывать *все* исключения, которые могут завернуть мое, и знать как они заворачивают, а на этапе компиляции этой информации запросто может не быть.
0
Смысл заворачивать исключение в том, что обработчик исключения может ловить определенные exceptions, зная точно его тип (ему нет надобности знать другие типы). А данные исходного исключения потом использовать при необходимости, напр. для информирования, что именно произошло на низком уровне.
Такой подход в DevExpress мы применяли для планировщика при парсинге из iCalendar-файлов (пример).
Свойство OriginalException там как раз исходное исключение в более высокоуровневом iCalendarParseException.
Такой подход в DevExpress мы применяли для планировщика при парсинге из iCalendar-файлов (пример).
Свойство OriginalException там как раз исходное исключение в более высокоуровневом iCalendarParseException.
0
Использую такую систему в одном web-проекте. Любая ошибка (не найден шаблон, mysql не отвечает и т.п.) считается фатальной: функция с нехитрым названием ke_bugCheck заваливает программу и выводит* сообщение c backtrace, многим похожее на BSOD:
* — на продакшене подробные сообщения сохраняются как html в каталоге, недоступном извне, а пользователю выводится сообщение без подробностей.
* — на продакшене подробные сообщения сохраняются как html в каталоге, недоступном извне, а пользователю выводится сообщение без подробностей.
+1
Этот «путь» далеко не все возможные ситуации. Банальный пример: какая-нибудь функция parseDocs(), которая обрабатывает «пачку» данных, например, парсит N документов. Если один документ имеет неправильный формат, валить всю функцию? Глупо. Сообщить вызывающему коду об ошибке, конечно, надо, но не эксепшеном, который просто сообщит «что-то где-то пошло не так». В строготипизированных языках вроде Haskell есть специальные «обёртки», которые позволяют вернуть либо «упакованное» значение, либо метку «Ничего»:
+2
(ох уж эти мне быстрые клавиши)
[...] либо «упакованное» значение, либо метку «Ничего»:
Maybe = Just x | Nothing
Причём управляющий код будет обязан проверить возвращаемое значение и в случае чего соответствующим образом обработать его. При таком варианте parseDocs() может спокойно вернуть коллекцию таких «обёрнутых» объектов и предоставить (и заставить) управляющий код решать, что с ними делать.
[...] либо «упакованное» значение, либо метку «Ничего»:
Maybe = Just x | Nothing
Причём управляющий код будет обязан проверить возвращаемое значение и в случае чего соответствующим образом обработать его. При таком варианте parseDocs() может спокойно вернуть коллекцию таких «обёрнутых» объектов и предоставить (и заставить) управляющий код решать, что с ними делать.
+1
Да, функциональные шняжки, в частности более строгая типизация null-значений могут здорово помочь при обработке ошибок. В большинстве же ОО языков (насколько я знаю, такая поддержка есть только в Eiffel), возврат null-ов — путь к беде.
В целом согласен. Но для решения этой проблемы в ОО языке достаточно вашу функцию разбить на 2: одна разбирает один объект/файл и бросает исключение, а вторая функция юзает первую и сама решает прерываться ей, если при обработке i-го файла произошла ошибка, или продолжать выполнение.
В целом согласен. Но для решения этой проблемы в ОО языке достаточно вашу функцию разбить на 2: одна разбирает один объект/файл и бросает исключение, а вторая функция юзает первую и сама решает прерываться ей, если при обработке i-го файла произошла ошибка, или продолжать выполнение.
+1
В общем-то, это как раз и был пример функции, которая не должна валиться если какая-то её часть (вызов другой функции или просто код, в данном случае неважно) бросила исключение. Эта функция может не быть функцией верхнего уровня и не уметь восстанавливаться (полностью), но при этом и пробрасывать исключение дальше она не имеет права. Она должна сама тем или иным образом позаботиться о сохранении управляющего класса в состоянии инварианта.
0
Есть еще одна стратегия, которая называется «Заменить неудачу списком успехов». В этом случае функция возвращает список хороших исходов, и если он пуст, значит, произошла какая-то ошибка.
+2
чем это отличается от возвращения null?
0
Особо ничем, кроме реализации. Но зато не нужно держать специальную константу (null), обозначающую fail. В языках вроде Haskell это становится удобным, поскольку можно использовать pattern matching — сопоставление с шаблоном. И, кроме того, пустой список — замечательная (и часто единственная) база для рекурсии.
0
Кстати, другой интересной реализацией системы обработки ошибок является система кондишенов в Common Lisp. Кондишены похожи на эксепшены, но они не раскручивают стек, позволяя управляющему коду решить, что делать с ошибкой, не выходя из текущей функции.
+2
Данный принцип добавляет очень важное свойство функции — однозначность.
Если результат получен, значит он валидный.
Если не получен — «надо что-то менять».
Если результат получен, значит он валидный.
Если не получен — «надо что-то менять».
0
Кстати, менять что-то совсем не обязательно. Главное, сказать вызывающему коду, что вы свою работу не смогли сделать, а не утаивать это:)
Тут, кстати, можно интересную параллель провести между функциями и работниками. Ни те, ни другие не любят говорить другим (и, в частности, руководству) о своих неудачах.
Тут, кстати, можно интересную параллель провести между функциями и работниками. Ни те, ни другие не любят говорить другим (и, в частности, руководству) о своих неудачах.
0
Согласен, в данном случае я использовал «надо что-то менять» как цитату, а не руководство к действиям.
Насчёт работников тоже соглашусь, так как постоянно сталкиваюсь с ситуацией, когда работника приходится «пинать», чтобы добиться от него ответа — так и функции есть, которые приходится ещё по несколько раз «пинать», чтобы понять, в чём же проблема.
Насчёт работников тоже соглашусь, так как постоянно сталкиваюсь с ситуацией, когда работника приходится «пинать», чтобы добиться от него ответа — так и функции есть, которые приходится ещё по несколько раз «пинать», чтобы понять, в чём же проблема.
0
>«если при выполнении своей работы внутренняя функция генерирует исключение, то это означает, что и ваша функция свою работу выполнить не сможет»
Нифига такого это не значит. Я вполне могу обработать это исключение и попытаться найти другой путь решения задачи. Например, я запускаю какой-то видеоконвертор для перекодирования видео, он кидает исключение (не понимает входной формат), я запускаю другой — и он срабатывает.
Нифига такого это не значит. Я вполне могу обработать это исключение и попытаться найти другой путь решения задачи. Например, я запускаю какой-то видеоконвертор для перекодирования видео, он кидает исключение (не понимает входной формат), я запускаю другой — и он срабатывает.
+1
/**
* @return null, если что-то не так или нет в базе, хотите подробностей пишите сами, благо не final
*/
public SomeEntry ReadEntryById(int id){
}
* @return null, если что-то не так или нет в базе, хотите подробностей пишите сами, благо не final
*/
public SomeEntry ReadEntryById(int id){
}
-1
ИМХО, фиговый контракт. А как узнать, что не так? Как по мне, самый простой способ — это использовать самый простой способ:)) Можешь получить данные — верни их, а если произошла какая-то хрень, то, не стесняйся и скажи об этом.
+3
Я к тому что бывает преждевременная оптимизация, а бывает преждевременная валидация. Сейчас всем расскажете про контракты, а мне потом все методы в try catch оборачивать, пишите лучше комментарии к функциям.
0
Значит фигово я про контракты рассказываю:)) В контрактах в .net-е при их нарушении специально бросаются «внутренние» исключения, которые невозможно оборачивать. Нарушение контракта — это баг, который можно исправить только путем исправления кода. Блоки же try/catch (кроме блоков самого высокого уровня) предусматривают возможность восстановления состояния класса/системы, что сделать, в случае нарушения контракт *невозможно*.
+1
Изложенный принцип является частным случаем следования одной из гарантий безопасности исключений Абрахамса (базовой, строгой или отсутствия) а также призывает (неясно в каких случаях) обеспечивать нейтральность по отношению к исключениям.
Обеспечение одной из гарантий является обязательным требованием для любого метода при написании хорошего кода, а нейтральность к исключениям требуется в основном в библиотеках. На практике, если функция обладает достаточными знаниями чтобы обработать исключение то она должна это сделать, иначе — преобразовать или прокинуть исключение наружу.
Обеспечение одной из гарантий является обязательным требованием для любого метода при написании хорошего кода, а нейтральность к исключениям требуется в основном в библиотеках. На практике, если функция обладает достаточными знаниями чтобы обработать исключение то она должна это сделать, иначе — преобразовать или прокинуть исключение наружу.
0
На всяк случай напомню, что о существовании приведенной ссылки я знаю, ибо я и есть автор этой статьи:))
И, да, о с последним абзацем согласен, ибо об этом же явно в текущей статье написано.
Главное, речь в том, что пустой блок catch, в большинстве случаев нифига не хороший способ «обработать исключение»:
А им, к сожалению, очень часто пользуются.
И, да, о с последним абзацем согласен, ибо об этом же явно в текущей статье написано.
Главное, речь в том, что пустой блок catch, в большинстве случаев нифига не хороший способ «обработать исключение»:
try
{
// Чего-то делаем
}
catch {}
А им, к сожалению, очень часто пользуются.
+1
«Так, самурай не будет выполнять никаких заданий, противоречащих его «кодексу чести»»
Насколько я читал, самурай выполняет все приказы своего сюзерена, а уже потом, если что не так, делает сеппуку.
Насколько я читал, самурай выполняет все приказы своего сюзерена, а уже потом, если что не так, делает сеппуку.
0
Sign up to leave a comment.
Articles
Change theme settings
Принцип самурая