1.1 Discuz! 的编码规范 第1部分
本篇涵盖:前言 ~ SQL语句
前言
本规范由编程原则组成,融合并提炼了开发人员长时间积累下来的成熟经验,意在帮助形成良好一致的编程风格。
适用范围
如无特殊说明,以下规则要求完全适用于Discuz!项目,同时也可大部分适用于腾讯云旗下其他PHP项目。
标准化的重要性和好处
当一个软件项目尝试着遵守公共一致的标准时,可以使参与项目的开发人员更容易了解项目中的代码、弄清程序的状况。使新的参与者可以很快的适应环境,防止部分参与者出于节省时间的需要,自创一套风格并养成终生的习惯,导致其它人在阅读时浪费过多的时间和精力。而且在一致的环境下,也可以减少编码出错的机会。缺陷是由于每个人的标准不同,所以需要一段时间来适应和改变自己的编码风格,暂时性的降底了工作效率。从使项目长远健康的发展以及后期更高的团队工作效率来考虑暂时的工作效率降低是值得的,也是必须要经过的一个过程。标准不是项目成功的关键,但可以帮助我们在团队协作中有更高的效率并且更加顺利的完成既定的任务。
- 程序员可以了解任何代码,弄清程序的状况
- 新人可以很快的适应环境
- 防止新接触PHP的人出于节省时间的需要,自创一套风格并养成终生的习惯
- 防止新接触PHP的人一次次的犯同样的错误
- 在一致的环境下,人们可以减少犯错的机
- 程序员们有了一致的敌人
PHP编码规范与原则
代码标记
PHP程序可以使用<?php ?>来界定 PHP 代码,在HTML页面中嵌入纯变量时,可以使用<?php echo $variablename;?>这样的形式。
注意:为了使代码进一步规范化和标准化,从Discuz! X2版本起开始禁用<? ?>和<?=$variablename?>这种速记形式。
注释
注释是对于那些容易忘记作用的代码添加简短的介绍性内容。请使用 C 样式的注释/* */和标准 C++ 注释//。 在Discuz!每一个程序文件头部中,应放入相应SVN控制字串,以方便SVN提交时自动更新:
- $Id$
例如:
/**
* [Discuz!] (C)2001-2099 Comsenz Inc.
* This is NOT a freeware, use is subject to license terms
*
* $Id$
*/
程序开发中难免留下一些临时代码和调试代码,此类代码必须添加注释,以免日后遗忘。所有临时性、调试性、试验性的代码,必须添加统一的注释标记//debug并后跟完整的注释信息,这样可以方便在程序发布和最终调试前批量检查程序中是否还存在有疑问的代码。例如:
$num = 1;
$flag = TRUE; //debug 这里不能确定是否需要对$flag进行赋值
if(empty($flag)) {
//Statements
}
书写规则
缩进
每个缩进的单位约定是一个TAB(4个空白字符宽度),需每个参与项目的开发人员在编辑器(UltraEdit、EditPlus、Zend Studio等)中进行强制设定,以防在编写代码时遗忘而造成格式上的不规范。 本缩进规范适用于PHP、JavaScript中的函数、类、逻辑结构、循环等。
大括号{}、if和switch
-
首括号与关键词同行,尾括号与关键字同列;
-
if结构中,else和elseif与前后两个大括号同行,左右各一个空格。另外,即便if后只有一行语句,仍然需要加入大括号,以保证结构清晰; -
switch结构中,通常当一个case块处理后,将跳过之后的case块处理,因此大多数情况下需要添加break。break的位置视程序逻辑,与case同在一行,或新起一行均可,但同一switch体中,break的位置格式应当保持一致。
以下是符合上述规范的例子:
if($condition) {
switch($var) {
case 1: echo 'var is 1'; break;
case 2: echo 'var is 2'; break;
default: echo 'var is neither 1 or 2'; break;
}
} 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 authcode($string, $operation, $key = '') {
if($flag) {
//Statement
}
//函数体
}
不符合标准的定义:
function authcode($string,$operation,$key = '')
{
//函数体
}
引号
PHP中单引号和双引号具有不同的含义,最大的几项区别如下:
-
单引号中,任何变量(
$var)、特殊转义字符(如\t、\r、\n等)不会被解析,因此PHP的解析速度更快,转义字符仅仅支持\'和\这样对单引号和反斜杠本身的转义; -
双引号中,变量(
$var)值会代入字符串中,特殊转义字符也会被解析成特定的单个字符,还有一些专门针对上述两项特性的特殊功能性转义,例如\$和{$array['key']}。这样虽然程序编写更加方便,但同时PHP的解析也很慢; -
数组中,如果下标不是整型,而是字符串类型,请务必用单引号将下标括起,正确的写法为
$array['key'],而不是$array[key],因为不正确的写法会使PHP解析器认为key是一个常量,进而先判断常量是否存在,不存在时才以key作为下标带入表达式中,同时出发错误事件,产生一条Notice级错误。 -
因此,在绝大多数可以使用单引号的场合,禁止使用双引号。依据上述分析,可以或必须使用单引号的情况包括但不限于下述:
-
字符串为固定值,不包含
\t等特殊转义字符; -
数组的固定下标,例如
$array['key']; -
表达式中不需要带入变量,例如
$string = 'test';,而非$string = "test$var";;
-
-
例外的,在正则表达式(用于
preg_系列函数)中,Discuz!全部使用双引号,这是为了人工分析和编写的方便,并保持正则表达式的统一,减少不必要的分析混淆。 -
数据库SQL语句中,所有数据必须加单引号,无论数值还是字串,以避免可能的注入漏洞和SQL错误。正确的写法为:
UPDATE cdb_members SET adminid='1' WHERE username='$admin' AND adminid='2';
- 所有数据在插入数据库之前,均需要进行
addslashes()处理,以免特殊字符未经转义在插入数据库的时候出现错误。Discuz!中所有通过GET,POST,FILE,取得的变量默认情况下已经使用了addslashes()进行了转义,不必重复进行。如果数据处理必要(例如用于直接显示),可以使用stripslashes()恢复,但数据在插入数据库之前必须再次进行转义。缓存文件中,一般对缓存数据的值采用addcslashes($string, '\'\')进行转义。
Discuz!语言问题
在功能设计阶段,当需要使用中文或者给出用户中文提示的时候可以直接在程序中插入简体中文文字,待程序整理和测试阶段由专人进行语言分离工作;
功能说明性语言、短语一般不使用"。"或者其他标点符号作为结束,文字中的标点符号,应使用全角。但注意:由于中英文模板与语言包问题,功能说明性的语言中,冒号使用半角":",而不使用全角;。
提示信息(用showmessage()和cpmsg()显示的)和使用技巧(如后台管理界面中的tips)等大篇幅文字中,当中的标点符号应当遵循中文语法规则,以使用全角中文标点为主,结尾应当加入全角句号。
尽管程序语言包是在最后整理阶段才进行提取,但程序中直接写出的中文内容,也应充分保证书面语的特征:语言通顺、简洁、得体、无歧义。应彻底杜绝认为直接写提示语言是临时性操作的想法,反复推敲,并总结之前提示语言的特征规范,加以应用。良好的语言文字表达能力,是每个优秀程序员必须具备的基本素质之一。
命名原则
命名是程序规划的核心。古人相信只要知道一个人真正的名字就会获得凌驾于那个人之上的不可思议的力量。只要你给事物想到正确的名字,就会给你以及后来的人带来比代码更强的力量。
名字就是事物在它所处的生态环境中一个长久而深远的结果。总的来说,只有了解系统的程序员才能为系统取出最合适的名字。如果所有的命名都与其自然相适合,则关系清晰,含义可以推导得出,一般人的推想也能在意料之中。
就一般约定而言,类、函数和变量的名字应该总是能够描述让代码阅读者能够容易的知道这些代码的作用。形式越简单、越有规则,就越容易让人感知和理解。应该避免使用模棱两可,晦涩不标准的命名。
变量、对象、函数名
-
变量、对象、函数名一律为小写格式,除非必要,单词之间一般不使用下划线"_"进行分割;
-
以标准计算机英文为蓝本,杜绝一切拼音、或拼音英文混杂的命名方式;
-
变量命名只能使用项目中有据可查的英文缩写方式,例如可以使用
$data而不可使用$data1、$data2这样容易产生混淆的形式,应当使用$threaddata、$postdata这样一目了然容易理解的形式; -
可以合理的对过长的命名进行缩写,例如
$bio($biography),$tpp($threadsPerPage),前提是英文中有这样既有的缩写形式,或字母符合英文缩写规范; -
必须清楚所使用英文单词的词性,在权限相关的范围内,大多使用
$allow***或$is***的形式,前者后面接动词,后者后面接形容词。
常量
-
常量应该总是全部使用大写字母命名,少数特别必要的情况下,可使用划线来分隔单词;
-
PHP 的内建值
TRUE、FALSE和NULL必须全部采用大写字母书写。
变量的初始化与逻辑检查
任何变量在进行累加、直接显示或存储前必需进行初使化,例如:
$number = 0; //数值型初始化
$string = ''; //字符串初始化
$array = array(); //数组初始化
- 判断一个无法确定(不知道是否已被赋值)的变量时,可用
empty()或isset(),而不要直接使用if($switch)的形式,除非你确切的知道此变量一定已经被初始化并赋值。
empty()和isset()的区别为:
bool empty(mixed var)-
如果
var是非空或非零的值,则empty()返回FALSE。换句话说,""、0、"0"、NULL、FALSE、array()、var $var; 以及没有任何属性的对象都将被认为是空的,如果var为空,则返回TRUE。 -
bool isset(mixed var[, mixed var[, ...]]) -
如果
var存在则返回TRUE,否则返回FALSE。 -
如果已经使用
unset()释放了一个变量之后,它将不再是isset()。若使用isset()测试一个被设置成NULL的变量,将返回FALSE。同时要注意的是一个NULL字节("\0")并不等同于PHP的NULL常数。 -
判断一个变量是否为数组,请使用
is_array(),这种判断尤其适用于对数组进行遍历的操作,例如foreach(),因为如果不事先判断,foreach()会对非数组类型的变量报错; -
判断一个数组元素是否存在,可使用
isset($array['key']),也可使用empty(),两者异同见上。
安全性
PHP中的变量不并不像C语言那样需要事先声明,解释器会在第一次使用时自动创建他们,同样类型也不需要指定,解释器会根据上下文环境自动确定。从开发人员的角度来看,这无疑是一种极其方便的处理方法。一个变量被创建了,就可以在程序中的任何地方使用。这导致的结果就是开发人员工经常不注意初始化变量。因此,为了提高程序的安全性,我们不能相信任何没有明确定义的变量。所有的变量在定义使用前要初使化以防止恶意构造提交的变量覆盖程序中使用的变量。
兼容性
-
代码设计应当兼顾PHP 高低版本的特性,当前,应仍然以
PHP 5.3作为最低通过平台,尽量不使用高版本PHP 新增的函数、常数或者常量。如果使用只在高版本才具备的函数,必须对其进行二次封装,自动判断当前PHP版本,并自行编写低版本下的兼容代码; -
对于个别函数,参数要求或者代码要求应当以较为严格的PHP版本为准;
-
除非必要,不要使用PHP扩展模块中的函数。使用时应当加入必要的判断,当服务器环境不支持此函数的时候,进行必要的处理。文档和程序中的功能说明中,也应加上兼容性说明。
代码重用
代码的有效重用可以减少效率的损失与资源的浪费。在开发软件项目时为了避免重复劳动和浪费时间。开发人员应尽量提高现有代码的重用率,同时将更多的精力用在新技术的应用和新功能的创新开发上面。
-
在需要多次使用代码,并且对于您希望实现的任务没有可用的内置 PHP 函数时,不吝啬定义函数或类。开发者须根据功能、调用情况,将函数和类放置于相应的
function或class中。超过3行,实现相同功能的程序切勿在不同程序中多次出现,这是无法容忍和回避的问题; -
在任何时候都不要出现同一个程序中出现两段或更多的相似代码或相同代码,即便在不同程序中,也应尽力避免。开发者应当总是有能力找到避免代码大段(超过10行)重复或类似的情况。
需要强调的是,本部分虽然篇幅较短,但却是十分需要经验,并将花费开发者大量时间和精力去进行优化的部分,任何产品开发者必须时刻清楚和理解代码重用的重要性和必要性,切实在增强产品效率、逻辑性和可读性上下功夫,这是一名优秀软件开发者所必须具备的基本素质。
其他细节问题
包含调用
-
包含调用程序文件,请全部使用
require_once,以避免可能的重复包含问题; -
包含调用缓存文件,由于缓存文件无法保证100%正确打开,请使用
include_once或include。在必要时,可以使用@include_once或@include的方式,以忽略错误提示; -
包含和调用代码中,须以
"./"或DISCUZ_ROOT.'./'开头,应避免直接写程序文件名(例如:require_once 'x.php';)的做法; -
所有被包含和调用的程序文件,包括但不限于程序、缓存或模板,通常其不能被直接URL请求。
Discuz!通过在./source/class/class_core.php中定义一个标记性常量IN_DISCUZ,来判断程序是否被合法调用。因此,在除了./source/class/class_core.php以外的任何一个被包含和调用的程序文件中,需要包含以下内容,以使得访问者无法直接通过URL请求该文件:
if(!defined('IN_DISCUZ')) {
exit('Access Denied');
}
错误报告级别
-
在软件开发和调试阶段,请使用
error_reporting(E_ALL);作为默认的错误报告级别,此级别最为严格,能够报告程序中所有的错误、警告和提示信息,以帮助开发者检查和核对代码,避免大多数安全性问题和逻辑错误、拼写错误。error_reporting()可以在config/config_global.php中添加一行$_config['debug'] = 1;debug值可以在0~2之前取值,数值越大报错等级越高。 -
在软件发布时,请使用
error_reporting(E_ERROR | E_WARNING | E_PARSE);作为默认的错误报告级别,以利于用户使用并将无谓错误提示信息降至最低。
数据库设计
字段
表和字段命名
表和字段的命名以前面《4.4命名原则》的约定为基本准则。 所有数据表名称,只要其名称是可数名词,则必须以复数方式命名,例如:cdb_members(用户表)、cdb_posts(帖子表);存储多项内容的字段,或代表数量的字段,也应当以复数方式命名,例如:params(parameters, vars, ...)、views(...)、replies(...)。 当几个表间的字段有关连时,要注意表与表之间关联字段命名的统一,如cdb_threads表中的tid与cdb_posts表中的tid。 代表id自增量的字段,通常用以下几种形式: - 最常用的核心id,或经常在URL中进行调用的,尽量用简写的形式,例如tid、pid、uid;
-
有功能性作用,URL中偶尔用到的
id,使用全称的形式,例如pluginid; -
没有功能性作用,只为管理和维护方便而设的
id,可以使用全称的形式,也可只将其命名为id。
所有与表、字段相关的命名,请务必大量参考Discuz!现有字段的命名方式,以保证命名的系统性和统一性。
字段结构
-
允许
NULL值的字段,数据库在进行比较操作时,会先判断其是否为NULL,非NULL时才进行值的比对。因此基于效率的考虑,所有字段均不能为空,即全部NOT NULL; -
预计不会存储非负数的字段,例如各项
id、发帖数等,必须设置为UNSIGNED类型。UNSIGNED类型比非UNSIGNED类型所能存储的正整数范围大一倍,因此能获得更大的数值存储空间; -
存储开关、选项数据的字段,通常使用
tinyint(1)非UNSIGNED类型,少数情况也可能使用enum()结果集的方式。tinyint作为开关字段时,通常1为打开;0为关闭;-1为特殊数据,例如N/A(不可用);高于1的为特殊结果或开关二进制数组合(详见Discuz!中相关代码); -
MEMORY/HEAP类型的表中,要尤其注意规划节约使用存储空间,这将节约更多内存。例如cdb_sessions表中,就将IP地址的存储拆分为4个tinyint(3) UNSIGNED类型的字段,而没有采用char(15)的方式; -
任何类型的数据表,字段空间应当本着足够用,不浪费的原则,数值类型的字段取值范围见下表:
| 字段类型 | 存储空间(b) | UNSIGNED | 取值范围 |
|---|---|---|---|
| tinyint | 1 | 否 | -128~127 |
| 是 | 0~255 | ||
| smallint | 2 | 否 | -32768~32767 |
| 是 | 0~65535 | ||
| mediumint | 3 | 否 | -8388608~8388607 |
| 是 | 0~16777215 | ||
| int | 4 | 否 | -2147483648~2147483647 |
| 是 | 0~4294967295 | ||
| bigint | 8 | 否 | -9223372036854775808~9223372036854775807 |
| 是 | 0~18446744073709551615 |
SQL语句
-
所有SQL语句中,除了表名、字段名称以外,全部语句和函数均需大写,应当杜绝小写方式或大小写混杂的写法。例如
select * from cdb_members;是不符合规范的写法。 -
很长的SQL语句应当有适当的断行,依据
JOIN、FROM、ORDER BY等关键字进行界定。 -
通常情况下,在对多表进行操作时,要根据不同表名称,对每个表指定一个1~2个字母的缩写,以利于语句简洁和可读性。
如下的语句范例,是符合规范的:
$query = $db->query("SELECT s.*, m.* FROM {$tablepre}sessions s, {$tablepre}members m WHERE m.uid=s.uid AND s.sid='$sid');
整理自 Discuz! 官方开放平台技术文档(open.dismall.com),由 WorkBuddy 整理发布。
