Что хуже - дублированный код или двойная попытка / кроме? - PullRequest
2 голосов
/ 25 июня 2019

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

Когда у меня есть исключение в этом куске кода, я хочу:

  1. Всегда выполнять операцию в стиле отката
  2. Если это исключение для конкретного приложения, я хочу выполнить регистрацию и проглотить исключение.

Так что я могу придумать два пути решения проблемы, оба безобразно:

# Method nested-try/except block
try:
    try:
        do_things()
    except:
        rollback()
        raise
except SpecificException as err:
    do_advanced_logging(err)
    return
# Method Duplicate Code
try:
    do_things()
except SpecificException as err:
    rollback()
    do_advanced_logging(err)
    return
except:
    rollback()
    raise

Оба будут иметь одинаковое поведение.

Я сам склоняюсь к вложенной попытке / кроме решения. Хотя это может быть немного медленнее, я не думаю, что разница в скорости здесь уместна - по крайней мере, не для моего конкретного случая. Дублирование кода - это то, чего я хочу избежать, потому что мой оператор rollback () несколько более сложен, чем просто откат базы данных, даже если он имеет точно такую ​​же цель (он включает веб-API).

Есть третий вариант, который я не заметил, который лучше? Или метод дублированного кода лучше? Обратите внимание, что функциональность rollback () уже разобрана в максимально возможной степени, но все еще содержит вызов функции и три аргумента, которые включают одну жестко закодированную строку. Поскольку эта строка уникальна, нет причин делать ее именованной константой.

Ответы [ 3 ]

7 голосов
/ 25 июня 2019

Как насчет проверки типа экземпляра исключения в коде?

# Method .. No Duplicate Code
try:
    do_things()
except Exception as e:
    rollback()
    if isinstance(e, SpecificException):
        do_advanced_logging(e)
        return
    raise
4 голосов
/ 25 июня 2019

как насчет помещения отката в предложение finally? что-то вроде:

do_rollback = True
try:
    do_things()
    do_rollback = False
except SpecificException as err:
    do_advanced_logging(err)
finally:
    if do_rollback:
        rollback()

альтернативой является использование предложения else, которое позволит вам делать больше в неисключительном случае и не будет иметь исключений, пойманных в одном месте:

do_rollback = True
try:
    do_things()
except SpecificException as err:
    do_advanced_logging(err)
else:
    record_success()
    do_rollback = False
finally:
    if do_rollback:
        rollback()

полезно, когда record_success может поднять SpecificException, но вы не хотите do_advanced_logging

2 голосов
/ 25 июня 2019

Вы могли бы написать менеджер контекста:

import random

class SpecificException(Exception):
    pass

def do_things(wot=None):
    print("in do_things, wot = {}".format(wot))
    if wot:
        raise wot("test")


def rollback():
    print("rollback")


def do_advance_logging(exc_type, exc_val, traceback):
    print("logging got {} ('{}')".format(exc_type, exc_val))


class rollback_on_error(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, traceback):
        # always rollback
        rollback()
        # log and swallow specific exceptions
        if exc_type and issubclass(exc_type, SpecificException):
            do_advance_logging(exc_type, exc_val, traceback)
            return True
        # propagate other exceptions
        return False


def test():
    try:
        with rollback_on_error():
            do_things(ValueError)
    except Exception as e:
        print("expected ValueError, got '{}'".format(type(e)))
    else:
        print("oops, should have caught a ValueError")

    try:
        with rollback_on_error():
            do_things(SpecificException)
    except Exception as e:
        print("oops, didn't expect exception '{}' here".format(e))
    else:
        print("ok, no exception")


    try:
        with rollback_on_error():
            do_things(None)
    except Exception as e:
        print("oops, didn't expect exception '{}' here".format(e))
    else:
        print("ok, no exception")



if __name__ == "__main__":
    test()

Но если у вас нет десятков вхождений этого шаблона, я бы предпочел придерживаться очень очевидных и совершенно питонических решений - либо вложенных обработчиков исключений, либо явной проверки типов(isinstance) в исключении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...