代码规范

【必须】PEP 8 规范

PEP 8 python 编码规范:https://www.python.org/dev/peps/pep-0008/

  • Eclipse 中配置 PEP 8 代码提示

    将 PyDev 升级到高于 2.3.0 版本,打开 Window > Preferences > PyDev > Editor > Code Analysis > pep8.py 设置即可

  • PyCharm 配置 PEP 8 代码提示

    直接在右下角调整 Highlighting Level 为 Inspections 就能自动 PEP 8 提示

  • 建议修改在使用的 IDE 中修改 PEP8 的每行字数不超 79 字符规范,可修改为 Django 建议的 119 字符

说明:其他编辑器或 IDE 配置请自行搜索

编码规范

【必须】代码编码

  1. 国际惯例,文件编码和 python 编码格式全部为 utf-8,例如:在 python 代码的开头,要统一加上 #-*- coding: utf-8 -*-,或者其他符合正则表达式 ^[\t\v]*#.*?coding [:=][\t]*([-_.a-zA-Z0-9]+) 的编码声明方式。详情参考:https://www.python.org/dev/peps/pep-0263/#defining-the-encoding

  2. python 代码中,非 ascii 字符的字符串,请需添加 u 前缀

     # -*- coding: utf-8 -*-
     a = u"中国"
  3. 若出现 python 编码问题,可按照以下操作尝试解决

    在 python 的安装路径中下的 /Lib/site-packages 下面创建文件 sitecustomize.py,内容如下:

     import sys
         sys.setdefaultencoding ('utf-8')

    如果没有加入该文件,则在有编码问题的 py 代码中,加入以下代码:

     import sys
     reload (sys)

Python 编码规范

【必须】Python 命名规则

  1. 包名、模块名、局部变量名、函数名: 全小写 + 下划线式驼峰

    示例:this_is_var

  2. 全局变量: 全大写 + 下划线式驼峰

    示例:GLOBAL_VAR

  3. 类名: 首字母大写式驼峰

    示例:ClassName ()

变量命名规范

无论是命名变量、函数还是类,都可以使用很多相同的原则。我们可以把名字当做一条小小的注释,尽管空间不算很大,但选择一个好名字可以让它承载很多信息。
所以,命名的关键思想是 “把信息装入名字中”,主要有以下一些技巧。

  • 选择专业的词。
  • 避免泛泛的名字(或者说要知道什么时候可以使用泛泛的名字)。
  • 用具体的名字代替抽象的名字。
  • 使用前缀或者后缀给名字附带更多信息。
  • 决定名字的长度。
  • 利用名字的格式来表达含义。
  • 使用避免误解的名字。

选择专业的词

“把信息装入名字中” 包括要选择非常专业的词,并且避免使用 “空洞” 的词。


# 例子 1
def get_size ():
    # ...


# 例子 2
class Thread (object):
    def stop ():
        # ...

例子 1 中,get这个词非常不专业,可能是从本地缓存获取、从数据库获取、从远程数据源获取,更专业的名字应该是acquirequeryfetch等。size也没有承载很多信息,可能表示高度、数量、空间,更专业的词应该是heightnum_nodesmemory_bytes 等。

例子 2 中,stop 这个名字也不够专业,如果是个重量级操作,不可恢复,不如改为 kill;如果可以恢复,可以改为 pause,这样也许会有对应的 resume 方法。

选择专业的名字,一般也更有表现力,下面更多的例子,可能适合你的语境。

单词 更多专业的选择
send deliver、dispatch、announce、distribute、route
find search、extract、locate、recover
start launch、create、begin、open
make create、set up、build、generate、compose、add、new

避免像 tmp 和 retval 这样泛泛的名字

使用像 tmpretvalfoo 这样的名字往往是 “我想不出名字” 的托辞,好的名字应该描述变量的目的或者它所承载的值。

例如 tmp 只应该应用于短期存在且临时性为其主要存在因素的变量。一般情况下,需要使用时最好带上类型,如 tmp_file

另外,大家一般使用 ijkindex 做索引和循环迭代器。尽管这些名字很空泛,但是大家一看就知道他们的意思。不过有时候,适当优化会有更好效果。

如:

for i in clubs:
    for j in i.members:
        for k in j.users:
            ...

ijk改为cimiui ,使迭代元素的首字符和数据的一致会更清晰,并且不易混淆。

用具体的名字代替抽象的名字

在给变量、函数还是类命名时,要把它描述得更具体而不是更抽象。

例如 def run_locally () ,只能从名字看出来是要本地运行使用,但是不知道它的具体作用。如果是使用本地数据库,可以改为 def use_local_database ()

使用前缀或者后缀给名字附带更多信息

如果你的变量是一个度量的话,最好把名字带上单位。例如 start_secssize_mdmax_kbps 等。

这种给名字附带额外信息的技巧不限于单位,对于这个变量存在危险或者意外的时候都应该采用。下面有更多的例子可以参考。

背景 变量名 更好的名字
一个 “纯文本” 的密码,需要加密后存储 password plaintext_password
一条用户输入的注释,需要转义后才能用于显示 comment unescaped_comment
已转换成 UTF-8 格式的 html html html_utf8

决定名字的长度

  • 在小的作用域使用短的名字

if cond: ...print (m)...

  • “不方便输入” 不应该作为避免使用长名字的理由

各种编辑器已经能支持自动补全和快速导入。

  • 首字母缩略词和缩写应该是通用的

doc 可以代替 document,但是 BEManager 没法替换 BackendManager,新成员会很难理解。

  • 丢掉没用的词

如果名字中的某些单词拿掉后不会损失任何消息,可以直接去掉。如 convert_to_string 不如 to_string 简短。

利用名字的格式来表达含义

在 Python 中,一般使用驼峰命名表示类,小写字母加下划线用来命名模块、文件、函数、普通变量,使用大写字母加下划线命名全局变量、常量。

使用避免误解的名字

  • 推荐使用 minmax 来表示极限。
  • 推荐使用 firstlast 来表示包含的范围。
  • 推荐使用 beginend 来表示左闭右开的范围。
  • 给布尔值命名时加上像 ishasshouldto 这样的词或者使用过去式格式的单词。

【必须】import 顺序

  1. 标准库

  2. 第三方库

  3. 项目自身的模块

不同类型的库之间空行分隔

注:尽量不要引用 *,例如 from xxx import *

【必须】models 内部定义顺序

  1. All database fields

  2. Custom manager attributes

  3. class Meta

  4. def __str__()

  5. def save ()

  6. def get_absolute_url ()

  7. Any custom methods

异常捕获处理原则

  1. 尽量只包含容易出错的位置,不要把整个函数 try catch

  2. 对于不会出现问题的代码,就不要再用 try catch 了

  3. 只捕获有意义,能显示处理的异常

  4. 能通过代码逻辑处理的部分,就不要用 try catch

  5. 异常忽略,一般情况下异常需要被捕获并处理,但有些情况下异常可被忽略,只需要用 log 记录即可,可参考一下代码:

return early 原则

提前判断并 return,减少代码层级,增强代码可读性

Fat model, thin view

逻辑代码和业务代码解耦分离,功能性函数以及对数据库的操作定义写在 models 里面,业务逻辑写在 view 里面。

权限校验装饰器异常抛出问题

建议权限不足时直接抛出异常,可以使用 django 自带的:

from django.core.exceptions import PermissionDenied

权限不足时抛出异常 PermissionDenied,之后应该返回什么样的页面由 handler 或者中间件去处理

分 method 获取 request 参数问题

一般可以分 method 获取 request 参数,这样能够使代码更可读,且之后修改 method 时不必每个参数都修改

kwargs = getattr (request, request.method)
business_name = kwargs.get ('business_name', '')
template_name = kwargs.get ('template_name', '')

使用数字、常量表示状态

两种的话改为 true/false,多种改为 enum 可读性更好

其他注意问题

  1. 【必须】去除代码中的 print,否则导致正式和测试环境 uwsgi 输出大量信息

  2. 逻辑块空行分隔

  3. 变量和其使用尽量放到一起

  4. 【必须】import 过长,要放在多行的时候,使用括号,不要用 \ 换行

     from xxx import (
         a,
         b,
         c
     )
  5. Django Model 定义的 choices 直接在定义在类里面

更多规范

性能优化

Python 性能优化

  1. 尽量使用 generator(生成器),如 xrange, yield, dict.iteritems(), itertools

  2. 【必须】正则表达式预编译, re.compile(…)

  3. 排序尽量使用 .sort(), 其中使用 key 比 cmp 效率更高

  4. 适当使用 list 迭代表达式,如 [i for i in xrane(10)]

  5. 使用 set 来判断元素的存在

  6. 使用 dequeue 来做双端队列

Django QuerySet 性能优化

  1. 【必须】尽量避免读取全部数据,优先使用 exists, count, only, defer,
    切片 [] 等。如:

    • 使用 QuerySet.values()QuerySet.values_list() 获取部分需要的表字段数据

    • 使用 QuerySet.Iterator() 迭代大数据

    • 只查询数量时用 QuerySet.count(),查询已得结果时用 len(QuerySet)

    • 只判断存在时用 QuerySet.exists(),查询已得结果时用 if QuerySet

  2. 外键关联过多,可使用 select_related 提前将关联表进行 join,移除查询读取相关数据,many-to-many 则使用 prefetch_related

  3. 建议使用数据库索引

  4. 【必须】使用正确的字段类型,避免 TextField 代替 CharFieldIntegerField 代替 BooleanField

  5. 强烈不建议在迭代循环中执行 查询 或者 更新 等会触发DB动作的函数任务,防止在数据量变大的时候产生大量重复链接导致请求变慢

    • 对于查询动作,可以考虑使用 Model.objects.filter(field__in=condition_list) 的方法批量获得查询结果
    • 对于更新动作,可以考虑使用 bulk_create或者QuerySet.update(filed=new_value) 批量更新

日志规范

生产和测试环境中需要日志来记录、跟踪和分析系统的运行状态,但是有太多带有杂讯的日志又会影响跟踪,甚至可能对系统的运行带来影响。

日志级别

  • ERROR

    ERROR 是最高级别错误,反映系统发生了非常严重的故障,无法自动恢复到正常态工作,必须通知到开发者及时修复。系统需要将错误相关痕迹以及错误细节记录 ERROR 日志中,方便后续人工回溯解决。

  • WARNING

    WARNING 是低级别异常日志,反映系统在业务处理时触发了异常流程,但系统可恢复到正常态,下一次业务可以正常执行。但 WARN 级别问题需要开发人员给予足够关注,往往表示有参数校验问题或者程序逻辑缺陷,当功能逻辑走入异常逻辑时,应该考虑记录 WARN 日志。

  • INFO

    INFO 日志主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,开发人员可以将初始化系统配置、业务状态变化信息,或者用户业务流程中的核心处理记录到 INFO 日志中,方便日常运维工作以及错误回溯时上下文场景复现。

  • DEBUG

    开发人员可以将各类详细信息记录到 DEBUG 里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等等。其他等级不方便显示的信息都可以通过 DEBUG 日志来记录。

日志格式

后台服务

时间戳 [模块 / 包 / 类名 | 方法名] 文件名 | 行号 | PID|TID|REQID 日志级别 日志内容

其中:

  • 时间戳 - YYYY-mm-dd HH:MM:SS
  • PID - 进程 ID
  • TID - 线程 ID
  • REQID - 用于链路追踪的请求 ID

根据的不同编程语言的特点,可以对日志格式做适当调整。不存在的字段不必完全相符,意义在于能快速识别产生日志内容的基本情况,缩小问题排查范围

日志内容要求

  • 打印日志时,必须记录上下文信息,避免使用固定字符串

    • 错误的例子

      2018-07-11 JOB 接口调用失败

    • 正确的例子

      2018-07-11 JOB 接口调用失败 接口名称 (manage_proc) 请求参数 (xxx=yyy) 返回参数 (xxx=yyy) 状态码 (403)

  • SaaS 请求日志,建议打印当前请求的用户名,便于问题定位

  • 禁止打印敏感信息,如 app_secret 等密钥信息

日志打印要求

  • 调用外部系统 API 时 必须 打印日志,记录请求参数和返回参数
  • 避免重复打印日志,浪费磁盘空间
  • 正确使用日志级别,生产环境中禁止输出 debug 日志
  • 谨慎记录日志,日志埋点并非越多越好,日志打印过于频繁会产生性能问题
  • 避免打印无意义的日志,会对问题排查造成干扰。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

svn/git 代码提交规范

提交规范

接口规范

接口请求规范

编码方式

统一采用 utf-8 字符集

Ajax 全局配置

$.ajaxSetup ({contentType: "application/json; charset=utf-8"});

Axios 全局配置

axios.defaults.headers.common ['Content-Type'] = 'application/json;charset=utf-8'

请求方法

API 的 Method,要符合实际请求的类型。

动词 含义
GET 查看
POST 创建
DELETE 删除
PUT 更新或创建

具体可以参考文档《RESTful Web Services Cookbook》 。

传参方式

  1. GET 请求

    参数放在请求路径后以 ? 开头的参数串中,参数以 urlencode 编码。

  2. POST/PUT/PATCH 请求

    对于复杂数据结构的传参,建议将参数 JSON 编码后放在请求体中。

请求参数

  1. 批量数据必须排序,例如:?sortOrder=asc&sortField=created_time

  2. 批量数据必须分页,例如:?page=5&pagesize=50

  3. 可以批量请求的 API,不允许轮询,例如:?id=1,2,3

接口响应规范

统一的返回格式

字段名 返回内容描述
code 现阶段可以不使用,0 代表正确,非 0 代表不同的错误情况;
data 成功时,返回的数据的内容
msg 失败时,返回的错误信息

统一且合理的数据格式

  1. 同一个接口,其返回的数据结构必须保持一致

  2. 对返回列表数据的接口,当数据集为空时,应返回空列表,而不是 None

     {
         "msg": "",
         "code": 200,
         "data": []
     }

合适的状态码

建议充分利用 HTTP Status Code 作为响应结果的基本状态码,基本状态码不能区分的
status,再用响应中 “约定” 的 code 进行补充。

状态码 含义
200 GET 请求成功,及 DELETE 或 PATCH 同步请求完成,或者 PUT 同步更新一个已存在的资源
201 POST 同步请求完成,或者 PUT 同步创建一个新的资源
401 Unauthorized : 用户未认证,请求失败
403 Forbidden : 用户无权限访问该资源,请求失败
429 Too Many Requests : 因为访问频繁,你已经被限制访问,稍后重试
500 Internal Server Error : 服务器错误,确认状态并报告问题

http 状态码详细说明请参考:https://zh.wikipedia.org/wiki/HTTP%

参数获取方式

  1. 使用 Django URL 的正则匹配获取参数

     url (r'\^area/(?P\<cityID\>\\d {6})/\$', 'get_area')
  2. 使用 Django Forms 获取参数

     class FilterForm (forms.Form):
    
         sys_type = forms.ChoiceField (choices=choices.SYS_CHOICES, required=True,label=u'类型')
    
     def my_view (request):
    
         form = FilterForm (request.GET)
    
         if not form.is_valid ():
             # 数据不合法
         else:
             # 通过 form.cleaned_data 获取数据

权限校验

  1. 垂直越权

    普通用户不允许访问管理员用户资源

  2. 平行越权

    普通用户不能访问没有授权的其他普通用户资源

错误码规范

错误码设计

下面是两种错误码设计方法,仅供参考

  1. 数字错误码设计

    | 200 | 05 | 02 |
    |————|————–|————–|
    | HTTP 状态码 | 服务模块代码 | 具体错误代码 |

  2. 英文错误码设计,格式:ERROR_错误名称。例如:

    ERROR_INVALID_FUNCTION

    ERROR_PATH_NOT_FOUND

    ERROR_TOO_MANY_OPEN_FILES

    ERROR_ACCESS_DENIED

错误提示应准确并有用

需要提供两个基本内容:

  1. 返回错误状态,解释原因

  2. 提示用户如何解决,例如:

    • 调用 XXX 接口异常,请稍后重试,或联系管理员 XXX
    • 连接 MySQL 数据库异常,请联系管理员 XXX
    • 您输入的 XXX 不符合格式要求,请输入 XXX 格式的数据

代码注释规范

【必须】Python 代码注释

  1. 方法必须使用标注注释,如果是公有方法或对外提供的 API 相关方法,则最好给出使用样例。如:

  2. Module 注释:在开头要加入对该模块的注释。如:

  3. 普通代码注释应该以 # 和单个空格开始。

  4. 如果有需要调整或者需要优化的地方,可以使用

     # TODO 这里是注释内容
     a = func ()

    进行注释,格式:’#‘+ 单个空格 +’TODO’+ 单个空格 + 注释内容。

  5. 方法的返回,如果数据结构比较复杂,则必须要对返回结果的每个属性做解释。