# 代码检查规范 ## 一、代码风格检查 * A、空格与缩进要注意。 ~~~ <php> protected function main() { $this->keyword = CInput::clean('r','keyword',TYPE_STR); $this->start = CInput::clean('r','start',TYPE_UINT); $this->keyword = trim($this->keyword); $this->num = 10; $this->total = 0; </php> ~~~ * B、if、while 代码块中均需要花括号,均需独立成行。 > 更详细的代码风格规范请见:http://wiki.kaixin009.com/index.php/PHP_%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83 ## 二、安全检查 * A、对输入项应该有合法性检查(javascript 的或者 ajax 的)。 ~~~ <javascript> document.form1.btn_fb.disabled=true; if (document.form1.word.value.strip() == "") { alert("记录的内容不能为空"); document.form1.word.value = ""; document.form1.word.focus(); document.form1.btn_fb.disabled=false; return false; } </javascript> ~~~ * B、对于输入项,如果能确定枚举类型的,应该限定在能枚举的范围之内。 ~~~ <php> static protected $orderlist = array("pop", "week_pop", "shield"); if (!in_array($this->order, self::$orderlist)) { $this->order = self::$orderlist[0]; } </php> ~~~ * C、对于输出转义。 HTML 代码中应该用 htmlspecialchars 进行转义。对于模板中的 javascript 代码中的变量,应该用 addslashes 进行转义。 参见《php中的文本处理.txt》 * D、对于 mysql 语句拼接过程中的变量,如果需要整数的,用 intval 进行强制转换。如果是输入为字符串的,用 mysql_escape_string 。 对于 mysql 语句的拼接,除非明确知道是输入项是整数,比如经过了 intval,应该加 单引号进行字段值分隔。对于输入项为数组再 implode 的,应该检查是否输入为空数组。 ~~~ <php> function getNewList($uids, $start, $num) { if(!is_array($uids) || count($uids) == 0) { return false; } $date = date("Y-m-d" , strtotime("-7 day")); $sql[0] = "select SQL_CALC_FOUND_ROWS * from s_user_record_new where privacy <> '2' and ctime >= '".$date."' and uid in ('".implode("', '", $uids)."') order by ctime desc limit ".intval($start).", ".intval($num); $sql[1] = "select FOUND_ROWS()"; $info = $this->_doSql("s_user_record_new", 1, $sql); if(false == $info) { return false; } return $info; } function updateNew($rid, $privacy) { $sql = "update s_user_record_new set privacy= '".mysql_escape_string($privacy)."' where rid = '".mysql_escape_string($rid)."'"; $info = $this->_doSql("s_user_record_new", 1, $sql); if(false == $info) { return false; } return true; } </php> ~~~ * E、对于UGC内容的提交次数限制。比如日记,每分钟,每天都有限制。 1. 对于可能引发炒作传播的内容生产渠道 2. 对于可能引发攻击的内容生产渠道 * F、对于提交内容的前后端一致性检查。比如JS中限制提交次数限制,那么,在真正的接收业务逻辑中,要进行实质的检验。 ~~~ <php> if (trim($this->word) == "") { header("Location: /!record/index.php"); exit; } </php> ~~~ ## 三、变量检查 * A、循环中的标记变量、计数变量是否进行了重置。 ~~~ <php> while ($info = mysql_fetch_array($result, MYSQL_ASSOC)) { $needmon = false; foreach($parts as $part) { if(...) { $needmon = true; .... } } if($needmon) { $warncount ++; break; } } </php> ~~~ * B、循环条件中是否存在复杂运算。举个最简单的例子: ~~~ <php> for($i=0;$i<$this->infos->rowNum(0);$i++) { $lids[] = $this->infos->get(0,$i,'lid'); $this->commentNums[] = array(APP_WHERE_ID,$this->infos->get(0,$i,'uid'),$this->infos->get(0,$i,'wid')); } for($j=0;$j<$localeinfos->rowNum();$j++) { $localeinfo = $localeinfos->getEx($j); $this->locales[$localeinfo['lid']] = $localeinfo; } function rowNum($i) { return count($this->sqlres[$i]->rows); } function rowNum() { return count($this->sqlres->rows); } </php> ~~~ * C、全局变量,是否使用了 global 来声明。 ~~~ <php> global $multilog; $multilog->addSysDebugLog("diary_content_bad_url_".$this->_uid."_".$this->_ip); </php> ~~~ * D、对于后台及生产中的代码,文件路径不要写死,IP地址也不要写死。对于提示字符串等,也不要写死。要换用宏或者类常量来说明。 ~~~ <php> $datafile2=DATA_PATH."/cache/appinfo.log"; $cmd = "/usr/bin/rsync --bwlimit=25000 ".$datafile2." ".$ga_hive_log_receive_server."::TEMP/userinfo"; echo $cmd."\n"; </php> ~~~ 对于人工操作的后台程序,最好能支持参数输入。 * E、if 语句分支过多时,可以考虑用查表法改造。 ~~~ <php> if ($host == "reg".COMMON_HOST) { return "reg"; } else if ($host == "login".COMMON_HOST) { return "login"; } else if ($host == "xyx".COMMON_HOST) { return "flashgame"; } else if ($host == "tuan".COMMON_HOST) { return "teambuy"; } </php> <php> $hostdir = array( "reg".COMMON_HOST=>"reg", "login".COMMON_HOST=>"login", "xyx".COMMON_HOST=>"flashgame", "tuan".COMMON_HOST=>"teambuy", ); if(isset($hostdir[$host])) { return $hostdir[$host]; } </php> ~~~ F、对于频繁调用的方法,不要在局部变量中声明大的数组变量(比如配置数组),可以用全局或者静态变量来代替 ~~~ <php> function getPublicStateIconArr() { static $status_icon_arr; if(empty($status_icon_arr)) { $status_icon_arr = array( '(#开心)' => '<img src="http://'.IMG_HOST.'/i/state/happy.gif" title="开心" align="absmiddle" />', '(#哭泣)' => '<img src="http://'.IMG_HOST.'/i/state/cry.gif" title="哭泣" align="absmiddle" />', '(#伤心)' => '<img src="http://'.IMG_HOST.'/i/state/sorrow.gif" title="伤心" align="absmiddle" />', '(#愤怒)' => '<img src="http://'.IMG_HOST.'/i/state/angry.gif" title="愤怒" align="absmiddle" />', '(#惊讶)' => '<img src="http://'.IMG_HOST.'/i/state/amazed.gif" title="惊讶" align="absmiddle" />', '(#想你)' => '<img src="http://'.IMG_HOST.'/i/state/missyou.gif" title="想你" align="absmiddle" />', '(#幸福)' => '<img src="http://'.IMG_HOST.'/i/state/happiness.gif" title="幸福" align="absmiddle" />', '(#纠结)' => '<img src="http://'.IMG_HOST.'/i/state/tanglement_3.gif" title="纠结" align="absmiddle" />', '(#囧)' => '<img src="http://'.IMG_HOST.'/i/state/daze.gif" title="囧" align="absmiddle" />', '(#闭嘴)' => '<img src="http://'.IMG_HOST.'/i/state/shutup.gif" title="闭嘴" align="absmiddle" />', '(#yy)' => '<img src="http://'.IMG_HOST.'/i/state/yy.gif" title="yy" align="absmiddle" />', '(#亲亲)' => '<img src="http://'.IMG_HOST.'/i/state/kiss.gif" title="亲亲" align="absmiddle" />', '(#色)' => '<img src="http://'.IMG_HOST.'/i/state/leer.gif" title="色" align="absmiddle" />', ... //以下为兼容旧的英文格式动态 ':)' => '<img src="http://'. IMG_HOST . '/i/state/happy.gif" title="开心" align="absmiddle" />', '//cry' => '<img src="http://' . IMG_HOST . '/i/state/cry.gif" title="哭泣" align="absmiddle" />', ':(' => '<img src="http://' .IMG_HOST. '/i/state/sorrow.gif" title="伤心" align="absmiddle" />', '//angry' => '<img src="http://' .IMG_HOST. '/i/state/angry.gif" title="愤怒" align="absmiddle" />', '//amazed' => '<img src="http://' .IMG_HOST. '/i/state/amazed.gif" title="惊讶" align="absmiddle" />', '//heart' => '<img src="http://' .IMG_HOST. '/i/state/heart.gif" title="芳心" align="absmiddle" />', '//loving' => '<img src="http://' .IMG_HOST. '/i/state/loving.gif" title="充满爱心" align="absmiddle" />', '//candle' => '<img src="http://' .IMG_HOST. '/i/state/candle.gif" title="蜡烛" align="absmiddle" />', '//riband' => '<img src="http://' .IMG_HOST. '/i/state/riband.gif" title="绿丝带" align="absmiddle" />', '//cake' => '<img src="http://' .IMG_HOST. '/i/state/cake.gif" title="生日蛋糕" align="absmiddle" />', '//sun' => '<img src="http://' .IMG_HOST. '/i/state/sun_3.gif" title="太阳" align="absmiddle" />', '//rainbow' => '<img src="http://' .IMG_HOST. '/i/state/rainbow.gif" title="彩虹" align="absmiddle" />', '//praise' => '<img src="http://' .IMG_HOST. '/i/state/praise_3.gif" title="赞扬" align="absmiddle" />', '//crown' => '<img src="http://' .IMG_HOST. '/i/state/crown.gif" title="皇冠" align="absmiddle" />', '//help' => '<img src="http://' .IMG_HOST. '/i/state/help_3.gif" title="需要帮助" align="absmiddle" />', '//music' => '<img src="http://' .IMG_HOST. '/i/state/music_3.gif" title="音乐" align="absmiddle" />', '//snow' => '<img src="http://' .IMG_HOST .'/i/state/snow_3.gif" title="雪花" align="absmiddle" />', .... ); } return $status_icon_arr; } </php> ~~~ G、用循环分段来处理大量数据,对于不能成段的数据不能漏了处理 ~~~ <php> foreach($lines as $line) { $flag = 0; $uid = trim($line); $uids[] = $uid; $count ++; if($count % 1000 == 0) { CUObject::$cache = array(); $ipdetails = $cuserreg->getRegIpDetails($uids); $ips = CHandle::objs2ids($ipdetails, "regip"); $ret = $ciplocator->getLocations($ips); $index = 0; foreach($ips as $key=>$ip) { echo $uids[$index++]." ".$key." ".$ip." ".$ret[$key]."\n"; } $uids = array(); } } if(count($uids) > 0) { $ipdetails = $cuserreg->getRegIpDetails($uids); $ips = CHandle::objs2ids($ipdetails, "regip"); $ret = $ciplocator->getLocations($ips); $index = 0; foreach($ips as $key=>$ip) { echo $uids[$index++]." ".$key." ".$ip." ".$ret[$key]."\n"; } } </php> ~~~ ## 四、性能检查 * A、考虑到每一次对中间层的调用都是有消耗的。 * B、保持效率意识。想到很费资源东西。比如打开关闭连接,网络操作,IO读取,这些都是很费资源的。应该想着怎么整合优化,严禁在循环操作中,重复做这些操作。(举例:在循环中获取用户信息。) * C、正则表达式是一个比较慢的操作。能不用最好不用。 ~~~ <php> while(list($k, $v) = each($this->varvals)) { $varvals_quoted[$k] = preg_replace(array('/\\\\/', '/\$/'), array('\\\\\\\\', '\\\\$'), $v); } while(list($k, $v) = each($this->varvals)) { if(strpos($v, "$")===false && strpos($v, "\\")===false) { $varvals_quoted[$k] = $v; } else { $varvals_quoted[$k] = str_replace(array("\\", "$"), array("\\\\", "\\$"), $v); } } </php> * D、对于后台程序要加锁。 <php> $lockfp = CLock::check($argv[0]); $lockfp = CLock::check($argv[0].$argv[1]); </php> ~~~ * E、对于后台程序中,大数据量的调用,要记得清空 CUObject 、CUser 等的缓存。 ~~~ <php> CUObject::$cache = array(); CUser::$info_cache = array(); CUser::$moreinfo_cache = array(); CUser::$verifycode_cache = array(); </php> ~~~ ## 五、中间层调用的检查 * A、明确 cachetime 的使用。如果存在 cachetime 使用的,要检查查询语句,是否带有时间参数,检查时间参数的精度。 ~~~ <php> function getRatingRelationNewPageByUidsAndCtime($uids,$ctime,$status=null) { if(empty($uids)) { return false; } $uids = array_unique($uids); $uidstr = "(".implode(',',$uids).")"; $status_str = ""; if(!empty($status)) { $status_str = " and status = ". mysql_escape_string($status). " "; } $ctime = date("Y-m-d H:00:00", strtotime($ctime)); $sql[0] = "select SQL_CALC_FOUND_ROWS rid,uid,ctime,status from s_user_rating_relation_new where uid in ".$uidstr.$status_str. " and ctime >= '". mysql_escape_string($ctime)."' order by ctime desc "; $sql[1] = "select FOUND_ROWS()"; $info = $this->_doSql('s_user_rating_relation_new',1,$sql, 300); ... } </php> ~~~ * B、getList、getAll 是否正确使用。 * C、CUObject 对象的更新是否使用了正确的方法。而在继承 CUObject 的类中,是否正确更新非 UObject 数据表。 ~~~ <php> function deleteRating($rid) { return $this->_delData('s_rating_info', $rid, 'rid', $rid); } function updateRatingUserNum($rid,$addnum=1) { return $this->_chgDataEx('s_rating_info',$rid,'rid',$rid,array(),array('usernum'=>intval($addnum))); } function updateRatingVoteByRidAndVid($data,$rid,$vid) { return CHandle::_chgData('s_rating_vote',$rid,'vid',$vid,$data); } function updateRatingVoteCommentNum($rid,$vid) { return CHandle::_chgDataEx('s_rating_vote',$rid,'vid',$vid,array(),array('commentnum'=>1)); } </php> * D、Memcached 或者 kvdb 分组调用错误。比如用 getUObjectInstance 代替 getInstance,是错误的。 <php> $mc = CMemCacheEx::getInstance(); $mc->delete("photo_num_".$uid); $key = "appdata_".$uid."_".APP_PHOTO_ID; $mc = CMemCacheEx::getUObjectInstance($key); $mc->delete($key); </php> ~~~ ## 六、数据库代码检查 * A、索引与调用检查。 * B、数据库调用,检查 order by 。严格控制 SQL_CALC_FOUND_ROWS 的使用。 ~~~ <php> function getFanListNoOrder($uid, $start, $num, $cache_time=0) { return $this->_getList("s_user_friend_fan", $uid, "", "", $start, $num, $cache_time); } </php> ~~~ * C、绝对禁止使用mysql的联合查询。(宁可查两次数据库)不能使用联合查询一个重要的原因是分库分表。 * D、没有太复杂的查询。尤其注意 or 的使用。如果要用到 or ,是否可以考虑抓成两个简单查询再合并。 对于复杂的查询,可以考虑拆分成多个简单的查询来进行。 ~~~ select * from s_user_comment where uid = '...' and (thread_cid in (...) or cid in (...)) and del = 0 order by cid desc; ~~~ 可以拆分成两个: ~~~ select * from s_user_comment where uid = '...' and (thread_cid in (...)) and del = 0 ; select * from s_user_comment where uid = '...' and (cid in (...)) and del = 0; ~~~ * E、注意不要把无效的查询扔到后台数据库去。 ~~~ <php> function getReplyListByTids($uid, $vuid, $tids) { if (empty($tids)) { $tids = array("-1"); } $where = "(thread_cid in ('".implode("', '", $tids)."'))"; if ($uid != $vuid) { $where .= " and del = 0"; } $info = $this->_getAll("s_user_comment", $uid, $where, "order by cid"); return $info; } function getReplyListByTids($uid, $vuid, $tids) { if (empty($tids)) { return CComment::getEmptyResult(); } $where = "(thread_cid in ('".implode("', '", $tids)."'))"; if ($uid != $vuid) { $where .= " and del = 0"; } $info = $this->_getAll("s_user_comment", $uid, $where, "order by cid"); return $info; } </php> ~~~ ## 七、业务检查 * A、业页提交与重定向。(应该不能提交给自己)。 * B、对于一时间大量写数据库的操作,比如送礼物,应该走队列。 * C、构造函数的参数使用检查。(pri, host等)。 * D、平台和组件代码分开的问题。在组件代码中,不能直接初始化 CUser 和 CFriend 去取用户和好友关系信息,只能走 CModuleApp.php提供出来的接口。 * E、后台管理系统的程序,一定要有权限控制。请在上线前仔细检查。是否漏了权限控制。 ~~~ <php> checkLevel(KXADMIN_SENIOR_PROD, array(), "组件"); </php> ~~~ ## 八、JS 检查 * A、是否只使用了 JQuery 框架。 * B、DOM 对象选择器使用检查。请参考 javascript 开发规范 http://wiki.kaixin009.com/index.php/JavaScript_%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83 * C、JSLint 检查(目前没有做到)。