Django项目重构小结
由于历史原因, 项目组中存在很多历史悠久
的项目.
这类项目, 有着一些共同特征: 历史悠久, 几度经手, 缺乏文档, 维护困难, 模块不合理, 代码坏味道, 版本老旧等等.
总之, 维护困难.
如果维护需求不多, 基本不动, 这类项目可以保持现状
, 稳定运行不出问题就行.
但是, 另一方面, 如果项目需求多, 需要频繁修改, 由于项目的原因导致变更困难/进度缓慢/问题频出, 那么就该考虑进行代码重构了.
梳理了一些django项目重构的事项, 供参考.
1. code review
将核心代码做个pr, 组织一次线上的code review
.
- 不要线下review, 此时代码可能非常庞大, 本身并不适合review
- 要有本项目历史维护者参与, 也要有非项目维护者的外部人员参与
线上reivew一周, 收集大家的comment, 之后做个分类汇总
2. 升级django版本
如果是django1.3及以下, 可以考虑升级到 1.8
做升级, 并做相应的变更, 使得程序能正常运行
可以考虑升级到django2.x(python3)
3. use CBV or DRF
具体区别可以参考 Class-Based Views vs. Function-Based Views
老旧项目, 90%以上都是 FBV(Function-Based-View
), 如果项目比较小, 使用FBV可以短平快地搞定需求.
但是如果项目变得比较大了, 可以考虑使用 CBV
对于大型项目, 建议使用 DRF(Django REST framework), 中小型项目不建议引入DRF的原因是, DRF有一定的学习门槛, 比较重, 可能引入不必要的复杂度.
- 微型项目: FBV or CBV
- 中小型项目: CBV
- 大型项目: DRF
此时会涉及(以下均是django 1.8文档)
可以:
- 权限控制使用mixin, 去掉了原先的权限控制decorator
- 定义抽象出自己需要的view, 例如MakoTemplateView / JsonView
- 合并相关的view
流程: url不动, 将所有FBV改成CBV, 抽象及合并代码, 合并相关view
├── common
│ ├── __init__.py
│ ├── constants.py
│ ├── context_processors.py
│ ├── decorators.py
│ ├── exceptions.py
│ ├── http.py
│ ├── log.py
│ ├── middlewares
│ │ ├── __init__.py
│ │ └── exception.py
│ ├── mixins
│ │ ├── __init__.py
│ │ └── base.py
│ ├── responses.py
│ ├── tests.py
│ ├── utils.py
│ └── views
│ ├── __init__.py
│ └── mako.py
4. 分层及模块切分
严格的分层,使得每一层职责清晰;
thin views, fat models
- 业务逻辑, 尽量瘦小简短
- 将view中, 跟model具体对象相关的逻辑, 挪到model的方法中, 或者
@property
中, 补充一系列porperty/classmethod/staticmethod
- 注意, 不包含
{application}.objects.xxxx
的ORM操作
fat managers
- 将view中, 跟ORM相关的复杂逻辑, 挪到manager中
- [拆分出{application}/manager.py]
- Django Manager
use form
- 使用django form替换掉所有参数处理及校验相关的代码
- 去除/合并原有的逻辑代码
- [拆分出{application}/forms.py]
use enum
- replace all the
magic number
and build the choices for model - [拆分出common/constants.py 和 {application}/constants.py]
- common/constants.py 放项目全局常量
- {application}/constants.py 放app局部使用常量
add utils helpers
- 将工具类代码逻辑进行抽离, 减少代码容易
- [拆分出common/utils.py 和 {applications}/utils.py]
├── app
│ ├── __init__.py
│ ├── admin.py
│ ├── constants.py
│ ├── forms.py
│ ├── manager.py
│ ├── migrations/
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ ├── utils.py
│ └── views.py
stupid template
- 将复杂逻辑, 移到后端来
- 如果使用django原生的模板, 可以善用 Built-in template tags and filters 以及 自定义Custom template tags and filters
- 如果使用mako的, 尽量将数据构造/判断等等, 放在后端搞定, 不要在模板中使用大量的判断逻辑, 模板尽量只渲染
5. 抽离第三方依赖api [拆分出components/xxx.py]
- 重灾区: 可能每个地方都有拼url, 调用, 处理返回值, 可能前期就一两个地方, 但是随着时间推移, 会出现腐烂的趋势, 很多地方直接拷贝, 改改用. 需要全部抽出
- 将原先散落在各处的依赖第三方系统的api调用, 全部抽出到同一个文件中
- 减少代码冗余
- 同时, 可以统一处理输入/输出/异常等
- 后续接口协议变更, 可以做统一变更
├── components
│ ├── __init__.py
│ ├── engine.py
│ ├── login.py
│ └── tests.py
6. 统一
重灾区, 由于历史及人员的不同习惯, 往往一个项目会引入非常多的不同的做法
去完成同一件事.
在重构的节点, 我们必须将其统一, 否则由于破窗理论, 整体项目会越来越混乱.
原则: 做同一件事,只存在一种方法; 不要存在重复的代码; 不能妥协.
常量
- 有时候, 同一个定义, 可能有几套常量, 每个人自己定义了一套
utils
- 重灾区, 不同模块下, 可能每个开发者自己将历史工具类/函数拷贝过来, 而不是使用公共的
- 可能需求略有区别, 所以公共的工具模块/类, 需要重构支持
错误码
- 统一所有错误码
异常
- 统一异常类
- 统一异常处理方式/风格
- 在同一层处理异常: 一种思路, 在middleware中统一处理所有类型异常, 而在view及其之下的调用链中可以减少
try-except
, 或者发现问题直接raise对应异常
协议
- 统一的返回值结构
- 统一的http错误码/状态码
- 可能涉及前端变更调用, 但是, 该改动必须改
- 可以通过自定义view/mixin等方式处理, 也可以对django的Response/JsonResponse做一层封装
unicode_literals
- 如果使用python2, 可以
from __future__ import unicode_literals
- 不强求
7. 逐行重构
-
删除无用的代码/注释/文档等
-
命名: 对所有变量名/常量名/函数名/类名/模块名等等重新思考,改成更合适的名字(此时编辑器或IDE的批量查找替换发挥作用)
-
归属问题: 函数/变量/方法等, 要思考是否属于这里, 不合适直接调整
-
表达式
-
控制流:
return early
/guard clauses
…. -
异常处理
-
函数等
-
……
-
同时, 根据code review结果, 逐一修改
-
该加注释加注释, 该加文档加文档
8. format the url
因为要动前端调用, 所以放靠后会更合适
- 推荐使用restful风格
- 格式化url
- 涉及前端调用处一并修改
- 如果被其他系统依赖, 可能需要兼容老的url/协议, 可用, 但标明
deprecated
, 并同时提供新版本的url/协议
9. fix all pep8 issues
flake8 --config=.flake8 .
- 开始不忽略任何错误, 完整修一遍
- 重构所有过于复杂的函数
C901 XXXX is too complex
- 能不加
# noqa
就不加
10. unittest
- 加单元测试, 优先核心模块的核心逻辑
11. 然后
- code review again
- 自测
- 测试
- 灰度
- 发布