代码改变世界

PHP编码规范改进版

2010-08-19 10:07  韩龙  阅读(803)  评论(0编辑  收藏  举报

 

 

1 介绍

       为了更好的提高开发的工作效率,保证开发的有效性和合理性,并最大程度的提高代码的可读性和可重复利用性,制订此规范。本规范包含了PHP开发时程序编码中代码缩进规则、控制结构、函数调用、函数定义、注释、包含代码、PHP标记、文件头的注释块、CVS标记、URL样例、常量命名等方面的规则。

 

1.1 标准化的重要

标准化问题的确在某些方面上让每个人头痛,让人人都觉得大家处于同样的境地。但是这往往是因为对标准化的误解。标准化不是束缚人,他是为了在大型开发过程中,能够提供项目整体的质量与开发进度。也许有人会说,恩,我不喜欢束缚,这么做会约束我的创造力。如果说你的创造力仅仅在命名,格式(而这恰恰是规范的主要内容)这样的表面层次,那么把你的创造力扔掉吧,没人喜欢这样的创造力。

1.2 优点

当一个项目尝试着遵守公用的标准时,会有以下好处:

· 程序员可以了解任何代码,弄清程序的状况

· 新人可以很快的适应环境

· 防止新接触php的人出于节省时间的需要,自创一套风格并养成终生的习惯

· 防止新接触php的人一次次的犯同样的错误

· 在一致的环境下,人们可以减少犯错的机会

· 程序员们有了一致的敌人

1.3 改进

规范并不是一成不变的,如果你觉得规范缺少东西或者有些不合适的地方,提出来,也许项目管理者会考虑加入,记住,任何项目都取决于团队合作的努力。

1.4 实施

一般情况下,规范的实施是由技术负责人或者项目经理执行和监督的。但是最好的方法还是在开发团队中达成共识,变成一种文化的存在。

2 命名规则

命名是程序规划的核心。名字就是事物在它所处的生态环境中一个长久而深远的结果。总的来说,只有了解系统的程序员才能为系统取出最合适的名字。如果所有的命名都与其自然相适合,则关系清晰,含义可以推导得出,一般人的推想也能在意料之中。
如果你发觉你的命名只有少量能和其对应事物相匹配的话, 最好还是重新好好再看看你的设计。

2.1 文件命名

文件名采用C GNU的惯例,所有的字母使用小写字母,使用`_`分割单词。

例如:

news_list.php

 

文件命名上有些几乎是约定俗成的单词,比如admin前缀表示是后台管理的文件名,inc.php后缀则表示包含文件或者类库文件,class.php后缀表示类库文件等等。config.php表示配置文件等。

例如:

admin_user.php 

data.inc.php

user.class.php

 

 

2.2 类命名

· 使用大写字母作为词的分隔,其他的字母均使用小写
· 名字的首字母使用大写
· 不要使用下划线(`_`)
例如
class NameOneTwo
class Name

 

注意:

· 在为类(class )命名前首先要知道它是什么。如果看到类名,你还是想不起这个类是什么的话,那么你的设计就还做的不够好。
· 超过三个词组成的混合名是容易造成系统各个实体间的混淆,再看看你的设计,尝试使用(CRC Session card)看看该命名所对应的实体是否有着那么多的功用。
· 对于派生类的命名应该避免带其父类名产生关系,一个类的名字只与它自身有关,和它的父类叫什么无关。
· 有些后缀名是几乎是约定俗成的,例如:如果你的系统使用了代理(agent ),那么就把某个部件命名为“下载代理”(DownloadAgent)用以真正的传送信息。

    当然,这些约定俗成的词汇不会自动出现,需要小组人员的不断总结。

 

2.2.1 缩写词不要全部使用大写字母

 

· 无论如何,当遇到以下情况,你可以用首字母大写其余字母小写来代替全部使用大写字母的方法来表示缩写词。
使用: GetHtmlStatistic.
不使用: GetHTMLStatistic.
理由
· 当命名含有缩略词时,人们似乎有着非常不同的直觉。统一规定是最好,这样一来,命名的含义就完全可以预知了。
举个NetworkABCKey的例子,注意C是应该是ABC里面的C还是key里面的C,这个是很令人费解的。有些人不在意这些,其他人却很讨厌这样。所以你会在不同的代码里看到不同的规则,使得你不知道怎么去叫它。
例如
class FluidOz // 不要写成 FluidOZ
class GetHtmlStatistic // 不要写成 GetHTMLStatistic

2.3 类库命名

 

· 目前命名空间正在越来越广泛的被采用,以避免不同厂商和团体类库间的类名冲突。
· 当尚未采用命名空间的时候,为了避免类名冲突,一般的做法是在类名前加上独特的前缀,两个字符就可以了,当然多用一些会更好。
例如
John Johnson的数据结构类库可以用Jj做为前缀,如下:
class JjLinkList
{
}
另一种折中方式是建立包含类库目录(事实上Java也是这么做的),以不通的目录代表不同的命名空间。
例如
Microsoft的数据库相关类库可以在:
/classes/com/Microsoft/ Database/DbConn.php
Apache的数据库相关类库可在:
/classes/org/apache/Database/DbConn.php

 

2.4 类属性命名

· 类属性采用C GNU的惯例,所有的字母使用小写字母,使用`_`分割单词。

例如
class NameOneTwo
{

function var_abc() {};

function error_number() {};

var $var;

var $error_number;

var $name;

}

 

2.5 方法命名

·函数名字采用C GNU的惯例,所有的字母使用小写字母,使用`_`分割单词。

例如
class NameOneTwo
{

function do_it() {};

function handle_error() {};

}

 

· 通常每个方法都是执行一个动作的,所以对它们的命名应该清楚的说明它们是做什么的:用check_for_errors()代替error_check(),用dump_data_to_file()代替data_file()。这么做也可以使功能和数据成为更可区分的物体。
· 有时后缀名是有用的:
o Max - 含义为某实体所能赋予的最大值。
o Cnt - 一个运行中的计数变量的当前值。
o Key - 键值。
例如:retry_max 表示最多重试次数,retry_cnt 表示当前重试次数。
· 有时前缀名是有用的:
o Is - 含义为问一个关于某样事物的问题。无论何时,当人们看到Is就会知道这是一个问题。
o Get - 含义为取得一个数值。
o Set - 含义为设定一个数值
例如:is_hit_retry_limit。

 

 

2.6 方法中参数命名

 

·函数名字采用C GNU的惯例,所有的字母使用小写字母,使用`_`分割单词。

 

例如
class NameOneTwo
{

function start_your_engines(

&$some_engine,

&$another_engine);

}

2.7 变量命名

 

· 所有字母都使用小写
· 使用`_`作为每个词的分界。
理由
· 通过这一途径,代码中变量的作用域是清晰的。
· 所有的变量在代码中都看起来不同,容易辨认。
例如
function handle_error($errorNumber)
{

$error = OsErr($errorNumber);

$time_of_error = $error->get_time_of_error();

$error_processor = $error->get_error_processor();
}

 

2.8全局常量

· 全局常量用`_`分隔每个单词,并且全部单词大写。

理由
这是命名全局常量的传统。你要注意不要与其它的定义相冲突。
例如
define("A_GLOBAL_CONSTANT", "Hello world!");

2.9 静态变量

· 静态变量应该带前缀‘s’。

理由
· 知道一个变量的作用域是非常重要的。
例如
function test()
{

static $msStatus = 0;

}

2.10 函数命名

 

· 函数名字采用C GNU的惯例,所有的字母使用小写字母,使用`_`分割单词。
理由

· 这样可以更易于区分相关联的类名。

例如

function some_bloody_function()
{

}

 

2.11 错误返回检测规则

· 检查所有的系统调用的错误信息,除非你要忽略错误。
· 为每条系统错误消息定义好系统错误文本以便include。

3 书写规则

3.1 大括号{}

在三种主要的大括号放置规则中,有两种是可以接受的,如下的第一种是最好的:
· 将大括号放置在关键词下方的同列处:

if ($condition)


... ...
}

while ($condition)

... ...
}
·
传统的UNIX的括号规则是,首括号与关键词同行,尾括号与关键字同列:
if ($condition) {

... ...

}

while ($condition) {
... ...
}

理由:

引起剧烈争论的非原则的问题可通过折衷的办法解决,两种方法任意一种都是可以接受的,然而对于大多数人来说更喜欢第一种。原因就是心理研究学习范畴的东西了。

对于更喜欢第一种还有着更多的原因。如果您使用的字符编辑器支持括号匹配功能的话(例如vi),最重要的就是有一个好的样式。为什么?我们说当你有一大块的程序而且想知道这一大块程序是在哪儿结束的话。你先移到开始的括号,按下按钮编辑器就会找到与之对应的结束括号,例如:

if ($very_long_condition && $second_very_long_condition)
{
...
}
else if (...)
{
...
}
从一个程序块移动到另一个程序块只需要用光标和你的括号匹配键就可以了,不需找匹配的括号。

3.2 缩进:制表符vs空格

在书写代码的时候,必须注意代码的缩进规则,我们规定:使用4个空格作为缩进,而不使用tab缩进(对于ultraedit,可以进行预先设置)。

·对于最大缩进层数,并没有一个固定的规矩,假如缩进层数大于四或者五层的时候,你可以考虑着将代码因数分解(factoring out code)。
理由
· 许多编程者支持制表符。 但制表符标准在各种编程工具下、各种平台下所代表的空格数或者意义是不同的。这样会使在一个平台下格式良好的代码在另一个平台下不堪入目。对于php的开发来说,通常情况下是在win平台下开发,linux下部署,尤其需要注意这个问题。
· 虽然此处没有限定最大的缩进层数,但是通常建议不要超过四层、五层。
例如:

function func()
{
if (something bad)
{
if (another thing bad)
{
while (more input)
{
}
}
}
}

3.3 小括号、关键词和函数

· 不要把小括号和关键词紧贴在一起,要用空格隔开它们。
·
小括号和函数名间没有空格;如$test = date("ymdhis")
·
除非必要,不要在Return返回语句中使用小括号。
理由
· 关键字不是函数。如果小括号紧贴着函数名和关键字,二者很容易被看成是一体的。
例如
if (condition)
{
}

while (condition)
{
}

strcmp($s, $s1);

return 1;

3.4 类的构造函数

别在构造函数中做实际的工作, 构造函数应该包含变量的初始化和(或)不会发生失败的操作。
理由
· 构造不能返回错误 。

例如
class Device
{
function device() { /* initialize and other stuff */ }
function open() { return FAIL; }
};

$dev = new Device;
if (FAIL == $dev->open()) exit(1);

 

3.5 =符号书写

在程序中=符号的书写遵循以下规则:

1)在=符号的两侧,均需留出一个空格;如$a = $b if ($a = = $b)等;

2)在一个申明块,或者实现同样功能的一个块中,要求=号尽量上下对齐,左边可以为了保持对齐使用多个空格,而右边要求空一个空格;如下例:

$testa   = $aaa;

$testaa = $bbb;

3.6 控制结构

3.6.1 if then else 格式

不同的花括号样式会产生些微不同的样观。一个通用方式是:
if (条件1) // 注释
{
}
else if (条件2) // 注释
{
}
else // 注释
{
}
如果你有用到else if 语句的话,通常最好有一个else块以用于处理未处理到的其他情况。可以的话放一个记录信息注释在else处,即使在else没有任何的动作。

·总是将恒量放在等号/不等号的左边,例如:
if ( 6 == $errorNum ) ...
一个原因是假如你在等式中漏了一个等号,语法检查器会为你报错。第二个原因是你能立刻找到数值而不是在你的表达式的末端找到它。需要一点时间来习惯这个格式,但是它确实很有用。

3.6.2 switch 格式

· 当一个case块处理后,直接转到下一个case块处理,在这个case块的最后应该加上注释。
· default case总应该存在,它应该不被到达,然而如果到达了就会触发一个错误。
· 如果你要创立一个变量,那就把所有的代码放在块中。
例如
switch (...)
{
case 1:
...
// FALL THROUGH
case 2:
{
$v = get_week_number();
...
}
break;

default:
}

3.6.3 continue break

continue 和 break 其实是变相的隐蔽的 goto方法。
continue 和 break 像 goto 一样,它们在代码中是有魔力的,所以要节俭(尽可能少)的使用它们。使用了这一简单的魔法,由于一些未公开的原因,读者将会被定向到只有上帝才知道的地方去。
continue有两个主要的问题:
· 它可以绕过测试条件。
· 它可以绕过等/不等表达式。
看看下面的例子,考虑一下问题都在哪儿发生:
while (TRUE)
{
...
// A lot of code
...
if (/* some condition */) {
continue;
}
...
// A lot of code
...
if ( $i++ > STOP_VALUE) break;
}
注意:"A lot of code"是必须的,这是为了让程序员们不能那么容易的找出错误。
通过以上的例子,我们可以得出更进一步的规则:continue 和 break 混合使用是引起灾难的正确方法。

3.6.4 ?:

?:本身没什么问题,问题在于人们往往试着在 ? 和 : 之间塞满了许多的代码。以下的是一些清晰的连接规则:
· 把条件放在括号内以使它和其他的代码相分离。
· 如果可能的话,动作可以用简单的函数。
· 把所做的动作,“?”,“:”放在不同的行,除非他们可以清楚的放在同一行。
例如
(condition) ? funct1() : func2();

or

(condition)
? long statement
: another long statement;

3.7 声明块的定位

· 声明代码块需要对齐。
理由
· 清晰。
· 变量初始化的类似代码块应该列表。
· &应靠近类型,而不是变量名。
例如
var $mDate
var& $mrDate
var& $mrName
var $mName

$mDate = 0;
$mrDate = NULL;
$mrName = 0;
$mName = NULL;

3.8 每行语句应尽量短

除非这些语句有很密切的联系,否则每行只写一个语句。

在代码书写中,遵循以下原则:

1)尽量保证程序语句一行就是一句,而不要让一行语句太长产生折行;

2)尽量不要使一行的代码太长,一般控制在80个字符以内;

3)如果一行代码太长,请使用类似 .= 的方式断行书写;

4 对于执行数据库的sql语句操作,尽量不要在函数内写sql语句,而先用变量定义sql语句,然后在执行操作的函数中调用定义的变量;

例如:

$sql = "SELECT username,password,address,age,postcode FROM test_t ";

$sql .= " WHERE username='aaa'";

$res = mysql_query($sql);

3.9 短方法

方法代码要限制在一页内。

3.10记录所有的空语句

总是记录下for或者是while的空块语句,以便清楚的知道该段代码是漏掉了,还是故意不写的。

while ($dest++ = $src++)
; // VOID

3.11 不要采用缺省方法测试非零值

不要采用缺省值测试非零值,也就是使用:

if (FAIL != f())
比下面的方法好:

if (f())

即使 FAIL 可以含有 0 值 ,即PHP认为是false。但当某人决定用-1代替0作为失败返回值时,一个显式的测试就可以帮助你了。即使是比较值不会变化也应该使用显式的比较;

例如:

if (!($bufsize % strlen($str)))

应该写成:

if (($bufsize % strlen($str)) == 0)

以表示测试的数值(不是布尔)型。一个经常出问题的地方就是使用strcmp来测试一个字符等式,结果永远也不会等于缺省值。
非零测试采用基于缺省值的做法,那么其他函数或表达式就会受到以下的限制:
· 只能返回0表示失败,不能为/有其他的值。
· 命名以便让一个真(true)的返回值是绝对显然的,调用函数IsValid()而不是Checkvalid()。

3.12 布尔逻辑类型

遵循以下规则:

1 不能使用0/1代替true/false,在PHP中,这是不相等的;

2 不要使用非零的表达式、变量或者方法直接进行true/false判断,而必须使用严格的完整true/false判断;

如:不使用if ($a) 或者if (checka()) 而使用if (FALSE != $a)或者 if (FALSE != check())

 


大部分函数在FALSE的时候返回0,但是并非0值就代表TRUE,因而不要用1(TRUE,YES,诸如此类)等式检测一个布尔值,应该用0(FALSE,NO,诸如此类)的不等式来代替:

if (TRUE == func()) { ...
应该写成:

if (FALSE != func()) { ...

3.13 通常避免嵌入式的赋值

 

有时候会看到嵌入式赋值的语句,这样的结构可读性强并不强

while ($a != ($c = getchar()))
{
    process the character
}
++
和--操作符类似于赋值语句。因此,出于许多的目的,在使用函数的时候会产生副作用。使用嵌入式赋值提高运行时性能是可能的。无论怎样,程序员在使用嵌入式赋值语句时需要考虑在增长的速度和减少的可维护性两者间加以权衡。例如:

a = b + c;
d = a + r;
不要写成:

d = (a = b + c) + r;

虽然后者可以节省一个周期。但在长远来看,随着程序的维护费用渐渐增长,程序的编写者对代码渐渐遗忘,就会减少在成熟期的最优化所得。


4 注释

PHP中有两种注释方法,一种是使用//开始,另外一种是使用/**///一般用来比较简短的注释。/**/则用在需要大量注释的代码中。

    注释要尽量清晰,扼要,同时要使得你的注释能被机器解析后,能以固定的格式放到手册中去。注释的种类主要包括:类的注释,属性注释、方法注释、变量注释以及关键算法、重要代码实现等。所有的这些部分编织在一起,使得人们在以后的时间里能够准确的知道你干了什么,为什么这么做。

    在注释时,有一些预定义的关键字用来表示方法的目的,作者等,这样那就不需要再起另外一套关键字或者说不懂中文而采用汉语拼音。

    注释是增加程序可读性、可维护性的一种方法,而不是唯一方法。可读性和可维护性主要还是在代码命名,项目组织处提高。

4.1 预定义关键字

关键字     含义
Purpose 表示类、属性、方法要做些什么或者什么含义。
Package Name 类名
Author 作者
Modifications 修改记录(编号规则为“No”+日期+“-”+序号)
Ref 参考
Method Name 方法名
Parameter 参数名(包括类型)
Return 返回值(包括类型)
Attribute/Variable Name 属性/变量名
Type 属性/变量类型

 

4.2 文件头注释

每个文件头部必须有统一的注释块,规则如下:

a 必须包含本文件的描述;

b 必须包含作者;

c 必须包含书写日期;

d 必须包含版本信息;

e 必须包含项目名称;

f 必须包含文件的名称;

g 重要的使用说明,如类的调用方法、注意事项等;

参考例子如下:

<?php

//

// +---------------------------------------------------------+

// | PHP version 4.0                                         |

// +---------------------------------------------------------+

// | Copyright (c) 1997-2001 The PHP Group                   |

// +---------------------------------------------------------+

// | This source file is subject to of the PHP license,     |

// | that is bundled with this packafile LICENSE, and is     |

// | available at through the world-web at                   |

// | http://www.php.net/license/2_02.txt.                    |

// | If you did not receive a copy of the and are unable to |

// | obtain it through the world-wide-web,end a note to      |

// | license@php.net so we can mail you a immediately.       |

// +---------------------------------------------------------+

// | Authors: Stig Bakken <ssb@fast.no>                      |

// |          Tomas V.V.Cox <cox@idecnet.com>                |

// |                                                         |

// +---------------------------------------------------------+

//

// $Id: Common.php,v 1.8.2.3 2001/11/13 01:26:48 ssb Exp $

 

 

4.3 类的注释

每个类注释需要包含以下项目:

a 必须包含类的描述;

b 必须包含作者;

c 必须包含书写日期;

d 必须包含版本信息;

e 名称空间(可选);

 f. 参考(可选);

参考例子如下:

/**
* @ Purpose:
*
访问数据库的类,以ODBC作为通用访问接口
* @Package Name: Database
* @Author: Forrest Gump gump@crtvu.edu.cn
* @Modifications:
* No20020523-100:
* odbc_fetch_into()参数位置第二和第三个位置调换
* John Johnson John@crtvu.edu.cn
* @ref: (参照)
*/
class Database
{
……
}

4.4 方法注释

每个类注释需要包含以下项目:

a 必须方法描述;

b 必须包含方法名称;

c 必须包含收入参数及类型;

d 必须包含输出参数及类型;

 

参考例子如下:

/**
* @Purpose:
*
执行一次查询
* @Method Name: query()
* @Parameter: string $queryStr SQL查询字符串
* @Return: mixed 查询返回值(结果集对象)
*/
function($queryStr){……}

4.5 属性注释

属性注释需要包含以下项目:

a.属性简短描述(必须)

 

参考例子如下:

//用户名

var mDbUserName;

 

4.6 变量注释

变量注释需要包含以下项目:

a.变量简短描述(可选)

 

变量在程序中出现次数是最多的,而且变量从其名字中应该能清楚看到其意义。对临时性变量、作用域较窄的变量可以省略注释,否则程序中将因为注释而变得非常臃肿。

 

参考例子如下:

//用户名

$user_name;

4.7 代码块注释

代码块注释仅在必要时才使用,例如程序关键算法等等。注释方法同变量和属性。

 

参考例子:

 

func f()

{

… …

    //此处使用的是冒泡排序

    … …

}

 

5 项目组织

       此节主要在项目层次对涉及的文档,源码,说明等做出一些建议性方法。根据项目属性的不同,下面的部分可进行一些增删处理。

5.1 项目结构

·设计文档

 主要是软件设计类图,包图,数据库结构,关键算法参考等

·项目说明

主要包含软件安装,使用说明,初始账号,注意事项等,以指导人们显示资源:

·源码

指能通过编译的或者运行正常的源码

·工程管理

包括需求文档,项目管理,Bug修复等。

除了上述文档外,可能含有其他形形色色的文档,到时按照项目定义的类型进行归档即可。

5.2 源码结构

此处主要介绍在开发企业网站或者小型网站中,常用的一些目录命名。当然如果你要开发的项目类似于百度或者新浪,你可能不会需要,因为那时会有专业的人员或者规范进行约束,方方面面都已经涉及。通常情况下,这些还是对你有帮助的。

一个完整独立的PHP项目目录结构如下:

/ 项目根目录

/manage(admin) 后台管理文件存放目录

/css css文件存放目录

/images 所有图片文件存放路径(在里面根据目录结构设立子目录)

/scripts 客户端js脚本存放目录

/tpl  网站模版文件存放目录

/uploads 上传文件目录

/inc(include) 全局函数包含文件目录

/cache 缓存目录

/install 安装程序目录

 

以上目录结构是通常的目录结构,根据具体应用的具体情况,可以考虑不用完全遵循,但是尽量做到规范化。

5.3 第三方开发包

对于在开发中引入的第三方开发包,如果可能,尽量将其放入单独目录进行管理,同时在项目文档中放入开发包的相关文档:帮助文档,原理介绍,项目地址等。