这是一篇Django实战项目文章!希望对你有所帮助!

引言

  这一篇要谈的内容,也是开发中不可忽视的环节。 开发中日志记录能帮我们记录信息定位问题;单元测试帮助我们在迭代开发过程及时发现问题,减少bug的引入; 而程序调优与重构,是一个永恒的话题。 


日志记录

日志的重要性想必不用多说。 在我看来,日志的作用主要有两点:

  1. 运营数据支撑。 比如页面访问情况,接口调用情况等等,方便运营人员后续的统计分析。  
  2. 错误回溯定位。 捕捉异常,并记录错误信息,方便在系统出现问题时快速进行定位。 

<关于日志的种类>

日志的种类很多,比如系统日志, nginx日志, 网络日志, 还有业务日志等等。

这里主要讨论的是业务日志,即我们在开发过程中为记录错误信息和业务信息的日志。

<关于异常捕捉>

异常捕捉是必要的,但是这里面有两个小建议:

1. 异常捕捉尽量不影响代码的可读性

2. 异常捕捉不要太笼统,尽量分得细致一点

举个例子,我比较喜欢下面的书写方式:

  •  
try:   <业务逻辑>   ...except Table1.DoesNotExist:   <错误处理>except KeyError:   <错误处理>except FooException:   <错误处理>except BarException:   <错误处理>except Exception:   # 最后才是其它错误的捕捉   <错误处理>      

<关于错误码定义>

一个系统越复杂,越容易出现问题。错误码的用途在于协助定位和修复问题。

最常见的错误码是http状态码,比如500代码软件内部错误,404代码找不到页面等

另外各大开发平台,对应的接口都会有自己的错误码,比如淘宝开放平台,新浪开放平台,

微信开放平台等,也都有自己一套错误码的设计规则。 错误码的设计规则遵循“足够短”,

“字面容易望文生义”, “尽量遵循已经达成共识”等。举个栗子(仅供参考):

  •  
CODE_OK= 0     # 成功CODE_ERROR_AUTH_FAIL=40100  # 权限错误CODE_ERROR_DB_NOTEXIST=50100  # 数据库错误CODE_ERROR_ACTIVITY_NOTSUPPORT=50200  # 活动业务逻辑错误CODE_ERROR_USER_NOTFOUND=50300  # 用户信息错误

扩展阅读:

错误码设计以及 Django 的异常统一处理  https://www.chenshaowen.com/blog/error-code-design-and-unified-processing-in-django.html

<关于日志配置>

Django使用python自带的logging 作为日志打印工具。简单介绍下logging。

logging 是线程安全的,其主要由4部分组成:

  1. Logger 
  2. 用户使用的直接接口,将日志传递给Handler
  3. Handler 
  4. 控制日志输出到哪里,console,file… 
  5. 一个logger可以有多个Handler
  6. Filter 
  7. 控制哪些日志可以从logger流向Handler
  8. Formatter 
  9. 控制日志的格式

在django settings配置文件中,可以进行logging的配置。 一个典型的logging配置示例:

  •  
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")LOGGING = {    'version': 1,  # 保留字    'disable_existing_loggers': False,  # 禁用已经存在的logger实例    # 日志文件的格式    'formatters': {        # 详细的日志格式        'standard': {            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'                      '[%(levelname)s][%(message)s]'        },        # 简单的日志格式        'simple': {            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'        },        # 定义一个特殊的日志格式        'collect': {            'format': '%(message)s'        }    },    # 过滤器    'filters': {        'require_debug_true': {            '()': 'django.utils.log.RequireDebugTrue',        },    },    # 处理器    'handlers': {        # 在终端打印        'console': {            'level': 'DEBUG',            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志            'class': 'logging.StreamHandler',  #            'formatter': 'simple'        },        # 默认的        'default': {            'level': 'INFO',            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M            'backupCount': 3,  # 最多备份几个            'formatter': 'standard',            'encoding': 'utf-8',        },        # 专门用来记错误日志        'error': {            'level': 'ERROR',            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M            'backupCount': 5,            'formatter': 'standard',            'encoding': 'utf-8',        },        # 专门定义一个收集特定信息的日志        'collect': {            'level': 'INFO',            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M            'backupCount': 5,            'formatter': 'collect',            'encoding': "utf-8"        }    },    'loggers': {       # 默认的logger应用如下配置        '': {            'handlers': ['default', 'console', 'error'],  # 上线之后可以把'console'移除            'level': 'DEBUG',            'propagate': True,  # 向不向更高级别的logger传递        },        # 名为 'collect'的logger还单独处理        'collect': {            'handlers': ['console', 'collect'],            'level': 'INFO',        }    },}

在文件中使用logging也很简单

  •  
import logging# 生成一个以当前文件名为名字的logger实例logger = logging.getLogger(__name__)logger.debug("这是一个debug级别的日志。。。。")logger.info("这是一个info级别的日志。。。。")

扩展阅读: 

Django之logging日志    https://cloud.tencent.com/developer/article/1093273

单元测试

当我们在谈及单元测试时,大家可能会说这个单元测试很好,好在哪里不知道,为什么要搞单元测试也不清楚。很多时候我们宁愿写花时间写业务代码,手动测试,也不愿意写单元测试。 

那单元测试的价值在哪里呢, 总结以下五点:

  1.  确保了代码在一定设定条件下的正确性,帮助我们很容易的检查出基本的语法错误和一般逻辑错误
  2.  确保了代码的改动不会影响现有的功能,这一点在代码重构的时候特别有用,如果没有单元测试,改动了一个地方,往往不知道会对原有代码产生什么影响
  3. 使开发人员更好的理解代码逻辑,良好的测试建立在对代码逻辑的理解上
  4. 良好的测试要求模块化, 解耦代码,这是良好设计的标志,换句话说测试使你的系统设计更好,如果你的系统不容易测试,那么就要思考一下系统的设计是否有问题
  5.  大大减少花在调试上的时间

前人总结的单元测试的一些最佳实践:

  1.  同样的输入要有同样的输出
  2. 原子性: 要么成功,要么失败,不能部分通过
  3. 单一职责: 一个单元测试只测试一个行为
  4. 单元测试之间无互相调用
  5. 隔离外部调用,不依赖数据库,网络,外部文件,本地系统时间,环境变量,如果需要可以进行mock
  6. 不要在业务代码里插入测试逻辑

django单元测试一般写在tests.py文件里, 一个典型的单元测试用例

  •  
from django.test import TestCasefrom myapp.models import Animal  class AnimalTestCase(TestCase):    def setUp(self):        Animal.objects.create(name="lion", sound="roar")        Animal.objects.create(name="cat", sound="meow")     def test_animals_can_speak(self):        """Animals that can speak are correctly identified"""        lion = Animal.objects.get(name="lion")        cat = Animal.objects.get(name="cat")        self.assertEqual(lion.speak(), 'The lion says "roar"')        self.assertEqual(cat.speak(), 'The cat says "meow"')

写完单元测试了,如何执行呢?可以执行下面的命令

  •  
python manage.py test  # 执行整个项目的全部测试python manage.py test packageA.tests  # 执行packageA的全部测试python manage.py test packageA.tests.AnimalTestCase # 执行packageA的某个testCasepython manage.py test packageA.tests.AnimalTestCase.test_animals_can_speak # 测试某个test case的某个方法

扩展阅读:

单元测试及最佳实践 https://www.jianshu.com/p/3b6daabeb91e

  


调优和重构

项目开发完不代表一劳永逸, 因为一直会有新的需求和新的情况出现。 所以调优和代码重构一直在路上。 

<settings区分不同环境>

正常创建完django 项目, settings只有一个settings.py。 但是我们有几个部署环境, 一个本地开发环境, 一个测试环境, 一个生产环境。 

单纯一个settings不能满足需求,于是把settings按照不同的环境进行拆分。 拆分后建立一个settings文件夹,把配置文件放在settings下,目录结构如:

  •  
settings   - default.py # 共用的变量放这里   - local.py # 放差异化的变量, 第一行是from .default import *导入共用变量   - test.py # 同上   - prod.py # 同上

进行拆分之后,要使用不同的环境,就需要设置环境变量来指定要使用的settings配置

  •  
export DJANGO_SETTINGS_MODULE=oakvip.settings.prod

<引入设计模式>

以前的开发是线性的, 一直堆测试逻辑,写一堆if--else, 代码一多,就容易出现各种问题,比如说可读性差,代码耦合多灵活性差(增加需求,往往要写很多的额外代码,还很容易出错),重复代码多难维护等等。 所以后面的代码重构引入了设计模式来改善这类问题。

 举一个例子, 运营经常会有各种各样的活动需求,需要增加活动模块。 以前的做法是很多活动的代码都混在一块,每次要加新的活动模块的时候,总是很容易影响之前的活动模块,导致其它活动执行代码时出错。 后面我把活动的业务逻辑抽象出来,写了一个基本类, 其它的活动都继承这个类,逻辑不一样的地方就重写方法即可。 然后放一个统一的入口,根据活动的名称路由到不同的活动代码(这里比较抽象,可以参考工厂模式, 模板模式,想了解更多设计模式相关的知识,请移步扩展阅读)。 这样做的好处是以后新增一个活动,只要新增一个继承活动基类的子类就可以了。 其它代码都不需要动。 

扩展阅读:

Python与设计模式系列连载  https://yq.aliyun.com/articles/70448

<其它优化>

还有其它优化,或者说必要的操作, 比如说将日志进行集中采集发送到ES并设置日志监控, 将一些异步操作和定时任务用Celery框架管理起来,增加Redis缓存提升接口性能等等。限于篇幅,这里就不展开了。后面用单独的文章来介绍。 


Python学习群:683380553,有大牛答疑,有资源共享!是一个非常不错的交流基地!欢迎喜欢Python的小伙伴!

总结

到此,我们讲了开发过程中的9个方面的内容,分别是: 版本选择,目录结构设计, 编程规范,模块划分, 表结构设计, 接口设计, 日志记录, 单元测试,以及代码的重构调优。 

篇幅有限,不可能涵盖开发所有方面的内容, 只能蜻蜓点水, 讲得不够深入。只希望借这三篇文章初步帮各位梳理开发的脉络,对各位能够有一点点启发。 

开发过程中其实也会遇到各种各样的问题,比如说并发问题,编码问题,还有一些奇奇怪怪的报错,都是在不断的探索。 也许我会在后面的文章单独我这些经验分享出来。欢迎大家关注本系列的后续更新,也欢迎和我一起交流探讨。

猜你喜欢

转载自blog.csdn.net/qq_42156420/article/details/88994900
今日推荐