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文档)

可以:

  1. 权限控制使用mixin, 去掉了原先的权限控制decorator
  2. 定义抽象出自己需要的view, 例如MakoTemplateView / JsonView
  3. 合并相关的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

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. 逐行重构

  • Python 代码规范小结
  • 重构 - 读书笔记(Python示例)

  • 删除无用的代码/注释/文档等

  • 命名: 对所有变量名/常量名/函数名/类名/模块名等等重新思考,改成更合适的名字(此时编辑器或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
  • 自测
  • 测试
  • 灰度
  • 发布

python

2411 Words

2018-12-06 08:00 +0800

comments powered by Disqus