Проверка состояния и обработка исключений

Когда обработка исключений более предпочтительна, чем проверка условий? Есть много ситуаций, когда я могу выбрать один или другой.

Например, это суммирующая функция, которая использует настраиваемое исключение:

# module mylibrary class WrongSummand(Exception): pass def sum_(a, b): """ returns the sum of two summands of the same type """ if type(a) != type(b): raise WrongSummand("given arguments are not of the same type") return a + b # module application using mylibrary from mylibrary import sum_, WrongSummand try: print sum_("A", 5) except WrongSummand: print "wrong arguments" 

И это та же самая функция, которая позволяет избежать использования исключений

 # module mylibrary def sum_(a, b): """ returns the sum of two summands if they are both of the same type """ if type(a) == type(b): return a + b # module application using mylibrary from mylibrary import sum_ c = sum_("A", 5) if c is not None: print c else: print "wrong arguments" 

Я думаю, что использование условий всегда более читаемо и управляемо. Или я ошибаюсь? Каковы правильные случаи для определения API, которые вызывают исключения и почему?

7 Solutions collect form web for “Проверка состояния и обработка исключений”

Исключения намного более управляемы, потому что они определяют общие семьи вещей, которые могут пойти не так. В вашем примере есть только одна возможная проблема, поэтому нет смысла использовать исключения. Но если у вас был другой класс, который выполняет деление, тогда он должен сигнализировать, что вы не можете делить на ноль. Простое возвращение None больше не будет работать.

С другой стороны, исключения могут быть подклассифицированы, и вы можете поймать определенные исключения, в зависимости от того, насколько вы заботитесь об основной проблеме. Например, у вас может быть базовое исключение и подклассы InvalidType такие как InvalidType и InvalidArgument . Если вы просто хотите получить результат, вы можете обернуть все вычисления в блоке, который ловит DoesntCompute , но вы все равно можете очень точно обрабатывать ошибки.

Как правило, вы хотите использовать проверку условий для ситуаций, которые понятны, ожидаемы и могут быть обработаны. Вы должны использовать исключения для случаев, которые являются некогерентными или необщимыми.

Итак, если вы думаете о своей функции «добавить». Он НИКОГДА не должен возвращать значение null. Это не последовательный результат для добавления двух вещей. В этом случае в аргументах, которые были переданы, есть ошибка, и функция не должна пытаться притвориться, что все в порядке. Это идеальный случай для исключения исключений.

Вы хотите использовать проверку состояния и вернуть значение null, если вы находитесь в обычном или обычном случае. Например, IsEqual может быть хорошим примером использования условий и возвращать false, если одно из ваших условий терпит неудачу. IE

 function bool IsEqual(obj a, obj b) { if(a is null) return false; if(b is null) return false; if(a.Type != b.Type) return false; bool result = false; //Do custom IsEqual comparison code return result; } 

В этом случае вы возвращаете false как для случаев исключения, так и «объекты не равны случаю». Это означает, что потребитель (вызывающая сторона) не может определить, было ли сравнение неудачным или объекты просто не равны . Если эти случаи нужно различать, вы должны использовать исключения вместо условий.

В конечном счете, вы хотите спросить себя, сможет ли потребитель конкретно обработать случай сбоя, с которым вы столкнулись. Если ваш метод / функция не может выполнить то, что ему нужно сделать, вы, вероятно, захотите выбросить исключение.

Если вы спрашиваете, вероятно, вы должны использовать исключения. Исключения используются для обозначения исключительных обстоятельств, в конкретном случае, когда все работает иначе, чем в других случаях. Это касается довольно многих ошибок и многих других вещей.

Во второй реализации sum_ пользователь должен каждый раз проверять, что это за значение. Это напоминает шаблон C / Fortran / other-languages ​​(и частой источник ошибок), где коды ошибок не проверяются, что мы избегаем. Вы должны писать такой код на всех уровнях, чтобы иметь возможность распространять ошибки. Это становится беспорядочным и особенно избегается в Python.

Еще пара примечаний:

  • Вам часто не нужно делать свои собственные исключения. Для многих случаев необходимы встроенные исключения, такие как ValueError и TypeError .
  • Когда я создаю новое исключение, которое довольно полезно, я часто пытаюсь подклассифицировать что-то более конкретное, чем Exception . Здесь находится встроенная иерархия исключений.
  • Я бы никогда не реализовал функцию типа sum_ , так как typechecking делает ваш код менее гибким, поддерживаемым и идиоматичным.

    Я просто напишу функцию

     def sum_(a, b): return a + b 

    который работал бы, если бы объекты были совместимы, и если бы он уже не выдавал исключение, TypeError который все привыкли видеть. Подумайте, как работает моя реализация

     >>> sum_(1, 4) 5 >>> sum_(4.5, 5.0) 9.5 >>> sum_([1, 2], [3, 4]) [1, 2, 3, 4] >>> sum_(3.5, 5) # This operation makes perfect sense, but would fail for you 8.5 >>> sum_("cat", 7) # This makes no sense and already is an error. Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in sum_ TypeError: cannot concatenate 'str' and 'int' objects 

    Мой код был короче и проще, но более надежный и гибкий, чем ваш. Вот почему мы избегаем проверки типов в Python.

Моя основная причина предпочитать исключения из возврата статуса связана с рассмотрением того, что произойдет, если программист забудет выполнять свою работу. За исключением случаев, вы можете забыть об исключении. В этом случае ваша система будет явно терпеть неудачу, и у вас будет возможность рассмотреть, где добавить улов. С возвратом статуса, если вы забудете проверить возврат, он будет молча игнорировать, и ваш код будет продолжен, возможно, позже не так загадочно. Я предпочитаю видимый отказ невидимому.

Существуют и другие причины, которые я объяснил здесь: Исключения против статуса Возвраты .

На самом деле проблема использования исключений заключается в бизнес-логике. Если ситуация является исключением (т. Е. Не должно происходить вообще), можно использовать исключение. Однако, если ситуация возможна с точки зрения бизнес-логики, когда она должна обрабатываться посредством условной проверки, даже если это условие выглядит намного сложнее.

Например, вот код, который я встретил в подготовленном операторе, когда разработчик устанавливает значения параметров (Java, а не Python):

 // Variant A try { ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId()); } catch (Exception e) { ps.setNull(1, Types.INTEGER); } 

С условной проверкой это будет написано так:

 // Variant B if (enterprise != null && enterprise.getSubRegion() != null && enterprise.getSubRegion().getRegion() != null && enterprise.getSubRegion().getRegion().getCountry() != null) { ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId()); } else { ps.setNull(1, Types.INTEGER); } 

Вариант B кажется гораздо более сложным с первого взгляда, однако он правильный , поскольку такая ситуация возможна с бизнес-точки зрения (страна не может быть указана). Использование исключений вызовет проблемы с производительностью и приведет к непониманию кода, поскольку неясно, приемлемо ли для страны быть пустым.

Вариант B можно улучшить, используя вспомогательные функции в EnterpriseBean , которые немедленно возвратят регион и страну:

 public RegionBean getRegion() { if (getSubRegion() != null) { return getSubRegion().getRegion(); } else { return null; } } public CountryBean getCountry() { if (getRegion() != null) { return getRegion().getCountry(); } else { return null; } } 

Этот код использует нечто вроде цепочки, и каждый метод get кажется достаточно простым и использует только одного предшественника. Поэтому вариант В можно переписать следующим образом:

 // Variant C if (enterprise != null && enterprise.getCountry() != null) { ps.setInt(1, enterprise.getCountry().getId()); } else { ps.setNull(1, Types.INTEGER); } 

Кроме того, прочитайте статью Джоэля о том, почему исключения не следует злоупотреблять. И эссе Раймона Чена.

Вы должны выбросить исключение, когда параметр содержит неожиданное значение.

В ваших примерах я бы рекомендовал исключить исключение, если два параметра имеют разные типы.

Бросить исключение – это элегантный способ прервать службу, не загромождая ваш код.

Может быть, sum_ выглядит в одиночестве. Что, если, знаете, на самом деле оно используется?

 #foo.py def sum_(a, b): if type(a) == type(b): return a + b 

 #egg.py from foo import sum_: def egg(c = 5): return sum_(3, c) 

 #bar.py from egg import egg def bar(): return len(egg("2")) if __name__ == "__main__": print bar() 

Если вы запустили bar.py вы получите:

 Traceback (most recent call last): File "bar.py", line 6, in <module> print bar() File "bar.py", line 4, in bar return len(egg("2")) TypeError: object of type 'NoneType' has no len() 

См. Обычно вызов функции с намерением действовать на ее вывод. Если вы просто «проглотите» исключение и вернете фиктивное значение, у которого будет ваш код, возникнет проблема с поиском неполадок. Во-первых, трассировка полностью бесполезна. Этого должно быть достаточно.

Кто хочет исправить эту ошибку, придется сначала дважды проверить bar.py , а затем проанализировать egg.py пытаясь выяснить, откуда именно вышло. После прочтения egg.py им придется читать sum_.py и, надеюсь, заметить неявный возврат None ; только тогда они понимают проблему: они не egg.py проверку типа из-за параметра egg.py установленного для них.

Положите немного реальной сложности в этом, и вещь становится уродливой очень быстро.

Python, в отличие от C, написан с понятием « Легче задавать прощение, чем принцип разрешения» : если что-то пойдет не так, я получу исключение . Если вы передадите мне None где я ожидаю фактического значения, все будет нарушено, исключение произойдет далеко от линии, фактически вызывающей его, и люди будут проклинать ваше общее направление на 20 разных языках, а затем изменить код, чтобы бросить подходящий exception ( TypeError("incompatible operand type") ).

Interesting Posts

Множественный индекс столбцов при использовании декларативного расширения ORM sqlalchemy

Python запрашивает аргументы / обрабатывает апигментацию

Проблема с установкой PyCurl на Mac Snow Leopard

Как такие языки, как Python, преодолевают пределы интегральных данных C?

Функция python subprocess.check_output возвращает CalledProcessError: команда возвращает ненулевой статус выхода

Класс слишком большой и сложный для добавления новых функций

Плавная реализация MATLAB (n-точечная скользящая средняя) в NumPy / Python

Zip и применить список функций по списку значений в Python

Как я могу войти в django, используя tastypie

Измените IntegerProperty на FloatProperty существующего AppEngine DataStore

Функция Python: найти изменение из суммы покупки

Левая обратная в numpy или scipy?

Python: Как написать список в файл, а затем вывести его обратно в память (dict, представленный как преобразование строки в dict) позже?

Могу ли я проверить XMLHttpRequest () в SDK с помощью localhost?

Могу ли я использовать код внутри Django как демон?

Python - лучший язык программирования в мире.