代码重构帮助 - 如何重新组织验证
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 模式或太复杂而无法维护。我认为我的方式更有意义,但我的审查员认为绑定验证和模型一起创作更有意义...)
【讨论】:
一般来说,尽量在构造函数之外保留尽可能多的逻辑。在构造函数中包含逻辑会使单元测试变得更加困难,而强大的单元测试对于任何重构或维护都非常重要。以上是关于代码重构帮助 - 如何重新组织验证的主要内容,如果未能解决你的问题,请参考以下文章