【问题标题】:Designing code for proper error handling in a Django / Python app?为 Django / Python 应用程序中的正确错误处理设计代码?
【发布时间】:2017-12-23 18:06:28
【问题描述】:

我正在构建一个 Django 应用程序,并希望获得一些关于处理我的代码中的错误和错误的正确方法的建议。

以下是我遇到的问题的典型示例:用户购买了产品。要处理购买,我的视图需要执行一些操作:

  1. 首先,视图应该在数据库中创建一个User 对象。
  2. 如果成功,视图应该创建一个Order 对象并将其分配给新创建的用户。
  3. 如果成功,我的代码应该创建一个Product 对象并将其添加到新创建的订单中。

当没有错误发生时,这一切都很好——但我发现偶尔的错误在我的代码中是不可避免的,我希望我的应用程序能够优雅地处理错误,而不是彻底崩溃。例如,如果由于某种原因无法创建 Order 对象,则视图应向用户显示错误并删除之前创建的 User 对象。而且,它应该抛出一个优雅的错误消息,而不是直接崩溃并为用户提供 Http 500 错误。

我能想到的唯一方法是一系列极其复杂的嵌套 try / except 子句,如下所示。但是以这种方式设计我的代码非常混乱和耗时,而且感觉不是正确的做事方式。我知道必须有更好的方法来设计 Django 和 Python 中的正确错误处理,但我不太确定它是什么。

对于如何在这种情况下更好地构建我的代码的任何建议,我将不胜感激。

示例代码:

try:

    # Create a new user
    u = User(email='test@test.com')
    u.save()

    try:

        # Create a new order
        o = Order(user=u, name='Order name')
        o.save()

        try:

            # Create a new product
            p = Product(order=o, name='Product name')
            p.save()

        # If a product cannot be created, print an error message and try deleting the user and order that were previously created
        except:

            messages.add_message(request, messages.ERROR, 'Product could not be created')

            # If deleting the order doesn't work for any reason (for example, o.save() didn't properly save the user), 'pass' to ensure my application doesn't crash
            try:
                o.delete()

            # I use these 'except: pass' clauses to ensure that if an error occurs, my app doesn't serve a Http 500 error and instead shows the user a graceful error
            except:
                pass

            # If deleting the user doesn't work for any reason (for example, u.save() didn't properly save the user), 'pass' to ensure my application doesn't crash
            try:
                u.delete()
            except:
                pass

    # If an order cannot be created, print an error message and try deleting the user that was previously created
    except:
        messages.add_message(request, messages.ERROR, 'Order could not be created')

        # If deleting the user doesn't work for any reason (for example, u.save() didn't properly save the user), 'pass' to ensure my application doesn't crash
        try:
            u.delete()
        except:
            pass

# If the user cannot be created, throw an error
except:
    messages.add_message(request, messages.ERROR, 'User could not be created')

【问题讨论】:

    标签: python django exception error-handling exception-handling


    【解决方案1】:

    我建议使用 transaction.atomic 块,它应该像这样包含您的模型创建(link 来自 django 文档):

    try:
        with transaction.atomic():
            create_your_objects()
    except IntegrityError:
        handle_exception()
    

    这样,在上下文管理器中所做的任何更改都将自动回滚,以防出现任何问题。

    附:实际上这是 django 默认处理每个视图的方式,但是对于您 expect 失败的情况,您可以摆脱 500 错误,并且在发生问题的情况下仍然可以获得干净的数据库而无需删除每个创建的对象。

    【讨论】:

    • 这正是我想要的。感谢您的帮助!
    【解决方案2】:

    这个怎么样?将您尝试创建的每个内容分配给None。如果实例化它们(调用构造函数)引发异常,它们将保持None。另一方面,如果它们被正确实例化,但没有正确保存,那么它们的主键将是None。因此:

    u = None
    o = None
    p = None
    
    try:
        # Create a new user
        u = User(email='test@test.com')
        u.save()
    
        # Create a new order
        o = Order(user=u, name='Order name')
        o.save()
    
        # Create a new product
        p = Product(order=o, name='Product name')
        p.save()
    
    except:
        if u == None or u.pk == None:
            messages.add_message(request, messages.ERROR, 'User could not be created')
    
        else if o == None or o.pk == None:
            messages.add_message(request, messages.ERROR, 'Order could not be created')
    
        else if p == None or p.pk == None:
            messages.add_message(request, messages.ERROR, 'Product could not be created')
    

    您可以通过循环 u、o 和 p 的列表来进一步改进它并使其更具联系性,而不是像我那样重复代码,但这应该会给您一个大致的想法。

    顺便说一句:函数式编程对此提供了一些见解,因为纯函数式编程禁止抛出异常。这样的语言有Maybe monads(名称可能不同),它基本上是一个包装类,它有两个可能的值:对象本身,或None,它有一个方法get_or_else,它接受一个参数foo , 如果存在则返回它存储的对象,如果为 null,则返回 foo。您可以在 Python 中轻松实现这一点,但我相信它被认为是非 Python 的。

    【讨论】:

    • 感谢您的回复!但是,这个答案并不能解决问题;例如,如果订单创建行失败,则您的示例中没有代码可以确保新创建的用户对象也被删除。
    • @Sam 似乎很容易修改-因为只有在有订单的情况下才能创建产品,并且只有在有用户的情况下才能创建订单,在每次 if 检查时,删除所有依赖对象。另一个选项是,您可以检查特定异常,而不是一般异常,因为 save 和构造函数会抛出差异异常。
    • 感谢@ubadub 的想法!将保留上述响应(re:transaction.atomic)作为批准的答案,但这也可以作为替代解决方案。
    • @Sam 我刚才读到了那个解决方案,你是对的,这是最好的解决方案。我不知道 Django 中有这个功能,很高兴知道
    猜你喜欢
    • 2010-09-28
    • 1970-01-01
    • 2010-12-02
    • 2012-12-12
    • 2019-06-20
    • 2013-02-01
    • 2011-03-12
    • 2020-02-01
    • 2018-07-30
    相关资源
    最近更新 更多