引言
<tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1554273996913 ql-align-center" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; text-align: left; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> imagePython学习群:683380553,有大牛答疑,有资源共享!是一个非常不错的交流基地!欢迎喜欢Python的小伙伴!
<input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>
这一篇要谈的内容,也是开发中不可忽视的环节。 开发中日志记录能帮我们记录信息定位问题;单元测试帮助我们在迭代开发过程及时发现问题,减少bug的引入; 而程序调优与重构,是一个永恒的话题。
日志记录
<tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1554273996917 ql-align-center" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; text-align: left; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image<input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>
日志的重要性想必不用多说。 在我看来,日志的作用主要有两点:
- 运营数据支撑。 比如页面访问情况,接口调用情况等等,方便运营人员后续的统计分析。
- 错误回溯定位。 捕捉异常,并记录错误信息,方便在系统出现问题时快速进行定位。
<关于日志的种类>
日志的种类很多,比如系统日志, nginx日志, 网络日志, 还有业务日志等等。
这里主要讨论的是业务日志,即我们在开发过程中为记录错误信息和业务信息的日志。
<关于异常捕捉>
异常捕捉是必要的,但是这里面有两个小建议:
1. 异常捕捉尽量不影响代码的可读性
2. 异常捕捉不要太笼统,尽量分得细致一点
举个例子,我比较喜欢下面的书写方式:
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">try: <业务逻辑> ...except Table1.DoesNotExist: <错误处理>except KeyError: <错误处理>except FooException: <错误处理>except BarException: <错误处理>except Exception: # 最后才是其它错误的捕捉 <错误处理>
</pre>
<关于错误码定义>
一个系统越复杂,越容易出现问题。错误码的用途在于协助定位和修复问题。
最常见的错误码是http状态码,比如500代码软件内部错误,404代码找不到页面等
另外各大开发平台,对应的接口都会有自己的错误码,比如淘宝开放平台,新浪开放平台,
微信开放平台等,也都有自己一套错误码的设计规则。 错误码的设计规则遵循“足够短”,
“字面容易望文生义”, “尽量遵循已经达成共识”等。举个栗子(仅供参考):
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">CODE_OK= 0 # 成功CODE_ERROR_AUTH_FAIL=40100 # 权限错误CODE_ERROR_DB_NOTEXIST=50100 # 数据库错误CODE_ERROR_ACTIVITY_NOTSUPPORT=50200 # 活动业务逻辑错误CODE_ERROR_USER_NOTFOUND=50300 # 用户信息错误
</pre>
扩展阅读:
错误码设计以及 Django 的异常统一处理 https://www.chenshaowen.com/blog/error-code-design-and-unified-processing-in-django.html
<关于日志配置>
Django使用python自带的logging 作为日志打印工具。简单介绍下logging。
logging 是线程安全的,其主要由4部分组成:
- Logger
- 用户使用的直接接口,将日志传递给Handler
- Handler
- 控制日志输出到哪里,console,file…
- 一个logger可以有多个Handler
- Filter
- 控制哪些日志可以从logger流向Handler
- Formatter
- 控制日志的格式
在django settings配置文件中,可以进行logging的配置。 一个典型的logging配置示例:
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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', } },}
</pre>
在文件中使用logging也很简单
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import logging# 生成一个以当前文件名为名字的logger实例logger = logging.getLogger(name)logger.debug("这是一个debug级别的日志。。。。")logger.info("这是一个info级别的日志。。。。")
</pre>
扩展阅读:
Django之logging日志 https://cloud.tencent.com/developer/article/1093273
单元测试
<tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1554273996924 ql-align-center" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; text-align: left; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image<input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>
当我们在谈及单元测试时,大家可能会说这个单元测试很好,好在哪里不知道,为什么要搞单元测试也不清楚。很多时候我们宁愿写花时间写业务代码,手动测试,也不愿意写单元测试。
那单元测试的价值在哪里呢, 总结以下五点:
- 确保了代码在一定设定条件下的正确性,帮助我们很容易的检查出基本的语法错误和一般逻辑错误
- 确保了代码的改动不会影响现有的功能,这一点在代码重构的时候特别有用,如果没有单元测试,改动了一个地方,往往不知道会对原有代码产生什么影响
- 使开发人员更好的理解代码逻辑,良好的测试建立在对代码逻辑的理解上
- 良好的测试要求模块化, 解耦代码,这是良好设计的标志,换句话说测试使你的系统设计更好,如果你的系统不容易测试,那么就要思考一下系统的设计是否有问题
- 大大减少花在调试上的时间
前人总结的单元测试的一些最佳实践:
- 同样的输入要有同样的输出
- 原子性: 要么成功,要么失败,不能部分通过
- 单一职责: 一个单元测试只测试一个行为
- 单元测试之间无互相调用
- 隔离外部调用,不依赖数据库,网络,外部文件,本地系统时间,环境变量,如果需要可以进行mock
- 不要在业务代码里插入测试逻辑
django单元测试一般写在tests.py文件里, 一个典型的单元测试用例
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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"')
</pre>
写完单元测试了,如何执行呢?可以执行下面的命令
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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的某个方法
</pre>
扩展阅读:
单元测试及最佳实践 https://www.jianshu.com/p/3b6daabeb91e
调优和重构
<tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1554273996930 ql-align-center" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; text-align: left; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image<input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>
项目开发完不代表一劳永逸, 因为一直会有新的需求和新的情况出现。 所以调优和代码重构一直在路上。
<settings区分不同环境>
正常创建完django 项目, settings只有一个settings.py。 但是我们有几个部署环境, 一个本地开发环境, 一个测试环境, 一个生产环境。
单纯一个settings不能满足需求,于是把settings按照不同的环境进行拆分。 拆分后建立一个settings文件夹,把配置文件放在settings下,目录结构如:
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">settings - default.py # 共用的变量放这里 - local.py # 放差异化的变量, 第一行是from .default import *导入共用变量 - test.py # 同上 - prod.py # 同上
</pre>
进行拆分之后,要使用不同的环境,就需要设置环境变量来指定要使用的settings配置
<pre spellcheck="false" style="box-sizing: border-box; margin: 5px 0px; padding: 5px 10px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 16px; line-height: inherit; font-family: inherit; vertical-align: baseline; cursor: text; counter-reset: list-1 0 list-2 0 list-3 0 list-4 0 list-5 0 list-6 0 list-7 0 list-8 0 list-9 0; background-color: rgb(240, 240, 240); border-radius: 3px; white-space: pre-wrap; color: rgb(34, 34, 34); letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">export DJANGO_SETTINGS_MODULE=oakvip.settings.prod
</pre>
<引入设计模式>
以前的开发是线性的, 一直堆测试逻辑,写一堆if--else, 代码一多,就容易出现各种问题,比如说可读性差,代码耦合多灵活性差(增加需求,往往要写很多的额外代码,还很容易出错),重复代码多难维护等等。 所以后面的代码重构引入了设计模式来改善这类问题。
举一个例子, 运营经常会有各种各样的活动需求,需要增加活动模块。 以前的做法是很多活动的代码都混在一块,每次要加新的活动模块的时候,总是很容易影响之前的活动模块,导致其它活动执行代码时出错。 后面我把活动的业务逻辑抽象出来,写了一个基本类, 其它的活动都继承这个类,逻辑不一样的地方就重写方法即可。 然后放一个统一的入口,根据活动的名称路由到不同的活动代码(这里比较抽象,可以参考工厂模式, 模板模式,想了解更多设计模式相关的知识,请移步扩展阅读)。 这样做的好处是以后新增一个活动,只要新增一个继承活动基类的子类就可以了。 其它代码都不需要动。
扩展阅读:
Python与设计模式系列连载 https://yq.aliyun.com/articles/70448
<其它优化>
还有其它优化,或者说必要的操作, 比如说将日志进行集中采集发送到ES并设置日志监控, 将一些异步操作和定时任务用Celery框架管理起来,增加Redis缓存提升接口性能等等。限于篇幅,这里就不展开了。后面用单独的文章来介绍。
总结
<tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1554273996934 ql-align-center" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; text-align: left; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image<input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>
到此,我们讲了开发过程中的9个方面的内容,分别是: 版本选择,目录结构设计, 编程规范,模块划分, 表结构设计, 接口设计, 日志记录, 单元测试,以及代码的重构调优。
篇幅有限,不可能涵盖开发所有方面的内容, 只能蜻蜓点水, 讲得不够深入。只希望借这三篇文章初步帮各位梳理开发的脉络,对各位能够有一点点启发。
开发过程中其实也会遇到各种各样的问题,比如说并发问题,编码问题,还有一些奇奇怪怪的报错,都是在不断的探索。 也许我会在后面的文章单独我这些经验分享出来。欢迎大家关注本系列的后续更新,也欢迎和我一起交流探讨。
网友评论