1、编码规范
1.【强制】代码中的命名不允许中英文混合命名,纯拼音的命名也要尽量避免。
2.【强制】类名使用驼峰式命名,UpperCamelCase风格。(一些通用的大写风格可以例外,但要易于阅读,比如PHPExcel类)。
Yii生成的Model都要统一带上Model后缀,避免与其他业务场景的类命名冲突。比如points_history表对应的model名称应该是PointsHistoryModel。
特定业务或者功能的类,尽量增加功能描述的后缀,易于理解。比如一个专门请求库存接口的类,属于Stock服务的客户端,命名为StockClientProxy,带上Proxy后缀,表示用于代理请求;比如库存的数据库操作类命名为StockDao,带上Dao后缀,表示用于数据库操作。
3.【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式。(首字母小写)。
专门对常量的描述的静态数组例外。
4 .【强制】数组中的key如果要对应数据库的字段名,统一使用全小写加下划线风格。不允许不同风格的key混用。
5.【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
【推荐】Model类中的字段表达枚举类型意义的,在model类中把意义用常量定义出来,并增加静态数组变量解释,使用大写,并使用ARR_做前缀。
例如对应的字段叫做reg_from,字段含义可以常量枚举(不是跨表动态关联),含义为1=PC端,2=M端,3=APP端:
const REG_FROM_PC = 1;
const REG_FROM_MOB =2;
const REG_FROM_APP =3;
$ARR_REG_FROM = [
self::REG_FROM_PC => 'PC端',
self::REG_FROM_MOB => 'M端',
self::REG_FROM_APP => 'APP端',
];
这种方式在业务中使用时,要列举该字段的可选内容时,要遍历这里的ARR_中的内容,而不是使用if ($regFromItem==1) {echo 'PC端';} elseif($regFromItem==2) {...}这种方式。
Proxy类,Dao类等其他业务类中可能也有类似的场景。可同样应用。
6.【强制】抽象类命名使用Abstract开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾;工厂类使用Factory结尾;Interface声明使用Interface结尾。
如果使用到了设计模式,建议在类名中体现出具体模式。 将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。
7.【推荐】模块名统一使用小写,使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
8.【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。
9.【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存相关的常量放在类:CacheConsts下;系统配置相关的常量放在类:ConfigConsts下。
10.【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有else等代码则不换行;表示终止右大括号后必须换行。
11.【强制】 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格。详见第5条下方正例提示。
12.【强制】if/for/while/switch/do等保留字与左右括号之间都必须加空格。
13.【参考】运算符左右加一个空格,但表示负责的逻辑判断,关系紧密的逻辑判断可以没有空格,但是尽量使用括号把关系紧密的逻辑包起来。
14.【强制】缩进采用4个空格,禁止使用tab字符。
15.【强制】字符串尽量使用'',以提高效率,需要复杂格式时,再使用""。
例子【9-15】:
public staticgetMemberName($memberId) {
// 缩进4个空格
$name = '';
// 运算符的左右必须有一个空格
// 关键词if与括号之间必须有一个空格,括号内的$与左括号,0与右括号不需要空格
// 左大括号前加空格且不换行;左大括号后换行
if ($memberId == 0) {
Yii::log('illegal memberId:'.$memberId,'error', __METHOD__);
// 右大括号前换行,右大括号后有else,不用换行
} else {
$name = MemberModel::getMemberNameByMemberId($memberId);
// 在右大括号后直接结束,则必须换行
}
return $name;
}
16.【推荐】单行字符数限不超过 120 个,超出需要换行时,换行遵循如下原则:
1) 第二行相对一缩进一层,从第三行开始不再继续缩进参考示例。
2) 运算符与下文一起换行。
3) 方法调用的->符号与下文一起换行。
4) 在多个参数超长,逗号后进行换行。
5) 在括号前不要换行。
例如:
if ($isLegalA && ....
|| $isLegalM || $isLegalN... //比上一行缩进一层,链接的运算符放到本行
|| $isLegalP || $isLegalQ... //与上一行保持一样的缩进
|| $isLegalU || $isLegalV) { //与上一行保持一样的缩进
$bizObj->checkA() ... ->checkB()
->checkM()->checkN();//比上一行缩进一层
}
17.【推荐】数组声明时,其中字段较多时(超过3个),使用每行一个'key'=>'val'。(key下有数组的使用同样的规则)
18.【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式换行符(LF),不要使用windows格式(CR+LF)。
19.【强制】不要使用字节序标记(BOM),和 UTF-16 和UTF-32 不一样, UTF-8 编码格式的文件不需要指定字节序。而且 BOM 会在 PHP 的输出中产生副作用, 它会阻止应用程序设置它的头信息。
PHP 起始标签的前面和结束标签的后面都不要留空格,输出是被缓存的,所以如果你的文件中有空格的话,这些空格会在框架输出它的内容之前被输出,从而会导致错误,而且也会导致框架无法发送正确的头信息。
20.【强制】注释
通常情况下,应该多写点注释,这不仅可以向那些缺乏经验的程序员描述代码的流程和意图, 而且当你几个月后再回过头来看自己的代码时仍能帮你很好的理解。注释并没有强制规定的格式,但是我们建议以下的形式。
DocBlock 风格的注释,写在类、方法和属性定义的前面,可以被 IDE 识别:
/**
* Super Class
*
* @package Package Name
* @subpackage Subpackage
* @category Category
* @author Author Name
* @link http://example.com
*/
class Super_class {
/**
* Encodes string for use in XML
*
* @param string $str Input string
* @return string
*/
functionxml_encode($str)
/**
* Data for class manipulation
*
* @var array
*/
public $data =array();
单行注释应该和代码合在一起,大块的注释和代码之间应该留一个空行。大的代码块之间也应该留空行,形成代码段落。
21. 【强制】对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。
22.【推荐】对于覆盖了父类的方法,函数头注释中增加@Override或适当说明。
23.【强制】对返回值进行比较以及类型转换
有一些 PHP 函数在失败时返回 FALSE ,但是也可能会返回 "" 或 0 这样的有效值, 这些值在松散类型比较时和 FALSE 是相等的。所以当你在条件中使用这些返回值作比较时,一定要使用严格类型比较,确保返回值确实是你想要的,而不是松散类型的其他值。
在检查你自己的返回值和变量时也要遵循这种严格的方式,必要时使用=== 和 !== 。
24.【强制】不要在你的提交中包含调试代码,就算是注释掉了也不行。像 var_dump() 、 print_r() 、 die() 和 exit() 这样的函数,都不应该包含在你的代码里, 除非它们用于除调试之外的其他特殊用途。
25.【强制】一个类一个文件
除非几个类是*紧密相关的*,否则每个类应该单独使用一个文件。
26.【强制】PHP错误处理
运行代码时不应该出现任何错误信息,并不是把警告和提示信息关掉来满足这一点。 例如,绝不要直接访问一个你没设置过的变量(例如,$_POST 数组), 你应该先使用 isset() 函数判断下。
当前测试环境和beta环境报错设置为:E_ALL &~ E_NOTICE &~ E_WARNING。
除了入口文件,代码中不允执行ini_set,display_errors等修改系统配置的方法。
27.【强制】缺省的函数参数
适当的时候,提供函数参数的缺省值,这有助于防止因错误的函数调用引起的PHP错误,另外提供常见的备选值可以节省几行代码。
28.【强制】每行只有一句代码。
29.【强制】构造方法里面禁止加入任何业务逻辑,及任何有可能出错的逻辑,例如数据库执行sql,如果有初始化逻辑,请放在init方法中。因为无法判断构造方法不允许错误。
30.【参考】注释掉的代码尽量要配合说明,而不是简单的注释掉。
欢迎补充。
2、业务设计规范
2.1 业务设计思路
Yii框架为我们提供了基础的MVC结构,并提供Module用于封装公用模块。
在日益增长的业务需求下,我们的业务需要扩展性,可维护性,所以我们引入应用分层概念。
如图,业务上层依赖下层,应当避免跨级调用。
页面显示层:
本应用的显示输出等。输出tpl转换的html,js等。
只做简单的参数校验,登陆状态,cookie/session等校验。(不允许污染$_GET,$_POST等数据。)不处理具体业务,业务交给下层的业务逻辑层处理。
http接口服务层:
对其他业务或平台提供基于http的接口服务。输出json等格式数据。
不处理具体业务,业务交给下层的业务逻辑层处理。
业务逻辑层:
处理具体的业务逻辑,会调用持久层,按情况调用缓存层,可以启用事务,可调用第三方数据,并有业务日志,错误时提供适当的错误信息返回。
比如订单逻辑,获取登陆用户数据和商品数据,生成订单,返回订单生成结果。
高级服务层:
对基础服务组件进行组装的一些相对复杂的服务,可提供给业务逻辑层使用。
比如发送短信服务,该服务实现判断手机号码是否正确,是否可疑,是否超过次数,调用发送短信的基础服务,并插入数据库。业务逻辑层可能是发送推广短信,可能是注册验证码短信。
数据持久层:
Dao层,Yii自动生成的Model,或者自己封装的Dao类,或者对Oracle,File等的操作。
基础服务组件层:
日志,第三方接口的client,缓存等。
服务或者业务内部,使用的数据必须独立,不能跨范围读别的业务的表,必须通过别的业务提供的公开的方法去访问。服务或业务内部保持数据的完整性,一致性。
2.2事务性逻辑说明:
事务使用try-catch语句块,事务开启后,必须有commit或rollback处理,不允许中途退出。
事务中途退出,在try块中使用throwexception的方式。
事务失败后,要有一定的error log,并通过返回值返回,保证错误原因可追溯。
有throw必然要有catch。本类中如不做catch,必须注释说明调用方主动catch。
2.3其他说明:
1.【强制】在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;多个case公用一个break的时候必须保持每个case不能有不一样的代码,必须使用公用代码;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
2.【强制】for/switch中不允许return/exit/die
不允许直接在case下return,或者在for/while中return,应该标记结果,在switch/for/while块外面return。
3.【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作(这个try-catch是否可以移至循环体外)。
4.【推荐】接口入参保护,这种场景常见的是用于做批量操作的接口。
5.【强制】基于互不信任原则,控制层调用业务层或者服务层方法时,控制层要做参数校验,服务层也要做参数校验。
对于严格的复杂的基于服务层方法的校验,可以由服务层校验。对于校验方法复杂,时间长,应该由服务层来实现该校验方法,并由服务层来校验。
6.【参考】方法中需要进行参数校验的场景:
1) 调用频次低的方法。
2) 执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是RPC/API/HTTP接口。
5) 敏感权限入口。
7.【参考】方法中不需要参数校验的场景:
1) 极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查。
2) 底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。
3) 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
8.【强制】如果方法返回的是一个固定格式的数组,或者对象,那不管走到任何if else等都要保证有按照格式或对象的内容返回,不允许只有if有返回,而else没有返回。
9.【强制】有throw必然要有catch。本类中如不做catch,必须注释说明调用方主动catch。
3、安全规范
1.【强制】隶属于用户个人的页面或者功能必须进行权限控制校验。
2.【强制】用户敏感数据禁止直接展示,必须对展示数据脱敏。
3.【强制】用户输入的SQL参数严格使用参数绑定或者Model::rules字段值限定,防止SQL注入,禁止字符串拼接SQL访问数据库。
4.【强制】用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
page size过大导致内存溢出
恶意order by导致数据库慢查询
任意重定向
SQL注入
反序列化注入
正则输入源串拒绝服务ReDoS
5.【强制】禁止向HTML页面输出未经安全过滤或未正确转义的用户数据。
6.【强制】表单、AJAX提交必须执行CSRF安全过滤。
说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者可以事先构造好URL,只要受害者用户一访问,后台便在用户不知情情况下对数据库中用户参数进行相应修改。
7.【强制】在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
8.【推荐】发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。
9.【强制】发送短信等大额消耗公司资源的功能,必须添加图形验证码校验,防止短信平台资源浪费。
4、数据库设计规范
(一) 建表规约
1. 【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint( 1表示是,0表示否),此规则同样适用于odps建表。
说明:任何字段如果为非负数,必须是unsigned。
2. 【强制】表名、字段名必须使用小写字母或数字;禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
正例:getter_admin,task_config,level3_name
反例:GetterAdmin,taskConfig,level_3_name
3. 【强制】表名不使用复数名词。 说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名也是单数形式,符合表达习惯。
4. 【强制】禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留字。
5. 【强制】唯一索引名为uk_字段名;普通索引名则为idx_字段名。
说明:uk_ 即 unique key;idx_ 即index的简称。
6. 【强制】小数类型为decimal,禁止使用float和double。 说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。
7. 【强制】如果存储的字符串长度几乎相等,使用char定长字符串类型。
8. 【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
9. 【强制】表必备三字段:id,gmt_create, gmt_modified。
说明:其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmt_create, gmt_modified的类型均为date_time类型。
10. 【推荐】表的命名最好是加上“业务名称_表的作用”。
正例:tiger_task / tiger_reader / mpp_config
11. 【推荐】库名与应用名称尽量一致。
12. 【推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
13. 【推荐】字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是varchar超长字段,更不能是text字段。 正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
14. 【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。 说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
15. 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:人的年龄用unsigned tinyint(表示范围0-255,人的寿命不会超过255岁);海龟就必须是smallint,但如果是太阳的年龄,就必须是int;如果是所有恒星的年龄都加起来,那么就必须使用bigint。
(二) 索引规约
1. 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
2. 【强制】 超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。说明:即使双表join也要注意表索引、SQL性能。
3. 【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。说明:索引文件具有B-Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
5. 【推荐】如果有orderby的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引a_b无法排序。
6. 【推荐】利用覆盖索引来进行查询操作,来避免回表操作。 说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。正例:能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种效果,用explain的结果,extra列会出现:using index。
7. 【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。正例:先快速定位需要获取的id段,然后再关联: SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
8. 【推荐】 SQL性能优化的目标:至少要达到range 级别,要求是ref级别,如果可以是consts最好。说明: 1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2)ref 指的是使用普通的索引(normal index)。 3)range 对索引进行范围检索。反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。
9. 【推荐】建组合索引的时候,区分度最高的在最左边。 正例:如果wherea=? and b=? ,a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使a的区分度更高,也必须把b放在索引的最前列。
10. 【参考】创建索引时避免有如下极端误解:
1)误认为一个查询就需要建一个索引。
2)误认为索引会消耗空间、严重拖慢更新和新增速度。
3)误认为唯一索引一律需要在应用层通过“先查后插”方式解决。
(三) SQL规约
1. 【强制】不要使用count(列名)或count(常量)来替代count(*),count(*)就是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
2. 【强制】count(distinctcol) 计算该列除NULL之外的不重复数量。注意 count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
3. 【强制】当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。 正例:可以使用如下方式来避免sum的NPE问题:SELECTIF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
4. 【强制】使用ISNULL()来判断是否为NULL值。注意:NULL与任何值的直接比较都为NULL。 说明: 1)NULL<>NULL的返回结果是NULL,而不是false。 2) NULL=NULL的返回结果是NULL,而不是true。 3) NULL<>1的返回结果是NULL,而不是true。
5. 【强制】 在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
6. 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。 说明:(概念解释)学生表中的student_id是主键,那么成绩表中的student_id则为外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,则为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
7. 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8. 【强制】数据订正时,删除和修改记录时,要先select,避免出现误删除,确认无误才能执行更新语句。
9. 【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。
10. 【参考】如果有全球化需要,所有的字符存储与表示,均以utf-8编码,那么字符计数方法注意:说明: SELECT LENGTH("轻松工作");返回为12 SELECT CHARACTER_LENGTH("轻松工作"); 返回为4 如果要使用表情,那么使用utfmb4来进行存储,注意它与utf-8编码的区别。
11. 【参考】 TRUNCATETABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
5、性能
1.【参考】判断和赋值,尽量写入循环体外面。
2.【参考】如果在数据库中查找某记录,尽量用sql精确定位,再取数据,而让sql返回所有数据,再进行遍历定位。
3.【参考】其他参考方向
执行性能
内存资源使用率
io性能(连接池mysql,memcache,redis等,或频繁请求远程http接口)
静态化(图片资源,html资源)