从linux kernel coding style看php代码规范

linux内核的编码是一种极端情况。

需要清晰明朗以供全世界的开发者学习、修改,对代码的质量要求较高。

相信linus大神在长期接触各种各式代码后脾气会变得更暴躁,就比如前段时间在某论坛痛斥C++。。。

节选,去除不适合PHP程序员阅读的部分。

如果你开发PHP程序的核心代码,比如框架,尤其建议好好思考。

虽然,它和一些权威的代码规范比如discuz和zend的有所冲突,但是依然能从中受益。

linux kernel coding style (针对PHPer作了节选)

linux kernel coding style的中文译者:

中文版维护者: 张乐 Zhang Le

中文版翻译者: 张乐 Zhang Le

中文版校译者: 王聪 Wang Cong

wheelz

管旭东 Xudong Guan

Li Zefan

Wang Chen

第一章:缩进

制表符是8个字符,所以缩进也是8个字符。有些异端运动试图将缩进变为4(乃至2)个字符深,这几乎相当于尝试将圆周率的值定义为3。

理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕连续看了20小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。

现在,有些人会抱怨8个字符的缩进会使代码向右边移动的太远,在80个字符的终端屏幕上就很难读这样的代码。这个问题的答案是,如果你需要3级以上的缩进,不管用何种方式你的代码已经有问题了,应该修正你的程序。

在switch语句中消除多级缩进的首选的方式是让“switch”和从属于它的“case”标签对齐于同一列,而不要“两次缩进”“case”标签。比如:

C++代码

switch (suffix) {

case 'G':

case 'g':

mem <<= 30;

break;

case 'M':

case 'm':

mem <<= 20;

break;

case 'K':

case 'k':

mem <<= 10;

/* fall through */

default:

break;

}
  不要把多个语句放在一行里,除非你有什么东西要隐藏:

  C++代码

  if (condition) do_this;

  do_something_everytime;

  也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读的表达式。

  除了注释、文档和Kconfig之外,不要使用空格来缩进,前面的例子是例外,是有意为之。

  选用一个好的编辑器,不要在行尾留空格。

  第二章:把长的行和字符串打散

  代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。

  每一行的长度的限制是80列,我们强烈建议您遵守这个惯例。

  长于80列的语句要打散成有意义的片段。每个片段要明显短于原来的语句,而且放置的位置也明显的靠右。同样的规则也适用于有很长参数列表的函数头。长字符串也要打散成较短的字符串。唯一的例外是超过80列可以大幅度提高可读性并且不会隐藏信息的情况。

  C++代码

  void fun(int a, int b, int c)

  {

  if (condition)

  printk(KERN_WARNING "Warning this is a long "

  "3 parameters a: %u b: %u "

  "c: %u \n", a, b, c);

  else

  next_statement;

  }

  第三章:大括号和空格的放置

  C语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放置策略并没有多少技术上的原因,不过首选的方式,就像Kernighan和Ritchie展示给我们的,是把起始大括号放在行尾,而把结束大括号放在行首,所以:

  C++代码

  if (x is true) {

  we do y

  }

  这适用于所有的非函数语句块(if、switch、for、while、do)。比如:

  C++代码

  switch (action) {

  case KOBJ_ADD:

  return "add";

  case KOBJ_REMOVE:

  return "remove";

  case KOBJ_CHANGE:

  return "change";

  default:

  return NULL;

  }

  不过,有一个例外,那就是函数:函数的起始大括号放置于下一行的开头,所以:

  C++代码

  int function(int x)

  {

  body of function

  }

  全世界的异端可能会抱怨这个不一致性是……呃……不一致的,不过所有思维健全的人都知道(a)K&R是_正确的_,并且(b)K&R是正确的。此外,不管怎样函数都是特殊的(在C语言中,函数是不能嵌套的)。

  注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,也就是do语句中的“while”或者if语句中的“else”,像这样:

  C++代码

  do {

  body of do-loop

  } while (condition);

  和

  C++代码

  if (x == y) {

  ..

  } else if (x > y) {

  ...

  } else {

  ....

  }

  理由:K&R。

  也请注意这种大括号的放置方式也能使空(或者差不多空的)行的数量最小化,同时不失可读性。因此,由于你的屏幕上的新行是不可再生资源(想想25行的终端屏幕),你将会有更多的空行来放置注释。

  当只有一个单独的语句的时候,不用加不必要的大括号。

  C++代码

  if (condition)

  action();

  这点不适用于本身为某个条件语句的一个分支的单独语句。这时需要在两个分支里都使用大括号。

  C++代码

  if (condition) {

  do_this();

  do_that();

  } else {

  otherwise();

  }

  3.1:空格

  Linux内核的空格使用方式(主要)取决于它是用于函数还是关键字。(大多数)关键字后要加一个空格。值得注意的例外是sizeof、typeof、alignof和__attribute__,这些关键字某些程度上看起来更像函数(它们在Linux里也常常伴随小括号而使用,尽管在C语言里这样的小括号不是必需的,就像“struct fileinfo info”声明过后的“sizeof info”)。

  所以在这些关键字之后放一个空格:

  if, switch, case, for, do, while

  但是不要在sizeof、typeof、alignof或者__attribute__这些关键字之后放空格。例如,

  C++代码

  s = sizeof(struct file);

  不要在小括号里的表达式两侧加空格。这是一个反例:

  C++代码

  s = sizeof( struct file );

  当声明指针类型或者返回指针类型的函数时,“*”的首选使用方式是使之靠近变量名或者函

  数名,而不是靠近类型名。例子:

  C++代码

  char *linux_banner;

  unsigned long long memparse(char *ptr, char **retptr);

  char *match_strdup(substring_t *s);

  在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:

  C++代码

  = + - < > * / % | & ^ <= >= == != ? :

  但是一元操作符后不要加空格:

  C++代码

  & * + - ~ ! sizeof typeof alignof __attribute__ defined

  后缀自加和自减一元操作符前不加空格:

  ++ --

  前缀自加和自减一元操作符后不加空格:

  ++ --

  “.”和“->”结构体成员操作符前后不加空格。

  不要在行尾留空白。有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就这样产生了。

  当git发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白;不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的上下文。

  第四章:命名

  C是一个简朴的语言,你的命名也应该这样。和Modula-2和Pascal程序员不同,C程序员不使用类似 ThisVariableIsATemporaryCounter 这样华丽的名字。C程序员会称那个变量为“tmp”,这样写起来会更容易,而且至少不会令其难于理解。

  不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的名字。称一个全局函数为“foo”是一个难以饶恕的错误。

  全局变量(只有当你真正需要它们的时候再用它)需要有一个具描述性的名字,就像全局函数。如果你有一个可以计算活动用户数量的函数,你应该叫它“count_active_users()”或者类似的名字,你不应该叫它“cntuser()”。

  在函数名中包含函数类型(所谓的匈牙利命名法)是脑子出了问题——编译器知道那些类型而且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微软总是制造出有问题的程序。

  本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计数器,它应该被称为“i”。叫它“loop_counter”并无益处,如果它没有被误解的可能的话。类似的,“tmp”可以用来称呼任意类型的临时变量。

  如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综合症。请看第六章(函数)。

  第六章:函数

  函数应该简短而漂亮,并且只完成一件事情。函数应该可以一屏或者两屏显示完(我们都知道ISO/ANSI屏幕大小是80x24),只做一件事情,而且把它做好。

  一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,如果你有一个理论上很简单的只有一个很长(但是简单)的case语句的函数,而且你需要在每个case里做很多很小的事情,这样的函数尽管很长,但也是可以的。

  不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一年级学生可能甚至搞不清楚这个函数的目的,你应该严格的遵守前面提到的长度限制。使用辅助函数,并为之取个具描述性的名字。

  函数的另外一个衡量标准是本地变量的数量。此数量不应超过5-10个,否则你的函数就有问题了。重新考虑一下你的函数,把它分拆成更小的函数。人的大脑一般可以轻松的同时跟踪7个不同的事物,如果再增多的话,就会糊涂了。即便你聪颖过人,你也可能会记不清你2个星期前做过的事情。

  在源文件里,使用空行隔开不同的函数。

  第八章:注释

  注释是好的,不过有过度注释的危险。永远不要在注释里解释你的代码是如何运作的:更好

  的做法是让别人一看你的代码就可以明白,解释写的很差的代码是浪费时间。

  一般的,你想要你的注释告诉别人你的代码做了什么,而不是怎么做的。也请你不要把注释

  放在一个函数体内部:如果函数复杂到你需要独立的注释其中的一部分,你很可能需要回到

  第六章看一看。你可以做一些小注释来注明或警告某些很聪明(或者槽糕)的做法,但不要

  加太多。你应该做的,是把注释放在函数的头部,告诉人们它做了什么,也可以加上它做这

  些事情的原因。

  当注释内核API函数时,请使用kernel-doc格式。请看

  Documentation/kernel-doc-nano-HOWTO.txt 和 scripts/kernel-doc 以获得详细信息。

  Linux的注释风格是C89“/* ... */”风格。不要使用C99风格“// ...”注释。

  长(多行)的首选注释风格是:

  C++代码

  /*

  * This is the preferred style for multi-line

  * comments in the Linux kernel source code.

  * Please use it consistently.

  *

  * Description: A column of asterisks on the left side,

  * with beginning and ending almost-blank lines.

  */

  注释数据也是很重要的,不管是基本类型还是衍生类型。为了方便实现这一点,每一行应只

  声明一个数据(不要使用逗号来一次声明多个数据)。这样你就有空间来为每个数据写一段

  小注释来解释它们的用途了。

  对于PHPER来讲,最好遵循PHPDOC风格的注释。

  第九章:你已经把事情弄糟了

  这没什么,我们都是这样。可能你的使用了很长时间Unix的朋友已经告诉你“GNU emacs”能

  自动帮你格式化C源代码,而且你也注意到了,确实是这样,不过它所使用的默认值和我们

  想要的相去甚远(实际上,甚至比随机打的还要差——无数个猴子在GNU emacs里打字永远不

  会创造出一个好程序)(译注:请参考Infinite Monkey Theorem)(对于PHPer,可能就是zend studio、eclipse之类的自动格式化工具)

  所以你要么放弃GNU emacs,要么改变它让它使用更合理的设定。

  第十二章:宏,枚举和RTL

  用于定义常量的宏的名字及枚举里的标签需要大写。

  C++代码

  #define CONSTANT 0x12345

  宏的名字请用大写字母,不过形如函数的宏的名字可以用小写字母。

  。。。

  第十三章:打印内核消息

  内核开发者应该是受过良好教育的。请一定注意内核信息的拼写,以给人以好的印象。不要

  用不规范的单词比如“dont”,而要用“do not”或者“don't”。保证这些信息简单、明了、无

  歧义。

  内核信息不必以句号(译注:英文句号,即点)结束。

  写出好的调试信息可以是一个很大的挑战;当你写出来之后,这些信息在远程除错的时候当DEBUG符号没有被定义的时候,这些信息不应该被编译进内核里

  就会成为极大的帮助。

  (也就是说,默认地,它们不应该被包含在内)。(对于PHPer也是同样)

  第十八章:编辑器模式行和其他需要罗嗦的事情

  有一些编辑器可以解释嵌入在源文件里的由一些特殊标记标明的配置信息。比如,emacs

  能够解释被标记成这样的行:

  -*- mode: c -*-

  或者这样的:

  /*

  Local Variables:

  compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"

  End:

  */

  Vim能够解释这样的标记:

  /* vim:set sw=8 noet */

  这包括有关缩进和模式配置的标记。人们可以使用他们自己定制的模

  式,或者使用其他可以产生正确的缩进的巧妙方法。

  不要在源代码中包含任何这样的内容。每个人都有他自己的编辑器配置,你的源文件不应

  该覆盖别人的配置。

  简而言之,8个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太深的

  时候可以给你警告。留心这个警告。

PHP_带smtp验证的发邮件函数

#echo send_mail($_POST["email"],$tosubject,$_POST["errortext"]);
echo send_mail('someone@126.com','only a test mail for test php mail function','一封垃圾邮件');
echo time();

function send_mail($to, $subject = 'No subject', $body) {
        $loc_host = "mail.server";            //发信计算机名,可随意
        $smtp_acc = "smp@163.com"; //Smtp认证的用户名
        $smtp_pass="pwdpwd";          //Smtp认证的密码,一般等同pop3密码
        $smtp_host="smtp.163.com";    //SMTP服务器地址,类似 smtp.tom.com
        $from="smp@163.com";       //发信人Email地址,你的发信信箱地址
    $deliver=$smtp_acc; //回复到指定邮箱
    $headers = "Content-Type: text/plain; charset=\"gb2312\"\r\nContent-Transfer-Encoding: base64";
    $lb="\r\n";                    //linebreak    
        $hdr = explode($lb,$headers);     //解析后的hdr
    if($body) {$bdy = preg_replace("/^\./","..",explode($lb,$body));}//解析后的Body

        $smtp = array(
      //1、EHLO,期待返回220或者250
      array("EHLO ".$loc_host.$lb,"220,250","HELO error: "),
      //2、发送Auth Login,期待返回334
      array("AUTH LOGIN".$lb,"334","AUTH error:"),
      //3、发送经过Base64编码的用户名,期待返回334
      array(base64_encode($smtp_acc).$lb,"334","AUTHENTIFICATION error : "),
      //4、发送经过Base64编码的密码,期待返回235
      array(base64_encode($smtp_pass).$lb,"235","AUTHENTIFICATION error : "));
      //5、发送Mail From,期待返回250
      $smtp[] = array("MAIL FROM: <".$from.">".$lb,"250","MAIL FROM error: ");
      //6、发送Rcpt To。期待返回250
      $smtp[] = array("RCPT TO: <".$to.">".$lb,"250","RCPT TO error: ");
      //7、发送DATA,期待返回354
      $smtp[] = array("DATA".$lb,"354","DATA error: ");
      //8.0、发送From
      $smtp[] = array("From: ".$deliver.$lb,"","");
      //8.2、发送To
      $smtp[] = array("To: ".$to.$lb,"","");
      //8.1、发送标题
      $smtp[] = array("Subject: ".$subject.$lb,"","");
      //8.3、发送其他Header内容
      foreach($hdr as $h) {$smtp[] = array($h.$lb,"","");}
      //8.4、发送一个空行,结束Header发送
      $smtp[] = array($lb,"","");
      //8.5、发送信件主体
      if($bdy) {foreach($bdy as $b) {$smtp[] = array(base64_encode($b.$lb).$lb,"","");}}
      //9、发送“.”表示信件结束,期待返回250
      $smtp[] = array(".".$lb,"250","DATA(end)error: ");
      //10、发送Quit,退出,期待返回221
      $smtp[] = array("QUIT".$lb,"221","QUIT error: ");

        //打开smtp服务器端口
        $fp = @fsockopen($smtp_host, 25);
        if (!$fp) echo "<b>Error:</b> Cannot conect to ".$smtp_host."<br>";
        while($result = @fgets($fp, 1024)){if(substr($result,3,1) == " ") { break; }}
       
        $result_str="";
        //发送smtp数组中的命令/数据
        foreach($smtp as $req){
                //发送信息
                @fputs($fp, $req[0]);
                //如果需要接收服务器返回信息,则
                if($req[1]){
                        //接收信息
                        while($result = @fgets($fp, 1024)){
                                if(substr($result,3,1) == " ") { break; }
                        };
                        if (!strstr($req[1],substr($result,0,3))){
                                $result_str.=$req[2].$result."<br>";
                        }
                }
        }
        //关闭连接
        @fclose($fp);
        return $result_str;
}