前言
一、编程规约
二、异常日志
三、单元测试
四、安全规约
4.1 【强制】用户页面/功能进行权限校验
隶属于用户个人的页面或者功能必须进行权限控制校验。
说明:防止没有做水平校验就可随意访问、修改、删除别人的数据,比如查看那他人的私信内容、修改他人的订单。
4.2 【强制】数据脱敏
中国大陆个人手机号码显示未 173****1314,隐藏中间 4 位,防止隐私泄露。
4.3 【强制】防止 SQL 注入
用户输入的 SQL 参数,严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库。
4.4【强制】校验用户传入的任何参数
如果忽略,可能会导致:
- page size 过大导致内存溢出
- 恶意 order by 导致数据库慢查询
- 任意重定向
- SQL 注入
- 反序列化注入
- 正则输入源拒绝服务 ReDoS
说明:Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的特殊构造的字符串来验证,有可能导致死循环的结构。
4.5 【强制】Html页面数据 必须过滤/转义
禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
4.6 【强制】表单、ajax 提交必须执行 CSRF 安全验证
CSRF(Cross-site request forgery)跨站 请求伪造是一类常见的编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造号 URL,只要受害用户一访问,后台便在用户不知情的情况下对数据库中用户参数进行相应修改。
4.7 【强制】三方平台资源调用监控/限制等
在用使用平台资源,譬如短信、邮件、电话、支付,必须实现正确的防重防的机制,如:
- 数量限制
- 疲劳度控制
- 验证码校验
- 避免被滥刷而导致资损
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能能骚扰到其他用户,并造成短信平台资源浪费。
4.8 【推荐】用户生成内容防刷、违禁词风控
说明:发帖、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。
五、MySQL 数据库
5.1 建表规约
5.1.1 【强制】是否字段
表达是与否概念的字段,必须使用 is_xxx
的方式命名。
数据类型是 unsigned tinyint
:
- 1 表示 是
- 0 表示 否
说明:任何字段如果为非负数,必须是 unsigned。
# 正例,删除
is_deleted 1表示删除、0表示未删除
5.1.2【强制】表名、字段 规范
- 表名、字段名必须使用小写字母或数据
- 禁止出现数字开头
- 禁止两个下划线中间只出现数字
数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
MySQL 在 Windows 中不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大小字母,避免节外生枝。
# 正例
aliyun_admin、rdc_config、level3_name
# 反例
AliyunAdmin、rdcConfig、level_3_name
5.1.3【强制】表名不使用负数名词
说明:表名应该仅仅表示里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
5.1.4【强制】禁用保留字
禁用保留字,如 desc、range、match、delayed等,请参考 MySQL 官方保留字。
5.1.5【强制】索引名
主键索引名为: pk_字段名
唯一索引名为:uk_字段名
普通索引名为:idx_字段名
说明:
pk_
即 primary keyuk_
即 unique keyidx_
即 index 的简称
5.1.6【强制】小数类型为 decimal
说明:在存储的时候,float 和 double 都存在精度损失问题,很可能在比较值的时候,得到不正确的结构。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
5.1.7【强制】字符串相等,使用 char
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
5.1.8【强制】varchar、text
varchar 是可变长字符串,不预选分配粗出空间,长度不超过 5000。
如果长度超过5000,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其他字段索引效率。
5.1.9【强制】表必备字段:id、create_time、update_time
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增,步长为1。
create_time、update_time 的类型均为 datetime 类型。
5.1.10【推荐】表命名规则
表命名最好遵循: 业务名称_表的作用
# 正例
alipay_task
force_project
teade_config
5.1.11【推荐】库名与应用名称尽量一致
库名与应用名称尽量一致
5.1.12【推荐】字段变动,加注释
如果修改字段含义,或对字段表示的状态追加时,需要及时更新字段注释。
5.1.13【推荐】字段允许适当冗余
字段允许适当冗余,已提高查询性能,但必须考虑数据一致。冗余字段应遵循:
- 不是频繁修改的字段
- 不是 varchar 超长字段,更不能是 text 字段
- 不是唯一索引的字段
# 正例
商品类目名称使用频率高。字段长度短,名称基本不变,可在相关联的表中冗余存储类目名称,避免关联查询。
5.1.14【推荐】分表分库
单表行数超过 500 万行,或者单表容量超过 2GB,才推荐进行分库分表。
说明:如果预计 3 年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
5.1.15【参考】合适的字符存储长度
合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 250岁之内 | tinyint unsigned | 1 | 无符号值:0~255 |
龟 | 数百岁 | smallint unsigned | 2 | 无符号值:0~65535 |
恐龙化石 | 数千万年 | int unsigned | 4 | 无符号值:0~约42.9亿 |
太阳 | 约 50 亿年 | bigint unsigned | 8 | 无符号值:0~约10的19次方 |
5.2 索引规约
5.2.1 【强制】唯一索引
业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
**说明:**不要以为唯一索引影响了 insert 的速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
5.2.2【强制】禁止3个以上表 join
超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;
多表关联查询时,保证被关联的字段需要有索引。
**说明:**即使双表 join 也需要注意表索引、SQL 性能。
5.2.3【强制】varchar 建立索引,必须指定长度
在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本长度,决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型的数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*)
的区分度来确定。
5.2.4 【强制】页面搜索,请走搜索引擎
页面搜索,严禁使用走模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tee 的最左前缀匹配的特性,如果左边的值未确定,那么无法使用此索引。
5.2.5 【推荐】order by 放最后
如果有 order by 的场景,请注意利用索引的有序性。order by 最火的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
# 正例
where a=? and b=? order by c;
# 反例
索引如果存在范围查询,那么索引的有序性无法利用,如:
where a>10 order by b;
其中,索引 a_b 无法排序。
5.2.6【推荐】覆盖索引,避免回表
利用覆盖索引来进行查询操作,避免回表。
说明:我们看一本,只需要就看目录即可知道章节标题,无需翻到指定页面查看查看标题。
# 正例
能够建立索引的种类分为:主键索引、唯一索引、普通索引三种,而覆盖索引指示一种查询的一种效果,用explain的结果,extra列就会出现:using index
5.2.7【推荐】利用延迟关联或者子查询优化超多分页场景
说明:MySQL 并不是跳过 offset 行,而是去 offset + N 行,然后返回放弃前 offsert行,返回 N 行,那当 offset 特别大的时候,效率就非常低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
# 正例
# 先快速定位需要获取的 id 段,然后在关联
select a.* from 表1 a,
(select id from 表1 where 条件 limit 100000, 20) b
where a.id=b.id;
5.2.8【推荐】SQL 性能优化目标
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
说明:
- consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
- ref 指的是使用普通的索引(normal index)
- range 对索引进行范围检索。
# 反例
explain 表中的结构,type=index,索引物理文件全扫描,速度非常慢,这个index界别比较range,还低,与全表扫描是大巫见小巫。
5.2.9【推荐】建立索引的时候,区分度高的放到最左边
# 正例
# 如果 a 列几乎接近于唯一值,那么只需要单键 idx_a 素索引即可。
where a=? and b=?
说明:存在非等号和等号混合是,在建立索引时,请吧等号条件列前置
where c>? and d=?
其中,c的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。
5.2.10【推荐】防止因字段类型不同,导致索引失效
防止因字段类型不同造成的隐式转换,导致索引失效。
5.2.11 【参考】创建索引时避免有如下极端误解
- 宁滥勿缺,认为一个查询就需要建立一个索引。
- 宁缺毋滥,任务索引会消耗空间、严重拖慢记录的更新,以及行的新增速度。
- 抵制唯一索引。认为业务的唯一性,一律需要在应用层通过“先查后插”方式解决。
5.3 SQL 语句
5.4 ORM 映射
六、工程结构
6.1 应用分层
6.1.1 【推荐】推荐分层
图中默认上层依赖于下层,箭头关系可表示直接依赖,如:开放接口层可以依赖于Web层,也可以直接依赖于 Service 层,依此类推:
-
开放接口层:
-
可以直接封装 Service 方法暴露成RPC接口;
-
通过Web封装成 http 接口;
-
进行网关安全控制、流量控制等
-
-
终端显示层
各个端的模板渲染并执行显示的层。当前主要是 velocity渲染、js渲染、jsp渲染、移动端展示、小程序等。
-
Web 层:
主要是对访问控制进行转发,各类基本参数校验,或者不服用的业务简单处理等。
-
Service 层:
相对具体的业务逻辑服务层。
-
Manage 层:通用业务处理层,它具有以下特征:
- 对第三方平台封装的层,预处理返回结果及转化异常信息。
- 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。
- 与 DAO层交互,对多个 DAO 的组合复用。
-
DAO 层:
数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。
-
外部接口或第三方平台:
包括其他部门 RPC 开放接口,基础平台,其他公司的 HTTP 接口。
6.1.2 【参考】封层异常处理规约
- 在 Dao 层,产生的异常类型很多,无法使用细粒度的异常进行 catch,可使用
Exception e
的方式,返回给上层 Manager/Service。 - 在 Serivice 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当与保护案发现场。
- 如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致;如果是单独部署,则采用与 Service 一致的方式。
- Web 层:不可以继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。
- 开放接口层要将异常处理成错误码和错误信息并返回。
6.2 二方库依赖
6.2.1【强制】定义 GAV 遵从以下规则:
(1)GroupID 格式:
com.{公司/BU}.业务线.[子业务线]
,最多四级。
# 正例
com.couragesteak.blog
com.couragesteak.dubbo.register
(2)ArtifactID 格式:
- 产品线名 - 模块名。
- 语义不重复不遗漏,先到中央仓库去查证一下。
# 正例
dubbo-client
fastjson-api
jstorm-tool
couragesteak-api
(3)Version:
- 详细规定参考下方
6.2.2【强制】二方库版本号命名方式
格式:主版本号.此版本号.修订号
(1)主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。
(2)此版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
(3)修订号:保持完全兼容,修复 BUG、新增次要功能特性等。
说明:
主要版本起始版本号必须为:1.0.0,而不是 0.0.1,正式发布的类库必须先去中央仓库进行查证,使版本号有连续性,正式版本号不允许覆盖升级。如当前版本:1.3.3,那么下一个合理的版本号:1.3.4 或 1.4.0 或 2.0.0
# 正例
1.0.0 # 最低版本号
1.0.1
6.2.3【强制】requirement.txt
必须声明所有依赖的版本号,避免自动下载最新依赖导致的不兼容。
# 正例
SQLAlchemy==1.4.43
aiomysql==0.1.1
6.2.4【参考】二方库 发布原则
(1)精简可控原则。
移除一切不需要的 API 和依赖,只包含 Service API、必要的领域模型对象、Utils类、常量、枚举等、如果依赖其他二方库,尽量是 provided 引入,让二方库使用者去依赖具体版本号;
无 log 具体实现,值依赖日志框架。
(2)稳定可追溯原则。
每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能方便查到。除非用户主动升级版本,否则公共二方库的行为不应该发生变化。
6.3 服务器
6.3.1【推荐】高并发服务器超时时间
说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务端会因为 time_wait 的连接数过多,可能无法建立新的连接,所以需要再服务器上调小此等待值。
# 正例
# 在 linux 服务器上通过变更 /etc/sysctl.conf 文件去修改该缺省值
net.ipv4.tcp_fin_timeout = 30
6.3.2 调大服务器所支持的最大文件句柄数
调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
说明:主流操作系统的设计是将 TCP/UDP 连接采用与连接文件一样的方式去管理,即一个连接对应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为1024,到那个并发连接数很大时很容易因为 fd 不足而出现 “open too many files” 错误,导致新的连接无法建立。建议将 linux 服务器所支持的最大句柄数调到最高数倍(与服务器的内存数量无关)。
6.3.3 【参考】重定向
服务器内部重定向使用 forward;外部重定向地址使用 URL 拼装工具类来生成,否则会带来 URL 维护不一致的问题和潜在的安全风险。
七、设计规约
7.1【强制】存储方案和底层数据结构
存储方案和底层数据结构的设计需要获得评审一致通过,并沉淀为文档。
说明:有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因历史数据迁移和系统平滑过渡而突然增加。所以,存储方案和数据结构需要认真地进行设计和评审,生产环境提交执行后,需要进行 二次检查。
# 正例
评审内容包括存储介质、
表结构设计能否满足技术方案、
存储性能和存储空间能否满足业务发展、
表活字段之间的辩证关系、字段名称、字段类型、索引等;
数据结构变更(如在原有表中新增字段),也需要进行评审通过后上线。
7.2【强制】业务多状态
如果某个业务对象的状态超过3个,使用状态图来表达并且明确状态变化的各个触发条件。
说明:状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,在明确触发状态的条件是什么。
# 正例
淘宝订单状态有:已下单、待付款、已付款、待发货、已发货、已收货等。
比如:已下单与已收货这两种状态之间是不可能有直接转换关系的。
7.3【强制】功能链路涉及对象超过3个时
当功能链路涉及对象超过3个时,使用时序图来表达并且明确各调用环节的输入与输出。
说明:时序图反应了一系列对象的交互与协作关系,清晰立体地反应系统的调用纵深链路。
7.4【推荐】需求分析考虑主干功能,同时考虑异常与业务边界
需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。
# 反例
用户在淘宝付款过程中,银行扣款成功,发送给用户扣款成功短信,但是支付宝入款时由于断网演练产生异常,淘宝订单页面依然显示未付款,导致用户投诉。
7.5【推荐】类在设计与实现时要符合单一原则
说明:单一原则最易理解,确是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。
7.6【推荐】谨慎使用继承的方式进行扩展
说明:不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方,子类一定能够出现。
比如:“把钱叫出来”,前的子类包括人民币、美元、欧元等都可以出现。
7.7【推荐】尽量依赖抽象类与接口
说明:低层次模块依赖于高层次模块的抽象,方便系统间的解耦。
7.8【推荐】线上代码不可改
说明:在极端情况下,交付上线生产环境的代码都是不可修改的,同一业务域内的需要变化,通过模块或类的扩展来实现
7.9【推荐】封装公共代码
系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,避免出现重复代码或重复配置的情况。
说明:随着代码的重复次数不断增加,维护成本指数级上升。
7.10【推荐】避免如下误解:敏捷开发=讲故事+编码+发布
**说明:**敏捷开发是快速交付迭代可用的系统,沈略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。
# 反例
某团队为了业务的快速发展,敏捷成了产品经理催进度的接口,系统中均是勉强能运行,但像面条一样的代码,可维护性和可扩展性极差,一年之后,不得不进行大规模重构,得不偿失。
7.11【参考】系统设计主要目的
系统设计主要目的是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。
**说明:**避免为了设计而设计,系统设计文档有助于后期的系统维护和重构,所以设计结果需要分类归档保存。
7.12【参考】设计的本质
设计的本质是识别和表达系统难点,找到系统的变化点,并隔离变化点。
说明:在众多设计模式中,目的均为隔离系统变化点。
7.13【参考】系统架构设计的目的
- 确定系统边界。确定系统在技术层面上的做与不做。
- 确定系统内模块之间的关系。确定模块之间的依赖关系及模块的宏观输入和输出。
- 确定指导后续设计与烟花的原则。使后续的子系统或模块设计在规定的框架内继续演化。
- 确定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等。
附:专有名词解释
- **IDE(Integrated Development Environment):**用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具,本《手册》泛指 IntelliJ IDEA、eclipse和Pycharm。
- **OOP(Object Oriented Programming):**本手册泛指类、对象的变成处理方式。
- **ORM(Object Relation Mapping):**对象关系映射,对象领域模型与底层数据之间的转换,常用的有:iBATIS、MyBatis、SQLALchemy。
- **SOA(Sercice-Oriented Architecture):**面相服务架构,它可以根据需求通过网络对低耦合的粗粒度应用组件进行分布式部署、组合和使用,有利于提升组件的可复用性,可维护性。
- **一方库:**本工程内部子项目模块依赖的库(项目、jar包等)。
- **二方库:**公司内部发布到中央仓库,可供公司内部其他应用依赖的库。(库、jar包等)
- **三方库:**公司之外的开源库(三方库、jar包等)
python
https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_language_rules/
java
https://github.com/chjw8016/alibaba-java-style-guide