文章存档 » 十一月 2012

对提高团队代码质量的些许愚见

做开发总是避免不了和Bug打交道。自从第一只真正的Bug被发现以后,Bug就像幽灵一样时刻伴随着软件开发。

Bug是不可避免的,这个是真理。听说有人写书叫”零Bug编程指南”,真是牛逼之极。莫非他是上帝?其实上帝也是会写出Bug的程序员,所以各个物种都在不断打补丁进化中。

虽然Bug不可避免,但是我们可以提高代码质量降低Bug出现的几率。代码也有熵的特质,特别是在现在多人多团队合作开发的情景里面,需要采取很多策略来提高代码质量。

首先对于团队来说,我觉得可以通过如下一些方式来提高代码质量:

一、最根本也是第一步要做的就是,在设计系统架构阶段就需要充分地考虑系统的可扩展性、模块的高聚合性以及接口灵活易用的性。要做到这一点,就和对需求的分析、系统的了解和经验有很大关系了,需要长期的积累和经常对架构的思考与实践。交给团队里面最牛逼的一个人去主导,比较牛逼的几人参与讨论来做这一步吧。

二、选用一个适合需求的开发框架,会让你事倍功半。PHP的框架有很多,各自有各自的特性和优劣,可以比较选择。Python的tornado在做Web应用的时候比较方便。同样前段也需要代码框架的支持,Javascript中jQuery能胜任大多数项目,HTML和CSS可以尝试使用Twitter的Bootstrap。使用框架对代码的健壮性有帮助吗?当然有!因为框架封装实现很多常用的功能,这样我们自己的代码量会大大减少。直观一点讲,代码量少了,bug自然就少了。其实最重要的是代码量少了,程序的逻辑和结构会更加清晰,从而减少Bug的出现。

三、多人协作需要良好的代码管理工具。SVN可以考虑淘汰了,用分布式的GIT。

四、团队还需要统一的开发环境。包括统一的编码规范、统一的语言版本、统一的编辑器配置(tab和空格之类)、统一的文件编码,统一的数据库等等。这样可以完全避免因为环境不同而导致的Bug。

五、较优秀的程序员应该负责较初级的程序员的代码质量,定期对初级程序员的代码进行review。同时团队内部应该有针对性对一些比较复杂或者变态的部分进行code review。

六、对于系统的破窗和肿瘤,要适时适量地清除,绝对不能放任不管。

后面的几点就是针对个人了。想要提高代码质量,对程序员个人的专业素质就要有高的要求。大到模块的划分和实现,小到变量的命名等,都能体现出一个程序员的专业素质。

一、类名函数名变量名有简洁有意义,复杂难懂的地方要给出详细的注释。

二、对代码进行合理的封装和复用。不要有过多的重复代码,也不要过度封装和过度面向对象。如果一部分功能代码出现了第二次,就应该思考可否提取成一个公共的方法。如果出现第三次,就应该警觉了。同时也要多利用数组和链表,不要什么数据都做成对象。

三、严禁太深的嵌套,算法复杂度会较高,同事也严禁过多的if else,这样会使程序员头晕的。

四、对数组下标要小心检查,不要越界。

五、个人要自律要按照大家约定的习惯和代码风格编码。这样有利于代码层面的交流,方面相互复用代码和修改代码。

六、逻辑复杂的地方先画图设计,再编码。

七、控制代码文件的长度。。。太长了只能割了。

八、预见性的编码,考虑更多的输入情况和容错,做到宽进严出。

当然,在提高代码质量的环节里,测试部门是不可或缺的:

一、尽量部署自动化测试,用手动测试去弥补自动化测试的不足。

二、QA应该对每个developer有一张反馈表,记录dev容易犯错或者忽略的地方,定期反馈。

要提高代码质量,团队和个人都需要努力。一旦团队磨合好了之后,代码质量在不断的迭代开发中也能得到保障。但提高代码质量也不是一朝一夕的事情,需要长期实行,定期反馈修正才可以长治久安。

MySQL存储引擎MyISAM与InnoDB的优劣

使用MySQL当然会接触到MySQL的存储引擎,在新建数据库和新建数据表的时候都会看到。

MySQL默认的存储引擎是MyISAM,其他常用的就是InnoDB了。

至于到底用哪种存储引擎比较好?这个问题是没有定论的,需要根据你的需求和环境来衡量。所以对这两种引擎的概念、原理、异同和各自的优劣点有了详细的了解之后,再根据自己的情况选择起来就容易多了。

MyISAM InnoDB
存储结构 每张表被存放在三个文件:
  1. frm-表格定义
  2. MYD(MYData)-数据文件
  3. MYI(MYIndex)-索引文件
所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB
存储空间 MyISAM可被压缩,存储空间较小 InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引
可移植性、备份及恢复 由于MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作 免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了
事务安全 不支持 每次查询具有原子性 支持 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表
AUTO_INCREMENT MyISAM表可以和其他字段一起建立联合索引 InnoDB中必须包含只有该字段的索引
SELECT MyISAM更优
INSERT InnoDB更优
UPDATE InnoDB更优
DELETE InnoDB更优 它不会重新建立表,而是一行一行的删除
COUNT without WHERE MyISAM更优。因为MyISAM保存了表的具体行数 InnoDB没有保存表的具体行数,需要逐行扫描统计,就很慢了
COUNT with WHERE 一样 一样,InnoDB也会锁表
只支持表锁 支持表锁、行锁 行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的
外键 不支持 支持
FULLTEXT全文索引 支持 不支持 可以通过使用Sphinx从InnoDB中获得全文索引,会慢一点

总的来说,MyISAM和InnoDB各有优劣,各有各的使用环境。

但是InnoDB的设计目标是处理大容量数据库系统,它的CPU利用率是其它基于磁盘的关系数据库引擎所不能比的。

我觉得使用InnoDB可以应对更为复杂的情况,特别是对并发的处理要比MyISAM高效。同时结合memcache也可以缓存SELECT来减少SELECT查询,从而提高整体性能。

用PHP Session和Javascript实现文件上传进度条

Web应用中常需要提供文件上传的功能。典型的场景包括用户头像上传、相册图片上传等。当需要上传的文件比较大的时候,提供一个显示上传进度的进度条就很有必要了。

在PHP 5.4以前,实现这样的进度条并不容易,主要有三种方法:

  1. 使用Flash, Java, ActiveX
  2. 使用PHP的APC扩展
  3. 使用HTML5的File API

第一种方法依赖第三方的浏览器插件,通用性不足,且易带来安全隐患。不过由于Flash的使用比较广泛,因此还是有很多网站使用Flash作为解决方案。

第二种方法的不足在于,它需要安装PHP的APC扩展库,要求用户能够控制服务器端的配置。另外,如果安装APC仅仅是为了实现一个上传进度条,那么显然有点杀鸡用牛刀的意思。

第三种方法应该是最为理想的方法,不需要服务器端的支持,仅在浏览器端使用Javascript即可。但是由于HTML5标准尚未确立,各浏览器厂商的支持也不相同,所以暂时这种方法还难以普及。

PHP 5.4中引入的基于session的上传进度监视功能(session.upload_progress),它提供了一个服务器端的上传进度监视解决方案。升级到PHP 5.4之后,可以不必安装APC扩展,仅使用原生PHP和前端的Javascript即可实现上传进度条。

下面我们就详细介绍一下 PHP 5.4 的这个 session.upload_progress 新特性。

原理介绍

当浏览器向服务器端上传一个文件时,PHP将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中。然后,随着上传的进行,周期性的更新session中的信息。这样,浏览器端就可以使用Ajax周期性的请求一个服务器端脚本,由该脚本返回session中的进度信息;浏览器端的Javascript即可根据这些信息显示/更新进度条了。

那么,文件上传信息具体是如何存储的?我们要如何访问它呢?下面我们来详细说明。

PHP 5.4 中引入了一些配置项(在php.ini中进行设置)

session.upload_progress.enabled = "1"
session.upload_progress.cleanup = "1"
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"

其中enabled控制upload_progress功能的开启与否,默认开启;cleanup 则设置当文件上传的请求提交完成后,是否清除session的相关信息,默认开启。

prefixname 两项用来设置进度信息在session中存储的变量名/键名。关于这两项的详细使用见下文。

freqmin_freq 两项用来设置服务器端对进度信息的更新频率。合理的设置这两项可以减轻服务器的负担。

在上传文件的表单中,需要为该次上传设置一个标识符,并在接下来的过程中使用该标识符来引用进度信息。具体的,在上传表单中需要有一个隐藏的input,它的name属性为php.ini中 session.upload_progress.name 的值;它的值为一个由你自己定义的标识符。如下:

<input type="hidden"
    name="<?php echo ini_get('session.upload_progress.name'); ?>"
    value="test" />

接到文件上传的表单后,PHP会在$_SESSION变量中新建键,键名是一个将session.upload_progress.prefix的值与上面你自定义的标识符连接后得到的字符串,可以这样得到:

$name = ini_get('session.upload_progress.name');
$key = ini_get('session.upload_progress.prefix') . $_POST[$name];

$_SESSION[$key]; // 这里就是此次文件上传的进度信息了

$_SESSION[$key]这个变量的结构是这样的:

$_SESSION["upload_progress_test"] = array(
 "start_time" => 1234567890,   // 开始时间
 "content_length" => 57343257, // POST请求的总数据长度
 "bytes_processed" => 453489,  // 已收到的数据长度
 "done" => false,              // 请求是否完成 true表示完成,false未完成

 // 单个文件的信息
 "files" => array(
  0 => array( ... ),
  // 同一请求中可包含多个文件
  1 => array( ... ),
 )
);

这样,我们就可以使用其中的 content_lengthbytes_processed 两项来得到进度百分比。

程序示例

原理介绍完了,下面我们来完整的实现一个基于PHP和Javascript的文件上传进度条。

本示例的代码仓库: Github: pureweber/samples/php-upload-progress

上传表单

首先,来编写我们的上传表单页面 index.php,代码如下:

<form id="upload-form"
    action="upload.php" method="POST" enctype="multipart/form-data"
    style="margin:15px 0" target="hidden_iframe">

        <input type="hidden" name="" value="test" />
        <p><input type="file" name="file1" /></p> 
        <p><input type="submit" value="Upload" /></p>
</form>     

<iframe id="hidden_iframe" name="hidden_iframe" src="about:blank" style="display:none;"></iframe>

<div id="progress" class="progress" style="margin-bottom:15px;display:none;">
        <div class="bar" style="width:0%;"></div>
        <div class="label">0%</div>
</div>

注意表单中的session.upload_progress.name隐藏项,值设置为了test。表单中仅有一个文件上传input,如果需要,你可以添加多个。

这里需要特别注意一下表单的target属性,这里设置指向了一个当前页面中的iframe。这一点很关键,通过设置target属性,让表单提交后的页面显示在iframe中,从而避免当前的页面跳转。因为我们还得在当前页面显示进度条呢。

#progress 这个div是用来显示进度条的。

注意 别忘了在index.php的最开始加上session_start()

处理上传的文件

表单的action指向upload.php,我们在upload.php中处理上传的文件,将它转存到当前目录。这里与通常情况下的上传处理没有区别。

if(is_uploaded_file($_FILES['file1']['tmp_name'])){
        move_uploaded_file($_FILES['file1']['tmp_name'], "./{$_FILES['file1']['name']}");
}
?>

Ajax获取进度信息

这一步是关键,我们需要建立一个 progress.php 文件,用来读取session中的进度信息; 然后我们在 index.php 中增加Javascript代码,向 progress.php 发起Ajax请求,然后根据获得的进度信息更新进度条。

progress.php 的代码如下:

session_start();

$i = ini_get('session.upload_progress.name');

$key = ini_get("session.upload_progress.prefix") . $_GET[$i];

if (!empty($_SESSION[$key])) {
        $current = $_SESSION[$key]["bytes_processed"];
        $total = $_SESSION[$key]["content_length"];
        echo $current < $total ? ceil($current / $total * 100) : 100;
}else{
        echo 100;
}
?>

在这里我们获得$_SESSION变量中的进度信息,然后输出一个进度百分比。

index.php 中,我们将如下代码添加到页面底部 (为简便,这里使用jQuery):

function fetch_progress(){
        $.get('progress.php',{ '' : 'test'}, function(data){
                var progress = parseInt(data);

                $('#progress .label').html(progress + '%');
                $('#progress .bar').css('width', progress + '%');

                if(progress < 100){
                        setTimeout('fetch_progress()', 100);
                }else{
            $('#progress .label').html('完成!');
        }
        }, 'html');
}

$('#upload-form').submit(function(){
        $('#progress').show();
        setTimeout('fetch_progress()', 100);
});

#upload-form被提交时,我们把进度条显示出来,然后反复调用 fetch_progress() 获得进度信息,并更新进度条,直到文件上传完毕,显示'完成!'

Done!

完整代码见: Github: pureweber/samples/php-upload-progress

注意事项

input标签的位置

name为session.upload_progress.name的input标签一定要放在文件input <input type="file" /> 的前面。

取消上传

通过设置 $_SESSION[$key]['cancel_upload'] = true 可取消当次上传。但仅能取消正在上传的文件和尚未开始的文件。已经上传成功的文件不会被删除。

setTimeout vs. setInterval

应该通过 setTimeout() 来调用 fetch_progress(),这样可以确保一次请求返回之后才开始下一次请求。如果使用 setInterval() 则不能保证这一点,有可能导致进度条出现'不进反退'。

参考资料

配图来自: dingatx