Django是一个Python界的Web开发框架,符合MVC模式,只不过对应到Django是MTV(T为Template,即视图层,V为View,即控制层)。当使用类似vue框架来实现前端开发后,其实Web框架的视图层已经用不到了,只需要处理数据层和控制层。但往往随着业务的不断变化,业务功能上线时间紧迫,控制层容易变的越来越乱。下面介绍一些改进和避免复杂性的方法。
乱是怎么产生的?
我总结3点常见的原因:
抽象度不够
抽象是复用的基础,复用越多,代码量越少,代码也就越清晰。如果代码里充斥着 copy-paste 方式的复用,情况会在需求改动时变得糟糕,因为要改好多个地方,累且容易出错,但这也是很明显的重构信号。如果这时候还没有抽象,那么累的工作下次还需要做一遍。
代码冗长
假设控制层是基于CBV(Class-Base-View)来实现的,即每个Class的get、post方法处理对应的路由请求,把所有逻辑都写在get或post方法里,行数过百,代码很长。
代码冗长首先带来可读性差的问题。例如下面变量的含义得往上翻才能看出来,所以一般一个方法的行数不超过一个屏幕。另外代码冗长不容易看出此方法的整体逻辑的结构,容易被细节所迷惑。
代码实现不够简洁
这里指可能只需要5行代码就可实现的逻辑,写了10多行,虽然运行结果一样,但实现不优雅,难维护。通常会出现在有很多条件判断+循环的代码中。所以觉得代码复杂了,不清晰了,需要自己停下来思考。
一些优化方法
分解抽象
还以 CBV 模式举例,大概的代码结构如下:
Class RequestCheck(object):
"请求参数校验"
def is_valid(self, *args, **kwargs):
pass
Class ResponseFormat(object):
"响应码+数据格式化"
def response(self, code, data={}, message):
pass
Class Service(RequestCheck, ResponseFormat):
"处理路由请求"
def post(self, request, *args, **kwargs):
# 参数验证,若有需要可以重写父类的 is_valid
valid, argname = self.is_valid(args, kwargs)
if not valid:
self.response(1, message=f"参数{argname}错误")
# 步骤1
self.__action1()
# 步骤2
self.__action2()
# 步骤3
self.__action3()
# 等等
# 返回结果
self.response(0, "xxxx")
def __action1():
pass
def __action2():
pass
def __action3():
pass
上面Service就是一个典型的接口路由处理类,参数校验和结果格式化继承通用的功能,若不满足可以重写方法。__action1、__action2、__action3 都是具体的业务逻辑代码,需要提前将业务逻辑的实现分为几个步骤,优化可读性的同时也帮助代码抽象。
上面的例子类似 Template design patten,针对复杂逻辑先定义好步骤然后对应实现。如果只有一个action,并且逻辑也简单,也可以直接写在 post 。但当发现逻辑不再简单或者逻辑内部有可复用的地方,需要及时抽象出独立方法。
action方法不一定是 Service 类的方法,也可以是调用其他Class的方法。
分层抽象
上面分解抽象做好后,Service 类相对就比较清晰了,但可能存在另一个问题,就是Service1 和 Service2 里的action功能类似,但却分布在不同的Service里,这时候就需要分层抽象,考虑从Service里抽象出相对稳定的逻辑实现,作为Service的下层,提供独立的服务。这样 MVC 里的控制层变成了2层。
这种方法可以继续使用,抽象出更稳定的独立服务作为第3层,形成倒金字塔型的依赖关系。上层依赖下层,减少同层之间的依赖,同层之间依赖太多也是乱的原因之一。
什么时候用继承来实现代码复用
一般有如下几种情况使用继承,其他情况选择调用、组合的方式来复用。
1、子类与父类有“是”的关系,例如毛笔是笔,钢笔是笔。
2、需要父类的全部功能;
3、除了使用父类的某些功能外还可能重写父类的方法;
4、实现多态的特性,即调用者只拿父类调用,不关心他实际是不是某个子类。
要不要对 ORM 层封装
如果只是对一个Model进行读写,没有其他业务逻辑,我觉得没必要封装。因为对于调用者来说没有更优。只是把 ModelName.objects.filter 换成了 query_obj 的名字而已,调用者还是需要对结果判断是否为空来继续后续逻辑。
如果对Model的读写包含了业务逻辑,或者是同时对多个Model进行读写,这就构成了一个功能了,有复用可能,这时候才有封装的必要。
代码扩展性
代码扩展性指应对需求变化的能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持。
提升可扩展性的一般方法:分层 + 设计模式。
分层通常有两种:一种是将变化封装在一个"变化层",将不变的部分封装在一个独立的"稳定层"。另一种是提炼一个"抽象层"和一个"实现层"。后一种在设计模式中经常见到。
Python中设计模式用的没Java重,因为Python的语言特性已经支持一些设计模式的效果(比如单例模式),而且Python语言讲究简单直接,不像Java弄很多抽象层。但设计模式相当于套路,掌握一些通用的套路以及对应解决的问题,能够提高写代码的敏感力,能及时发现需要重构的地方。
但太强调扩展性也不好,如果预测变化不准确可能导致过度设计,那种变化可能永远不会到来。过度设计的代码层次特别多,可读性差。所以扩展性适度就好,何为适度,就是在不影响整体架构和开发速度的情况下,方便就做下扩展,不方便就使用最简单的方式实现,后面如果有变化再根据变化持续迭代重构。