[TOC=1,4] ### 编码规范 ### ### 前言 ### 如果您已经决定向加入微擎社区并贡献代码,请详细阅读以下规范,并严格遵守。本规范由编程原则组成,融合并提炼了开发人员长时间积累下来的成熟经验,意在帮助开发者养成良好一致的编程风格。如果有需要,本文档会不定期更新。 ### 标准化的重要性和好处 ### 当一个软件项目尝试着遵守公共一致的标准时,可以使参与项目的开发人员更容易了解项目中的代码、弄清程序的状况。使新的参与者可以很快的适应环境,防止部分参与者出于节省时间的需要,自创一套风格并养成终生的习惯,导致其他人在阅读时浪费过多的时间和精力。而且在一致的环境下,也可以减少编码出错的机会。缺陷是由于每个人的标准不同,所以需要一段时间来适应和改变自己的编码风格,暂时性的降底了工作效率。从使项目长远健康的发展以及后期更高的团队工作效率来考虑暂时的工作效率降低是值得的,也是必须要经过的一个过程。标准不是项目成功的关键,但可以帮助我们在团队协作中有更高的效率并且更加顺利的完成既定的任务。 * 程序员可以了解任何代码,弄清程序的状况 * 新人可以很快的适应环境 * 防止新接触微擎的开发人员出于节省时间的需要,自创一套风格并养成终生的习惯 * 防止新接触PHP或微擎的开发人员一次次的犯同样的错误 * 在一致的环境下,开发人员们可以减少犯错的机会 * 程序员们有了一致的敌人 ### PHP编码规范与原则 ### #### 文件编码及编辑器格式 #### 请在开始编辑微擎代码之前调整你的编辑器设置。 **本条规范同样适用于 PHP、HTML、CSS、JavaScript** ##### **编码** 请调整您的编辑器文件编码为 UTF-8,并关闭 UTF-8 BOM((Byte Order Mark))的功能。**切记请不要使用windows自带的记事本编辑项目文件。** <br/>*注意:请确认你的编辑器不会有意或无意的保存文件为 UTF-8 BOM 格式, 否则可能造成微擎系统通信不正常。* ##### **缩进** 每个缩进的单位约定是一个Tab(禁止设置为空格替代,Tab宽度应表示为4个空白字符宽度),需每个参与项目的开发人员在编辑器(UltraEdit、EditPlus、ZendStudio等)中进行强制设定,以防在编写代码时遗忘而造成格式上的不规范。 ##### **换行** 微擎项目中使用Unix风格的换行符,即只有换行( LF 或 "\n" )没有回车( CR 或 "\r" ),请在你的编辑器内调整 #### 代码标记 PHP程序需使用 **<?php ?>** 来界定 PHP 代码,在HTML页面中嵌入纯变量时,可以使用 **<?php echo $variablename;?>** 这样的形式。 *注意:为了使代码规范化和标准化,微擎开发中禁止使用 **<? ?>** 和 **<?=$variablename?>** 这种速记形式。* ##### **注释** 注释是对于那些容易忘记作用的代码添加简短的介绍性内容。请使用 C 样式的注释“/* */”和标准 C++ 注释“//”。 **官方开发组请注意:每一个程序文件头部中,应放入相应HG控制字串 $sn$,以方便Mercurial提交时自动更新** 例如: ``` /** * 微擎接口初始化文件 * [WeEngine System] Copyright (c) 2013 WE7.CC * $sn$ */ ``` 开发留下的临时代码和调试代码必须添加注释,以免日后遗忘。所有临时性、调试性、试验性的代码,必须添加统一的注释标记“//debug”并后跟完整的注释信息,这样可以方便在程序发布和最终调试前批量检查程序中是否还存在有疑问的代码。 例如: ``` $num = 1; $flag = TRUE; //debug 这里不能确定是否需要对$flag进行赋值 if(empty($flag)) { //Statements } ``` #### 书写规则 #### ##### **大括号{}、if和switch** * 首括号与关键词同行,尾括号与关键字同列; * if 结构中,else 和 elseif 与前后两个大括号同行,左右各一个空格。另外,即便 if 后只有一行语句,仍然需要加入大括号,以保证结构清晰; * switch 结构中,通常当一个 case 块处理后,将跳过之后的 case 块处理,因此大多数情况下需要添加 break。break 的位置视程序逻辑,与 case 同在一行,或新起一行均可,但同一 switch 体中,break 的位置格式应当保持一致。 以下是符合上述规范的例子: ``` if ($condition) { //Statements } else { switch ($str) { case 'abc': $result = 'abc'; break; default: $result = 'unknown'; break; } } ``` ##### **运算符、小括号、空格、关键词和函数** * 每个运算符与两边参与运算的值或表达式中间要有一个空格; * 左括号“(” 应和函数关键词紧贴在一起,除此以外应当使用空格将“(”同前面内容分开; * 右括号“)”除后面是“)”,其他一律用空格隔开它们; * 除字符串中特意需要,一般情况下,在程序以及HTML中不出现两个连续的空格; * 任何情况下,PHP程序中不能出现空白的带有TAB或空格的行,即:这类空白行应当不包含任何TAB或空格。同时,任何程序行尾也不能出现多余的TAB或空格; * 每段较大的程序体,上、下应当加入空白行,两个程序块之间只使用1个空行,禁止使用多行。 * 程序块划分尽量合理,过大或者过小的分割都会影响他人对代码的阅读和理解。一般可以以较大函数定义、逻辑结构、功能结构来进行划分。少于15行的程序块,可不加上下空白行; * 说明或显示部分中,内容如含有中文、数字、英文单词混杂,应当在数字或者英文单词的前后加入空格。 根据上述原则,以下举例说明正确的书写格式: ``` $result = (($a + 1) * 3 / 2 + $num)) . 'Test'; $condition ? func1($var) : func2($var); $condition ? $long_statement : $another_long_statement; if ($flag) { //Statements //More than 15 lines } showmessage('请使用 restore.php 工具恢复数据。'); ``` ##### **函数定义** * 参数的名字和变量的命名规范一致; * 函数定义中的左小括号,与函数名紧挨,中间无需空格; * 开始的左大括号与函数定义为同一行,中间加一个空格,不要另起一行; * 具有默认值的参数应该位于参数列表的后面; * 函数调用与定义的时候参数与参数之间加入一个空格; * 必须仔细检查并切实杜绝函数起始缩进位置与结束缩进位置不同的现象; 例如,符合标准的定义: ``` function message($string, $operation, $key = '') { if ($flag) { //Statement } //函数体 } ``` 不符合标准的定义: ``` function authcode($string,$operation,$key = '') { //函数体 } ``` ##### **引号** 由于PHP中单引号和双引号具有不同的含义,因此在使用时有如下原则: * 在能使用单引号的情况下,禁止使用双引号。 * 字符串为固定值,不包含换号、制表等特殊转义时,需使用单引号。 * 字符串作为数据索引时,需使用单引号。 * 字符串不需要带入变量,需使用单引号。 * 数据库SQL语句一律使用双引号,SQL语句中所有数据必须加单引号,无论数值还是字串,以避免可能的注入漏洞和SQL错误。 ``` $sql = "UPDATE " . tablename('members') . " SET adminid='1' WHERE AND adminid='2'"; ``` ##### **数据库操作** 为保证数据操作安全,数据库操作有以下处理及书写原则: * 所有数据库操作时必须使用PDO操作 * 所有SQL查询关键字大写,方便代码审查 * 所有SQL对象(表名,字段名,索引名等)必须用反引号包括 * 所有编码参数查询,必须使用PDO的参数绑定机制处理 * 不能绑定参数处理的查询(唯一的例外是 IN () 语句),必须处理好变量检测及字符串转义 这是一个完整的数据库操作示例: ``` $tids = array(); if (!empty($_GPC['select'])) { foreach ($_GPC['select'] as $t) { $tids[] = intval($t); //---- 必须将输入参数转换为无安全隐患的格式,数字列必须转换为数字列,字符串列必须使用 addslashes } } if (!empty($tids)) { $sql = 'SELECT * FROM ' . tablename('trades') . ' WHERE `username`=:username AND `tid` IN (' . implode($tids) . ')'; $pars = array(); $pars[':username'] = $_GPC['username']; $trades = pdo_fetchall($sql, $pars); } ``` #### 命名原则 命名是程序规划的核心。古人相信只要知道一个人真正的名字就会获得凌驾于那个人之上的不可思议的力量。只要你给事物想到正确的名字,就会给你以及后来的人带来比代码更强的力量。 名字就是事物在它所处的生态环境中一个长久而深远的结果。总的来说,只有了解系统的程序员才能为系统取出最合适的名字。如果所有的命名都与其自然相适合,则关系清晰,含义可以推导得出,一般人的推想也能在意料之中。 就一般约定而言,类、函数和变量的名字应该总是能够描述让代码阅读者能够容易的知道这些代码的作用。形式越简单、越有规则,就越容易让人感知和理解。应该避免使用模棱两可,晦涩不标准的命名。 ##### **变量、函数名** * 变量、函数名一律为小写格式; * 以标准计算机英文为蓝本,杜绝一切拼音、或拼音英文混杂的命名方式; * 变量命名只能使用项目中有据可查的英文缩写方式,例如可以使用 $data 而不可使用 $data1、$data2 这样容易产生混淆的形式,应当使用 $trade、$product 这样一目了然容易理解的形式; * 可以合理的对过长的命名进行缩写,例如 $bio($biography),$tpp($threadsPerPage),前提是英文中有这样既有的缩写形式,或字母符合英文缩写规范; * 必须清楚所使用英文单词的词性,在权限相关的范围内,大多使用 $allowXxx 或 $isXxx 的形式,前者后面接动词,后者后面接形容词。 * 变量名标识符不应当使用下划线“_”进行分割,函数名根据需要可按照模块单元名称使用下划线添加前缀,以实现命名空间的效果。但每个函数名标识符尽量避免出现三个以上的下划线。 ##### **类和接口名** * 类和接口的命名采用混合大小写字母的Pascal命名法 * 具体描述为:首字母大写,后续的每个单词也需要大写首字母。如:MySQLConnector、CacheProvider。 * 接口的命名与类相似,但需要以大写字母“I”开头,以作区分。 * 对象成员的命名则采用混合大小写字母的Camel命名法 * 具体描述为:公有成员及保护成员应首字母小写,后续的每个单词首字母大写 * 私有成员除需保持共有成员的命名方式外,还需要以下划线“_”开头,以作区分。 ##### **JavaScript代码** * JavaScript中类和全局对象应使用混合大小写字母的Pascal命名法 * 具体描述为:首字母大写,后续的每个单词也需要大写首字母。如:MySQLConnector、CacheProvider。 * JavaScript中变量、函数名应采用混合大小写字母的Camel命名法。 * 具体描述为首字母小写,后续的每个单词首字母大写 ##### **常量** * 常量应该总是全部使用大写字母命名,必要的情况下,可使用划线来分隔单词; * PHP 的内建值 true、false 和null必须全部采用小写字母书写。 #### 变量的初始化与逻辑检查 任何变量在进行累加、直接显示或存储前必需进行初使化 ,例如: ``` $number = 0; // 数值型初始化 $string = ''; // 字符串初始化 $array = array(); // 数组初始化 ``` * 判断一个无法确定(不知道是否已被赋值)的变量时,可用empty()或isset(),而不要直接使用if($switch)的形式。 * empty()和isset()的区别为:请参阅PHP手册。 * 如果已经使用 unset() 释放了一个变量之后,它将不再是 isset()。若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。同时要注意的是一个 NULL 字节("\0")并不等同于 PHP 的 NULL 常数。 * 判断一个变量是否为数组,请使用is_array(),这种判断尤其适用于对数组进行遍历的操作,例如foreach(),因为如果不事先判断,foreach()会对非数组类型的变量报错; * 判断一个数组元素是否存在,可使用isset($array['key']),也可使用empty()。 #### 安全性 * PHP中的变量不并不像C语言那样需要事先声明,解释器会在第一次使用时自动创建他们,同样类型也不需要指定,解释器会根据上下文环境自动确定。从开发人员的角度来看,这无疑是一种极其方便的处理方法。一个变量被创建了,就可以在程序中的任何地方使用。这导致的结果就是开发人员工经常不注意初始化变量。因此,为了提高程序的安全性,我们不能相信任何没有明确定义的变量。所有的变量在定义使用前要初使化以防止恶意构造提交的变量覆盖程序中使用的变量。 * 不要相信任何客户端提交的数据是安全的。(包括:$_GET、$_POST、$_COOKIE、$_FILES、$_SERVER、$_REQUEST) #### 其他细节问题 ##### **包含调用** * 包含调用程序文件,请尽量使用require,以保证效率。必要情况下,如难以避免的重复调用时可以使用require_once。 * 包含调用缓存文件,由于缓存文件无法保证100%正确打开,请使用 include_once 或 include。在必要时,可以使用 @include_once 或 @include 的方式,以忽略错误提示; * 包含和调用代码中,须以 IA_ROOT . '/'开头,应避免直接写程序文件名(例如:require_once 'x.php';)的做法; * 所有被包含和调用的程序文件,包括但不限于程序、缓存或模板,通常其不能被直接URL请求。程序通过在 IA_ROOT/source/bootstrap.inc.php中定义一个标记性常量IN_IA,来判断程序是否被合法调用。因此,在除了IA_ROOT/source/bootstrap.inc.php以外的任何一个被包含和调用的程序文件中,需要包含以下内容,以使得访问者无法直接通过URL请求该文件: <code php> if (!defined('IN_IA')) { exit('Access Denied'); } </code> ##### **错误报告级别** * 在软件开发和调试阶段,修改 /data/config.php 中的 $config['setting']['development'] = 1;,打开错误报告以便能够报告程序中所有的错误、警告和提示信息,以帮助开发者检查和核对代码,避免大多数安全性问题和逻辑错误、拼写错误。 * 在软件发布时,请修改 /data/config.php 中的 $config['setting']['development'] = 0;,来关闭错误报告,以利于用户使用并将无谓错误提示信息降至最低。 ##### **其他注意要点** * 正则表达式操作请使用perl兼容正则表达式,即preg_ 系列的函数。以提升效率。 * 尽量使用高版本的函数。 * 本项目不使用命名空间,因此需要按照传统的方式使用文件名和文件夹来规划代码结构。 ### 数据库设计 ### #### 表和字段命名 * 表和字段的命名以[PHP编码中的命名规范]()为基本准则。 * 所有数据表名称,只要其名称是可数名词,则必须以复数方式命名,例如:ims_members(用户表)、ims_rules(规则定义表); * 存储多项内容的字段,或代表数量的字段,也应当以复数方式命名,例如:params(parameters,参数个数)、views(查看次数)、replies(回复次数)。 * 当几个表间的字段有关连时,要注意表与表之间关联字段命名的统一,如 ims_rule_keywords表中的rid与ims_rules表中的rid。 代表id自增量的字段,通常用以下几种形式: * 最常用的核心id,或经常在URL中进行调用的,尽量用简写的形式,例如rid、weid、uid; * 有功能性作用,URL中偶尔用到的id,使用全称的形式,例如pluginid; * 没有功能性作用,只为管理和维护方便而设的id,可以使用全称的形式,也可只将其命名为id。 所有与表、字段相关的命名,请参考微擎系统现有字段的命名方式,以保证命名的系统性和统一性。 #### 字段结构 * 基于效率的考虑,所有字段均不能为空,即全部NOT NULL,可以设置默认值来代替。 * 预计不会存储非负数的字段,例如各项id、统计数等,必须设置为UNSIGNED类型。UNSIGNED类型比非UNSIGNED类型所能存储的正整数范围大一倍,因此能获得更大的数值存储空间。 * 储开关、选项数据的字段,通常使用tinyint(1)非UNSIGNED类型,少数情况也可能使用enum()结果集的方式。tinyint作为开关字段时,通常1为打开;0为关闭;-1为特殊数据,例如N/A(不可用),高于1的为特殊结果或开关二进制数组合。 * 任何类型的数据表,字段空间应当本着足够用,不浪费的原则。MEMORY/HEAP类型的表中,尤其要注意规划节约使用存储空间,这将节约更多内存。 #### SQL语句 * 所有SQL语句中,除了表名、字段名称以外,全部语句和函数均需大写,应当杜绝小写方式或大小写混杂的写法。例如select * from ims_members;是不符合规范的写法。 * 很长的SQL语句应当有适当的断行,依据JOIN、FROM、ORDER BY等关键字进行界定。 * 通常情况下,在对多表进行操作时,要根据不同表名称,对每个表指定一个1~2个字母的缩写,以利于语句简洁和可读性。 * 参考[PHP编码规范中的数据库操作指南]() #### 运算与检索 * 数值运算一般比字符串运算更快。例如比较运算,可在单一运算中对数进行比较。而串运算涉及几个逐字节的比较,如果串更长的话,这种比较还要多。 * 如果串列的值数目有限,应该利用普通整型或emum类型来获得数值运算的优越性。 * 更小的字段类型永远比更大的字段类型处理要快得多。对于字符串,其处理时间与串长度直接相关。一般情况下,较小的表处理更快。对于定长表,应该选择最小的类型,只要能存储所需范围的值即可。例如,如果mediumint够用,就不要选择bigint。对于可变长类型,也仍然能够节省空间。一个TEXT 类型的值用2 字节记录值的长度,而一个LONGTEXT 则用4字节记录其值的长度。如果存储的值长度永远不会超过64KB,使用TEXT 将使每个值节省2字节。 #### 性能优化 主要分为:表结构优化,索引优化,查询优化。取决于开发人员经验和个人能力。不详述。 ### 模板及界面设计原则 #### 标记及界面书写规则 #### ##### **模板标记**#### * 模板方案采用通用的PHP编译模板方案,模板标记为`<!—{}-->或{},建议使用加入注释的模板标记<!—{}-->`,这样可以使得模板可读性更好。 * 在HTML标记中使用的逻辑体无需使用HTML注释`(<!-- -->),即<input type=”text”{if xxx} value=”1”{/if} />` ##### **HTML** * 所有HTML标记参数赋值需使用双引号包含,例如,应当使用`<input type=”text” name=”test” value=”ok” />`,而绝对不能使用`<input type=text name=test value=ok />。` * 开始标记和结束标记必须成对出现 * 所有标记名称,属性名称,CSS必须全部小写 * HTML元素要求尽量指定title,以利于SEO。 * 尽量避免使用可视化编辑工具通过拖拽进行撰写或修改,可能会因此造成界面错乱 ##### **变量** * 模板中使用的变量,依据作用和出现位置不同,分为几种方式: * 逻辑体中,即被 { } 包围起来的部分,例如 {if isset($array['key'])} 这种形式,其中的变量书写规范与PHP程序中完全一致; * 模板内容中,直接输入的变量内容,需要使用 {} 将变量括起来,以免出现模板编译错误; * 直接输入内容为数组的,需要加单引号“'”来讲索引包括起来。如下: * 数组的下标为变量的,正确的写法为{$trade['details']['title']}; * 其他变量十分复杂的情况,为保证兼容性。应使用 {php echo $var;}。如:{php echo (!empty($row['dateline']) ? date('Y-m-d', $row['dateline']) : '');} ##### **缩进** * 在微擎系统的*.html模板文件中,应尽量按照HTML本身的缩进风格缩进,另外如果存在逻辑语句,逻辑语句也应该保持对应的缩进结构。缩进采用TAB方式,不使用空格作为缩进符号。例如: ``` <!--{loop $trades $trade}--> <table class="tb"> <tr> <td>{$trade[username]}</td> </tr> </table> <!--{/loop}--> ``` #### CSS书写规则 样式表书写应该遵循以下原则: * 编码方式要求采用utf-8,即:<meta http-equiv="content-type" content="text/html; charset=utf-8" /> * CSS尽量使用外部链接方式,尽可能压缩CSS文件的数量,尽可能的避免使用行内样式。 * 分区域注释。 * CSS定义不要使花括号换行。 * 类,ID命名原则:CSS命名应全部采用小写字母,多个单词间应使用连字符“-”链接。应遵循通用的CSS命名习惯:header,content,container,footer,column,message,btn等等。 * 结构化设计CSS * 标识类图片,应该使用CSS Spirit方案解决(即:CSS贴图定位),以尽量减少请求开销,增强用户体验。 * 切图命名,应该按照图片作用,或者图片位置等来命名,尽量做到见名知意。命名原则同CSS类,ID命名 #### JavaScript 书写规则 脚本书写应该遵循以下原则: * 编码方式要求采用utf-8; * 缩进及代码格式应与[PHP编码书写规则]()相同; * 结构化设计JavaScript #### **文件及目录** #### 文件命名 * 所有包含PHP代码的程序文件或半程序文件,应以小写.php作为扩展名,而不要使用.phtml、.php3、.inc、.class等作为扩展名。 * 目录中如果包含多个词组,用中横线(-)分隔 ##### **普通程序** 能够被URL直接调用的程序,例如home.php、index.php、forum.php,直接使用程序名+.php的方式命名 ##### **函数库和类库程序** 分别以小写 xxxx.func.php 和 xxxx.class.php的格式命名书写。函数库和类库程序只能被其他程序引用,而不能独立运行。其中不能包含任何流程性的、不属于任何函数或类的程序代码。 ##### **模板源文件** 以小写.html作为扩展名。模板源文件按照[微擎模板机制]()编码规则进行编写,不是可以执行的程序,而只能被微擎模板模板编译器所解析。 #### 空目录索引 * 请在所有不包含普通程序(即能够被URL直接调用的程序)的目录中放置一个1字节的index.html文件,内容为一个空格。几乎除微擎系统根目录以外,所有目录都属于这一类型,因此开发者需要在这些目录全部放入空index.html文件,以避免当http服务器的Directory Listing打开时,服务器文件被索引和列表。 * 附件目录等敏感目录,要在程序中实现相应功能,当新建下级目录时,必须自动写入一个空的index.html文件,以避免新建目录被索引的问题。