代码重构帮助 - 如何重新组织验证

Posted

技术标签:

【中文标题】代码重构帮助 - 如何重新组织验证【英文标题】:Code refactoring help - how to reorganize validations 【发布时间】:2010-09-27 21:51:32 【问题描述】:

我们有一个 Web 应用程序,它接受用户输入或数据库查找来对某些物理资源进行一些操作。设计可以简单地用下图表示:

用户输入模型对象数据库存储

来自用户输入的请求需要验证,但来自数据库查找命中的请求不需要验证(因为如果存在记录,这些属性之前必须已经验证过)。我正在尝试重构代码,以便验证发生在对象构造函数中,而不是旧的方式(单独的几个验证例程)

您将如何决定哪种方式更好? (方法 1(旧方法)和 2 的根本区别在于 1 中的验证不是强制性的,并且与对象实例化解耦,但 2 绑定它们并使它们对所有请求都是强制性的)

以下是设计 1 和 2 的两个示例代码 sn-ps:

方法一:

# For processing single request.
# Steps: 1. Validate all incoming data. 2. instantiate the object.
ValidateAttribures(request) # raise Exceptions if failed
resource = Resource(**request)

方法二:

# Have to extract out this since it does not have anything to do with
# the object.
# raise Exceptions if some required params missing.
# steps: 1. Check whether its a batching request. 2. instantiate the object.
#           (validations are performed inside the constructor)
CheckIfBatchRequest(request) 
resource = Resource(**request) # raise Exceptions when validations failed

在批处理请求中: 方法一:

# steps: 1. validate each request and return error to the client if any found.
#        2. perform the object instantiate and creation process. Exceptions are
#           captured.
#        3. when all finished, email out any errors.
for request in batch_requests:
    try:    
        ValidateAttribute(request)
    except SomeException, e:
        return ErrorPage(e)
errors = []
for request in batch_requests:
    try:
        CreatResource(Resource(**request), request)
    except CreationError, e:
        errors.append('failed to create with error: %s', e)
email(errors)

方法二:

# steps: 1. validate batch job related data from the request.
#        2. If success, create objects for each request and do the validations.
#        3. If exception, return error found, otherwise, 
#           return a list of pairs with (object, request)
#        4. Do the creation process and email out any errors if encountered.
CheckIfBatchRequest(request)
request_objects = []
for request in batch_requests:
    try:
        resource = Resource(**request)
    except SomeException, e:
        return ErrorPage(e)
    request_objects.append((resource, request))
email(CreateResource(request_objects)) # the CreateResource will also need to be refactored.

我在这里看到的优点和缺点是:

    方法 1 更接近业务逻辑。当对象来自数据库查找时,没有多余的验证检查。验证例程更易于维护和阅读。 方法 2 使调用者变得简单而干净。即使来自数据库查找,验证也是强制性的。验证的可维护性和可读性较差。

【问题讨论】:

我建议你缩短你的问题,总结并重新格式化代码(嵌入式编辑器有“代码”标签按钮)。 你绝对应该缩短这个问题。这也真的是一个 django 项目。如果是,你应该解释为什么没有在表单上实现验证(就像 Django 方式一样)。 是的。最初开发人员没有使用表单,而是使用带有数据的上下文,即在客户端构建的表单。我拿的时候这个设计没变。 是的,这个问题太不清楚了,无法回答。上面的代码 sn-ps 脱离了上下文,引用了未解释的名称、方法和对象。我什至无法弄清楚你想做什么。 这段代码有点java的味道 【参考方案1】:

在构造函数中进行验证确实不是“Django 方式”。由于您需要验证的数据来自客户端,因此使用new forms(可能使用ModelForm)是最惯用的验证方法,因为它将您的所有关注点包装到一个API 中:它提供了合理的验证默认值(具有轻松自定义的能力),加上模型表单将数据输入端(html 表单)与数据提交(model.save())集成在一起。

但是,听起来您的遗留项目可能是一团糟;至少在最初,重写所有表单处理以使用新表单可能超出了您的时间范围。所以这是我的想法:

首先,在模型本身中进行一些验证并不是“非 Djangonic”——毕竟,html 表单提交可能不是新数据的唯一来源。您可以覆盖 save() 方法或使用signals 来清除保存时的数据或对无效数据抛出异常。从长远来看,Django 会有模型验证,但还没有;在此期间,您应该将其视为“安全”,以确保您不会将无效数据提交到您的数据库。换句话说,您仍然需要在提交之前逐个字段地验证,以便知道在无效输入时向用户显示什么错误。

我建议的是这个。为您需要验证的每个项目创建新的表单类,即使您最初没有使用它们。 Malcolm Tredinnick 概述了一种使用表单系统中提供的挂钩进行模型验证的技术。阅读它(它真的非常简单和优雅),并融入你的模型。一旦你定义了新表单类并开始工作,你会发现这不是很困难——实际上会大大简化你的代码——如果你去掉现有的表单模板和相应的验证,并使用处理表单 POST形成框架。有一点学习曲线,但表单 API 是经过深思熟虑的,你会很感激它会让你的代码变得更简洁。

【讨论】:

【参考方案2】:

感谢丹尼尔的回复。特别是对于 newforms API,我一定会花时间深入研究它,看看我是否可以采用它以获得更好的长期利益。但只是为了完成这次迭代的工作(在 EOY 之前满足我的最后期限),我可能仍然必须坚持当前的遗留结构,毕竟,无论哪种方式都会让我得到我想要的,只是这样我想让它尽可能的干净整洁,但又不会破坏太多。

所以听起来在模型中进行验证并不是一个坏主意,但从另一个意义上说,我在视图中针对请求进行验证的旧方法似乎也接近将它们封装在 newforms API 中的概念(数据验证与模型创建分离)。你认为保留我的旧设计可以吗?对我来说,用 newforms API 来解决这个问题比现在玩弄它们更有意义......

(好吧,我从我的代码审查员那里得到了这个重构建议,但我真的不太确定我的旧方法违反了任何 mvc 模式或太复杂而无法维护。我认为我的方式更有意义,但我的审查员认为绑定验证和模型一起创作更有意义...)

【讨论】:

一般来说,尽量在构造函数之外保留尽可能多的逻辑。在构造函数中包含逻辑会使单元测试变得更加困难,而强大的单元测试对于任何重构或维护都非常重要。

以上是关于代码重构帮助 - 如何重新组织验证的主要内容,如果未能解决你的问题,请参考以下文章

重构改善既有代码设计--重构手法 之重新组织你的函数总结

重构改善既有代码的设计--第6章--重新组织函数

PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数

重构·改善既有代码的设计.03之重构手法(上)

重构.改善既有代码的设计8重新组织数据(更优雅的面向对象)

重构之--重新组织函数的几种方法